diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/app/BaseWebAppActivity.java b/app/src/main/java/br/com/nullexcept/webappmanager/app/BaseWebAppActivity.java index 17b4df6..c5da176 100644 --- a/app/src/main/java/br/com/nullexcept/webappmanager/app/BaseWebAppActivity.java +++ b/app/src/main/java/br/com/nullexcept/webappmanager/app/BaseWebAppActivity.java @@ -1,15 +1,22 @@ package br.com.nullexcept.webappmanager.app; +import android.app.Activity; import android.app.ActivityManager; import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; import android.os.Process; +import android.util.Log; +import android.view.Menu; +import android.view.View; import android.view.Window; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -17,23 +24,34 @@ import androidx.appcompat.app.AppCompatActivity; import org.mozilla.gecko.mozglue.GeckoLoader; +import org.mozilla.geckoview.AllowOrDeny; +import org.mozilla.geckoview.GeckoResult; import org.mozilla.geckoview.GeckoRuntime; import org.mozilla.geckoview.GeckoRuntimeSettings; import org.mozilla.geckoview.GeckoSession; import org.mozilla.geckoview.GeckoView; +import org.mozilla.geckoview.WebExtension; +import org.mozilla.geckoview.WebExtensionController; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; +import java.io.PrintStream; import java.net.URL; import java.net.URLConnection; import java.util.Arrays; +import java.util.List; import br.com.nullexcept.webappmanager.R; +import br.com.nullexcept.webappmanager.app.basewebapp.DialogOptions; import br.com.nullexcept.webappmanager.config.Config; +import br.com.nullexcept.webappmanager.web.WebSession; public class BaseWebAppActivity extends AppCompatActivity { Config config; + public static BaseWebAppActivity CURRENT_CONTEXT; + public static Config CURRENT_CONFIG; public static GeckoSession session; public static GeckoRuntime runtime; @@ -43,6 +61,10 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { config = new Config(this, getClass().getName()); config.load(); + CURRENT_CONFIG = config; + CURRENT_CONTEXT = this; + + if (!config.enable){ Toast.makeText(this, R.string.not_exists, Toast.LENGTH_LONG).show(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -53,92 +75,88 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { return; } - setTitle(config.name); setContentView(R.layout.webapp_acitivity); - GeckoView view = findViewById(R.id.webview); - if (runtime == null){ - session = new GeckoSession(); + if (session == null){ GeckoRuntimeSettings.Builder settings = new GeckoRuntimeSettings.Builder(); settings = settings.arguments(new String[]{ "--profile", config.getProfileDir().getAbsolutePath() }); - - session.getSettings().setUserAgentOverride(config.user_agent); - + session = new WebSession(this); runtime = GeckoRuntime.create(this, settings.build()); session.open(runtime); - view.setSession(session); - session.loadUri(config.url); } - view.setSession(session); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setTaskDescription(new ActivityManager.TaskDescription(config.name, null, Color.WHITE)); + + ((GeckoView)findViewById(R.id.webview)).setSession(session); + log(session); + + findViewById(R.id.options).setOnClickListener(v -> { + new DialogOptions(this).getDialog().show(); + }); + + if (!config.action_bar){ + findViewById(R.id.header).setVisibility(View.INVISIBLE); + findViewById(R.id.content).setPadding(0,0,0,0); } - setTitle(config.name); - checkIcon(); } - private void loadIcon() { - File dir = new File(config.getSaveDir(), "icon.png"); - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setTaskDescription(new ActivityManager.TaskDescription(config.name, BitmapFactory.decodeFile(dir.getAbsolutePath()), Color.WHITE)); - } - }catch (Exception e){} + + public GeckoRuntime getRuntime() { + return runtime; } - private long oldTime = 0; + public GeckoSession getSession() { + return session; + } - @Override - public void onBackPressed() { - session.goBack(); - //Check double back click - if(System.currentTimeMillis() - oldTime < 500){ - super.onBackPressed(); + public void error(Object... items){ + for (Object obj: items){ + _log(2, obj); } - oldTime = System.currentTimeMillis(); } - @Override - protected void onPause() { - super.onPause(); + public void log(Object... items){ + for (Object obj: items){ + _log(1, obj); + } } - @Override - protected void onDestroy() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - //finishAndRemoveTask(); - //Process.killProcess(Process.myPid()); + private void _log(int level, Object obj){ + if (obj instanceof Throwable){ + try { + Throwable th = (Throwable) obj; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(out); + th.printStackTrace(stream); + stream.flush(); + out.close(); + obj = out.toString("utf8"); + } catch (Exception e){} + } + switch (level){ + case 0: Log.i(getClass().getSimpleName(), obj+""); break; + case 1: Log.d(getClass().getSimpleName(), obj+""); break; + case 2: Log.e(getClass().getSimpleName(), obj+""); break; } - super.onDestroy(); } + + private long LAST_BACK = 0; @Override - public File getCacheDir() { - File file = new File(config.getSaveDir(), "cache"); - file.mkdirs(); - return file; + public void onBackPressed() { + if (System.currentTimeMillis()-LAST_BACK < 500){ + super.onBackPressed(); + } else { + if (session != null){ + session.goBack(); + } + } + LAST_BACK = System.currentTimeMillis(); } - private void checkIcon(){ - new Thread(()->{ - try { - File dir = new File(config.getSaveDir(), "icon.png"); - dir.getParentFile().mkdirs(); - if (dir.exists()){ - loadIcon(); - return; - } - - String url = config.url.substring(config.url.indexOf("://")+3); - if (url.contains("/")){ - url = url.substring(0, url.indexOf("/")); - } - //@TODO Need add code to get Icon from GeckoSession - }catch (Exception e){e.printStackTrace();} - }).start(); + public void loadUrl(String url) { + session.loadUri(url); } } diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/app/basewebapp/DialogOptions.java b/app/src/main/java/br/com/nullexcept/webappmanager/app/basewebapp/DialogOptions.java new file mode 100644 index 0000000..30daa19 --- /dev/null +++ b/app/src/main/java/br/com/nullexcept/webappmanager/app/basewebapp/DialogOptions.java @@ -0,0 +1,33 @@ +package br.com.nullexcept.webappmanager.app.basewebapp; + +import android.view.View; + +import androidx.appcompat.app.AlertDialog; + +import br.com.nullexcept.webappmanager.R; +import br.com.nullexcept.webappmanager.app.BaseWebAppActivity; + +public class DialogOptions { + private AlertDialog dialog; + public DialogOptions(BaseWebAppActivity ctx){ + ctx.runOnUiThread(()->{ + AlertDialog.Builder builder = new AlertDialog.Builder(ctx); + View view = ctx.getLayoutInflater().inflate(R.layout.dialog_actions,null, false); + + view.findViewById(R.id.opt_addons_store).setOnClickListener(v -> { + dialog.dismiss(); + ctx.loadUrl("https://addons.mozilla.org/pt-BR/android/"); + }); + view.findViewById(R.id.opt_gecko_settings).setOnClickListener(v -> { + dialog.dismiss(); + ctx.loadUrl("about:preferences"); + }); + builder.setView(view); + dialog = builder.create(); + }); + } + + public AlertDialog getDialog() { + return dialog; + } +} diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/web/WebSession.java b/app/src/main/java/br/com/nullexcept/webappmanager/web/WebSession.java new file mode 100644 index 0000000..8ea1401 --- /dev/null +++ b/app/src/main/java/br/com/nullexcept/webappmanager/web/WebSession.java @@ -0,0 +1,45 @@ +package br.com.nullexcept.webappmanager.web; + +import org.mozilla.geckoview.GeckoSession; +import org.mozilla.geckoview.MediaSession; + +import br.com.nullexcept.webappmanager.app.BaseWebAppActivity; +import br.com.nullexcept.webappmanager.config.Config; +import br.com.nullexcept.webappmanager.web.delegates.ContentListener; +import br.com.nullexcept.webappmanager.web.delegates.NavigationListener; +import br.com.nullexcept.webappmanager.web.delegates.ProcessListener; +import br.com.nullexcept.webappmanager.web.delegates.PromptListener; + +public class WebSession extends GeckoSession { + private Class current; + public WebSession(Object current){ + this.current = current.getClass(); + getSettings().setUserAgentOverride(config().user_agent); + setProgressDelegate(new ProcessListener(this)); + setContentDelegate(new ContentListener(this)); + setNavigationDelegate(new NavigationListener(this)); + setPromptDelegate(new PromptListener(this)); + } + + public void log(Object... items){ + context().log(items); + } + + public void error(Object... items){ + context().error(items); + } + + public Config config(){ + try { + return (Config) current.getField("CURRENT_CONFIG").get(null); + } catch (Exception e) {} + return null; + } + + public BaseWebAppActivity context(){ + try { + return (BaseWebAppActivity) current.getField("CURRENT_CONTEXT").get(null); + } catch (Exception e) {} + return null; + } +} diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/ContentListener.java b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/ContentListener.java new file mode 100644 index 0000000..2d1d974 --- /dev/null +++ b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/ContentListener.java @@ -0,0 +1,26 @@ +package br.com.nullexcept.webappmanager.web.delegates; + +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.mozilla.geckoview.GeckoSession; + +import br.com.nullexcept.webappmanager.R; +import br.com.nullexcept.webappmanager.web.WebSession; + +public class ContentListener implements GeckoSession.ContentDelegate { + private WebSession session; + public ContentListener(WebSession session){ + this.session = session; + } + + @Override + public void onTitleChange(@NonNull GeckoSession current, @Nullable String title) { + GeckoSession.ContentDelegate.super.onTitleChange(current, title); + session.context().runOnUiThread(()->{ + ((TextView)session.context().findViewById(R.id.title)).setText(title); + }); + } +} diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/NavigationListener.java b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/NavigationListener.java new file mode 100644 index 0000000..fb2f1cb --- /dev/null +++ b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/NavigationListener.java @@ -0,0 +1,54 @@ +package br.com.nullexcept.webappmanager.web.delegates; + +import android.annotation.SuppressLint; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.mozilla.geckoview.AllowOrDeny; +import org.mozilla.geckoview.GeckoResult; +import org.mozilla.geckoview.GeckoSession; +import org.mozilla.geckoview.WebExtension; +import org.mozilla.geckoview.WebExtensionController; + +import java.util.List; + +import br.com.nullexcept.webappmanager.R; +import br.com.nullexcept.webappmanager.web.WebSession; + +public class NavigationListener implements GeckoSession.NavigationDelegate { + private WebSession session; + public NavigationListener(WebSession session){ + this.session = session; + } + @Nullable + @Override + public GeckoResult onNewSession(@NonNull GeckoSession current, @NonNull String uri) { + session.loadUri(uri); + session.log("Requested new session!!"); + return null; + } + + @SuppressLint("WrongThread") + @Nullable + @Override + public GeckoResult onLoadRequest(@NonNull GeckoSession session, @NonNull LoadRequest request) { + String url = request.uri+""; + if (url.startsWith("https://addons.mozilla.org/") && url.endsWith(".xpi")){ + this.session.log("REQUIRES INSTALL NEW PLUGIN"); + this.session.context().getRuntime().getWebExtensionController().install(url).accept(webExtension -> { + NavigationListener.this.session.context().getRuntime().getWebExtensionController().enable(webExtension, WebExtensionController.EnableSource.USER); + }); + } + return GeckoSession.NavigationDelegate.super.onLoadRequest(session, request); + } + + @Override + public void onLocationChange(@NonNull GeckoSession current, @Nullable String url, @NonNull List perms) { + GeckoSession.NavigationDelegate.super.onLocationChange(current, url, perms); + session.context().runOnUiThread(()->{ + ((TextView)session.context().findViewById(R.id.url)).setText(url); + }); + } +} diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/PluginPromptListener.java b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/PluginPromptListener.java new file mode 100644 index 0000000..4c13b94 --- /dev/null +++ b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/PluginPromptListener.java @@ -0,0 +1,52 @@ +package br.com.nullexcept.webappmanager.web.delegates; + +import android.content.DialogInterface; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import org.mozilla.geckoview.AllowOrDeny; +import org.mozilla.geckoview.GeckoResult; +import org.mozilla.geckoview.WebExtension; +import org.mozilla.geckoview.WebExtensionController; + +import br.com.nullexcept.webappmanager.R; +import br.com.nullexcept.webappmanager.web.WebSession; + +public class PluginPromptListener implements WebExtensionController.PromptDelegate { + private WebSession session; + public PluginPromptListener(WebSession session){ + this.session = session; + } + + @Nullable + @Override + public GeckoResult onInstallPrompt(@NonNull WebExtension extension) { + GeckoResult result = new GeckoResult<>(); + session.log("REQUIRES INSTALL ADDON"); + + session.context().runOnUiThread(()->{ + AlertDialog.Builder builder = new AlertDialog.Builder(session.context()); + View view = session.context().getLayoutInflater().inflate(R.layout.dialog_install_plugin, null, false); + builder.setOnCancelListener(dialog -> { + result.complete(AllowOrDeny.DENY); + }); + ((TextView)view.findViewById(R.id.title)).setText(extension.metaData.name); + ((TextView)view.findViewById(R.id.desc)).setText("From: "+extension.location+"\n\n"+extension.metaData.description); + + builder.setView(view); + AlertDialog dialog = builder.create(); + view.findViewById(R.id.cancel).setOnClickListener(v -> {dialog.cancel();}); + view.findViewById(R.id.install).setOnClickListener(v -> { + result.complete(AllowOrDeny.DENY); + dialog.dismiss(); + }); + dialog.show(); + }); + + return result; + } +} diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/ProcessListener.java b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/ProcessListener.java new file mode 100644 index 0000000..ade2308 --- /dev/null +++ b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/ProcessListener.java @@ -0,0 +1,43 @@ +package br.com.nullexcept.webappmanager.web.delegates; + +import android.view.View; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; + +import org.mozilla.geckoview.GeckoSession; + +import br.com.nullexcept.webappmanager.R; +import br.com.nullexcept.webappmanager.web.WebSession; + +public class ProcessListener implements GeckoSession.ProgressDelegate { + private WebSession session; + public ProcessListener(WebSession session){ + this.session = session; + } + + @Override + public void onProgressChange(@NonNull GeckoSession current, int progress) { + GeckoSession.ProgressDelegate.super.onProgressChange(current, progress); + session.context().runOnUiThread(()->{ + ((ProgressBar)session.context().findViewById(R.id.loading_bar)).setProgress(progress); + }); + } + + @Override + public void onPageStop(@NonNull GeckoSession current, boolean success) { + GeckoSession.ProgressDelegate.super.onPageStop(current, success); + session.context().runOnUiThread(()->{ + ((ProgressBar)session.context().findViewById(R.id.loading_bar)).setVisibility(View.INVISIBLE); + }); + } + + @Override + public void onPageStart(@NonNull GeckoSession current, @NonNull String url) { + GeckoSession.ProgressDelegate.super.onPageStart(current, url); + session.context().runOnUiThread(()->{ + ((ProgressBar)session.context().findViewById(R.id.loading_bar)).setVisibility(View.VISIBLE); + ((ProgressBar)session.context().findViewById(R.id.loading_bar)).setProgress(0); + }); + } +} diff --git a/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/PromptListener.java b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/PromptListener.java new file mode 100644 index 0000000..719450a --- /dev/null +++ b/app/src/main/java/br/com/nullexcept/webappmanager/web/delegates/PromptListener.java @@ -0,0 +1,12 @@ +package br.com.nullexcept.webappmanager.web.delegates; + +import org.mozilla.geckoview.GeckoSession; + +import br.com.nullexcept.webappmanager.web.WebSession; + +public class PromptListener implements GeckoSession.PromptDelegate { + private WebSession session; + public PromptListener(WebSession session){ + this.session = session; + } +} diff --git a/app/src/main/res/drawable/addons_store.xml b/app/src/main/res/drawable/addons_store.xml new file mode 100644 index 0000000..04f67ba --- /dev/null +++ b/app/src/main/res/drawable/addons_store.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/icon_more_vertical.xml b/app/src/main/res/drawable/icon_more_vertical.xml new file mode 100644 index 0000000..7a32c17 --- /dev/null +++ b/app/src/main/res/drawable/icon_more_vertical.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/dialog_actions.xml b/app/src/main/res/layout/dialog_actions.xml new file mode 100644 index 0000000..d722702 --- /dev/null +++ b/app/src/main/res/layout/dialog_actions.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + +