Skip to content

Commit

Permalink
Web browser enhancements
Browse files Browse the repository at this point in the history
Added back, forward, clear and bookmarks buttons to the tool bar.
User-Agent switcher - Menu > Desktop version.
Keyboard input enhancements. Closes #18.
  • Loading branch information
AndreyPavlenko committed Mar 17, 2021
1 parent 35df184 commit 445e8c2
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 27 deletions.
6 changes: 6 additions & 0 deletions modules/web/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
<!-- The location permissions are used by the web view -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.CAMERA" />

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

<dist:module
dist:instant="false"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package me.aap.fermata.addon.web;

import android.Manifest;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.GeolocationPermissions;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.FrameLayout;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import me.aap.fermata.BuildConfig;
import me.aap.fermata.ui.activity.MainActivityDelegate;
import me.aap.utils.app.App;
import me.aap.utils.async.FutureSupplier;
Expand Down Expand Up @@ -44,6 +57,47 @@ public FermataWebView getWebView() {
return web;
}

@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Context ctx = view.getContext();
ActivityDelegate.get(ctx).createDialogBuilder(ctx)
.setTitle(android.R.drawable.ic_dialog_alert, android.R.string.dialog_alert_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, (d, w) -> result.confirm())
.show();
return true;
}

@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
Context ctx = view.getContext();
ActivityDelegate.get(ctx).createDialogBuilder(ctx)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, (d, w) -> result.cancel())
.setPositiveButton(android.R.string.ok, (d, w) -> result.confirm())
.show();
return true;
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Context ctx = view.getContext();
ActivityDelegate a = ActivityDelegate.get(ctx);
EditText text = a.createEditText(ctx);
text.setSingleLine();
text.setText(defaultValue);
a.createDialogBuilder(ctx)
.setTitle(message).setView(text)
.setNegativeButton(android.R.string.cancel, (d, i) -> result.cancel())
.setPositiveButton(android.R.string.ok, (d, i) -> result.confirm(text.getText().toString())).show();
return true;
}

@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
return onJsConfirm(view, url, message, result);
}

@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (view instanceof ViewGroup) {
Expand Down Expand Up @@ -197,4 +251,59 @@ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermiss
});
}
}

@Override
public void onPermissionRequest(PermissionRequest request) {
Log.d("Permissions requested: ", Arrays.toString(request.getResources()));
Map<String, String> perms = new HashMap<>();

for (String p : request.getResources()) {
switch (p) {
case PermissionRequest.RESOURCE_AUDIO_CAPTURE:
perms.put(Manifest.permission.RECORD_AUDIO, p);
break;
case PermissionRequest.RESOURCE_VIDEO_CAPTURE:
perms.put(Manifest.permission.CAMERA, p);
break;
}
}

if (perms.isEmpty()) {
Log.d("No permissions granted");
request.deny();
return;
}

MainActivityDelegate a = MainActivityDelegate.get(getWebView().getContext());

if (BuildConfig.AUTO && a.isCarActivity()) {
// Activity.checkPermissions() is not supported by AA
Log.d("Granted permissions: ", perms.values());
request.grant(perms.values().toArray(new String[0]));
return;
}

String[] keys = perms.keySet().toArray(new String[0]);
FutureSupplier<int[]> perm = a.getAppActivity().checkPermissions(keys);
perm.onCompletion((r, err) -> {
if (err != null) {
Log.e(err, "Permission request failed");
request.deny();
} else {
List<String> granted = new ArrayList<>(r.length);

for (int i = 0; i < r.length; i++) {
if (r[i] == PERMISSION_GRANTED) granted.add(perms.get(keys[i]));
}

if (granted.isEmpty()) {
Log.d("No permissions granted");
request.deny();
} else {
Log.d("Granted permissions: ", granted);
request.grant(granted.toArray(new String[0]));
}
}
});
}
}
100 changes: 82 additions & 18 deletions modules/web/src/main/java/me/aap/fermata/addon/web/FermataWebView.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import androidx.webkit.WebSettingsCompat;
import androidx.webkit.WebViewFeature;

import java.util.List;

