diff options
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/GeckoApp.java')
-rw-r--r-- | mobile/android/base/java/org/mozilla/gecko/GeckoApp.java | 2878 |
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; - } -} |