summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/GeckoApp.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoApp.java2878
1 files changed, 0 insertions, 2878 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
deleted file mode 100644
index 05fa2bbf8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ /dev/null
@@ -1,2878 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.gecko;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.gfx.FullScreenState;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.health.HealthRecorder;
-import org.mozilla.gecko.health.SessionInformation;
-import org.mozilla.gecko.health.StubbedHealthRecorder;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.GeckoMenuInflater;
-import org.mozilla.gecko.menu.MenuPanel;
-import org.mozilla.gecko.notifications.NotificationClient;
-import org.mozilla.gecko.notifications.NotificationHelper;
-import org.mozilla.gecko.util.IntentUtils;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.preferences.ClearOnShutdownPref;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.prompts.PromptService;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.text.FloatingToolbarTextSelection;
-import org.mozilla.gecko.text.TextSelection;
-import org.mozilla.gecko.updater.UpdateServiceHelper;
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.ActivityUtils;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.FileUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.PrefUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.Sensor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.StrictMode;
-import android.provider.ContactsContract;
-import android.provider.MediaStore.Images.Media;
-import android.support.annotation.WorkerThread;
-import android.support.design.widget.Snackbar;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Base64;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.Window;
-import android.widget.AbsoluteLayout;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.ListView;
-import android.widget.RelativeLayout;
-import android.widget.SimpleAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-public abstract class GeckoApp
- extends GeckoActivity
- implements
- ContextGetter,
- GeckoAppShell.GeckoInterface,
- GeckoEventListener,
- GeckoMenu.Callback,
- GeckoMenu.MenuPresenter,
- NativeEventListener,
- Tabs.OnTabsChangedListener,
- ViewTreeObserver.OnGlobalLayoutListener {
-
- private static final String LOGTAG = "GeckoApp";
- private static final long ONE_DAY_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
-
- public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ALERT_CALLBACK";
- public static final String ACTION_HOMESCREEN_SHORTCUT = "org.mozilla.gecko.BOOKMARK";
- public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG";
- public static final String ACTION_LAUNCH_SETTINGS = "org.mozilla.gecko.SETTINGS";
- public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD";
- public static final String ACTION_INIT_PW = "org.mozilla.gecko.INIT_PW";
- public static final String ACTION_SWITCH_TAB = "org.mozilla.gecko.SWITCH_TAB";
-
- public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
-
- public static final String EXTRA_STATE_BUNDLE = "stateBundle";
-
- public static final String LAST_SELECTED_TAB = "lastSelectedTab";
-
- public static final String PREFS_ALLOW_STATE_BUNDLE = "allowStateBundle";
- public static final String PREFS_VERSION_CODE = "versionCode";
- public static final String PREFS_WAS_STOPPED = "wasStopped";
- public static final String PREFS_CRASHED_COUNT = "crashedCount";
- public static final String PREFS_CLEANUP_TEMP_FILES = "cleanupTempFiles";
-
- public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
- public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
-
- // Delay before running one-time "cleanup" tasks that may be needed
- // after a version upgrade.
- private static final int CLEANUP_DEFERRAL_SECONDS = 15;
-
- private static boolean sAlreadyLoaded;
-
- private static WeakReference<GeckoApp> lastActiveGeckoApp;
-
- protected RelativeLayout mRootLayout;
- protected RelativeLayout mMainLayout;
-
- protected RelativeLayout mGeckoLayout;
- private OrientationEventListener mCameraOrientationEventListener;
- public List<GeckoAppShell.AppStateListener> mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
- protected MenuPanel mMenuPanel;
- protected Menu mMenu;
- protected boolean mIsRestoringActivity;
-
- /** Tells if we're aborting app launch, e.g. if this is an unsupported device configuration. */
- protected boolean mIsAbortingAppLaunch;
-
- private PromptService mPromptService;
- protected TextSelection mTextSelection;
-
- protected DoorHangerPopup mDoorHangerPopup;
- protected FormAssistPopup mFormAssistPopup;
-
-
- protected GeckoView mLayerView;
- private AbsoluteLayout mPluginContainer;
-
- private FullScreenHolder mFullScreenPluginContainer;
- private View mFullScreenPluginView;
-
- private final HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
-
- protected boolean mLastSessionCrashed;
- protected boolean mShouldRestore;
- private boolean mSessionRestoreParsingFinished = false;
-
- private EventDispatcher eventDispatcher;
-
- private int lastSelectedTabId = -1;
-
- private static final class LastSessionParser extends SessionParser {
- private JSONArray tabs;
- private JSONObject windowObject;
- private boolean isExternalURL;
-
- private boolean selectNextTab;
- private boolean tabsWereSkipped;
- private boolean tabsWereProcessed;
-
- public LastSessionParser(JSONArray tabs, JSONObject windowObject, boolean isExternalURL) {
- this.tabs = tabs;
- this.windowObject = windowObject;
- this.isExternalURL = isExternalURL;
- }
-
- public boolean allTabsSkipped() {
- return tabsWereSkipped && !tabsWereProcessed;
- }
-
- @Override
- public void onTabRead(final SessionTab sessionTab) {
- if (sessionTab.isAboutHomeWithoutHistory()) {
- // This is a tab pointing to about:home with no history. We won't restore
- // this tab. If we end up restoring no tabs then the browser will decide
- // whether it needs to open about:home or a different 'homepage'. If we'd
- // always restore about:home only tabs then we'd never open the homepage.
- // See bug 1261008.
-
- if (sessionTab.isSelected()) {
- // Unfortunately this tab is the selected tab. Let's just try to select
- // the first tab. If we haven't restored any tabs so far then remember
- // to select the next tab that gets restored.
-
- if (!Tabs.getInstance().selectLastTab()) {
- selectNextTab = true;
- }
- }
-
- // Do not restore this tab.
- tabsWereSkipped = true;
- return;
- }
-
- tabsWereProcessed = true;
-
- JSONObject tabObject = sessionTab.getTabObject();
-
- int flags = Tabs.LOADURL_NEW_TAB;
- flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
- flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
- flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
-
- final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
-
- if (selectNextTab) {
- // We did not restore the selected tab previously. Now let's select this tab.
- Tabs.getInstance().selectTab(tab.getId());
- selectNextTab = false;
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- tab.updateTitle(sessionTab.getTitle());
- }
- });
-
- try {
- tabObject.put("tabId", tab.getId());
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
- tabs.put(tabObject);
- }
-
- @Override
- public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
- windowObject.put("closedTabs", closedTabData);
- }
- };
-
- protected boolean mInitialized;
- protected boolean mWindowFocusInitialized;
- private Telemetry.Timer mJavaUiStartupTimer;
- private Telemetry.Timer mGeckoReadyStartupTimer;
-
- private String mPrivateBrowsingSession;
-
- private volatile HealthRecorder mHealthRecorder;
- private volatile Locale mLastLocale;
-
- protected Intent mRestartIntent;
-
- private boolean mWasFirstTabShownAfterActivityUnhidden;
-
- abstract public int getLayout();
-
- protected void processTabQueue() {};
-
- protected void openQueuedTabs() {};
-
- @SuppressWarnings("serial")
- class SessionRestoreException extends Exception {
- public SessionRestoreException(Exception e) {
- super(e);
- }
-
- public SessionRestoreException(String message) {
- super(message);
- }
- }
-
- void toggleChrome(final boolean aShow) { }
-
- void focusChrome() { }
-
- @Override
- public Context getContext() {
- return this;
- }
-
- @Override
- public SharedPreferences getSharedPreferences() {
- return GeckoSharedPrefs.forApp(this);
- }
-
- @Override
- public Activity getActivity() {
- return this;
- }
-
- @Override
- public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
- mAppStateListeners.add(listener);
- }
-
- @Override
- public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
- mAppStateListeners.remove(listener);
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- // When a tab is closed, it is always unselected first.
- // When a tab is unselected, another tab is always selected first.
- switch (msg) {
- case UNSELECTED:
- break;
-
- case LOCATION_CHANGE:
- // We only care about location change for the selected tab.
- if (!Tabs.getInstance().isSelectedTab(tab))
- break;
- // Fall through...
- case SELECTED:
- invalidateOptionsMenu();
- if (mFormAssistPopup != null)
- mFormAssistPopup.hide();
- break;
-
- case DESKTOP_MODE_CHANGE:
- if (Tabs.getInstance().isSelectedTab(tab))
- invalidateOptionsMenu();
- break;
- }
- }
-
- public void refreshChrome() { }
-
- @Override
- public void invalidateOptionsMenu() {
- if (mMenu == null) {
- return;
- }
-
- onPrepareOptionsMenu(mMenu);
-
- super.invalidateOptionsMenu();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- mMenu = menu;
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.gecko_app_menu, mMenu);
- return true;
- }
-
- @Override
- public MenuInflater getMenuInflater() {
- return new GeckoMenuInflater(this);
- }
-
- public MenuPanel getMenuPanel() {
- if (mMenuPanel == null) {
- onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
- invalidateOptionsMenu();
- }
- return mMenuPanel;
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- return onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onMenuItemLongClick(MenuItem item) {
- return false;
- }
-
- @Override
- public void openMenu() {
- openOptionsMenu();
- }
-
- @Override
- public void showMenu(final View menu) {
- // On devices using the custom menu, focus is cleared from the menu when its tapped.
- // Close and then reshow it to avoid these issues. See bug 794581 and bug 968182.
- closeMenu();
-
- // Post the reshow code back to the UI thread to avoid some optimizations Android
- // has put in place for menus that hide/show themselves quickly. See bug 985400.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mMenuPanel.removeAllViews();
- mMenuPanel.addView(menu);
- openOptionsMenu();
- }
- });
- }
-
- @Override
- public void closeMenu() {
- closeOptionsMenu();
- }
-
- @Override
- public View onCreatePanelView(int featureId) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- if (mMenuPanel == null) {
- mMenuPanel = new MenuPanel(this, null);
- } else {
- // Prepare the panel every time before showing the menu.
- onPreparePanel(featureId, mMenuPanel, mMenu);
- }
-
- return mMenuPanel;
- }
-
- return super.onCreatePanelView(featureId);
- }
-
- @Override
- public boolean onCreatePanelMenu(int featureId, Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- if (mMenuPanel == null) {
- mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
- }
-
- GeckoMenu gMenu = new GeckoMenu(this, null);
- gMenu.setCallback(this);
- gMenu.setMenuPresenter(this);
- menu = gMenu;
- mMenuPanel.addView(gMenu);
-
- return onCreateOptionsMenu(menu);
- }
-
- return super.onCreatePanelMenu(featureId, menu);
- }
-
- @Override
- public boolean onPreparePanel(int featureId, View view, Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- return onPrepareOptionsMenu(menu);
- }
-
- return super.onPreparePanel(featureId, view, menu);
- }
-
- @Override
- public boolean onMenuOpened(int featureId, Menu menu) {
- // exit full-screen mode whenever the menu is opened
- if (mLayerView != null && mLayerView.isFullScreen()) {
- GeckoAppShell.notifyObservers("FullScreen:Exit", null);
- }
-
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- if (mMenu == null) {
- // getMenuPanel() will force the creation of the menu as well
- MenuPanel panel = getMenuPanel();
- onPreparePanel(featureId, panel, mMenu);
- }
-
- // Scroll custom menu to the top
- if (mMenuPanel != null)
- mMenuPanel.scrollTo(0, 0);
-
- return true;
- }
-
- return super.onMenuOpened(featureId, menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == R.id.quit) {
- // Make sure the Guest Browsing notification goes away when we quit.
- GuestSession.hideNotification(this);
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
- final Set<String> clearSet =
- PrefUtils.getStringSet(prefs, ClearOnShutdownPref.PREF, new HashSet<String>());
-
- final JSONObject clearObj = new JSONObject();
- for (String clear : clearSet) {
- try {
- clearObj.put(clear, true);
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Error adding clear object " + clear, ex);
- }
- }
-
- final JSONObject res = new JSONObject();
- try {
- res.put("sanitize", clearObj);
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Error adding sanitize object", ex);
- }
-
- // If the user has opted out of session restore, and does want to clear history
- // we also want to prevent the current session info from being saved.
- if (clearObj.has("private.data.history")) {
- final String sessionRestore = getSessionRestorePreference(getSharedPreferences());
- try {
- res.put("dontSaveSession", "quit".equals(sessionRestore));
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Error adding session restore data", ex);
- }
- }
-
- GeckoAppShell.notifyObservers("Browser:Quit", res.toString());
- // We don't call doShutdown() here because this creates a race condition which can
- // cause the clearing of private data to fail. Instead, we shut down the UI only after
- // we're done sanitizing.
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onOptionsMenuClosed(Menu menu) {
- mMenuPanel.removeAllViews();
- mMenuPanel.addView((GeckoMenu) mMenu);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Handle hardware menu key presses separately so that we can show a custom menu in some cases.
- if (keyCode == KeyEvent.KEYCODE_MENU) {
- openOptionsMenu();
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
- outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
- outState.putInt(LAST_SELECTED_TAB, lastSelectedTabId);
- }
-
- @Override
- protected void onRestoreInstanceState(final Bundle inState) {
- lastSelectedTabId = inState.getInt(LAST_SELECTED_TAB);
- }
-
- public void addTab() { }
-
- public void addPrivateTab() { }
-
- public void showNormalTabs() { }
-
- public void showPrivateTabs() { }
-
- public void hideTabs() { }
-
- /**
- * Close the tab UI indirectly (not as the result of a direct user
- * action). This does not force the UI to close; for example in Firefox
- * tablet mode it will remain open unless the user explicitly closes it.
- *
- * @return True if the tab UI was hidden.
- */
- public boolean autoHideTabs() { return false; }
-
- @Override
- public boolean areTabsShown() { return false; }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message,
- final EventCallback callback) {
- if ("Accessibility:Ready".equals(event)) {
- GeckoAccessibility.updateAccessibilitySettings(this);
-
- } else if ("Bookmark:Insert".equals(event)) {
- final String url = message.getString("url");
- final String title = message.getString("title");
- final Context context = this;
- final BrowserDB db = BrowserDB.from(getProfile());
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final boolean bookmarkAdded = db.addBookmark(getContentResolver(), title, url);
- final int resId = bookmarkAdded ? R.string.bookmark_added : R.string.bookmark_already_added;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- SnackbarBuilder.builder(GeckoApp.this)
- .message(resId)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- });
- }
- });
-
- } else if ("Contact:Add".equals(event)) {
- final String email = message.optString("email", null);
- final String phone = message.optString("phone", null);
- if (email != null) {
- Uri contactUri = Uri.parse(email);
- Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
- startActivity(i);
- } else if (phone != null) {
- Uri contactUri = Uri.parse(phone);
- Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
- startActivity(i);
- } else {
- // something went wrong.
- Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number");
- }
-
- } else if ("DevToolsAuth:Scan".equals(event)) {
- DevToolsAuthHelper.scan(this, callback);
-
- } else if ("DOMFullScreen:Start".equals(event)) {
- // Local ref to layerView for thread safety
- LayerView layerView = mLayerView;
- if (layerView != null) {
- layerView.setFullScreenState(message.getBoolean("rootElement")
- ? FullScreenState.ROOT_ELEMENT : FullScreenState.NON_ROOT_ELEMENT);
- }
-
- } else if ("DOMFullScreen:Stop".equals(event)) {
- // Local ref to layerView for thread safety
- LayerView layerView = mLayerView;
- if (layerView != null) {
- layerView.setFullScreenState(FullScreenState.NONE);
- }
-
- } else if ("Image:SetAs".equals(event)) {
- String src = message.getString("url");
- setImageAs(src);
-
- } else if ("Locale:Set".equals(event)) {
- setLocale(message.getString("locale"));
-
- } else if ("Permissions:Data".equals(event)) {
- final NativeJSObject[] permissions = message.getObjectArray("permissions");
- showSiteSettingsDialog(permissions);
-
- } else if ("PrivateBrowsing:Data".equals(event)) {
- mPrivateBrowsingSession = message.optString("session", null);
-
- } else if ("Session:StatePurged".equals(event)) {
- onStatePurged();
-
- } else if ("Sanitize:Finished".equals(event)) {
- if (message.getBoolean("shutdown")) {
- // Gecko is shutting down and has called our sanitize handlers,
- // so we can start exiting, too.
- doShutdown();
- }
-
- } else if ("Share:Text".equals(event)) {
- final String text = message.getString("text");
- final Tab tab = Tabs.getInstance().getSelectedTab();
- String title = "";
- if (tab != null) {
- title = tab.getDisplayTitle();
- }
- IntentHelper.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title, false);
-
- // Context: Sharing via chrome list (no explicit session is active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "text");
-
- } else if ("Snackbar:Show".equals(event)) {
- SnackbarBuilder.builder(this)
- .fromEvent(message)
- .callback(callback)
- .buildAndShow();
-
- } else if ("SystemUI:Visibility".equals(event)) {
- setSystemUiVisible(message.getBoolean("visible"));
-
- } else if ("ToggleChrome:Focus".equals(event)) {
- focusChrome();
-
- } else if ("ToggleChrome:Hide".equals(event)) {
- toggleChrome(false);
-
- } else if ("ToggleChrome:Show".equals(event)) {
- toggleChrome(true);
-
- } else if ("Update:Check".equals(event)) {
- UpdateServiceHelper.checkForUpdate(this);
- } else if ("Update:Download".equals(event)) {
- UpdateServiceHelper.downloadUpdate(this);
- } else if ("Update:Install".equals(event)) {
- UpdateServiceHelper.applyUpdate(this);
- } else if ("RuntimePermissions:Prompt".equals(event)) {
- String[] permissions = message.getStringArray("permissions");
- if (callback == null || permissions == null) {
- return;
- }
-
- Permissions.from(this)
- .withPermissions(permissions)
- .andFallback(new Runnable() {
- @Override
- public void run() {
- callback.sendSuccess(false);
- }
- })
- .run(new Runnable() {
- @Override
- public void run() {
- callback.sendSuccess(true);
- }
- });
- }
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals("Gecko:Ready")) {
- mGeckoReadyStartupTimer.stop();
- geckoConnected();
-
- // This method is already running on the background thread, so we
- // know that mHealthRecorder will exist. That doesn't stop us being
- // paranoid.
- // This method is cheap, so don't spawn a new runnable.
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
- }
-
- GeckoApplication.get().onDelayedStartup();
-
- } else if (event.equals("Gecko:Exited")) {
- // Gecko thread exited first; let GeckoApp die too.
- doShutdown();
- return;
-
- } else if (event.equals("Accessibility:Event")) {
- GeckoAccessibility.sendAccessibilityEvent(message);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- void onStatePurged() { }
-
- /**
- * @param permissions
- * Array of JSON objects to represent site permissions.
- * Example: { type: "offline-app", setting: "Store Offline Data", value: "Allow" }
- */
- private void showSiteSettingsDialog(final NativeJSObject[] permissions) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.site_settings_title);
-
- final ArrayList<HashMap<String, String>> itemList =
- new ArrayList<HashMap<String, String>>();
- for (final NativeJSObject permObj : permissions) {
- final HashMap<String, String> map = new HashMap<String, String>();
- map.put("setting", permObj.getString("setting"));
- map.put("value", permObj.getString("value"));
- itemList.add(map);
- }
-
- // setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with
- // setSingleChoiceItems and changing the choiceMode below when we create the dialog
- builder.setSingleChoiceItems(new SimpleAdapter(
- GeckoApp.this,
- itemList,
- R.layout.site_setting_item,
- new String[] { "setting", "value" },
- new int[] { R.id.setting, R.id.value }
- ), -1, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) { }
- });
-
- builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- ListView listView = ((AlertDialog) dialog).getListView();
- SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions();
-
- // An array of the indices of the permissions we want to clear
- JSONArray permissionsToClear = new JSONArray();
- for (int i = 0; i < checkedItemPositions.size(); i++)
- if (checkedItemPositions.get(i))
- permissionsToClear.put(i);
-
- GeckoAppShell.notifyObservers("Permissions:Clear", permissionsToClear.toString());
- }
- });
-
- builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- AlertDialog dialog = builder.create();
- dialog.show();
-
- final ListView listView = dialog.getListView();
- if (listView != null) {
- listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- }
-
- final Button clearButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- clearButton.setEnabled(false);
-
- dialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
- if (listView.getCheckedItemCount() == 0) {
- clearButton.setEnabled(false);
- } else {
- clearButton.setEnabled(true);
- }
- }
- });
- }
- });
- }
-
-
-
- /* package */ void addFullScreenPluginView(View view) {
- if (mFullScreenPluginView != null) {
- Log.w(LOGTAG, "Already have a fullscreen plugin view");
- return;
- }
-
- setFullScreen(true);
-
- view.setWillNotDraw(false);
- if (view instanceof SurfaceView) {
- ((SurfaceView) view).setZOrderOnTop(true);
- }
-
- mFullScreenPluginContainer = new FullScreenHolder(this);
-
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- Gravity.CENTER);
- mFullScreenPluginContainer.addView(view, layoutParams);
-
-
- FrameLayout decor = (FrameLayout)getWindow().getDecorView();
- decor.addView(mFullScreenPluginContainer, layoutParams);
-
- mFullScreenPluginView = view;
- }
-
- @Override
- public void addPluginView(final View view) {
-
- if (ThreadUtils.isOnUiThread()) {
- addFullScreenPluginView(view);
- } else {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- addFullScreenPluginView(view);
- }
- });
- }
- }
-
- /* package */ void removeFullScreenPluginView(View view) {
- if (mFullScreenPluginView == null) {
- Log.w(LOGTAG, "Don't have a fullscreen plugin view");
- return;
- }
-
- if (mFullScreenPluginView != view) {
- Log.w(LOGTAG, "Passed view is not the current full screen view");
- return;
- }
-
- mFullScreenPluginContainer.removeView(mFullScreenPluginView);
-
- // We need do do this on the next iteration in order to avoid
- // a deadlock, see comment below in FullScreenHolder
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mLayerView.showSurface();
- }
- });
-
- FrameLayout decor = (FrameLayout)getWindow().getDecorView();
- decor.removeView(mFullScreenPluginContainer);
-
- mFullScreenPluginView = null;
-
- GeckoScreenOrientation.getInstance().unlock();
- setFullScreen(false);
- }
-
- @Override
- public void removePluginView(final View view) {
- if (ThreadUtils.isOnUiThread()) {
- removePluginView(view);
- } else {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- removeFullScreenPluginView(view);
- }
- });
- }
- }
-
- // This method starts downloading an image synchronously and displays the Chooser activity to set the image as wallpaper.
- private void setImageAs(final String aSrc) {
- boolean isDataURI = aSrc.startsWith("data:");
- Bitmap image = null;
- InputStream is = null;
- ByteArrayOutputStream os = null;
- try {
- if (isDataURI) {
- int dataStart = aSrc.indexOf(",");
- byte[] buf = Base64.decode(aSrc.substring(dataStart + 1), Base64.DEFAULT);
- image = BitmapUtils.decodeByteArray(buf);
- } else {
- int byteRead;
- byte[] buf = new byte[4192];
- os = new ByteArrayOutputStream();
- URL url = new URL(aSrc);
- is = url.openStream();
-
- // Cannot read from same stream twice. Also, InputStream from
- // URL does not support reset. So converting to byte array.
-
- while ((byteRead = is.read(buf)) != -1) {
- os.write(buf, 0, byteRead);
- }
- byte[] imgBuffer = os.toByteArray();
- image = BitmapUtils.decodeByteArray(imgBuffer);
- }
- if (image != null) {
- // Some devices don't have a DCIM folder and the Media.insertImage call will fail.
- File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
-
- if (!dcimDir.mkdirs() && !dcimDir.isDirectory()) {
- SnackbarBuilder.builder(this)
- .message(R.string.set_image_path_fail)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- return;
- }
- String path = Media.insertImage(getContentResolver(), image, null, null);
- if (path == null) {
- SnackbarBuilder.builder(this)
- .message(R.string.set_image_path_fail)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- return;
- }
- final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setData(Uri.parse(path));
-
- // Removes the image from storage once the chooser activity ends.
- Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title));
- ActivityResultHandler handler = new ActivityResultHandler() {
- @Override
- public void onActivityResult (int resultCode, Intent data) {
- getContentResolver().delete(intent.getData(), null, null);
- }
- };
- ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
- } else {
- SnackbarBuilder.builder(this)
- .message(R.string.set_image_fail)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- } catch (OutOfMemoryError ome) {
- Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
- } catch (IOException ioe) {
- Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ioe) {
- Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
- }
- }
- if (os != null) {
- try {
- os.close();
- } catch (IOException ioe) {
- Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
- }
- }
- }
- }
-
- private int getBitmapSampleSize(BitmapFactory.Options options, int idealWidth, int idealHeight) {
- int width = options.outWidth;
- int height = options.outHeight;
- int inSampleSize = 1;
- if (height > idealHeight || width > idealWidth) {
- if (width > height) {
- inSampleSize = Math.round((float)height / idealHeight);
- } else {
- inSampleSize = Math.round((float)width / idealWidth);
- }
- }
- return inSampleSize;
- }
-
- public void requestRender() {
- mLayerView.requestRender();
- }
-
- @Override
- public void setFullScreen(final boolean fullscreen) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- ActivityUtils.setFullScreen(GeckoApp.this, fullscreen);
- }
- });
- }
-
- /**
- * Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified.
- **/
- protected static void earlyStartJavaSampler(SafeIntent intent) {
- String env = intent.getStringExtra("env0");
- for (int i = 1; env != null; i++) {
- if (env.startsWith("MOZ_PROFILER_STARTUP=")) {
- if (!env.endsWith("=")) {
- GeckoJavaSampler.start(10, 1000);
- Log.d(LOGTAG, "Profiling Java on startup");
- }
- break;
- }
- env = intent.getStringExtra("env" + i);
- }
- }
-
- /**
- * Called when the activity is first created.
- *
- * Here we initialize all of our profile settings, Firefox Health Report,
- * and other one-shot constructions.
- **/
- @Override
- public void onCreate(Bundle savedInstanceState) {
- GeckoAppShell.ensureCrashHandling();
-
- eventDispatcher = new EventDispatcher();
-
- // Enable Android Strict Mode for developers' local builds (the "default" channel).
- if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
- enableStrictMode();
- }
-
- if (!HardwareUtils.isSupportedSystem()) {
- // This build does not support the Android version of the device: Show an error and finish the app.
- mIsAbortingAppLaunch = true;
- super.onCreate(savedInstanceState);
- showSDKVersionError();
- finish();
- return;
- }
-
- // The clock starts...now. Better hurry!
- mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
- mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
-
- final SafeIntent intent = new SafeIntent(getIntent());
-
- earlyStartJavaSampler(intent);
-
- // GeckoLoader wants to dig some environment variables out of the
- // incoming intent, so pass it in here. GeckoLoader will do its
- // business later and dispose of the reference.
- GeckoLoader.setLastIntent(intent);
-
- // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
- try {
- Class.forName("android.os.AsyncTask");
- } catch (ClassNotFoundException e) { }
-
- MemoryMonitor.getInstance().init(getApplicationContext());
-
- // GeckoAppShell is tightly coupled to us, rather than
- // the app context, because various parts of Fennec (e.g.,
- // GeckoScreenOrientation) use GAS to access the Activity in
- // the guise of fetching a Context.
- // When that's fixed, `this` can change to
- // `(GeckoApplication) getApplication()` here.
- GeckoAppShell.setContextGetter(this);
- GeckoAppShell.setGeckoInterface(this);
-
- // Tell Stumbler to register a local broadcast listener to listen for preference intents.
- // We do this via intents since we can't easily access Stumbler directly,
- // as it might be compiled outside of Fennec.
- getApplicationContext().sendBroadcast(
- new Intent(INTENT_REGISTER_STUMBLER_LISTENER)
- );
-
- // Did the OS locale change while we were backgrounded? If so,
- // we need to die so that Gecko will re-init add-ons that touch
- // the UI.
- // This is using a sledgehammer to crack a nut, but it'll do for
- // now.
- // Our OS locale pref will be detected as invalid after the
- // restart, and will be propagated to Gecko accordingly, so there's
- // no need to touch that here.
- if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
- Log.i(LOGTAG, "System locale changed. Restarting.");
- doRestart();
- return;
- }
-
- if (sAlreadyLoaded) {
- // This happens when the GeckoApp activity is destroyed by Android
- // without killing the entire application (see Bug 769269).
- mIsRestoringActivity = true;
- Telemetry.addToHistogram("FENNEC_RESTORING_ACTIVITY", 1);
-
- } else {
- final String action = intent.getAction();
- final String args = intent.getStringExtra("args");
-
- sAlreadyLoaded = true;
- GeckoThread.init(/* profile */ null, args, action,
- /* debugging */ ACTION_DEBUG.equals(action));
-
- // Speculatively pre-fetch the profile in the background.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- getProfile();
- }
- });
-
- final String uri = getURIFromIntent(intent);
- if (!TextUtils.isEmpty(uri)) {
- // Start a speculative connection as soon as Gecko loads.
- GeckoThread.speculativeConnect(uri);
- }
- }
-
- // GeckoThread has to register for "Gecko:Ready" first, so GeckoApp registers
- // for events after initializing GeckoThread but before launching it.
-
- getAppEventDispatcher().registerGeckoThreadListener((GeckoEventListener)this,
- "Gecko:Ready",
- "Gecko:Exited",
- "Accessibility:Event");
-
- getAppEventDispatcher().registerGeckoThreadListener((NativeEventListener)this,
- "Accessibility:Ready",
- "Bookmark:Insert",
- "Contact:Add",
- "DevToolsAuth:Scan",
- "DOMFullScreen:Start",
- "DOMFullScreen:Stop",
- "Image:SetAs",
- "Locale:Set",
- "Permissions:Data",
- "PrivateBrowsing:Data",
- "RuntimePermissions:Prompt",
- "Sanitize:Finished",
- "Session:StatePurged",
- "Share:Text",
- "Snackbar:Show",
- "SystemUI:Visibility",
- "ToggleChrome:Focus",
- "ToggleChrome:Hide",
- "ToggleChrome:Show",
- "Update:Check",
- "Update:Download",
- "Update:Install");
-
- GeckoThread.launch();
-
- Bundle stateBundle = IntentUtils.getBundleExtraSafe(getIntent(), EXTRA_STATE_BUNDLE);
- if (stateBundle != null) {
- // Use the state bundle if it was given as an intent extra. This is
- // only intended to be used internally via Robocop, so a boolean
- // is read from a private shared pref to prevent other apps from
- // injecting states.
- final SharedPreferences prefs = getSharedPreferences();
- if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) {
- prefs.edit().remove(PREFS_ALLOW_STATE_BUNDLE).apply();
- savedInstanceState = stateBundle;
- }
- } else if (savedInstanceState != null) {
- // Bug 896992 - This intent has already been handled; reset the intent.
- setIntent(new Intent(Intent.ACTION_MAIN));
- }
-
- super.onCreate(savedInstanceState);
-
- GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
-
- setContentView(getLayout());
-
- // Set up Gecko layout.
- mRootLayout = (RelativeLayout) findViewById(R.id.root_layout);
- mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
- mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
- mLayerView = (GeckoView) findViewById(R.id.layer_view);
-
- Tabs.getInstance().attachToContext(this, mLayerView);
-
- // Use global layout state change to kick off additional initialization
- mMainLayout.getViewTreeObserver().addOnGlobalLayoutListener(this);
-
- if (Versions.preMarshmallow) {
- mTextSelection = new ActionBarTextSelection(this);
- } else {
- mTextSelection = new FloatingToolbarTextSelection(this, mLayerView);
- }
- mTextSelection.create();
-
- // Determine whether we should restore tabs.
- mLastSessionCrashed = updateCrashedState();
- mShouldRestore = getSessionRestoreState(savedInstanceState);
- if (mShouldRestore && savedInstanceState != null) {
- boolean wasInBackground =
- savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
-
- // Don't log OOM-kills if only one activity was destroyed. (For example
- // from "Don't keep activities" on ICS)
- if (!wasInBackground && !mIsRestoringActivity) {
- Telemetry.addToHistogram("FENNEC_WAS_KILLED", 1);
- }
-
- mPrivateBrowsingSession = savedInstanceState.getString(SAVED_STATE_PRIVATE_SESSION);
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // If we are doing a restore, read the session data so we can send it to Gecko later.
- String restoreMessage = null;
- if (!mIsRestoringActivity && mShouldRestore) {
- final boolean isExternalURL = invokedWithExternalURL(getIntentURI(new SafeIntent(getIntent())));
- try {
- // restoreSessionTabs() will create simple tab stubs with the
- // URL and title for each page, but we also need to restore
- // session history. restoreSessionTabs() will inject the IDs
- // of the tab stubs into the JSON data (which holds the session
- // history). This JSON data is then sent to Gecko so session
- // history can be restored for each tab.
- restoreMessage = restoreSessionTabs(isExternalURL, false);
- } catch (SessionRestoreException e) {
- // If mShouldRestore was set to false in restoreSessionTabs(), this means
- // either that we intentionally skipped all tabs read from the session file,
- // or else that the file was syntactically valid, but didn't contain any
- // tabs (e.g. because the user cleared history), therefore we don't need
- // to switch to the backup copy.
- if (mShouldRestore) {
- Log.e(LOGTAG, "An error occurred during restore, switching to backup file", e);
- // To be on the safe side, we will always attempt to restore from the backup
- // copy if we end up here.
- // Since we will also hit this situation regularly during first run though,
- // we'll only report it in telemetry if we failed to restore despite the
- // file existing, which means it's very probably damaged.
- if (getProfile().sessionFileExists()) {
- Telemetry.addToHistogram("FENNEC_SESSIONSTORE_DAMAGED_SESSION_FILE", 1);
- }
- try {
- restoreMessage = restoreSessionTabs(isExternalURL, true);
- Telemetry.addToHistogram("FENNEC_SESSIONSTORE_RESTORING_FROM_BACKUP", 1);
- } catch (SessionRestoreException ex) {
- if (!mShouldRestore) {
- // Restoring only "failed" because the backup copy was deliberately empty, too.
- Telemetry.addToHistogram("FENNEC_SESSIONSTORE_RESTORING_FROM_BACKUP", 1);
- } else {
- // Restoring the backup failed, too, so do a normal startup.
- Log.e(LOGTAG, "An error occurred during restore", ex);
- mShouldRestore = false;
- }
- }
- }
- }
- }
-
- synchronized (GeckoApp.this) {
- mSessionRestoreParsingFinished = true;
- GeckoApp.this.notifyAll();
- }
-
- // If we are doing a restore, send the parsed session data to Gecko.
- if (!mIsRestoringActivity) {
- GeckoAppShell.notifyObservers("Session:Restore", restoreMessage);
- }
-
- // Make sure sessionstore.old is either updated or deleted as necessary.
- getProfile().updateSessionFile(mShouldRestore);
- }
- });
-
- // Perform background initialization.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
-
- // Wait until now to set this, because we'd rather throw an exception than
- // have a caller of BrowserLocaleManager regress startup.
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
- localeManager.initialize(getApplicationContext());
-
- SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
- if (previousSession.wasKilled()) {
- Telemetry.addToHistogram("FENNEC_WAS_KILLED", 1);
- }
-
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(GeckoAppShell.PREFS_OOM_EXCEPTION, false);
-
- // Put a flag to check if we got a normal `onSaveInstanceState`
- // on exit, or if we were suddenly killed (crash or native OOM).
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
-
- editor.apply();
-
- // The lifecycle of mHealthRecorder is "shortly after onCreate"
- // through "onDestroy" -- essentially the same as the lifecycle
- // of the activity itself.
- final String profilePath = getProfile().getDir().getAbsolutePath();
- final EventDispatcher dispatcher = getAppEventDispatcher();
-
- // This is the locale prior to fixing it up.
- final Locale osLocale = Locale.getDefault();
-
- // Both of these are Java-format locale strings: "en_US", not "en-US".
- final String osLocaleString = osLocale.toString();
- String appLocaleString = localeManager.getAndApplyPersistedLocale(GeckoApp.this);
- Log.d(LOGTAG, "OS locale is " + osLocaleString + ", app locale is " + appLocaleString);
-
- if (appLocaleString == null) {
- appLocaleString = osLocaleString;
- }
-
- mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
- profilePath,
- dispatcher,
- osLocaleString,
- appLocaleString,
- previousSession);
-
- final String uiLocale = appLocaleString;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- GeckoApp.this.onLocaleReady(uiLocale);
- }
- });
-
- // We use per-profile prefs here, because we're tracking against
- // a Gecko pref. The same applies to the locale switcher!
- BrowserLocaleManager.storeAndNotifyOSLocale(GeckoSharedPrefs.forProfile(GeckoApp.this), osLocale);
- }
- });
-
- IntentHelper.init(this);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- mWasFirstTabShownAfterActivityUnhidden = false; // onStart indicates we were hidden.
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- // Overriding here is not necessary, but we do this so we don't
- // forget to add the abort if we override this method later.
- if (mIsAbortingAppLaunch) {
- return;
- }
- }
-
- /**
- * At this point, the resource system and the rest of the browser are
- * aware of the locale.
- *
- * Now we can display strings!
- *
- * You can think of this as being something like a second phase of onCreate,
- * where you can do string-related operations. Use this in place of embedding
- * strings in view XML.
- *
- * By contrast, onConfigurationChanged does some locale operations, but is in
- * response to device changes.
- */
- @Override
- public void onLocaleReady(final String locale) {
- if (!ThreadUtils.isOnUiThread()) {
- throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
- }
-
- final Locale loc = Locales.parseLocaleCode(locale);
- if (loc.equals(mLastLocale)) {
- Log.d(LOGTAG, "New locale same as old; onLocaleReady has nothing to do.");
- }
-
- // The URL bar hint needs to be populated.
- TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
- if (urlBar != null) {
- final String hint = getResources().getString(R.string.url_bar_default_text);
- urlBar.setHint(hint);
- } else {
- Log.d(LOGTAG, "No URL bar in GeckoApp. Not loading localized hint string.");
- }
-
- mLastLocale = loc;
-
- // Allow onConfigurationChanged to take care of the rest.
- // We don't call this.onConfigurationChanged, because (a) that does
- // work that's unnecessary after this locale action, and (b) it can
- // cause a loop! See Bug 1011008, Comment 12.
- super.onConfigurationChanged(getResources().getConfiguration());
- }
-
- protected void initializeChrome() {
- mDoorHangerPopup = new DoorHangerPopup(this);
- mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
- mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
- }
-
- /**
- * Loads the initial tab at Fennec startup. If we don't restore tabs, this
- * tab will be about:home, or the homepage if the user has set one.
- * If we've temporarily disabled restoring to break out of a crash loop, we'll show
- * the Recent Tabs folder of the Combined History panel, so the user can manually
- * restore tabs as needed.
- * If we restore tabs, we don't need to create a new tab.
- */
- protected void loadStartupTab(final int flags) {
- if (!mShouldRestore) {
- if (mLastSessionCrashed) {
- // The Recent Tabs panel no longer exists, but BrowserApp will redirect us
- // to the Recent Tabs folder of the Combined History panel.
- Tabs.getInstance().loadUrl(AboutPages.getURLForBuiltinPanelType(PanelType.DEPRECATED_RECENT_TABS), flags);
- } else {
- final String homepage = getHomepage();
- Tabs.getInstance().loadUrl(!TextUtils.isEmpty(homepage) ? homepage : AboutPages.HOME, flags);
- }
- }
- }
-
- /**
- * Loads the initial tab at Fennec startup. This tab will load with the given
- * external URL. If that URL is invalid, a startup tab will be loaded.
- *
- * @param url External URL to load.
- * @param intent External intent whose extras modify the request
- * @param flags Flags used to load the load
- */
- protected void loadStartupTab(final String url, final SafeIntent intent, final int flags) {
- // Invalid url
- if (url == null) {
- loadStartupTab(flags);
- return;
- }
-
- Tabs.getInstance().loadUrlWithIntentExtras(url, intent, flags);
- }
-
- public String getHomepage() {
- return null;
- }
-
- private String getIntentURI(SafeIntent intent) {
- final String passedUri;
- final String uri = getURIFromIntent(intent);
-
- if (!TextUtils.isEmpty(uri)) {
- passedUri = uri;
- } else {
- passedUri = null;
- }
- return passedUri;
- }
-
- private boolean invokedWithExternalURL(String uri) {
- return uri != null && !AboutPages.isAboutHome(uri);
- }
-
- private void initialize() {
- mInitialized = true;
-
- final boolean isFirstTab = !mWasFirstTabShownAfterActivityUnhidden;
- mWasFirstTabShownAfterActivityUnhidden = true; // Reset since we'll be loading a tab.
-
- final SafeIntent intent = new SafeIntent(getIntent());
- final String action = intent.getAction();
-
- final String passedUri = getIntentURI(intent);
-
- final boolean isExternalURL = invokedWithExternalURL(passedUri);
-
- // Start migrating as early as possible, can do this in
- // parallel with Gecko load.
- checkMigrateProfile();
-
- Tabs.registerOnTabsChangedListener(this);
-
- initializeChrome();
-
- // We need to wait here because mShouldRestore can revert back to
- // false if a parsing error occurs and the startup tab we load
- // depends on whether we restore tabs or not.
- synchronized (this) {
- while (!mSessionRestoreParsingFinished) {
- try {
- wait();
- } catch (final InterruptedException e) {
- // Ignore and wait again.
- }
- }
- }
-
- // External URLs should always be loaded regardless of whether Gecko is
- // already running.
- if (isExternalURL) {
- // Restore tabs before opening an external URL so that the new tab
- // is animated properly.
- Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
- processActionViewIntent(new Runnable() {
- @Override
- public void run() {
- int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
- if (ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
- flags |= Tabs.LOADURL_PINNED;
- }
- if (isFirstTab) {
- flags |= Tabs.LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN;
- }
- loadStartupTab(passedUri, intent, flags);
- }
- });
- } else {
- if (!mIsRestoringActivity) {
- loadStartupTab(Tabs.LOADURL_NEW_TAB);
- }
-
- Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
-
- processTabQueue();
- }
-
- recordStartupActionTelemetry(passedUri, action);
-
- // Check if launched from data reporting notification.
- if (ACTION_LAUNCH_SETTINGS.equals(action)) {
- Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
- // Copy extras.
- settingsIntent.putExtras(intent.getUnsafe());
- startActivity(settingsIntent);
- }
-
- //app state callbacks
- mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
-
- mPromptService = new PromptService(this);
-
- // Trigger the completion of the telemetry timer that wraps activity startup,
- // then grab the duration to give to FHR.
- mJavaUiStartupTimer.stop();
- final long javaDuration = mJavaUiStartupTimer.getElapsed();
-
- ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.recordJavaStartupTime(javaDuration);
- }
- }
- }, 50);
-
- final int updateServiceDelay = 30 * 1000;
- ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- UpdateServiceHelper.registerForUpdates(GeckoAppShell.getApplicationContext());
- }
- }, updateServiceDelay);
-
- if (mIsRestoringActivity) {
- Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
- }
-
- if (GeckoThread.isRunning()) {
- geckoConnected();
- if (mLayerView != null) {
- mLayerView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
- }
- }
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- @Override
- public void onGlobalLayout() {
- if (Versions.preJB) {
- mMainLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
- } else {
- mMainLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- if (!mInitialized) {
- initialize();
- }
- }
-
- protected void processActionViewIntent(final Runnable openTabsRunnable) {
- // We need to ensure that if we receive a VIEW action and there are tabs queued then the
- // site loaded from the intent is on top (last loaded) and selected with all other tabs
- // being opened behind it. We process the tab queue first and request a callback from the JS - the
- // listener will open the url from the intent as normal when the tab queue has been processed.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- if (TabQueueHelper.TAB_QUEUE_ENABLED && TabQueueHelper.shouldOpenTabQueueUrls(GeckoApp.this)) {
-
- getAppEventDispatcher().registerGeckoThreadListener(new NativeEventListener() {
- @Override
- public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
- if ("Tabs:TabsOpened".equals(event)) {
- getAppEventDispatcher().unregisterGeckoThreadListener(this, "Tabs:TabsOpened");
- openTabsRunnable.run();
- }
- }
- }, "Tabs:TabsOpened");
- TabQueueHelper.openQueuedUrls(GeckoApp.this, getProfile(), TabQueueHelper.FILE_NAME, true);
- } else {
- openTabsRunnable.run();
- }
- }
- });
- }
-
- @WorkerThread
- private String restoreSessionTabs(final boolean isExternalURL, boolean useBackup) throws SessionRestoreException {
- try {
- String sessionString = getProfile().readSessionFile(useBackup);
- if (sessionString == null) {
- throw new SessionRestoreException("Could not read from session file");
- }
-
- // If we are doing an OOM restore, parse the session data and
- // stub the restored tabs immediately. This allows the UI to be
- // updated before Gecko has restored.
- final JSONArray tabs = new JSONArray();
- final JSONObject windowObject = new JSONObject();
- final boolean sessionDataValid;
-
- LastSessionParser parser = new LastSessionParser(tabs, windowObject, isExternalURL);
-
- if (mPrivateBrowsingSession == null) {
- sessionDataValid = parser.parse(sessionString);
- } else {
- sessionDataValid = parser.parse(sessionString, mPrivateBrowsingSession);
- }
-
- if (tabs.length() > 0) {
- windowObject.put("tabs", tabs);
- sessionString = new JSONObject().put("windows", new JSONArray().put(windowObject)).toString();
- } else {
- if (parser.allTabsSkipped() || sessionDataValid) {
- // If we intentionally skipped all tabs we've read from the session file, we
- // set mShouldRestore back to false at this point already, so the calling code
- // can infer that the exception wasn't due to a damaged session store file.
- // The same applies if the session file was syntactically valid and
- // simply didn't contain any tabs.
- mShouldRestore = false;
- }
- throw new SessionRestoreException("No tabs could be read from session file");
- }
-
- JSONObject restoreData = new JSONObject();
- restoreData.put("sessionString", sessionString);
- return restoreData.toString();
- } catch (JSONException e) {
- throw new SessionRestoreException(e);
- }
- }
-
- public static EventDispatcher getEventDispatcher() {
- final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
- return geckoApp.getAppEventDispatcher();
- }
-
- @Override
- public EventDispatcher getAppEventDispatcher() {
- return eventDispatcher;
- }
-
- @Override
- public GeckoProfile getProfile() {
- return GeckoThread.getActiveProfile();
- }
-
- /**
- * Check whether we've crashed during the last browsing session.
- *
- * @return True if the crash reporter ran after the last session.
- */
- protected boolean updateCrashedState() {
- try {
- File crashFlag = new File(GeckoProfileDirectories.getMozillaDirectory(this), "CRASHED");
- if (crashFlag.exists() && crashFlag.delete()) {
- // Set the flag that indicates we were stopped as expected, as
- // the crash reporter has run, so it is not a silent OOM crash.
- getSharedPreferences().edit().putBoolean(PREFS_WAS_STOPPED, true).apply();
- return true;
- }
- } catch (NoMozillaDirectoryException e) {
- // If we can't access the Mozilla directory, we're in trouble anyway.
- Log.e(LOGTAG, "Cannot read crash flag: ", e);
- }
- return false;
- }
-
- /**
- * Determine whether the session should be restored.
- *
- * @param savedInstanceState Saved instance state given to the activity
- * @return Whether to restore
- */
- protected boolean getSessionRestoreState(Bundle savedInstanceState) {
- final SharedPreferences prefs = getSharedPreferences();
- boolean shouldRestore = false;
-
- final int versionCode = getVersionCode();
- if (mLastSessionCrashed) {
- if (incrementCrashCount(prefs) <= getSessionStoreMaxCrashResumes(prefs) &&
- getSessionRestoreAfterCrashPreference(prefs)) {
- shouldRestore = true;
- } else {
- shouldRestore = false;
- }
- } else if (prefs.getInt(PREFS_VERSION_CODE, 0) != versionCode) {
- // If the version has changed, the user has done an upgrade, so restore
- // previous tabs.
- prefs.edit().putInt(PREFS_VERSION_CODE, versionCode).apply();
- shouldRestore = true;
- } else if (savedInstanceState != null ||
- getSessionRestorePreference(prefs).equals("always") ||
- getRestartFromIntent()) {
- // We're coming back from a background kill by the OS, the user
- // has chosen to always restore, or we restarted.
- shouldRestore = true;
- }
-
- return shouldRestore;
- }
-
- private int incrementCrashCount(SharedPreferences prefs) {
- final int crashCount = getSuccessiveCrashesCount(prefs) + 1;
- prefs.edit().putInt(PREFS_CRASHED_COUNT, crashCount).apply();
- return crashCount;
- }
-
- private int getSuccessiveCrashesCount(SharedPreferences prefs) {
- return prefs.getInt(PREFS_CRASHED_COUNT, 0);
- }
-
- private int getSessionStoreMaxCrashResumes(SharedPreferences prefs) {
- return prefs.getInt(GeckoPreferences.PREFS_RESTORE_SESSION_MAX_CRASH_RESUMES, 1);
- }
-
- private boolean getSessionRestoreAfterCrashPreference(SharedPreferences prefs) {
- return prefs.getBoolean(GeckoPreferences.PREFS_RESTORE_SESSION_FROM_CRASH, true);
- }
-
- private String getSessionRestorePreference(SharedPreferences prefs) {
- return prefs.getString(GeckoPreferences.PREFS_RESTORE_SESSION, "always");
- }
-
- private boolean getRestartFromIntent() {
- return IntentUtils.getBooleanExtraSafe(getIntent(), "didRestart", false);
- }
-
- /**
- * Enable Android StrictMode checks (for supported OS versions).
- * http://developer.android.com/reference/android/os/StrictMode.html
- */
- private void enableStrictMode() {
- Log.d(LOGTAG, "Enabling Android StrictMode");
-
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyLog()
- .build());
-
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectAll()
- .penaltyLog()
- .build());
- }
-
- @Override
- public void enableOrientationListener() {
- // Start listening for orientation events
- mCameraOrientationEventListener = new OrientationEventListener(this) {
- @Override
- public void onOrientationChanged(int orientation) {
- if (mAppStateListeners != null) {
- for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
- listener.onOrientationChanged();
- }
- }
- }
- };
- mCameraOrientationEventListener.enable();
- }
-
- @Override
- public void disableOrientationListener() {
- if (mCameraOrientationEventListener != null) {
- mCameraOrientationEventListener.disable();
- mCameraOrientationEventListener = null;
- }
- }
-
- @Override
- public String getDefaultUAString() {
- return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET :
- AppConstants.USER_AGENT_FENNEC_MOBILE;
- }
-
- @Override
- public void createShortcut(final String title, final String url) {
- Icons.with(this)
- .pageUrl(url)
- .skipNetwork()
- .skipMemory()
- .forLauncherIcon()
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- doCreateShortcut(title, url, response.getBitmap());
- }
- });
- }
-
- private void doCreateShortcut(final String aTitle, final String aURI, final Bitmap aIcon) {
- // The intent to be launched by the shortcut.
- Intent shortcutIntent = new Intent();
- shortcutIntent.setAction(GeckoApp.ACTION_HOMESCREEN_SHORTCUT);
- shortcutIntent.setData(Uri.parse(aURI));
- shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
- AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-
- Intent intent = new Intent();
- intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, GeckoAppShell.getPreferredIconSize()));
-
- if (aTitle != null) {
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
- } else {
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
- }
-
- // Do not allow duplicate items.
- intent.putExtra("duplicate", false);
-
- intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
- getApplicationContext().sendBroadcast(intent);
-
- // Remember interaction
- final UrlAnnotations urlAnnotations = BrowserDB.from(getApplicationContext()).getUrlAnnotations();
- urlAnnotations.insertHomeScreenShortcut(getContentResolver(), aURI, true);
-
- // After shortcut is created, show the mobile desktop.
- ActivityUtils.goToHomeScreen(this);
- }
-
- private Bitmap getLauncherIcon(Bitmap aSource, int size) {
- final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f };
- final int kOffset = 6;
- final int kRadius = 5;
-
- int insetSize = aSource != null ? size * 2 / 3 : size;
-
- Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
-
- // draw a base color
- Paint paint = new Paint();
- if (aSource == null) {
- // If we aren't drawing a favicon, just use an orange color.
- paint.setColor(Color.HSVToColor(DEFAULT_LAUNCHER_ICON_HSV));
- canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
- } else if (aSource.getWidth() >= insetSize || aSource.getHeight() >= insetSize) {
- // Otherwise, if the icon is large enough, just draw it.
- Rect iconBounds = new Rect(0, 0, size, size);
- canvas.drawBitmap(aSource, null, iconBounds, null);
- return bitmap;
- } else {
- // otherwise use the dominant color from the icon + a layer of transparent white to lighten it somewhat
- int color = BitmapUtils.getDominantColor(aSource);
- paint.setColor(color);
- canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
- paint.setColor(Color.argb(100, 255, 255, 255));
- canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
- }
-
- // draw the overlay
- Bitmap overlay = BitmapUtils.decodeResource(this, R.drawable.home_bg);
- canvas.drawBitmap(overlay, null, new Rect(0, 0, size, size), null);
-
- // draw the favicon
- if (aSource == null)
- aSource = BitmapUtils.decodeResource(this, R.drawable.home_star);
-
- // by default, we scale the icon to this size
- int sWidth = insetSize / 2;
- int sHeight = sWidth;
-
- int halfSize = size / 2;
- canvas.drawBitmap(aSource,
- null,
- new Rect(halfSize - sWidth,
- halfSize - sHeight,
- halfSize + sWidth,
- halfSize + sHeight),
- null);
-
- return bitmap;
- }
-
- @Override
- protected void onNewIntent(Intent externalIntent) {
- final SafeIntent intent = new SafeIntent(externalIntent);
-
- final boolean isFirstTab = !mWasFirstTabShownAfterActivityUnhidden;
- mWasFirstTabShownAfterActivityUnhidden = true; // Reset since we'll be loading a tab.
-
- // if we were previously OOM killed, we can end up here when launching
- // from external shortcuts, so set this as the intent for initialization
- if (!mInitialized) {
- setIntent(externalIntent);
- return;
- }
-
- final String action = intent.getAction();
-
- final String uri = getURIFromIntent(intent);
- final String passedUri;
- if (!TextUtils.isEmpty(uri)) {
- passedUri = uri;
- } else {
- passedUri = null;
- }
-
- if (ACTION_LOAD.equals(action)) {
- Tabs.getInstance().loadUrl(intent.getDataString());
- lastSelectedTabId = -1;
- } else if (Intent.ACTION_VIEW.equals(action)) {
- processActionViewIntent(new Runnable() {
- @Override
- public void run() {
- final String url = intent.getDataString();
- int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
- if (isFirstTab) {
- flags |= Tabs.LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN;
- }
- Tabs.getInstance().loadUrlWithIntentExtras(url, intent, flags);
- }
- });
- lastSelectedTabId = -1;
- } else if (ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
- mLayerView.loadUri(uri, GeckoView.LOAD_SWITCH_TAB);
- } else if (Intent.ACTION_SEARCH.equals(action)) {
- mLayerView.loadUri(uri, GeckoView.LOAD_NEW_TAB);
- } else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
- NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
- } else if (ACTION_LAUNCH_SETTINGS.equals(action)) {
- // Check if launched from data reporting notification.
- Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
- // Copy extras.
- settingsIntent.putExtras(intent.getUnsafe());
- startActivity(settingsIntent);
- } else if (ACTION_SWITCH_TAB.equals(action)) {
- final int tabId = intent.getIntExtra("TabId", -1);
- Tabs.getInstance().selectTab(tabId);
- lastSelectedTabId = -1;
- }
-
- recordStartupActionTelemetry(passedUri, action);
- }
-
- /**
- * Handles getting a URI from an intent in a way that is backwards-
- * compatible with our previous implementations.
- */
- protected String getURIFromIntent(SafeIntent intent) {
- final String action = intent.getAction();
- if (ACTION_ALERT_CALLBACK.equals(action) ||
- NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
- return null;
- }
-
- return intent.getDataString();
- }
-
- protected int getOrientation() {
- return GeckoScreenOrientation.getInstance().getAndroidOrientation();
- }
-
- @Override
- public void onResume()
- {
- // After an onPause, the activity is back in the foreground.
- // Undo whatever we did in onPause.
- super.onResume();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- GeckoAppShell.setGeckoInterface(this);
-
- if (lastSelectedTabId >= 0 && (lastActiveGeckoApp == null || lastActiveGeckoApp.get() != this)) {
- Tabs.getInstance().selectTab(lastSelectedTabId);
- }
-
- int newOrientation = getResources().getConfiguration().orientation;
- if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
- refreshChrome();
- }
-
- if (mAppStateListeners != null) {
- for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
- listener.onResume();
- }
- }
-
- // We use two times: a pseudo-unique wall-clock time to identify the
- // current session across power cycles, and the elapsed realtime to
- // track the duration of the session.
- final long now = System.currentTimeMillis();
- final long realTime = android.os.SystemClock.elapsedRealtime();
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // Now construct the new session on HealthRecorder's behalf. We do this here
- // so it can benefit from a single near-startup prefs commit.
- SessionInformation currentSession = new SessionInformation(now, realTime);
-
- SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
-
- if (!mLastSessionCrashed) {
- // The last session terminated normally,
- // so we can reset the count of successive crashes.
- editor.putInt(GeckoApp.PREFS_CRASHED_COUNT, 0);
- }
-
- currentSession.recordBegin(editor);
- editor.apply();
-
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.setCurrentSession(currentSession);
- rec.processDelayed();
- } else {
- Log.w(LOGTAG, "Can't record session: rec is null.");
- }
- }
- });
-
- Restrictions.update(this);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
-
- if (!mWindowFocusInitialized && hasFocus) {
- mWindowFocusInitialized = true;
- // XXX our editor tests require the GeckoView to have focus to pass, so we have to
- // manually shift focus to the GeckoView. requestFocus apparently doesn't work at
- // this stage of starting up, so we have to unset and reset the focusability.
- mLayerView.setFocusable(false);
- mLayerView.setFocusable(true);
- mLayerView.setFocusableInTouchMode(true);
- getWindow().setBackgroundDrawable(null);
- }
- }
-
- @Override
- public void onPause()
- {
- if (mIsAbortingAppLaunch) {
- super.onPause();
- return;
- }
-
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- lastSelectedTabId = selectedTab.getId();
- }
- lastActiveGeckoApp = new WeakReference<GeckoApp>(this);
-
- final HealthRecorder rec = mHealthRecorder;
- final Context context = this;
-
- // In some way it's sad that Android will trigger StrictMode warnings
- // here as the whole point is to save to disk while the activity is not
- // interacting with the user.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
- if (rec != null) {
- rec.recordSessionEnd("P", editor);
- }
-
- // onPause might in fact be called even after a crash, but in that case the
- // crash reporter will record this fact for us and we'll pick it up in onCreate.
- mLastSessionCrashed = false;
-
- // If we haven't done it before, cleanup any old files in our old temp dir
- if (prefs.getBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, true)) {
- File tempDir = GeckoLoader.getGREDir(GeckoApp.this);
- FileUtils.delTree(tempDir, new FileUtils.NameAndAgeFilter(null, ONE_DAY_MS), false);
-
- editor.putBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, false);
- }
-
- editor.apply();
- }
- });
-
- if (mAppStateListeners != null) {
- for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
- listener.onPause();
- }
- }
-
- super.onPause();
- }
-
- @Override
- public void onRestart() {
- if (mIsAbortingAppLaunch) {
- super.onRestart();
- return;
- }
-
- // Faster on main thread with an async apply().
- final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
- try {
- SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit();
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
- editor.apply();
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
-
- super.onRestart();
- }
-
- @Override
- public void onDestroy() {
- if (mIsAbortingAppLaunch) {
- // This build does not support the Android version of the device:
- // We did not initialize anything, so skip cleaning up.
- super.onDestroy();
- return;
- }
-
- getAppEventDispatcher().unregisterGeckoThreadListener((GeckoEventListener)this,
- "Gecko:Ready",
- "Gecko:Exited",
- "Accessibility:Event");
-
- getAppEventDispatcher().unregisterGeckoThreadListener((NativeEventListener)this,
- "Accessibility:Ready",
- "Bookmark:Insert",
- "Contact:Add",
- "DevToolsAuth:Scan",
- "DOMFullScreen:Start",
- "DOMFullScreen:Stop",
- "Image:SetAs",
- "Locale:Set",
- "Permissions:Data",
- "PrivateBrowsing:Data",
- "RuntimePermissions:Prompt",
- "Sanitize:Finished",
- "Session:StatePurged",
- "Share:Text",
- "Snackbar:Show",
- "SystemUI:Visibility",
- "ToggleChrome:Focus",
- "ToggleChrome:Hide",
- "ToggleChrome:Show",
- "Update:Check",
- "Update:Download",
- "Update:Install");
-
- if (mPromptService != null)
- mPromptService.destroy();
-
- final HealthRecorder rec = mHealthRecorder;
- mHealthRecorder = null;
- if (rec != null && rec.isEnabled()) {
- // Closing a HealthRecorder could incur a write.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- rec.close(GeckoApp.this);
- }
- });
- }
-
- super.onDestroy();
-
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- public void showSDKVersionError() {
- final String message = getString(R.string.unsupported_sdk_version, Build.CPU_ABI, Build.VERSION.SDK_INT);
- Toast.makeText(this, message, Toast.LENGTH_LONG).show();
- }
-
- // Get a temporary directory, may return null
- public static File getTempDirectory() {
- File dir = GeckoApplication.get().getExternalFilesDir("temp");
- return dir;
- }
-
- // Delete any files in our temporary directory
- public static void deleteTempFiles() {
- File dir = getTempDirectory();
- if (dir == null)
- return;
- File[] files = dir.listFiles();
- if (files == null)
- return;
- for (File file : files) {
- file.delete();
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
-
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
- final Locale changed = localeManager.onSystemConfigurationChanged(this, getResources(), newConfig, mLastLocale);
- if (changed != null) {
- onLocaleChanged(Locales.getLanguageTag(changed));
- }
-
- // onConfigurationChanged is not called for 180 degree orientation changes,
- // we will miss such rotations and the screen orientation will not be
- // updated.
- if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) {
- if (mFormAssistPopup != null)
- mFormAssistPopup.hide();
- refreshChrome();
- }
- super.onConfigurationChanged(newConfig);
- }
-
- public String getContentProcessName() {
- return AppConstants.MOZ_CHILD_PROCESS_NAME;
- }
-
- public void addEnvToIntent(Intent intent) {
- Map<String, String> envMap = System.getenv();
- Set<Map.Entry<String, String>> envSet = envMap.entrySet();
- Iterator<Map.Entry<String, String>> envIter = envSet.iterator();
- int c = 0;
- while (envIter.hasNext()) {
- Map.Entry<String, String> entry = envIter.next();
- intent.putExtra("env" + c, entry.getKey() + "="
- + entry.getValue());
- c++;
- }
- }
-
- @Override
- public void doRestart() {
- doRestart(null, null);
- }
-
- public void doRestart(String args) {
- doRestart(args, null);
- }
-
- public void doRestart(Intent intent) {
- doRestart(null, intent);
- }
-
- public void doRestart(String args, Intent restartIntent) {
- if (restartIntent == null) {
- restartIntent = new Intent(Intent.ACTION_MAIN);
- }
-
- if (args != null) {
- restartIntent.putExtra("args", args);
- }
-
- mRestartIntent = restartIntent;
- Log.d(LOGTAG, "doRestart(\"" + restartIntent + "\")");
-
- doShutdown();
- }
-
- private void doShutdown() {
- // Shut down GeckoApp activity.
- runOnUiThread(new Runnable() {
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- @Override public void run() {
- if (!isFinishing() && (Versions.preJBMR1 || !isDestroyed())) {
- finish();
- }
- }
- });
- }
-
- private void checkMigrateProfile() {
- final File profileDir = getProfile().getDir();
-
- if (profileDir != null) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- Handler handler = new Handler();
- handler.postDelayed(new DeferredCleanupTask(), CLEANUP_DEFERRAL_SECONDS * 1000);
- }
- });
- }
- }
-
- private static class DeferredCleanupTask implements Runnable {
- // The cleanup-version setting is recorded to avoid repeating the same
- // tasks on subsequent startups; CURRENT_CLEANUP_VERSION may be updated
- // if we need to do additional cleanup for future Gecko versions.
-
- private static final String CLEANUP_VERSION = "cleanup-version";
- private static final int CURRENT_CLEANUP_VERSION = 1;
-
- @Override
- public void run() {
- final Context context = GeckoAppShell.getApplicationContext();
- long cleanupVersion = GeckoSharedPrefs.forApp(context).getInt(CLEANUP_VERSION, 0);
-
- if (cleanupVersion < 1) {
- // Reduce device storage footprint by removing .ttf files from
- // the res/fonts directory: we no longer need to copy our
- // bundled fonts out of the APK in order to use them.
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=878674.
- File dir = new File("res/fonts");
- if (dir.exists() && dir.isDirectory()) {
- for (File file : dir.listFiles()) {
- if (file.isFile() && file.getName().endsWith(".ttf")) {
- file.delete();
- }
- }
- if (!dir.delete()) {
- Log.w(LOGTAG, "unable to delete res/fonts directory (not empty?)");
- }
- }
- }
-
- // Additional cleanup needed for future versions would go here
-
- if (cleanupVersion != CURRENT_CLEANUP_VERSION) {
- SharedPreferences.Editor editor = GeckoSharedPrefs.forApp(context).edit();
- editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION);
- editor.apply();
- }
- }
- }
-
- protected void onDone() {
- moveTaskToBack(true);
- }
-
- @Override
- public void onBackPressed() {
- if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
- super.onBackPressed();
- return;
- }
-
- if (autoHideTabs()) {
- return;
- }
-
- if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) {
- mDoorHangerPopup.dismiss();
- return;
- }
-
- if (mFullScreenPluginView != null) {
- GeckoAppShell.onFullScreenPluginHidden(mFullScreenPluginView);
- removeFullScreenPluginView(mFullScreenPluginView);
- return;
- }
-
- if (mLayerView != null && mLayerView.isFullScreen()) {
- GeckoAppShell.notifyObservers("FullScreen:Exit", null);
- return;
- }
-
- final Tabs tabs = Tabs.getInstance();
- final Tab tab = tabs.getSelectedTab();
- if (tab == null) {
- onDone();
- return;
- }
-
- // Give Gecko a chance to handle the back press first, then fallback to the Java UI.
- GeckoAppShell.sendRequestToGecko(new GeckoRequest("Browser:OnBackPressed", null) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- if (!nativeJSObject.getBoolean("handled")) {
- // Default behavior is Gecko didn't prevent.
- onDefault();
- }
- }
-
- @Override
- public void onError(NativeJSObject error) {
- // Default behavior is Gecko didn't prevent, via failure.
- onDefault();
- }
-
- // Return from Gecko thread, then back-press through the Java UI.
- private void onDefault() {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (tab.doBack()) {
- return;
- }
-
- if (tab.isExternal()) {
- onDone();
- Tab nextSelectedTab = Tabs.getInstance().getNextTab(tab);
- if (nextSelectedTab != null) {
- int nextSelectedTabId = nextSelectedTab.getId();
- GeckoAppShell.notifyObservers("Tab:KeepZombified", Integer.toString(nextSelectedTabId));
- }
- tabs.closeTab(tab);
- return;
- }
-
- final int parentId = tab.getParentId();
- final Tab parent = tabs.getTab(parentId);
- if (parent != null) {
- // The back button should always return to the parent (not a sibling).
- tabs.closeTab(tab, parent);
- return;
- }
-
- onDone();
- }
- });
- }
- });
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- Permissions.onRequestPermissionsResult(this, permissions, grantResults);
- }
-
- @Override
- public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
-
- private static final String CPU = "cpu";
- private static final String SCREEN = "screen";
-
- // Called when a Gecko Hal WakeLock is changed
- @Override
- // We keep the wake lock independent from the function scope, so we need to
- // suppress the linter warning.
- @SuppressLint("Wakelock")
- public void notifyWakeLockChanged(String topic, String state) {
- PowerManager.WakeLock wl = mWakeLocks.get(topic);
- if (state.equals("locked-foreground") && wl == null) {
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
-
- if (CPU.equals(topic)) {
- wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, topic);
- } else if (SCREEN.equals(topic)) {
- // ON_AFTER_RELEASE is set, the user activity timer will be reset when the
- // WakeLock is released, causing the illumination to remain on a bit longer.
- wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, topic);
- }
-
- if (wl != null) {
- wl.acquire();
- mWakeLocks.put(topic, wl);
- }
- } else if (!state.equals("locked-foreground") && wl != null) {
- wl.release();
- mWakeLocks.remove(topic);
- }
- }
-
- @Override
- public void notifyCheckUpdateResult(String result) {
- GeckoAppShell.notifyObservers("Update:CheckResult", result);
- }
-
- private void geckoConnected() {
- mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
- }
-
- @Override
- public void setAccessibilityEnabled(boolean enabled) {
- }
-
- @Override
- public boolean openUriExternal(String targetURI, String mimeType, String packageName, String className, String action, String title) {
- // Default to showing prompt in private browsing to be safe.
- return IntentHelper.openUriExternal(targetURI, mimeType, packageName, className, action, title, true);
- }
-
- public static class MainLayout extends RelativeLayout {
- private TouchEventInterceptor mTouchEventInterceptor;
- private MotionEventInterceptor mMotionEventInterceptor;
-
- public MainLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- }
-
- public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
- mTouchEventInterceptor = interceptor;
- }
-
- public void setMotionEventInterceptor(MotionEventInterceptor interceptor) {
- mMotionEventInterceptor = interceptor;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) {
- return true;
- }
- return super.onInterceptTouchEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mTouchEventInterceptor != null && mTouchEventInterceptor.onTouch(this, event)) {
- return true;
- }
- return super.onTouchEvent(event);
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event) {
- if (mMotionEventInterceptor != null && mMotionEventInterceptor.onInterceptMotionEvent(this, event)) {
- return true;
- }
- return super.onGenericMotionEvent(event);
- }
-
- @Override
- public void setDrawingCacheEnabled(boolean enabled) {
- // Instead of setting drawing cache in the view itself, we simply
- // enable drawing caching on its children. This is mainly used in
- // animations (see PropertyAnimator)
- super.setChildrenDrawnWithCacheEnabled(enabled);
- }
- }
-
- private class FullScreenHolder extends FrameLayout {
-
- public FullScreenHolder(Context ctx) {
- super(ctx);
- setBackgroundColor(0xff000000);
- }
-
- @Override
- public void addView(View view, int index) {
- /**
- * This normally gets called when Flash adds a separate SurfaceView
- * for the video. It is unhappy if we have the LayerView underneath
- * it for some reason so we need to hide that. Hiding the LayerView causes
- * its surface to be destroyed, which causes a pause composition
- * event to be sent to Gecko. We synchronously wait for that to be
- * processed. Simultaneously, however, Flash is waiting on a mutex so
- * the post() below is an attempt to avoid a deadlock.
- */
- super.addView(view, index);
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mLayerView.hideSurface();
- }
- });
- }
-
- /**
- * The methods below are simply copied from what Android WebKit does.
- * It wasn't ever called in my testing, but might as well
- * keep it in case it is for some reason. The methods
- * all return true because we don't want any events
- * leaking out from the fullscreen view.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyDown(keyCode, event);
- }
- mFullScreenPluginView.onKeyDown(keyCode, event);
- return true;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyUp(keyCode, event);
- }
- mFullScreenPluginView.onKeyUp(keyCode, event);
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return true;
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- mFullScreenPluginView.onTrackballEvent(event);
- return true;
- }
- }
-
- private int getVersionCode() {
- int versionCode = 0;
- try {
- versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
- } catch (NameNotFoundException e) {
- Log.wtf(LOGTAG, getPackageName() + " not found", e);
- }
- return versionCode;
- }
-
- // FHR reason code for a session end prior to a restart for a
- // locale change.
- private static final String SESSION_END_LOCALE_CHANGED = "L";
-
- /**
- * This exists so that a locale can be applied in two places: when saved
- * in a nested activity, and then again when we get back up to GeckoApp.
- *
- * GeckoApp needs to do a bunch more stuff than, say, GeckoPreferences.
- */
- protected void onLocaleChanged(final String locale) {
- final boolean startNewSession = true;
- final boolean shouldRestart = false;
-
- // If the HealthRecorder is not yet initialized (unlikely), the locale change won't
- // trigger a session transition and subsequent events will be recorded in an environment
- // with the wrong locale.
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.onAppLocaleChanged(locale);
- rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
- }
-
- if (!shouldRestart) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- GeckoApp.this.onLocaleReady(locale);
- }
- });
- return;
- }
-
- // Do this in the background so that the health recorder has its
- // time to finish.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GeckoApp.this.doRestart();
- }
- });
- }
-
- /**
- * Use BrowserLocaleManager to change our persisted and current locales,
- * and poke the system to tell it of our changed state.
- */
- protected void setLocale(final String locale) {
- if (locale == null) {
- return;
- }
-
- final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale);
- if (resultant == null) {
- return;
- }
-
- onLocaleChanged(resultant);
- }
-
- private void setSystemUiVisible(final boolean visible) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (visible) {
- mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
- } else {
- mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
- }
- }
- });
- }
-
- protected HealthRecorder createHealthRecorder(final Context context,
- final String profilePath,
- final EventDispatcher dispatcher,
- final String osLocale,
- final String appLocale,
- final SessionInformation previousSession) {
- // GeckoApp does not need to record any health information - return a stub.
- return new StubbedHealthRecorder();
- }
-
- protected void recordStartupActionTelemetry(final String passedURL, final String action) {
- }
-
- @Override
- public void checkUriVisited(String uri) {
- GlobalHistory.getInstance().checkUriVisited(uri);
- }
-
- @Override
- public void markUriVisited(final String uri) {
- final Context context = getApplicationContext();
- final BrowserDB db = BrowserDB.from(context);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GlobalHistory.getInstance().add(context, db, uri);
- }
- });
- }
-
- @Override
- public void setUriTitle(final String uri, final String title) {
- final Context context = getApplicationContext();
- final BrowserDB db = BrowserDB.from(context);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GlobalHistory.getInstance().update(context.getContentResolver(), db, uri, title);
- }
- });
- }
-
- @Override
- public String[] getHandlersForMimeType(String mimeType, String action) {
- Intent intent = IntentHelper.getIntentForActionString(action);
- if (mimeType != null && mimeType.length() > 0)
- intent.setType(mimeType);
- return IntentHelper.getHandlersForIntent(intent);
- }
-
- @Override
- public String[] getHandlersForURL(String url, String action) {
- // May contain the whole URL or just the protocol.
- Uri uri = url.indexOf(':') >= 0 ? Uri.parse(url) : new Uri.Builder().scheme(url).build();
-
- Intent intent = IntentHelper.getOpenURIIntent(getApplicationContext(), uri.toString(), "",
- TextUtils.isEmpty(action) ? Intent.ACTION_VIEW : action, "");
-
- return IntentHelper.getHandlersForIntent(intent);
- }
-
- @Override
- public String getDefaultChromeURI() {
- // Use the chrome URI specified by Gecko's defaultChromeURI pref.
- return null;
- }
-
- public GeckoView getGeckoView() {
- return mLayerView;
- }
-}