import me.aap.fermata.BuildConfig;
import me.aap.fermata.ui.activity.FermataActivity;
import me.aap.fermata.ui.activity.MainActivityDelegate;
Expand All @@ -35,7 +37,7 @@
* @author Andrey Pavlenko
*/
public class FermataWebView extends WebView implements TextChangedListener,
TextView.OnEditorActionListener {
TextView.OnEditorActionListener, PreferenceStore.Listener {
private final boolean isCar;
private WebBrowserAddon addon;
private FermataChromeClient chrome;
Expand All @@ -60,22 +62,55 @@ public void init(WebBrowserAddon addon, FermataWebClient webClient, FermataChrom
setWebViewClient(webClient);
setWebChromeClient(chromeClient);
WebSettings s = getSettings();
s.setJavaScriptEnabled(true);
s.setSupportZoom(true);
s.setBuiltInZoomControls(true);
s.setDisplayZoomControls(false);
s.setDatabaseEnabled(true);
s.setDomStorageEnabled(true);
s.setAllowFileAccess(true);
s.setLoadWithOverviewMode(true);
s.setAllowUniversalAccessFromFileURLs(true);
s.setJavaScriptEnabled(true);
s.setJavaScriptCanOpenWindowsAutomatically(true);

setDesktopMode(addon.isDesktopVersion(), false);
addon.getPreferenceStore().addBroadcastListener(this);
addJavascriptInterface(createJsInterface(), FermataJsInterface.NAME);
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true);

if (WebViewFeature.isFeatureSupported(FORCE_DARK)) {
PreferenceStore store = addon.getPreferenceStore();
if (store.getBooleanPref(addon.getForceDarkPref())) {
if (addon.isForceDark()) {
WebSettingsCompat.setForceDark(s, WebSettingsCompat.FORCE_DARK_ON);
}
}
}

@Override
public void onPreferenceChanged(PreferenceStore store, List<PreferenceStore.Pref<?>> prefs) {
WebBrowserAddon addon = getAddon();

if ((addon != null) && prefs.contains(addon.getDesktopVersionPref())) {
setDesktopMode(store.getBooleanPref(addon.getDesktopVersionPref()), true);
}
}

private void setDesktopMode(boolean v, boolean reload) {
WebSettings s = getSettings();
s.setUseWideViewPort(v);

if (v) {
String ua = s.getUserAgentString();
int i1 = ua.indexOf('(') + 1;
int i2 = ua.indexOf(')', i1);
ua = ua.substring(0, i1) + "X11; Linux x86_64" + ua.substring(i2).replace("Mobile ", "");
Log.d("Changing UserAgent to " + ua);
s.setUserAgentString(ua);
} else {
s.setUserAgentString(null);
}

if (reload) reload();
}

protected FermataJsInterface createJsInterface() {
return new FermataJsInterface(this);
}
Expand Down Expand Up @@ -112,7 +147,13 @@ protected void pageLoaded(String uri) {
if (f == null) return;

ToolBarView.Mediator m = f.getToolBarMediator();
if (m instanceof WebToolBarMediator) ((WebToolBarMediator) m).setAddress(a.getToolBar(), uri);

if (m instanceof WebToolBarMediator) {
WebToolBarMediator wm = (WebToolBarMediator) m;
ToolBarView tb = a.getToolBar();
wm.setAddress(tb, uri);
wm.setButtonsVisibility(tb, canGoBack(), canGoForward());
}

CookieManager.getInstance().flush();
}
Expand Down Expand Up @@ -151,21 +192,33 @@ private void setTextInput(CharSequence text) {
Log.d(text);
loadUrl("javascript:\n" +
"var e = document.activeElement;\n" +
"if (e.isContentEditable) e.innerText = '" + text + "';\n" +
"else e.value = '" + text + "';\n" +
"e.dispatchEvent(new KeyboardEvent('keyup'));"
"var text = '" + text + "';\n" +
"if (e.isContentEditable) e.innerText = text;\n" +
"else e.value = text;\n" +
"e.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true }));\n" +
"e.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true }));\n" +
"e.dispatchEvent(new InputEvent('input', { bubbles: true, data: text, inputType: 'insertText' }));\n" +
"e.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true }));\n" +
"e.dispatchEvent(new Event('change', { bubbles: true }));"
);
}

protected void sendEnterEvent() {

protected void submitForm() {
if (!BuildConfig.AUTO) return;
loadUrl("javascript:\n" +
"var e = new KeyboardEvent('keydown',\n" +
"{ code: 'Enter', key: 'Enter', charKode: 13, keyCode: 13, view: window });\n" +
"document.activeElement.dispatchEvent(e);\n" +
"e = new KeyboardEvent('keyup',\n" +
"{ code: 'Enter', key: 'Enter', charKode: 13, keyCode: 13, view: window });\n" +
"document.activeElement.dispatchEvent(e);");
"var ae = document.activeElement;\n" +
"if (ae.form != null) {\n" +
" ae.form.submit();\n" +
"} else {\n" +
" var e = new KeyboardEvent('keydown',\n" +
" { code: 'Enter', key: 'Enter', keyCode: 13, view: window, bubbles: true });\n" +
" ae.dispatchEvent(e);\n" +
" e = new KeyboardEvent('keyup',\n" +
" { code: 'Enter', key: 'Enter', keyCode: 13, view: window, bubbles: true });\n" +
" ae.dispatchEvent(e);\n" +
"}"
);
}

public void showKeyboard(String text) {
Expand Down Expand Up @@ -213,7 +266,7 @@ public boolean onEditorAction(TextView v, int actionId, @Nullable KeyEvent event
case EditorInfo.IME_ACTION_SEND:
case EditorInfo.IME_ACTION_NEXT:
case EditorInfo.IME_ACTION_DONE:
sendEnterEvent();
submitForm();
hideKeyboard();
}

Expand All @@ -223,7 +276,18 @@ public boolean onEditorAction(TextView v, int actionId, @Nullable KeyEvent event
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
FermataChromeClient chrome = getWebChromeClient();
if ((chrome != null) && chrome.isFullScreen()) chrome.onTouchEvent(this, ev);

if ((chrome != null) && chrome.isFullScreen()) {
chrome.onTouchEvent(this, ev);
} else if (BuildConfig.AUTO) {
FermataActivity a = MainActivityDelegate.get(getContext()).getAppActivity();

if (a.isInputActive()) {
a.stopInput(this);
return true;
}
}

return super.onInterceptTouchEvent(ev);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
public class WebBrowserAddon implements FermataAddon {
private static final Pref<Supplier<String>> LAST_URL = Pref.s("LAST_URL", "http://google.com");
private static final Pref<BooleanSupplier> FORCE_DARK = Pref.b("FORCE_DARK", false);
private static final Pref<BooleanSupplier> DESKTOP_VERSION = Pref.b("DESKTOP_VERSION", false);
private static final Pref<Supplier<String[]>> BOOKMARKS = Pref.sa("BOOKMARKS");
private final SharedPreferenceStore preferenceStore;

Expand Down Expand Up @@ -64,6 +65,22 @@ public Pref<BooleanSupplier> getForceDarkPref() {
return FORCE_DARK;
}

public boolean isForceDark() {
return getPreferenceStore().getBooleanPref(getForceDarkPref());
}

public Pref<BooleanSupplier> getDesktopVersionPref() {
return DESKTOP_VERSION;
}

public boolean isDesktopVersion() {
return getPreferenceStore().getBooleanPref(getDesktopVersionPref());
}

public void setDesktopVersion(boolean v) {
getPreferenceStore().applyBooleanPref(DESKTOP_VERSION, v);
}

Map<String, String> getBookmarks() {
String[] p = getPreferenceStore().getStringArrayPref(BOOKMARKS);
if (p.length == 0) return Collections.emptyMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,9 @@ public void contributeToNavBarMenu(OverlayMenu.Builder b) {
b.addItem(me.aap.fermata.R.id.refresh, me.aap.fermata.R.drawable.refresh,
me.aap.fermata.R.string.refresh).setHandler(this);

if (v.canGoForward()) {
b.addItem(R.id.browser_forward, R.drawable.forward, R.string.go_forward).setHandler(this);
if (isDesktopVersionSupported()) {
b.addItem(R.id.desktop_version, R.drawable.desktop, R.string.desktop_version)
.setChecked(getAddon().isDesktopVersion()).setHandler(this);
}

FermataChromeClient chrome = v.getWebChromeClient();
Expand All @@ -171,6 +172,10 @@ public void contributeToNavBarMenu(OverlayMenu.Builder b) {
me.aap.fermata.R.string.bookmarks).setSubmenu(this::bookmarksMenu);
}

protected boolean isDesktopVersionSupported() {
return true;
}

@Override
public boolean menuItemSelected(OverlayMenuItem item) {
FermataWebView v = getWebView();
Expand All @@ -182,8 +187,9 @@ public boolean menuItemSelected(OverlayMenuItem item) {
case me.aap.fermata.R.id.refresh:
v.reload();
return true;
case R.id.browser_forward:
v.goForward();
case R.id.desktop_version:
WebBrowserAddon addon = getAddon();
addon.setDesktopVersion(!addon.isDesktopVersion());
return true;
case R.id.fullscreen:
case R.id.fullscreen_exit:
Expand All @@ -197,7 +203,7 @@ public boolean menuItemSelected(OverlayMenuItem item) {
return false;
}

private void bookmarksMenu(OverlayMenu.Builder b) {
public void bookmarksMenu(OverlayMenu.Builder b) {
WebBrowserAddon a = getAddon();
if (a == null) return;

Expand Down
Loading

0 comments on commit 445e8c2

Please sign in to comment.