Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.geckoview_example;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
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.SystemClock;
import android.text.InputType;
import android.util.Log;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONObject;
import org.mozilla.geckoview.AllowOrDeny;
import org.mozilla.geckoview.Autocomplete;
import org.mozilla.geckoview.BasicSelectionActionDelegate;
import org.mozilla.geckoview.ContentBlocking;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoRuntimeSettings;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSession.PermissionDelegate;
import org.mozilla.geckoview.GeckoSession.PermissionDelegate.ContentPermission;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
import org.mozilla.geckoview.GeckoWebExecutor;
import org.mozilla.geckoview.Image;
import org.mozilla.geckoview.MediaSession;
import org.mozilla.geckoview.OrientationController;
import org.mozilla.geckoview.SlowScriptResponse;
import org.mozilla.geckoview.TranslationsController;
import org.mozilla.geckoview.WebExtension;
import org.mozilla.geckoview.WebExtensionController;
import org.mozilla.geckoview.WebNotification;
import org.mozilla.geckoview.WebNotificationDelegate;
import org.mozilla.geckoview.WebRequest;
import org.mozilla.geckoview.WebRequestError;
import org.mozilla.geckoview.WebResponse;
interface WebExtensionDelegate {
default GeckoSession toggleBrowserActionPopup(boolean force) {
return null;
}
default void onActionButton(ActionButton button) {}
default TabSession getSession(GeckoSession session) {
return null;
}
default TabSession getCurrentSession() {
return null;
}
default void closeTab(TabSession session) {}
default void updateTab(TabSession session, WebExtension.UpdateTabDetails details) {}
default TabSession openNewTab(WebExtension.CreateTabDetails details) {
return null;
}
}
class WebExtensionManager
implements WebExtension.ActionDelegate,
WebExtension.SessionTabDelegate,
WebExtension.TabDelegate,
WebExtensionController.PromptDelegate,
WebExtensionController.DebuggerDelegate,
TabSessionManager.TabObserver {
public WebExtension extension;
private LruCache<Image, Bitmap> mBitmapCache = new LruCache<>(5);
private GeckoRuntime mRuntime;
private WebExtension.Action mDefaultAction;
private TabSessionManager mTabManager;
private WeakReference<WebExtensionDelegate> mExtensionDelegate;
@Nullable
@Override
public GeckoResult<AllowOrDeny> onInstallPrompt(final @NonNull WebExtension extension) {
return GeckoResult.allow();
}
@Nullable
@Override
public GeckoResult<AllowOrDeny> onUpdatePrompt(
@NonNull WebExtension currentlyInstalled,
@NonNull WebExtension updatedExtension,
@NonNull String[] newPermissions,
@NonNull String[] newOrigins) {
return GeckoResult.allow();
}
@Nullable
@Override
public GeckoResult<AllowOrDeny> onOptionalPrompt(
final @NonNull WebExtension extension, final String[] permissions, final String[] origins) {
return GeckoResult.allow();
}
@Override
public void onExtensionListUpdated() {
refreshExtensionList();
}
// We only support either one browserAction or one pageAction
private void onAction(
final WebExtension extension, final GeckoSession session, final WebExtension.Action action) {
WebExtensionDelegate delegate = mExtensionDelegate.get();
if (delegate == null) {
return;
}
WebExtension.Action resolved;
if (session == null) {
// This is the default action
mDefaultAction = action;
resolved = actionFor(delegate.getCurrentSession());
} else {
if (delegate.getSession(session) == null) {
return;
}
delegate.getSession(session).action = action;
if (delegate.getCurrentSession() != session) {
// This update is not for the session that we are currently displaying,
// no need to update the UI
return;
}
resolved = action.withDefault(mDefaultAction);
}
updateAction(resolved);
}
@Override
public GeckoResult<GeckoSession> onNewTab(
WebExtension source, WebExtension.CreateTabDetails details) {
WebExtensionDelegate delegate = mExtensionDelegate.get();
if (delegate == null) {
return GeckoResult.fromValue(null);
}
return GeckoResult.fromValue(delegate.openNewTab(details));
}
@Override
public GeckoResult<AllowOrDeny> onCloseTab(WebExtension extension, GeckoSession session) {
final WebExtensionDelegate delegate = mExtensionDelegate.get();
if (delegate == null) {
return GeckoResult.deny();
}
final TabSession tabSession = mTabManager.getSession(session);
if (tabSession != null) {
delegate.closeTab(tabSession);
}
return GeckoResult.allow();
}
@Override
public GeckoResult<AllowOrDeny> onUpdateTab(
WebExtension extension, GeckoSession session, WebExtension.UpdateTabDetails updateDetails) {
final WebExtensionDelegate delegate = mExtensionDelegate.get();
if (delegate == null) {
return GeckoResult.deny();
}
final TabSession tabSession = mTabManager.getSession(session);
if (tabSession != null) {
delegate.updateTab(tabSession, updateDetails);
}
return GeckoResult.allow();
}
@Override
public void onPageAction(
final WebExtension extension, final GeckoSession session, final WebExtension.Action action) {
onAction(extension, session, action);
}
@Override
public void onBrowserAction(
final WebExtension extension, final GeckoSession session, final WebExtension.Action action) {
onAction(extension, session, action);
}
private GeckoResult<GeckoSession> togglePopup(boolean force) {
WebExtensionDelegate extensionDelegate = mExtensionDelegate.get();
if (extensionDelegate == null) {
return null;
}
GeckoSession session = extensionDelegate.toggleBrowserActionPopup(false);
if (session == null) {
return null;
}
return GeckoResult.fromValue(session);
}
@Override
public GeckoResult<GeckoSession> onTogglePopup(
final @NonNull WebExtension extension, final @NonNull WebExtension.Action action) {
return togglePopup(false);
}
@Override
public GeckoResult<GeckoSession> onOpenPopup(
final @NonNull WebExtension extension, final @NonNull WebExtension.Action action) {
return togglePopup(true);
}
private WebExtension.Action actionFor(TabSession session) {
if (session.action == null) {
return mDefaultAction;
} else {
return session.action.withDefault(mDefaultAction);
}
}
private void updateAction(WebExtension.Action resolved) {
WebExtensionDelegate extensionDelegate = mExtensionDelegate.get();
if (extensionDelegate == null) {
return;
}
if (resolved == null || resolved.enabled == null || !resolved.enabled) {
extensionDelegate.onActionButton(null);
return;
}
if (resolved.icon != null) {
if (mBitmapCache.get(resolved.icon) != null) {
extensionDelegate.onActionButton(
new ActionButton(
mBitmapCache.get(resolved.icon),
resolved.badgeText,
resolved.badgeTextColor,
resolved.badgeBackgroundColor));
} else {
resolved
.icon
.getBitmap(100)
.accept(
bitmap -> {
mBitmapCache.put(resolved.icon, bitmap);
extensionDelegate.onActionButton(
new ActionButton(
bitmap,
resolved.badgeText,
resolved.badgeTextColor,
resolved.badgeBackgroundColor));
});
}
} else {
extensionDelegate.onActionButton(null);
}
}
public void onClicked(TabSession session) {
WebExtension.Action action = actionFor(session);
if (action != null) {
action.click();
}
}
public void setExtensionDelegate(WebExtensionDelegate delegate) {
mExtensionDelegate = new WeakReference<>(delegate);
}
@Override
public void onCurrentSession(TabSession session) {
if (mDefaultAction == null) {
// No action was ever defined, so nothing to do
return;
}
if (session.action != null) {
updateAction(session.action.withDefault(mDefaultAction));
} else {
updateAction(mDefaultAction);
}
}
public GeckoResult<Void> unregisterExtension() {
if (extension == null) {
return GeckoResult.fromValue(null);
}
mTabManager.unregisterWebExtension();
return mRuntime
.getWebExtensionController()
.uninstall(extension)
.accept(
(unused) -> {
extension = null;
mDefaultAction = null;
updateAction(null);
});
}
public GeckoResult<WebExtension> updateExtension() {
if (extension == null) {
return GeckoResult.fromValue(null);
}
return mRuntime
.getWebExtensionController()
.update(extension)
.map(
newExtension -> {
registerExtension(newExtension);
return newExtension;
});
}
public void registerExtension(WebExtension extension) {
extension.setActionDelegate(this);
extension.setTabDelegate(this);
mTabManager.setWebExtensionDelegates(extension, this, this);
this.extension = extension;
}
private void refreshExtensionList() {
mRuntime
.getWebExtensionController()
.list()
.accept(
extensions -> {
for (final WebExtension extension : extensions) {
registerExtension(extension);
}
});
}
public WebExtensionManager(GeckoRuntime runtime, TabSessionManager tabManager) {
mTabManager = tabManager;
mRuntime = runtime;
refreshExtensionList();
}
}
public class GeckoViewActivity extends AppCompatActivity
implements ToolbarLayout.TabListener,
WebExtensionDelegate,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String LOGTAG = "GeckoViewActivity";
private static final String FULL_ACCESSIBILITY_TREE_EXTRA = "full_accessibility_tree";
private static final String SEARCH_URI_BASE = "https://www.google.com/search?q=";
private static final String ACTION_SHUTDOWN = "org.mozilla.geckoview_example.SHUTDOWN";
private static final String CHANNEL_ID = "GeckoViewExample";
private static final int REQUEST_FILE_PICKER = 1;
private static final int REQUEST_PERMISSIONS = 2;
private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 3;
private static GeckoRuntime sGeckoRuntime;
private static WebExtensionManager sExtensionManager;
private TabSessionManager mTabSessionManager;
private GeckoView mGeckoView;
private boolean mFullAccessibilityTree;
private boolean mUsePrivateBrowsing;
private boolean mCollapsed;
private boolean mKillProcessOnDestroy;
private boolean mDesktopMode;
private TabSession mPopupSession;
private View mPopupView;
private ContentPermission mTrackingProtectionPermission;
private boolean mShowNotificationsRejected;
private ArrayList<String> mAcceptedPersistentStorage = new ArrayList<String>();
private ToolbarLayout mToolbarView;
private String mCurrentUri;
private boolean mCanGoBack;
private boolean mCanGoForward;
private boolean mFullScreen;
private boolean mExpectedTranslate = false;
private boolean mTranslateRestore = false;
private String mDetectedLanguage = null;
private HashMap<String, Integer> mNotificationIDMap = new HashMap<>();
private int mLastID = 100;
private ProgressBar mProgressView;
private LinkedList<WebResponse> mPendingDownloads = new LinkedList<>();
private int mNextActivityResultCode = 10;
private HashMap<Integer, GeckoResult<Intent>> mPendingActivityResult = new HashMap<>();
private LocationView.CommitListener mCommitListener =
new LocationView.CommitListener() {
@Override
public void onCommit(String text) {
if (text.startsWith("data:")
|| ((text.contains(".") || text.contains(":")) && !text.contains(" "))) {
mTabSessionManager.getCurrentSession().loadUri(text);
} else {
mTabSessionManager.getCurrentSession().loadUri(SEARCH_URI_BASE + text);
}
mGeckoView.requestFocus();
}
};
@Override
public TabSession openNewTab(WebExtension.CreateTabDetails details) {
final TabSession newSession = createSession(details.cookieStoreId);
mToolbarView.updateTabCount();
if (details.active == Boolean.TRUE) {
setGeckoViewSession(newSession, false);
}
return newSession;
}
private final List<Setting<?>> SETTINGS = new ArrayList<>();
private abstract class Setting<T> {
private int mKey;
private int mDefaultKey;
private final boolean mReloadCurrentSession;
private T mValue;
public Setting(final int key, final int defaultValueKey, final boolean reloadCurrentSession) {
mKey = key;
mDefaultKey = defaultValueKey;
mReloadCurrentSession = reloadCurrentSession;
SETTINGS.add(this);
}
public void onPrefChange(SharedPreferences pref) {
final T defaultValue = getDefaultValue(mDefaultKey, getResources());
final String key = getResources().getString(this.mKey);
final T value = getValue(key, defaultValue, pref);
if (!value().equals(value)) {
setValue(value);
}
}
private void setValue(final T newValue) {
mValue = newValue;
for (final TabSession session : mTabSessionManager.getSessions()) {
setValue(session.getSettings(), value());
}
if (sGeckoRuntime != null) {
setValue(sGeckoRuntime.getSettings(), value());
if (sExtensionManager != null) {
setValue(sGeckoRuntime.getWebExtensionController(), value());
}
}
final GeckoSession current = mTabSessionManager.getCurrentSession();
if (mReloadCurrentSession && current != null) {
current.reload();
}
}
public T value() {
return mValue == null ? getDefaultValue(mDefaultKey, getResources()) : mValue;
}
protected abstract T getDefaultValue(final int key, final Resources res);
protected abstract T getValue(
final String key, final T defaultValue, final SharedPreferences preferences);
/** Override one of these to define the behavior when this setting changes. */
protected void setValue(final GeckoSessionSettings settings, final T value) {}
protected void setValue(final GeckoRuntimeSettings settings, final T value) {}
protected void setValue(final WebExtensionController controller, final T value) {}
}
private class StringSetting extends Setting<String> {
public StringSetting(final int key, final int defaultValueKey) {
this(key, defaultValueKey, false);
}
public StringSetting(
final int key, final int defaultValueKey, final boolean reloadCurrentSession) {
super(key, defaultValueKey, reloadCurrentSession);
}
@Override
protected String getDefaultValue(int key, final Resources res) {
return res.getString(key);
}
@Override
public String getValue(
final String key, final String defaultValue, final SharedPreferences preferences) {
return preferences.getString(key, defaultValue);
}
}
private class BooleanSetting extends Setting<Boolean> {
public BooleanSetting(final int key, final int defaultValueKey) {
this(key, defaultValueKey, false);
}
public BooleanSetting(
final int key, final int defaultValueKey, final boolean reloadCurrentSession) {
super(key, defaultValueKey, reloadCurrentSession);
}
@Override
protected Boolean getDefaultValue(int key, Resources res) {
return res.getBoolean(key);
}
@Override
public Boolean getValue(
final String key, final Boolean defaultValue, final SharedPreferences preferences) {
return preferences.getBoolean(key, defaultValue);
}
}
private class IntSetting extends Setting<Integer> {
public IntSetting(final int key, final int defaultValueKey) {
this(key, defaultValueKey, false);
}
public IntSetting(
final int key, final int defaultValueKey, final boolean reloadCurrentSession) {
super(key, defaultValueKey, reloadCurrentSession);
}
@Override
protected Integer getDefaultValue(int key, Resources res) {
return res.getInteger(key);
}
@Override
public Integer getValue(
final String key, final Integer defaultValue, final SharedPreferences preferences) {
return Integer.parseInt(preferences.getString(key, Integer.toString(defaultValue)));
}
}
private final IntSetting mDisplayMode =
new IntSetting(R.string.key_display_mode, R.integer.display_mode_default) {
@Override
public void setValue(final GeckoSessionSettings settings, final Integer value) {
settings.setDisplayMode(value);
}
};
private final IntSetting mPreferredColorScheme =
new IntSetting(
R.string.key_preferred_color_scheme,
R.integer.preferred_color_scheme_default,
/* reloadCurrentSession */ true) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Integer value) {
settings.setPreferredColorScheme(value);
}
};
private final StringSetting mUserAgent =
new StringSetting(
R.string.key_user_agent_override,
R.string.user_agent_override_default,
/* reloadCurrentSession */ true) {
@Override
public void setValue(final GeckoSessionSettings settings, final String value) {
settings.setUserAgentOverride(value.isEmpty() ? null : value);
}
};
private final BooleanSetting mRemoteDebugging =
new BooleanSetting(R.string.key_remote_debugging, R.bool.remote_debugging_default) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Boolean value) {
settings.setRemoteDebuggingEnabled(value);
}
};
private final BooleanSetting mJavascriptEnabled =
new BooleanSetting(
R.string.key_javascript_enabled,
R.bool.javascript_enabled_default,
/* reloadCurrentSession */ true) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Boolean value) {
settings.setJavaScriptEnabled(value);
}
};
private final BooleanSetting mGlobalPrivacyControlEnabled =
new BooleanSetting(
R.string.key_global_privacy_control_enabled,
R.bool.global_privacy_control_enabled_default,
/* reloadCurrentSession */ true) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Boolean value) {
settings.setGlobalPrivacyControl(value);
}
};
private final BooleanSetting mEtbPrivateModeEnabled =
new BooleanSetting(
R.string.key_etb_private_mode_enabled,
R.bool.etb_private_mode_enabled_default,
/* reloadCurrentSession */ true) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Boolean value) {
settings.getContentBlocking().setEmailTrackerBlockingPrivateBrowsing(value);
}
};
private final BooleanSetting mExtensionsProcessEnabled =
new BooleanSetting(
R.string.key_extensions_process_enabled,
R.bool.extensions_process_enabled_default,
/* reloadCurrentSession */ true) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Boolean value) {
settings.setExtensionsProcessEnabled(value);
}
};
private final BooleanSetting mTrackingProtection =
new BooleanSetting(R.string.key_tracking_protection, R.bool.tracking_protection_default) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Boolean value) {
mTabSessionManager.setUseTrackingProtection(value);
settings.getContentBlocking().setStrictSocialTrackingProtection(value);
}
};
private final StringSetting mEnhancedTrackingProtection =
new StringSetting(
R.string.key_enhanced_tracking_protection,
R.string.enhanced_tracking_protection_default) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final String value) {
int etpLevel;
switch (value) {
case "disabled":
etpLevel = ContentBlocking.EtpLevel.NONE;
break;
case "standard":
etpLevel = ContentBlocking.EtpLevel.DEFAULT;
break;
case "strict":
etpLevel = ContentBlocking.EtpLevel.STRICT;
break;
default:
throw new RuntimeException("Invalid ETP level: " + value);
}
settings.getContentBlocking().setEnhancedTrackingProtectionLevel(etpLevel);
}
};
private final StringSetting mCookieBannerHandling =
new StringSetting(
R.string.key_cookie_banner_handling, R.string.cookie_banner_handling_default) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final String value) {
int cbMode;
switch (value) {
case "disabled":
cbMode = ContentBlocking.CookieBannerMode.COOKIE_BANNER_MODE_DISABLED;
break;
case "reject_all":
cbMode = ContentBlocking.CookieBannerMode.COOKIE_BANNER_MODE_REJECT;
break;
case "reject_accept_all":
cbMode = ContentBlocking.CookieBannerMode.COOKIE_BANNER_MODE_REJECT_OR_ACCEPT;
break;
default:
throw new RuntimeException("Invalid Cookie Banner Handling mode: " + value);
}
settings.getContentBlocking().setCookieBannerMode(cbMode);
}
};
private final StringSetting mCookieBannerHandlingPrivateMode =
new StringSetting(
R.string.key_cookie_banner_handling_pb, R.string.cookie_banner_handling_pb_default) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final String value) {
int cbPrivateMode;
switch (value) {
case "disabled":
cbPrivateMode = ContentBlocking.CookieBannerMode.COOKIE_BANNER_MODE_DISABLED;
break;
case "reject_all":
cbPrivateMode = ContentBlocking.CookieBannerMode.COOKIE_BANNER_MODE_REJECT;
break;
case "reject_accept_all":
cbPrivateMode = ContentBlocking.CookieBannerMode.COOKIE_BANNER_MODE_REJECT_OR_ACCEPT;
break;
default:
throw new RuntimeException("Invalid Cookie Banner Handling private mode: " + value);
}
settings.getContentBlocking().setCookieBannerModePrivateBrowsing(cbPrivateMode);
}
};
private final BooleanSetting mDynamicFirstPartyIsolation =
new BooleanSetting(R.string.key_dfpi, R.bool.dfpi_default) {
@Override
public void setValue(final GeckoRuntimeSettings settings, final Boolean value) {
int cookieBehavior =
value
? ContentBlocking.CookieBehavior.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
: ContentBlocking.CookieBehavior.ACCEPT_NON_TRACKERS;
settings.getContentBlocking().setCookieBehavior(cookieBehavior);
}
};
private final BooleanSetting mAllowAutoplay =
new BooleanSetting(
R.string.key_autoplay, R.bool.autoplay_default, /* reloadCurrentSession */ true);
private final BooleanSetting mAllowExtensionsInPrivateBrowsing =
new BooleanSetting(
R.string.key_allow_extensions_in_private_browsing,
R.bool.allow_extensions_in_private_browsing_default) {
@Override
public void setValue(final WebExtensionController controller, final Boolean value) {
controller.setAllowedInPrivateBrowsing(sExtensionManager.extension, value);
}
};
private void onPreferencesChange(SharedPreferences preferences) {
for (Setting<?> setting : SETTINGS) {
setting.onPrefChange(preferences);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// We might have been started because the user clicked on a notification
WebNotification notification = getIntent().getParcelableExtra("onClick");
if (notification != null) {
getIntent().removeExtra("onClick");
notification.click();
}
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + " - application start");
createNotificationChannel();
setContentView(R.layout.geckoview_activity);
mGeckoView = findViewById(R.id.gecko_view);
mGeckoView.setActivityContextDelegate(new ExampleActivityDelegate());
mTabSessionManager = new TabSessionManager();
setSupportActionBar(findViewById(R.id.toolbar));
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
preferences.registerOnSharedPreferenceChangeListener(this);
// Read initial preference state
onPreferencesChange(preferences);
mToolbarView = new ToolbarLayout(this, mTabSessionManager);
mToolbarView.setId(R.id.toolbar_layout);
mToolbarView.setTabListener(this);
getSupportActionBar()
.setCustomView(
mToolbarView,
new ActionBar.LayoutParams(
ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.WRAP_CONTENT));
getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
mFullAccessibilityTree = getIntent().getBooleanExtra(FULL_ACCESSIBILITY_TREE_EXTRA, false);
mProgressView = findViewById(R.id.page_progress);
if (sGeckoRuntime == null) {
final GeckoRuntimeSettings.Builder runtimeSettingsBuilder =
new GeckoRuntimeSettings.Builder();
if (BuildConfig.DEBUG) {
// In debug builds, we want to load JavaScript resources fresh with
// each build.
runtimeSettingsBuilder.arguments(new String[] {"-purgecaches"});
}
final Bundle extras = getIntent().getExtras();
if (extras != null) {
runtimeSettingsBuilder.extras(extras);
}
runtimeSettingsBuilder
.remoteDebuggingEnabled(mRemoteDebugging.value())
.consoleOutput(true)
.contentBlocking(
new ContentBlocking.Settings.Builder()
.antiTracking(
ContentBlocking.AntiTracking.DEFAULT | ContentBlocking.AntiTracking.STP)
.safeBrowsing(ContentBlocking.SafeBrowsing.DEFAULT)
.cookieBehavior(ContentBlocking.CookieBehavior.ACCEPT_NON_TRACKERS)
.cookieBehaviorPrivateMode(ContentBlocking.CookieBehavior.ACCEPT_NON_TRACKERS)
.enhancedTrackingProtectionLevel(ContentBlocking.EtpLevel.DEFAULT)
.emailTrackerBlockingPrivateMode(mEtbPrivateModeEnabled.value())
.build())
.crashHandler(ExampleCrashHandler.class)
.preferredColorScheme(mPreferredColorScheme.value())
.javaScriptEnabled(mJavascriptEnabled.value())
.extensionsProcessEnabled(mExtensionsProcessEnabled.value())
.globalPrivacyControlEnabled(mGlobalPrivacyControlEnabled.value())
.aboutConfigEnabled(true);
sGeckoRuntime = GeckoRuntime.create(this, runtimeSettingsBuilder.build());
sExtensionManager = new WebExtensionManager(sGeckoRuntime, mTabSessionManager);
mTabSessionManager.setTabObserver(sExtensionManager);
sGeckoRuntime.getWebExtensionController().setDebuggerDelegate(sExtensionManager);
sGeckoRuntime.setAutocompleteStorageDelegate(new ExampleAutocompleteStorageDelegate());
sGeckoRuntime.getOrientationController().setDelegate(new ExampleOrientationDelegate());
sGeckoRuntime.setServiceWorkerDelegate(
new GeckoRuntime.ServiceWorkerDelegate() {
@NonNull
@Override
public GeckoResult<GeckoSession> onOpenWindow(@NonNull String url) {
return mNavigationDelegate.onNewSession(null, url);
}
});
// `getSystemService` call requires API level 23
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
sGeckoRuntime.setWebNotificationDelegate(
new WebNotificationDelegate() {
NotificationManager notificationManager = getSystemService(NotificationManager.class);
@Override
public void onShowNotification(@NonNull WebNotification notification) {
Intent clickIntent = new Intent(GeckoViewActivity.this, GeckoViewActivity.class);
clickIntent.putExtra("onClick", notification);
PendingIntent dismissIntent =
PendingIntent.getActivity(
GeckoViewActivity.this, mLastID, clickIntent, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(GeckoViewActivity.this, CHANNEL_ID)
.setContentTitle(notification.title)
.setContentText(notification.text)
.setSmallIcon(R.drawable.ic_status_logo)
.setContentIntent(dismissIntent)
.setAutoCancel(true);
mNotificationIDMap.put(notification.tag, mLastID);
if (notification.imageUrl != null && notification.imageUrl.length() > 0) {
final GeckoWebExecutor executor = new GeckoWebExecutor(sGeckoRuntime);
GeckoResult<WebResponse> response =
executor.fetch(
new WebRequest.Builder(notification.imageUrl)
.addHeader("Accept", "image")
.build());
response.accept(
value -> {
Bitmap bitmap = BitmapFactory.decodeStream(value.body);
builder.setLargeIcon(bitmap);
notificationManager.notify(mLastID++, builder.build());
});
} else {
notificationManager.notify(mLastID++, builder.build());
}
}
@Override
public void onCloseNotification(@NonNull WebNotification notification) {
if (mNotificationIDMap.containsKey(notification.tag)) {
int id = mNotificationIDMap.get(notification.tag);
notificationManager.cancel(id);
mNotificationIDMap.remove(notification.tag);
}
}
});
}
sGeckoRuntime.setDelegate(
() -> {
mKillProcessOnDestroy = true;
finish();
});
sGeckoRuntime.setActivityDelegate(
pendingIntent -> {
final GeckoResult<Intent> result = new GeckoResult<>();
try {
final int code = mNextActivityResultCode++;
mPendingActivityResult.put(code, result);
GeckoViewActivity.this.startIntentSenderForResult(
pendingIntent.getIntentSender(), code, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
result.completeExceptionally(e);
}
return result;
});
}
sExtensionManager.setExtensionDelegate(this);
if (savedInstanceState == null) {
TabSession session = getIntent().getParcelableExtra("session");
if (session != null) {
connectSession(session);
if (!session.isOpen()) {
session.open(sGeckoRuntime);
}
mFullAccessibilityTree = session.getSettings().getFullAccessibilityTree();
mTabSessionManager.addSession(session);
session.open(sGeckoRuntime);
setGeckoViewSession(session);
} else {
session = createSession();
session.open(sGeckoRuntime);
mTabSessionManager.setCurrentSession(session);
mGeckoView.setSession(session);
sGeckoRuntime.getWebExtensionController().setTabActive(session, true);
}
loadFromIntent(getIntent());
}
mGeckoView.setDynamicToolbarMaxHeight(findViewById(R.id.toolbar).getLayoutParams().height);
mToolbarView.getLocationView().setCommitListener(mCommitListener);
mToolbarView.updateTabCount();
}
private void openSettingsActivity() {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
}
@Override
public TabSession getSession(GeckoSession session) {
return mTabSessionManager.getSession(session);
}
@Override
public TabSession getCurrentSession() {
return mTabSessionManager.getCurrentSession();
}
@Override
public void onActionButton(ActionButton button) {
mToolbarView.setBrowserActionButton(button);
}
@Override
public GeckoSession toggleBrowserActionPopup(boolean force) {
if (mPopupSession == null) {
openPopupSession();
}
ViewGroup.LayoutParams params = mPopupView.getLayoutParams();
boolean shouldShow = force || params.width == 0;
setViewVisibility(mPopupView, shouldShow);
return shouldShow ? mPopupSession : null;
}
private static void setViewVisibility(final View view, final boolean visible) {
if (view == null) {
return;
}
ViewGroup.LayoutParams params = view.getLayoutParams();
if (visible) {
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
params.height = 0;
params.width = 0;
}
view.setLayoutParams(params);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
onPreferencesChange(sharedPreferences);
}
private class PopupSessionContentDelegate implements GeckoSession.ContentDelegate {
@Override
public void onCloseRequest(final GeckoSession session) {
setViewVisibility(mPopupView, false);
if (mPopupSession != null) {
mPopupSession.close();
}
mPopupSession = null;
mPopupView = null;
}
}
private void openPopupSession() {
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
mPopupView = inflater.inflate(R.layout.browser_action_popup, null);
GeckoView geckoView = mPopupView.findViewById(R.id.gecko_view_popup);
geckoView.setViewBackend(GeckoView.BACKEND_TEXTURE_VIEW);
mPopupSession = new TabSession();
mPopupSession.setContentDelegate(new PopupSessionContentDelegate());
mPopupSession.open(sGeckoRuntime);
geckoView.setSession(mPopupSession);
mPopupView.setOnFocusChangeListener(this::hideBrowserAction);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(0, 0);
params.addRule(RelativeLayout.ABOVE, R.id.toolbar);
mPopupView.setLayoutParams(params);
mPopupView.setFocusable(true);
((ViewGroup) findViewById(R.id.main)).addView(mPopupView);
}
private void hideBrowserAction(View view, boolean hasFocus) {
if (!hasFocus) {
ViewGroup.LayoutParams params = mPopupView.getLayoutParams();
params.height = 0;
params.width = 0;
mPopupView.setLayoutParams(params);
}
}
private void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.app_name);
String description = getString(R.string.activity_label);
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
private TabSession createSession(final @Nullable String cookieStoreId) {
GeckoSessionSettings.Builder settingsBuilder = new GeckoSessionSettings.Builder();
settingsBuilder
.usePrivateMode(mUsePrivateBrowsing)
.fullAccessibilityTree(mFullAccessibilityTree)
.userAgentOverride(mUserAgent.value())
.viewportMode(
mDesktopMode
? GeckoSessionSettings.VIEWPORT_MODE_DESKTOP
: GeckoSessionSettings.VIEWPORT_MODE_MOBILE)
.userAgentMode(
mDesktopMode
? GeckoSessionSettings.USER_AGENT_MODE_DESKTOP
: GeckoSessionSettings.USER_AGENT_MODE_MOBILE)
.useTrackingProtection(mTrackingProtection.value())
.displayMode(mDisplayMode.value());
if (cookieStoreId != null) {
settingsBuilder.contextId(cookieStoreId);
}
TabSession session = mTabSessionManager.newSession(settingsBuilder.build());
connectSession(session);
return session;
}
private TabSession createSession() {
return createSession(null);
}
private final GeckoSession.NavigationDelegate mNavigationDelegate =
new ExampleNavigationDelegate();
private void connectSession(GeckoSession session) {
session.setContentDelegate(new ExampleContentDelegate());
session.setHistoryDelegate(new ExampleHistoryDelegate());
final ExampleContentBlockingDelegate cb = new ExampleContentBlockingDelegate();
session.setContentBlockingDelegate(cb);
session.setProgressDelegate(new ExampleProgressDelegate(cb));
session.setNavigationDelegate(mNavigationDelegate);
final BasicGeckoViewPrompt prompt = new BasicGeckoViewPrompt(this);
prompt.filePickerRequestCode = REQUEST_FILE_PICKER;
session.setPromptDelegate(prompt);
final ExamplePermissionDelegate permission = new ExamplePermissionDelegate();
permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
session.setPermissionDelegate(permission);
session.setMediaDelegate(new ExampleMediaDelegate(this));
session.setMediaSessionDelegate(new ExampleMediaSessionDelegate(this));
session.setTranslationsSessionDelegate(new ExampleTranslationsSessionDelegate());
session.setSelectionActionDelegate(new BasicSelectionActionDelegate(this));
if (sExtensionManager.extension != null) {
final WebExtension.SessionController sessionController = session.getWebExtensionController();
sessionController.setActionDelegate(sExtensionManager.extension, sExtensionManager);
sessionController.setTabDelegate(sExtensionManager.extension, sExtensionManager);
}
updateDesktopMode(session);
}
private void recreateSession() {
recreateSession(mTabSessionManager.getCurrentSession());
}
private void recreateSession(TabSession session) {
if (session != null) {
mTabSessionManager.closeSession(session);
}
session = createSession();
session.open(sGeckoRuntime);
mTabSessionManager.setCurrentSession(session);
mGeckoView.setSession(session);
sGeckoRuntime.getWebExtensionController().setTabActive(session, true);
if (mCurrentUri != null) {
session.loadUri(mCurrentUri);
}
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && mGeckoView.getSession() != null) {
mTabSessionManager.setCurrentSession((TabSession) mGeckoView.getSession());
sGeckoRuntime.getWebExtensionController().setTabActive(mGeckoView.getSession(), true);
} else {
recreateSession();
}
}
private void updateDesktopMode(GeckoSession session) {
session
.getSettings()
.setViewportMode(
mDesktopMode
? GeckoSessionSettings.VIEWPORT_MODE_DESKTOP
: GeckoSessionSettings.VIEWPORT_MODE_MOBILE);
session
.getSettings()
.setUserAgentMode(
mDesktopMode
? GeckoSessionSettings.USER_AGENT_MODE_DESKTOP
: GeckoSessionSettings.USER_AGENT_MODE_MOBILE);
}
@Override
public void onBackPressed() {
GeckoSession session = mTabSessionManager.getCurrentSession();
if (mFullScreen && session != null) {
session.exitFullScreen();
return;
}
if (mCanGoBack && session != null) {
session.goBack();
return;
}
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actions, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.action_pb).setChecked(mUsePrivateBrowsing);
menu.findItem(R.id.collapse).setChecked(mCollapsed);
menu.findItem(R.id.desktop_mode).setChecked(mDesktopMode);
menu.findItem(R.id.action_tpe)
.setChecked(
mTrackingProtectionPermission != null
&& mTrackingProtectionPermission.value == ContentPermission.VALUE_ALLOW);
menu.findItem(R.id.action_forward).setEnabled(mCanGoForward);
final boolean hasSession = mTabSessionManager.getCurrentSession() != null;
menu.findItem(R.id.action_reload).setEnabled(hasSession);
menu.findItem(R.id.action_forward).setEnabled(hasSession);
menu.findItem(R.id.action_close_tab).setEnabled(hasSession);
menu.findItem(R.id.action_tpe).setEnabled(hasSession && mTrackingProtectionPermission != null);
menu.findItem(R.id.action_pb).setEnabled(hasSession);
menu.findItem(R.id.desktop_mode).setEnabled(hasSession);
menu.findItem(R.id.translate).setVisible(mExpectedTranslate);
menu.findItem(R.id.translate_restore).setVisible(mTranslateRestore);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
GeckoSession session = mTabSessionManager.getCurrentSession();
switch (item.getItemId()) {
case R.id.action_reload:
session.reload();
break;
case R.id.action_forward:
session.goForward();
break;
case R.id.action_tpe:
sGeckoRuntime
.getStorageController()
.setPermission(
mTrackingProtectionPermission,
mTrackingProtectionPermission.value == ContentPermission.VALUE_ALLOW
? ContentPermission.VALUE_DENY
: ContentPermission.VALUE_ALLOW);
session.reload();
break;
case R.id.desktop_mode:
mDesktopMode = !mDesktopMode;
updateDesktopMode(session);
session.reload();
break;
case R.id.action_pb:
mUsePrivateBrowsing = !mUsePrivateBrowsing;
recreateSession();
break;
case R.id.collapse:
mCollapsed = !mCollapsed;
setViewVisibility(mGeckoView, !mCollapsed);
break;
case R.id.install_addon:
installAddon();
break;
case R.id.update_addon:
updateAddon();
break;
case R.id.settings:
openSettingsActivity();
break;
case R.id.action_new_tab:
createNewTab();
break;
case R.id.action_close_tab:
closeTab((TabSession) session);
break;
case R.id.save_pdf:
savePdf(session);
break;
case R.id.print_page:
printPage(session);
break;
case R.id.shopping_actions:
shoppingActions(session, mCurrentUri);
break;
case R.id.translate:
translate(session);
break;
case R.id.translate_restore:
translateRestore(session);
break;
case R.id.translate_manage:
translateManage();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
private void installAddon() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.install_addon);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setHint(R.string.install_addon_hint);
builder.setView(input);
builder.setPositiveButton(
R.string.install,
(dialog, which) -> {
final String uri = input.getText().toString();
// We only suopport one extension at a time, so remove the currently installed
// extension if there is one
setViewVisibility(mPopupView, false);
mPopupView = null;
mPopupSession = null;
sExtensionManager
.unregisterExtension()
.then(
unused -> {
final WebExtensionController controller =
sGeckoRuntime.getWebExtensionController();
controller.setPromptDelegate(sExtensionManager);
return controller.install(uri, null);
})
.then(
extension ->
sGeckoRuntime
.getWebExtensionController()
.setAllowedInPrivateBrowsing(
extension, mAllowExtensionsInPrivateBrowsing.value()))
.accept(extension -> sExtensionManager.registerExtension(extension));
});
builder.setNegativeButton(
R.string.cancel,
(dialog, which) -> {
// Nothing to do
});
builder.show();
}
private void updateAddon() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.update_addon);
sExtensionManager
.updateExtension()
.accept(
extension -> {
if (extension != null) {
builder.setMessage("Success");
} else {
builder.setMessage("No addon to update");
}
builder.show();
},
exception -> {
builder.setMessage("Failed: " + exception);
builder.show();
});
}
private void createNewTab() {
Double startTime = sGeckoRuntime.getProfilerController().getProfilerTime();
TabSession newSession = createSession();
newSession.open(sGeckoRuntime);
setGeckoViewSession(newSession);
mToolbarView.updateTabCount();
sGeckoRuntime.getProfilerController().addMarker("Create new tab", startTime);
}
@SuppressLint("WrongThread")
@UiThread
private void savePdf(GeckoSession session) {
session
.saveAsPdf()
.accept(
pdfStream -> {
try {
WebResponse response =
new WebResponse.Builder(null)
.body(pdfStream)
.addHeader("Content-Type", "application/pdf")
.addHeader("Content-Disposition", "attachment; filename=PDFDownload.pdf")
.build();
session.getContentDelegate().onExternalResponse(session, response);
} catch (Exception e) {
Log.d(LOGTAG, e.getMessage());
}
});
}
private void printPage(GeckoSession session) {
session.didPrintPageContent();
}
private void translate(GeckoSession session) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.translate);
Spinner fromSelect = new Spinner(this);
Spinner toSelect = new Spinner(this);
// Set spinners with data
TranslationsController.RuntimeTranslation.listSupportedLanguages()
.then(
supportedLanguages -> {
// Just a check if sorting is working on the Language object by reversing, Languages
// should generally come from the API in the display order.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Collections.reverse(supportedLanguages.fromLanguages);
}
ArrayAdapter<TranslationsController.Language> fromData =
new ArrayAdapter<TranslationsController.Language>(
this.getBaseContext(),
android.R.layout.simple_spinner_item,
supportedLanguages.fromLanguages);
fromSelect.setAdapter(fromData);
// Set detected language
final int index =
fromData.getPosition(
new TranslationsController.Language(mDetectedLanguage, null));
fromSelect.setSelection(index);
ArrayAdapter<TranslationsController.Language> toData =
new ArrayAdapter<TranslationsController.Language>(
this.getBaseContext(),
android.R.layout.simple_spinner_item,
supportedLanguages.toLanguages);
toSelect.setAdapter(toData);
// Set preferred language
TranslationsController.RuntimeTranslation.preferredLanguages()
.then(
preferredList -> {
Log.d(LOGTAG, "Preferred Translation Languages: " + preferredList);
// Reorder dropdown listing based on preferences
for (int i = preferredList.size() - 1; i >= 0; i--) {
final int langIndex =
toData.getPosition(
new TranslationsController.Language(preferredList.get(i), null));
TranslationsController.Language displayLanguage =
toData.getItem(langIndex);
toData.remove(displayLanguage);
toData.insert(displayLanguage, 0);
if (i == 0) {
toSelect.setSelection(0);
}
}
return null;
});
return null;
});
builder.setView(
translateLayout(
fromSelect,
R.string.translate_language_from_hint,
toSelect,
R.string.translate_language_to_hint,
-1));
builder.setPositiveButton(
R.string.translate_action,
(dialog, which) -> {
final TranslationsController.Language fromLang =
(TranslationsController.Language) fromSelect.getSelectedItem();
final TranslationsController.Language toLang =
(TranslationsController.Language) toSelect.getSelectedItem();
session.getSessionTranslation().translate(fromLang.code, toLang.code, null);
mTranslateRestore = true;
});
builder.setNegativeButton(
R.string.cancel,
(dialog, which) -> {
// Nothing to do
});
builder.show();
}
private void translateRestore(GeckoSession session) {
session
.getSessionTranslation()
.restoreOriginalPage()
.then(
new GeckoResult.OnValueListener<Void, Object>() {
@Nullable
@Override
public GeckoResult<Object> onValue(@Nullable Void value) throws Throwable {
mTranslateRestore = false;
return null;
}
});
}
private void translateManage() {
Spinner languageSelect = new Spinner(this);
Spinner operationSelect = new Spinner(this);
// Should match ModelOperation choices
List<String> operationChoices =
new ArrayList<>(
Arrays.asList(
new String[] {
TranslationsController.RuntimeTranslation.DELETE.toString(),
TranslationsController.RuntimeTranslation.DOWNLOAD.toString()
}));
ArrayAdapter<String> operationData =
new ArrayAdapter<String>(
this.getBaseContext(), android.R.layout.simple_spinner_item, operationChoices);
operationSelect.setAdapter(operationData);
// Get current model states
GeckoResult<List<TranslationsController.RuntimeTranslation.LanguageModel>> currentStates =
TranslationsController.RuntimeTranslation.listModelDownloadStates();
currentStates.then(
models -> {
List<TranslationsController.Language> languages =
new ArrayList<TranslationsController.Language>();
// Pseudo container of "all" just to simplify spinner for GVE
languages.add(new TranslationsController.Language("all", "All Models"));
for (var model : models) {
Log.i(LOGTAG, "Translate Model State: " + model);
languages.add(model.language);
}
ArrayAdapter<TranslationsController.Language> languageData =
new ArrayAdapter<TranslationsController.Language>(
this.getBaseContext(), android.R.layout.simple_spinner_item, languages);
languageSelect.setAdapter(languageData);
return null;
});
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.translate_manage);
builder.setView(
translateLayout(
languageSelect,
R.string.translate_manage_languages,
operationSelect,
R.string.translate_manage_operations,
R.string.translate_display_hint));
builder.setPositiveButton(
R.string.translate_manage_action,
(dialog, which) -> {
final TranslationsController.Language selectedLanguage =
(TranslationsController.Language) languageSelect.getSelectedItem();
final String operation = (String) operationSelect.getSelectedItem();
String operationLevel = TranslationsController.RuntimeTranslation.LANGUAGE;
// Pseudo option for ease of GVE
if (selectedLanguage.code.equals("all")) {
operationLevel = TranslationsController.RuntimeTranslation.ALL;
}
TranslationsController.RuntimeTranslation.ModelManagementOptions options =
new TranslationsController.RuntimeTranslation.ModelManagementOptions.Builder()
.languageToManage(selectedLanguage.code)
.operation(operation)
.operationLevel(operationLevel)
.build();
// Complete Operation
GeckoResult<Void> requestOperation =
TranslationsController.RuntimeTranslation.manageLanguageModel(options);
requestOperation.then(
opt -> {
// Log Changes
GeckoResult<List<TranslationsController.RuntimeTranslation.LanguageModel>>
reportChanges =
TranslationsController.RuntimeTranslation.listModelDownloadStates();
reportChanges.then(
models -> {
for (var model : models) {
Log.i(LOGTAG, "Translate Model State: " + model);
}
return null;
});
return null;
});
});
builder.setNegativeButton(
R.string.cancel,
(dialog, which) -> {
// Nothing to do
});
builder.show();
}
private RelativeLayout translateLayout(
Spinner spinnerA, int labelA, Spinner spinnerB, int labelB, int labelInfo) {
// From fields
TextView fromLangLabel = new TextView(this);
fromLangLabel.setText(labelA);
LinearLayout from = new LinearLayout(this);
from.setId(View.generateViewId());
from.addView(fromLangLabel);
from.addView(spinnerA);
RelativeLayout.LayoutParams fromParams =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
fromParams.setMarginStart(30);
// To fields
TextView toLangLabel = new TextView(this);
toLangLabel.setText(labelB);
LinearLayout to = new LinearLayout(this);
to.setId(View.generateViewId());
to.addView(toLangLabel);
to.addView(spinnerB);
RelativeLayout.LayoutParams toParams =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
toParams.setMarginStart(30);
toParams.addRule(RelativeLayout.BELOW, from.getId());
// Layout
RelativeLayout layout = new RelativeLayout(this);
layout.addView(from, fromParams);
layout.addView(to, toParams);
// Hint
TextView info = new TextView(this);
if (labelInfo != -1) {
RelativeLayout.LayoutParams infoParams =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
infoParams.setMarginStart(30);
infoParams.addRule(RelativeLayout.BELOW, to.getId());
info.setText(labelInfo);
layout.addView(info, infoParams);
}
return layout;
}
@Override
public void closeTab(TabSession session) {
mTabSessionManager.closeSession(session);
TabSession tabSession = mTabSessionManager.getCurrentSession();
setGeckoViewSession(tabSession);
if (tabSession != null) {
tabSession.reload();
}
mToolbarView.updateTabCount();
}
@Override
public void updateTab(TabSession session, WebExtension.UpdateTabDetails details) {
if (details.active == Boolean.TRUE) {
switchToSession(session, false);
}
}
public void onBrowserActionClick() {
sExtensionManager.onClicked(mTabSessionManager.getCurrentSession());
}
public void switchToSession(TabSession session, boolean activateTab) {
TabSession currentSession = mTabSessionManager.getCurrentSession();
if (session != currentSession) {
setGeckoViewSession(session, activateTab);
mCurrentUri = session.getUri();
if (!session.isOpen()) {
// Session's process was previously killed; reopen
session.open(sGeckoRuntime);
session.loadUri(mCurrentUri);
}
mToolbarView.getLocationView().setText(mCurrentUri);
}
}
public void switchToTab(int index) {
TabSession nextSession = mTabSessionManager.getSession(index);
switchToSession(nextSession, true);
}
private void setGeckoViewSession(TabSession session) {
setGeckoViewSession(session, true);
}
private void setGeckoViewSession(TabSession session, boolean activateTab) {
final WebExtensionController controller = sGeckoRuntime.getWebExtensionController();
final GeckoSession previousSession = mGeckoView.getSession();
if (previousSession != null) {
controller.setTabActive(previousSession, false);
}
final boolean hasSession = session != null;
final LocationView view = mToolbarView.getLocationView();
// No point having the URL bar enabled if there's no session to navigate to
view.setEnabled(hasSession);
if (hasSession) {
mGeckoView.setSession(session);
if (activateTab) {
controller.setTabActive(session, true);
}
mTabSessionManager.setCurrentSession(session);
} else {
mGeckoView.coverUntilFirstPaint(Color.WHITE);
view.setText("");
}
}
@Override
public void onDestroy() {
if (mKillProcessOnDestroy) {
android.os.Process.killProcess(android.os.Process.myPid());
}
super.onDestroy();
}
@Override
protected void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
if (ACTION_SHUTDOWN.equals(intent.getAction())) {
mKillProcessOnDestroy = true;
if (sGeckoRuntime != null) {
sGeckoRuntime.shutdown();
}
finish();
return;
}
if (intent.hasExtra("onClick")) {
WebNotification notification = intent.getExtras().getParcelable("onClick");
if (notification != null) {
intent.removeExtra("onClick");
notification.click();
}
}
setIntent(intent);
if (intent.getData() != null) {
loadFromIntent(intent);
}
}
private void loadFromIntent(final Intent intent) {
final Uri uri = intent.getData();
if (uri != null) {
mTabSessionManager
.getCurrentSession()
.load(
new GeckoSession.Loader()
.uri(uri.toString())
.flags(GeckoSession.LOAD_FLAGS_EXTERNAL));
}
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == REQUEST_FILE_PICKER) {
final BasicGeckoViewPrompt prompt =
(BasicGeckoViewPrompt) mTabSessionManager.getCurrentSession().getPromptDelegate();
prompt.onFileCallbackResult(resultCode, data);
} else if (mPendingActivityResult.containsKey(requestCode)) {
final GeckoResult<Intent> result = mPendingActivityResult.remove(requestCode);
if (resultCode == Activity.RESULT_OK) {
result.complete(data);
} else {
result.completeExceptionally(new RuntimeException("Unknown error"));
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onRequestPermissionsResult(
final int requestCode, final String[] permissions, final int[] grantResults) {
if (requestCode == REQUEST_PERMISSIONS) {
final ExamplePermissionDelegate permission =
(ExamplePermissionDelegate)
mTabSessionManager.getCurrentSession().getPermissionDelegate();
permission.onRequestPermissionsResult(permissions, grantResults);
} else if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
continueDownloads();
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void continueDownloads() {
final LinkedList<WebResponse> downloads = mPendingDownloads;
mPendingDownloads = new LinkedList<>();
for (final WebResponse response : downloads) {
downloadFile(response);
}
}
private void downloadFile(final WebResponse response) {
if (response.body == null) {
return;
}
if (ContextCompat.checkSelfPermission(
GeckoViewActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
mPendingDownloads.add(response);
ActivityCompat.requestPermissions(
GeckoViewActivity.this,
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_WRITE_EXTERNAL_STORAGE);
return;
}
final String filename = getFileName(response);
try {
String downloadsPath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getAbsolutePath()
+ "/"
+ filename;
Log.i(LOGTAG, "Downloading to: " + downloadsPath);
int bufferSize = 1024; // to read in 1Mb increments
byte[] buffer = new byte[bufferSize];
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(downloadsPath))) {
int len;
while ((len = response.body.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (Throwable e) {
Log.i(LOGTAG, String.valueOf(e.getStackTrace()));
}
} catch (Throwable e) {
Log.i(LOGTAG, String.valueOf(e.getStackTrace()));
}
}
private String getFileName(final WebResponse response) {
String filename;
String contentDispositionHeader;
if (response.headers.containsKey("content-disposition")) {
contentDispositionHeader = response.headers.get("content-disposition");
} else {
contentDispositionHeader =
response.headers.getOrDefault("Content-Disposition", "default filename=GVDownload");
}
Pattern pattern = Pattern.compile("(filename=\"?)(.+)(\"?)");
Matcher matcher = pattern.matcher(contentDispositionHeader);
if (matcher.find()) {
filename = matcher.group(2).replaceAll("\\s", "%20");
} else {
filename = "GVEdownload";
}
return filename;
}
private static boolean isForeground() {
final ActivityManager.RunningAppProcessInfo appProcessInfo =
new ActivityManager.RunningAppProcessInfo();
ActivityManager.getMyMemoryState(appProcessInfo);
return appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
|| appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
}
private String mErrorTemplate;
private String createErrorPage(final String error) {
if (mErrorTemplate == null) {
InputStream stream = null;
BufferedReader reader = null;
StringBuilder builder = new StringBuilder();
try {
stream = getResources().getAssets().open("error.html");
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append("\n");
}
mErrorTemplate = builder.toString();
} catch (IOException e) {
Log.d(LOGTAG, "Failed to open error page template", e);
return null;
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template stream", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template reader", e);
}
}
}
}
return mErrorTemplate.replace("$ERROR", error);
}
private class ExampleHistoryDelegate implements GeckoSession.HistoryDelegate {
private final HashSet<String> mVisitedURLs;
private ExampleHistoryDelegate() {
mVisitedURLs = new HashSet<String>();
}
@Override
public GeckoResult<Boolean> onVisited(
GeckoSession session, String url, String lastVisitedURL, int flags) {
Log.i(LOGTAG, "Visited URL: " + url);
mVisitedURLs.add(url);
return GeckoResult.fromValue(true);
}
@Override
public GeckoResult<boolean[]> getVisited(GeckoSession session, String[] urls) {
boolean[] visited = new boolean[urls.length];
for (int i = 0; i < urls.length; i++) {
visited[i] = mVisitedURLs.contains(urls[i]);
}
return GeckoResult.fromValue(visited);
}
@Override
public void onHistoryStateChange(
final GeckoSession session, final GeckoSession.HistoryDelegate.HistoryList state) {
Log.i(LOGTAG, "History state updated");
}
}
private class ExampleAutocompleteStorageDelegate implements Autocomplete.StorageDelegate {
private Map<String, Autocomplete.LoginEntry> mStorage = new HashMap<>();
@Nullable
@Override
public GeckoResult<Autocomplete.LoginEntry[]> onLoginFetch() {
return GeckoResult.fromValue(mStorage.values().toArray(new Autocomplete.LoginEntry[0]));
}
@Override
public void onLoginSave(@NonNull Autocomplete.LoginEntry login) {
mStorage.put(login.guid, login);
}
}
private class ExampleOrientationDelegate implements OrientationController.OrientationDelegate {
@Override
public GeckoResult<AllowOrDeny> onOrientationLock(@NonNull int aOrientation) {
setRequestedOrientation(aOrientation);
return GeckoResult.allow();
}
@Override
public void onOrientationUnlock() {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
private class ExampleContentDelegate implements GeckoSession.ContentDelegate {
@Override
public void onTitleChange(GeckoSession session, String title) {
Log.i(LOGTAG, "Content title changed to " + title);
TabSession tabSession = mTabSessionManager.getSession(session);
if (tabSession != null) {
tabSession.setTitle(title);
}
}
@Override
public void onFullScreen(final GeckoSession session, final boolean fullScreen) {
getWindow()
.setFlags(
fullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
mFullScreen = fullScreen;
if (fullScreen) {
getSupportActionBar().hide();
} else {
getSupportActionBar().show();
}
}
@Override
public void onFocusRequest(final GeckoSession session) {
Log.i(LOGTAG, "Content requesting focus");
}
@Override
public void onCloseRequest(final GeckoSession session) {
final TabSession currentSession = mTabSessionManager.getCurrentSession();
if (session == currentSession) {
closeTab(currentSession);
}
}
@Override
public void onContextMenu(
final GeckoSession session, int screenX, int screenY, final ContextElement element) {
Log.d(
LOGTAG,
"onContextMenu screenX="
+ screenX
+ " screenY="
+ screenY
+ " type="
+ element.type
+ " linkUri="
+ element.linkUri
+ " title="
+ element.title
+ " alt="
+ element.altText
+ " srcUri="
+ element.srcUri);
}
@Override
public void onExternalResponse(@NonNull GeckoSession session, @NonNull WebResponse response) {
downloadFile(response);
}
@Override
public void onCrash(GeckoSession session) {
Log.e(LOGTAG, "Crashed, reopening session");
session.open(sGeckoRuntime);
}
@Override
public void onKill(GeckoSession session) {
TabSession tabSession = mTabSessionManager.getSession(session);
if (tabSession == null) {
return;
}
if (tabSession != mTabSessionManager.getCurrentSession()) {
Log.e(LOGTAG, "Background session killed");
return;
}
if (isForeground()) {
throw new IllegalStateException("Foreground content process unexpectedly killed by OS!");
}
Log.e(LOGTAG, "Current session killed, reopening");
tabSession.open(sGeckoRuntime);
tabSession.loadUri(tabSession.getUri());
}
@Override
public void onFirstComposite(final GeckoSession session) {
Log.d(LOGTAG, "onFirstComposite");
}
@Override
public void onWebAppManifest(final GeckoSession session, JSONObject manifest) {
Log.d(LOGTAG, "onWebAppManifest: " + manifest);
}
private boolean activeAlert = false;
@Override
public GeckoResult<SlowScriptResponse> onSlowScript(
final GeckoSession geckoSession, final String scriptFileName) {
BasicGeckoViewPrompt prompt =
(BasicGeckoViewPrompt) mTabSessionManager.getCurrentSession().getPromptDelegate();
if (prompt != null) {
GeckoResult<SlowScriptResponse> result = new GeckoResult<SlowScriptResponse>();
if (!activeAlert) {
activeAlert = true;
prompt.onSlowScriptPrompt(geckoSession, getString(R.string.slow_script), result);
}
return result.then(
value -> {
activeAlert = false;
return GeckoResult.fromValue(value);
});
}
return null;
}
@Override
public void onMetaViewportFitChange(final GeckoSession session, final String viewportFit) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return;
}
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
if (viewportFit.equals("cover")) {
layoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
} else if (viewportFit.equals("contain")) {
layoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
} else {
layoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
}
getWindow().setAttributes(layoutParams);
}
@Override
public void onProductUrl(@NonNull final GeckoSession session) {
Log.d("Gecko", "onProductUrl");
}
@Override
public void onShowDynamicToolbar(final GeckoSession session) {
final View toolbar = findViewById(R.id.toolbar);
if (toolbar != null) {
toolbar.setTranslationY(0f);
mGeckoView.setVerticalClipping(0);
}
}
@Override
public void onCookieBannerDetected(final GeckoSession session) {
Log.d("BELL", "A cookie banner was detected on this website");
}
@Override
public void onCookieBannerHandled(final GeckoSession session) {
Log.d("BELL", "A cookie banner was handled on this website");
}
}
private class ExampleProgressDelegate implements GeckoSession.ProgressDelegate {
private ExampleContentBlockingDelegate mCb;
private ExampleProgressDelegate(final ExampleContentBlockingDelegate cb) {
mCb = cb;
}
@Override
public void onPageStart(GeckoSession session, String url) {
Log.i(LOGTAG, "Starting to load page at " + url);
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + " - page load start");
mCb.clearCounters();
mExpectedTranslate = false;
mTranslateRestore = false;
}
@Override
public void onPageStop(GeckoSession session, boolean success) {
Log.i(LOGTAG, "Stopping page load " + (success ? "successfully" : "unsuccessfully"));
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + " - page load stop");
mCb.logCounters();
}
@Override
public void onProgressChange(GeckoSession session, int progress) {
Log.i(LOGTAG, "onProgressChange " + progress);
mProgressView.setProgress(progress);
if (progress > 0 && progress < 100) {
mProgressView.setVisibility(View.VISIBLE);
} else {
mProgressView.setVisibility(View.GONE);
}
}
@Override
public void onSecurityChange(GeckoSession session, SecurityInformation securityInfo) {
Log.i(LOGTAG, "Security status changed to " + securityInfo.securityMode);
}
@Override
public void onSessionStateChange(GeckoSession session, GeckoSession.SessionState state) {
Log.i(LOGTAG, "New Session state: " + state.toString());
}
}
private class ExamplePermissionDelegate implements PermissionDelegate {
public int androidPermissionRequestCode = 1;
private Callback mCallback;
class ExampleNotificationCallback implements PermissionDelegate.Callback {
private final PermissionDelegate.Callback mCallback;
ExampleNotificationCallback(final PermissionDelegate.Callback callback) {
mCallback = callback;
}
@Override
public void reject() {
mShowNotificationsRejected = true;
mCallback.reject();
}
@Override
public void grant() {
mShowNotificationsRejected = false;
mCallback.grant();
}
}
class ExamplePersistentStorageCallback implements PermissionDelegate.Callback {
private final PermissionDelegate.Callback mCallback;
private final String mUri;
ExamplePersistentStorageCallback(final PermissionDelegate.Callback callback, String uri) {
mCallback = callback;
mUri = uri;
}
@Override
public void reject() {
mCallback.reject();
}
@Override
public void grant() {
mAcceptedPersistentStorage.add(mUri);
mCallback.grant();
}
}
public void onRequestPermissionsResult(final String[] permissions, final int[] grantResults) {
if (mCallback == null) {
return;
}
final Callback cb = mCallback;
mCallback = null;
for (final int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
// At least one permission was not granted.
cb.reject();
return;
}
}
cb.grant();
}
@Override
public void onAndroidPermissionsRequest(
final GeckoSession session, final String[] permissions, final Callback callback) {
if (Build.VERSION.SDK_INT >= 23) {
// requestPermissions was introduced in API 23.
mCallback = callback;
requestPermissions(permissions, androidPermissionRequestCode);
} else {
callback.grant();
}
}
@Override
public GeckoResult<Integer> onContentPermissionRequest(
final GeckoSession session, final ContentPermission perm) {
final int resId;
switch (perm.permission) {
case PERMISSION_GEOLOCATION:
resId = R.string.request_geolocation;
break;
case PERMISSION_DESKTOP_NOTIFICATION:
resId = R.string.request_notification;
break;
case PERMISSION_PERSISTENT_STORAGE:
resId = R.string.request_storage;
break;
case PERMISSION_XR:
resId = R.string.request_xr;
break;
case PERMISSION_AUTOPLAY_AUDIBLE:
case PERMISSION_AUTOPLAY_INAUDIBLE:
if (!mAllowAutoplay.value()) {
return GeckoResult.fromValue(ContentPermission.VALUE_DENY);
} else {
return GeckoResult.fromValue(ContentPermission.VALUE_ALLOW);
}
case PERMISSION_MEDIA_KEY_SYSTEM_ACCESS:
resId = R.string.request_media_key_system_access;
break;
case PERMISSION_STORAGE_ACCESS:
resId = R.string.request_storage_access;
break;
default:
return GeckoResult.fromValue(ContentPermission.VALUE_DENY);
}
final String title = getString(resId, Uri.parse(perm.uri).getAuthority());
final BasicGeckoViewPrompt prompt =
(BasicGeckoViewPrompt) mTabSessionManager.getCurrentSession().getPromptDelegate();
return prompt.onPermissionPrompt(session, title, perm);
}
private String[] normalizeMediaName(final MediaSource[] sources) {
if (sources == null) {
return null;
}
String[] res = new String[sources.length];
for (int i = 0; i < sources.length; i++) {
final int mediaSource = sources[i].source;
final String name = sources[i].name;
if (MediaSource.SOURCE_CAMERA == mediaSource) {
if (name.toLowerCase(Locale.ROOT).contains("front")) {
res[i] = getString(R.string.media_front_camera);
} else {
res[i] = getString(R.string.media_back_camera);
}
} else if (!name.isEmpty()) {
res[i] = name;
} else if (MediaSource.SOURCE_MICROPHONE == mediaSource) {
res[i] = getString(R.string.media_microphone);
} else {
res[i] = getString(R.string.media_other);
}
}
return res;
}
@Override
public void onMediaPermissionRequest(
final GeckoSession session,
final String uri,
final MediaSource[] video,
final MediaSource[] audio,
final MediaCallback callback) {
// If we don't have device permissions at this point, just automatically reject the request
// as we will have already have requested device permissions before getting to this point
// and if we've reached here and we don't have permissions then that means that the user
// denied them.
if ((audio != null
&& ContextCompat.checkSelfPermission(
GeckoViewActivity.this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED)
|| (video != null
&& ContextCompat.checkSelfPermission(
GeckoViewActivity.this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED)) {
callback.reject();
return;
}
final String host = Uri.parse(uri).getAuthority();
final String title;
if (audio == null) {
title = getString(R.string.request_video, host);
} else if (video == null) {
title = getString(R.string.request_audio, host);
} else {
title = getString(R.string.request_media, host);
}
String[] videoNames = normalizeMediaName(video);
String[] audioNames = normalizeMediaName(audio);
final BasicGeckoViewPrompt prompt =
(BasicGeckoViewPrompt) mTabSessionManager.getCurrentSession().getPromptDelegate();
prompt.onMediaPrompt(session, title, video, audio, videoNames, audioNames, callback);
}
}
private ContentPermission getTrackingProtectionPermission(final List<ContentPermission> perms) {
for (ContentPermission perm : perms) {
if (perm.permission == PermissionDelegate.PERMISSION_TRACKING) {
return perm;
}
}
return null;
}
public void shoppingActions(@NonNull final GeckoSession session, @NonNull final String url) {
Spinner actionSelect = new Spinner(this);
List<String> actions =
new ArrayList<>(
Arrays.asList(
new String[] {
"Get Analysis",
"Get Recommendations",
"Create Analysis",
"Get Analysis Status",
"Poll Until Analysis Completed",
"Report Back in Stock",
}));
ArrayAdapter<String> actionData =
new ArrayAdapter<String>(
this.getBaseContext(), android.R.layout.simple_spinner_item, actions);
actionSelect.setAdapter(actionData);
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.shopping_actions);
builder.setView(
shoppingLayout(
actionSelect, R.string.shopping_manage_actions, R.string.shopping_display_log));
builder.setPositiveButton(
R.string.shopping_query,
(dialog, which) -> {
final String action = (String) actionSelect.getSelectedItem();
switch (action) {
case "Get Analysis":
requestAnalysis(session, url);
break;
case "Get Recommendations":
requestRecommendations(session, url);
break;
case "Create Analysis":
requestCreateAnalysis(session, url);
break;
case "Get Analysis Status":
requestAnalysisCreationStatus(session, url);
break;
case "Poll Until Analysis Completed":
pollForAnalysisCompleted(session, url);
break;
case "Report Back in Stock":
reportBackInStock(session, url);
break;
default:
throw new RuntimeException("Unknown action: " + action);
}
});
builder.setNegativeButton(
R.string.cancel,
(dialog, which) -> {
// Nothing to do
});
builder.show();
}
private RelativeLayout shoppingLayout(Spinner spinnerA, int labelA, int labelInfo) {
TextView fromLangLabel = new TextView(this);
fromLangLabel.setText(labelA);
LinearLayout action = new LinearLayout(this);
action.setId(View.generateViewId());
action.addView(fromLangLabel);
action.addView(spinnerA);
RelativeLayout.LayoutParams actionParams =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
actionParams.setMarginStart(30);
// Layout
RelativeLayout layout = new RelativeLayout(this);
layout.addView(action, actionParams);
// Hint
TextView info = new TextView(this);
if (labelInfo != -1) {
RelativeLayout.LayoutParams infoParams =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
infoParams.setMarginStart(30);
infoParams.addRule(RelativeLayout.BELOW, action.getId());
info.setText(labelInfo);
layout.addView(info, infoParams);
}
return layout;
}
public void requestAnalysis(@NonNull final GeckoSession session, @NonNull final String url) {
GeckoResult<GeckoSession.ReviewAnalysis> result = session.requestAnalysis(url);
result.map(
analysis -> {
Log.d(LOGTAG, "Shopping Action: Get analysis: " + analysis);
return analysis;
});
}
public void requestCreateAnalysis(
@NonNull final GeckoSession session, @NonNull final String url) {
GeckoResult<String> result = session.requestCreateAnalysis(url);
result.map(
status -> {
Log.d(LOGTAG, "Shopping Action: Create analysis, status: " + status);
return status;
});
}
public void requestAnalysisCreationStatus(
@NonNull final GeckoSession session, @NonNull final String url) {
GeckoResult<GeckoSession.AnalysisStatusResponse> result = session.requestAnalysisStatus(url);
result.map(
status -> {
Log.d(LOGTAG, "Shopping Action: Get analysis status: " + status.status);
Log.d(LOGTAG, "Shopping Action: Get analysis status Progress: " + status.progress);
return status;
});
}
public void pollForAnalysisCompleted(
@NonNull final GeckoSession session, @NonNull final String url) {
Log.d(LOGTAG, "Shopping Action: Poll until analysis completed");
GeckoResult<String> result = session.pollForAnalysisCompleted(url);
result.map(
status -> {
Log.d(LOGTAG, "Shopping Action: Get analysis status: " + status);
return status;
});
}
public void reportBackInStock(@NonNull final GeckoSession session, @NonNull final String url) {
Log.d(LOGTAG, "Shopping Action: Report back in stock");
GeckoResult<String> result = session.reportBackInStock(url);
result.map(
message -> {
Log.d(LOGTAG, "Shopping Action: Back in stock status: " + message);
return message;
});
}
public void requestRecommendations(
@NonNull final GeckoSession session, @NonNull final String url) {
GeckoResult<List<GeckoSession.Recommendation>> result = session.requestRecommendations(url);
result.map(
recs -> {
List<String> aids = new ArrayList<>();
for (int i = 0; i < recs.size(); ++i) {
aids.add(recs.get(i).aid);
}
if (aids.size() >= 1) {
Log.d(
LOGTAG, "Shopping Action: Sending attribution events to first AID: " + aids.get(0));
session
.sendClickAttributionEvent(aids.get(0))
.then(
new GeckoResult.OnValueListener<Boolean, Void>() {
@Override
public GeckoResult<Void> onValue(final Boolean isSuccessful) {
Log.d(
LOGTAG,
"Shopping Action: Success of click attribution event: " + isSuccessful);
return null;
}
});
session
.sendImpressionAttributionEvent(aids.get(0))
.then(
new GeckoResult.OnValueListener<Boolean, Void>() {
@Override
public GeckoResult<Void> onValue(final Boolean isSuccessful) {
Log.d(
LOGTAG,
"Shopping Action: Success of impression attribution event: "
+ isSuccessful);
return null;
}
});
session
.sendPlacementAttributionEvent(aids.get(0))
.then(
new GeckoResult.OnValueListener<Boolean, Void>() {
@Override
public GeckoResult<Void> onValue(final Boolean isSuccessful) {
Log.d(
LOGTAG,
"Shopping Action: Success of placement attribution event: "
+ isSuccessful);
return null;
}
});
} else {
Log.d(LOGTAG, "Shopping Action: No recommendations. No attribution events were sent.");
}
return recs;
});
}
private class ExampleNavigationDelegate implements GeckoSession.NavigationDelegate {
@Override
public void onLocationChange(
GeckoSession session,
final String url,
final List<ContentPermission> perms,
Boolean hasUserGesture) {
mToolbarView.getLocationView().setText(url);
TabSession tabSession = mTabSessionManager.getSession(session);
if (tabSession != null) {
tabSession.onLocationChange(url);
}
mTrackingProtectionPermission = getTrackingProtectionPermission(perms);
mCurrentUri = url;
}
@Override
public void onCanGoBack(GeckoSession session, boolean canGoBack) {
mCanGoBack = canGoBack;
}
@Override
public void onCanGoForward(GeckoSession session, boolean canGoForward) {
mCanGoForward = canGoForward;
}
@Override
public GeckoResult<AllowOrDeny> onLoadRequest(
final GeckoSession session, final LoadRequest request) {
Log.d(
LOGTAG,
"onLoadRequest="
+ request.uri
+ " triggerUri="
+ request.triggerUri
+ " where="
+ request.target
+ " isRedirect="
+ request.isRedirect
+ " isDirectNavigation="
+ request.isDirectNavigation);
return GeckoResult.allow();
}
@Override
public GeckoResult<AllowOrDeny> onSubframeLoadRequest(
final GeckoSession session, final LoadRequest request) {
Log.d(
LOGTAG,
"onSubframeLoadRequest="
+ request.uri
+ " triggerUri="
+ request.triggerUri
+ " isRedirect="
+ request.isRedirect
+ "isDirectNavigation="
+ request.isDirectNavigation);
return GeckoResult.allow();
}
@Override
public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
final TabSession newSession = createSession();
mToolbarView.updateTabCount();
setGeckoViewSession(newSession);
// A reference to newSession is stored by mTabSessionManager,
// which prevents the session from being garbage-collected.
return GeckoResult.fromValue(newSession);
}
private String categoryToString(final int category) {
switch (category) {
case WebRequestError.ERROR_CATEGORY_UNKNOWN:
return "ERROR_CATEGORY_UNKNOWN";
case WebRequestError.ERROR_CATEGORY_SECURITY:
return "ERROR_CATEGORY_SECURITY";
case WebRequestError.ERROR_CATEGORY_NETWORK:
return "ERROR_CATEGORY_NETWORK";
case WebRequestError.ERROR_CATEGORY_CONTENT:
return "ERROR_CATEGORY_CONTENT";
case WebRequestError.ERROR_CATEGORY_URI:
return "ERROR_CATEGORY_URI";
case WebRequestError.ERROR_CATEGORY_PROXY:
return "ERROR_CATEGORY_PROXY";
case WebRequestError.ERROR_CATEGORY_SAFEBROWSING:
return "ERROR_CATEGORY_SAFEBROWSING";
default:
return "UNKNOWN";
}
}
private String errorToString(final int error) {
switch (error) {
case WebRequestError.ERROR_UNKNOWN:
return "ERROR_UNKNOWN";
case WebRequestError.ERROR_SECURITY_SSL:
return "ERROR_SECURITY_SSL";
case WebRequestError.ERROR_SECURITY_BAD_CERT:
return "ERROR_SECURITY_BAD_CERT";
case WebRequestError.ERROR_NET_RESET:
return "ERROR_NET_RESET";
case WebRequestError.ERROR_NET_INTERRUPT:
return "ERROR_NET_INTERRUPT";
case WebRequestError.ERROR_NET_TIMEOUT:
return "ERROR_NET_TIMEOUT";
case WebRequestError.ERROR_CONNECTION_REFUSED:
return "ERROR_CONNECTION_REFUSED";
case WebRequestError.ERROR_UNKNOWN_PROTOCOL:
return "ERROR_UNKNOWN_PROTOCOL";
case WebRequestError.ERROR_UNKNOWN_HOST:
return "ERROR_UNKNOWN_HOST";
case WebRequestError.ERROR_UNKNOWN_SOCKET_TYPE:
return "ERROR_UNKNOWN_SOCKET_TYPE";
case WebRequestError.ERROR_UNKNOWN_PROXY_HOST:
return "ERROR_UNKNOWN_PROXY_HOST";
case WebRequestError.ERROR_MALFORMED_URI:
return "ERROR_MALFORMED_URI";
case WebRequestError.ERROR_REDIRECT_LOOP:
return "ERROR_REDIRECT_LOOP";
case WebRequestError.ERROR_SAFEBROWSING_PHISHING_URI:
return "ERROR_SAFEBROWSING_PHISHING_URI";
case WebRequestError.ERROR_SAFEBROWSING_MALWARE_URI:
return "ERROR_SAFEBROWSING_MALWARE_URI";
case WebRequestError.ERROR_SAFEBROWSING_UNWANTED_URI:
return "ERROR_SAFEBROWSING_UNWANTED_URI";
case WebRequestError.ERROR_SAFEBROWSING_HARMFUL_URI:
return "ERROR_SAFEBROWSING_HARMFUL_URI";
case WebRequestError.ERROR_CONTENT_CRASHED:
return "ERROR_CONTENT_CRASHED";
case WebRequestError.ERROR_OFFLINE:
return "ERROR_OFFLINE";
case WebRequestError.ERROR_PORT_BLOCKED:
return "ERROR_PORT_BLOCKED";
case WebRequestError.ERROR_PROXY_CONNECTION_REFUSED:
return "ERROR_PROXY_CONNECTION_REFUSED";
case WebRequestError.ERROR_FILE_NOT_FOUND:
return "ERROR_FILE_NOT_FOUND";
case WebRequestError.ERROR_FILE_ACCESS_DENIED:
return "ERROR_FILE_ACCESS_DENIED";
case WebRequestError.ERROR_INVALID_CONTENT_ENCODING:
return "ERROR_INVALID_CONTENT_ENCODING";
case WebRequestError.ERROR_UNSAFE_CONTENT_TYPE:
return "ERROR_UNSAFE_CONTENT_TYPE";
case WebRequestError.ERROR_CORRUPTED_CONTENT:
return "ERROR_CORRUPTED_CONTENT";
case WebRequestError.ERROR_HTTPS_ONLY:
return "ERROR_HTTPS_ONLY";
case WebRequestError.ERROR_BAD_HSTS_CERT:
return "ERROR_BAD_HSTS_CERT";
default:
return "UNKNOWN";
}
}
private String createErrorPage(final int category, final int error) {
if (mErrorTemplate == null) {
InputStream stream = null;
BufferedReader reader = null;
StringBuilder builder = new StringBuilder();
try {
stream = getResources().getAssets().open("error.html");
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append("\n");
}
mErrorTemplate = builder.toString();
} catch (IOException e) {
Log.d(LOGTAG, "Failed to open error page template", e);
return null;
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template stream", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
Log.e(LOGTAG, "Failed to close error page template reader", e);
}
}
}
}
return GeckoViewActivity.this.createErrorPage(
categoryToString(category) + " : " + errorToString(error));
}
@Override
public GeckoResult<String> onLoadError(
final GeckoSession session, final String uri, final WebRequestError error) {
Log.d(
LOGTAG,
"onLoadError=" + uri + " error category=" + error.category + " error=" + error.code);
return GeckoResult.fromValue("data:text/html," + createErrorPage(error.category, error.code));
}
}
private class ExampleContentBlockingDelegate implements ContentBlocking.Delegate {
private int mBlockedAds = 0;
private int mBlockedAnalytics = 0;
private int mBlockedSocial = 0;
private int mBlockedContent = 0;
private int mBlockedTest = 0;
private int mBlockedStp = 0;
private void clearCounters() {
mBlockedAds = 0;
mBlockedAnalytics = 0;
mBlockedSocial = 0;
mBlockedContent = 0;
mBlockedTest = 0;
mBlockedStp = 0;
}
private void logCounters() {
Log.d(
LOGTAG,
"Trackers blocked: "
+ mBlockedAds
+ " ads, "
+ mBlockedAnalytics
+ " analytics, "
+ mBlockedSocial
+ " social, "
+ mBlockedContent
+ " content, "
+ mBlockedTest
+ " test, "
+ mBlockedStp
+ "stp");
}
@Override
public void onContentBlocked(
final GeckoSession session, final ContentBlocking.BlockEvent event) {
Log.d(
LOGTAG,
"onContentBlocked"
+ " AT: "
+ event.getAntiTrackingCategory()
+ " SB: "
+ event.getSafeBrowsingCategory()
+ " CB: "
+ event.getCookieBehaviorCategory()
+ " URI: "
+ event.uri);
if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.TEST) != 0) {
mBlockedTest++;
}
if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.AD) != 0) {
mBlockedAds++;
}
if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.ANALYTIC) != 0) {
mBlockedAnalytics++;
}
if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.SOCIAL) != 0) {
mBlockedSocial++;
}
if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.CONTENT) != 0) {
mBlockedContent++;
}
if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.STP) != 0) {
mBlockedStp++;
}
}
@Override
public void onContentLoaded(
final GeckoSession session, final ContentBlocking.BlockEvent event) {
Log.d(
LOGTAG,
"onContentLoaded"
+ " AT: "
+ event.getAntiTrackingCategory()
+ " SB: "
+ event.getSafeBrowsingCategory()
+ " CB: "
+ event.getCookieBehaviorCategory()
+ " URI: "
+ event.uri);
}
}
private class ExampleMediaDelegate implements GeckoSession.MediaDelegate {
private Integer mLastNotificationId = 100;
private Integer mNotificationId;
private final Activity mActivity;
public ExampleMediaDelegate(Activity activity) {
mActivity = activity;
}
@Override
public void onRecordingStatusChanged(@NonNull GeckoSession session, RecordingDevice[] devices) {
String message;
int icon;
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mActivity);
RecordingDevice camera = null;
RecordingDevice microphone = null;
for (RecordingDevice device : devices) {
if (device.type == RecordingDevice.Type.CAMERA) {
camera = device;
} else if (device.type == RecordingDevice.Type.MICROPHONE) {
microphone = device;
}
}
if (camera != null && microphone != null) {
Log.d(LOGTAG, "ExampleDeviceDelegate:onRecordingDeviceEvent display alert_mic_camera");
message = getResources().getString(R.string.device_sharing_camera_and_mic);
icon = R.drawable.alert_mic_camera;
} else if (camera != null) {
Log.d(LOGTAG, "ExampleDeviceDelegate:onRecordingDeviceEvent display alert_camera");
message = getResources().getString(R.string.device_sharing_camera);
icon = R.drawable.alert_camera;
} else if (microphone != null) {
Log.d(LOGTAG, "ExampleDeviceDelegate:onRecordingDeviceEvent display alert_mic");
message = getResources().getString(R.string.device_sharing_microphone);
icon = R.drawable.alert_mic;
} else {
Log.d(LOGTAG, "ExampleDeviceDelegate:onRecordingDeviceEvent dismiss any notifications");
if (mNotificationId != null) {
notificationManager.cancel(mNotificationId);
mNotificationId = null;
}
return;
}
if (mNotificationId == null) {
mNotificationId = ++mLastNotificationId;
}
Intent intent = new Intent(mActivity, GeckoViewActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent =
PendingIntent.getActivity(
mActivity.getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(mActivity.getApplicationContext(), CHANNEL_ID)
.setSmallIcon(icon)
.setContentTitle(getResources().getString(R.string.app_name))
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setCategory(NotificationCompat.CATEGORY_SERVICE);
notificationManager.notify(mNotificationId, builder.build());
}
}
private class ExampleTranslationsSessionDelegate
implements TranslationsController.SessionTranslation.Delegate {
@Override
public void onOfferTranslate(@NonNull GeckoSession session) {
Log.i(LOGTAG, "onOfferTranslate");
}
@Override
public void onExpectedTranslate(@NonNull GeckoSession session) {
Log.i(LOGTAG, "onExpectedTranslate");
mExpectedTranslate = true;
}
@Override
public void onTranslationStateChange(
@NonNull GeckoSession session,
@Nullable TranslationsController.SessionTranslation.TranslationState translationState) {
Log.i(LOGTAG, "onTranslationStateChange");
if (translationState.detectedLanguages != null) {
mDetectedLanguage = translationState.detectedLanguages.docLangTag;
}
}
}
private class ExampleMediaSessionDelegate implements MediaSession.Delegate {
private final Activity mActivity;
public ExampleMediaSessionDelegate(Activity activity) {
mActivity = activity;
}
@Override
public void onFullscreen(
@NonNull final GeckoSession session,
@NonNull final MediaSession mediaSession,
final boolean enabled,
@Nullable final MediaSession.ElementMetadata meta) {
Log.d(LOGTAG, "onFullscreen: Metadata=" + (meta != null ? meta.toString() : "null"));
if (!enabled) {
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
return;
}
if (meta == null) {
return;
}
if (meta.width > meta.height) {
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
} else {
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
}
}
}
private class ExampleActivityDelegate implements GeckoView.ActivityContextDelegate {
public Context getActivityContext() {
return GeckoViewActivity.this;
}
}
}