mobile/android/base/GeckoApp.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import java.io.BufferedOutputStream;
     9 import java.io.ByteArrayOutputStream;
    10 import java.io.File;
    11 import java.io.IOException;
    12 import java.io.InputStream;
    13 import java.io.OutputStream;
    14 import java.net.HttpURLConnection;
    15 import java.net.URL;
    16 import java.text.DateFormat;
    17 import java.text.SimpleDateFormat;
    18 import java.util.ArrayList;
    19 import java.util.Arrays;
    20 import java.util.Date;
    21 import java.util.HashMap;
    22 import java.util.Iterator;
    23 import java.util.LinkedList;
    24 import java.util.List;
    25 import java.util.Locale;
    26 import java.util.Map;
    27 import java.util.Set;
    28 import java.util.regex.Matcher;
    29 import java.util.regex.Pattern;
    31 import org.json.JSONArray;
    32 import org.json.JSONException;
    33 import org.json.JSONObject;
    34 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
    35 import org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService;
    36 import org.mozilla.gecko.db.BrowserDB;
    37 import org.mozilla.gecko.favicons.Favicons;
    38 import org.mozilla.gecko.gfx.BitmapUtils;
    39 import org.mozilla.gecko.gfx.Layer;
    40 import org.mozilla.gecko.gfx.LayerView;
    41 import org.mozilla.gecko.gfx.PluginLayer;
    42 import org.mozilla.gecko.health.HealthRecorder;
    43 import org.mozilla.gecko.health.SessionInformation;
    44 import org.mozilla.gecko.health.StubbedHealthRecorder;
    45 import org.mozilla.gecko.menu.GeckoMenu;
    46 import org.mozilla.gecko.menu.GeckoMenuInflater;
    47 import org.mozilla.gecko.menu.MenuPanel;
    48 import org.mozilla.gecko.mozglue.GeckoLoader;
    49 import org.mozilla.gecko.preferences.GeckoPreferences;
    50 import org.mozilla.gecko.prompts.PromptService;
    51 import org.mozilla.gecko.updater.UpdateService;
    52 import org.mozilla.gecko.updater.UpdateServiceHelper;
    53 import org.mozilla.gecko.util.ActivityResultHandler;
    54 import org.mozilla.gecko.util.FileUtils;
    55 import org.mozilla.gecko.util.GeckoEventListener;
    56 import org.mozilla.gecko.util.HardwareUtils;
    57 import org.mozilla.gecko.util.ThreadUtils;
    58 import org.mozilla.gecko.util.UiAsyncTask;
    59 import org.mozilla.gecko.webapp.EventListener;
    60 import org.mozilla.gecko.webapp.UninstallListener;
    61 import org.mozilla.gecko.widget.ButtonToast;
    63 import android.app.Activity;
    64 import android.app.AlertDialog;
    65 import android.app.Dialog;
    66 import android.content.Context;
    67 import android.content.DialogInterface;
    68 import android.content.Intent;
    69 import android.content.SharedPreferences;
    70 import android.content.pm.PackageManager.NameNotFoundException;
    71 import android.content.res.Configuration;
    72 import android.graphics.Bitmap;
    73 import android.graphics.BitmapFactory;
    74 import android.graphics.RectF;
    75 import android.graphics.drawable.Drawable;
    76 import android.hardware.Sensor;
    77 import android.hardware.SensorEvent;
    78 import android.hardware.SensorEventListener;
    79 import android.location.Location;
    80 import android.location.LocationListener;
    81 import android.net.Uri;
    82 import android.net.wifi.ScanResult;
    83 import android.net.wifi.WifiManager;
    84 import android.os.Build;
    85 import android.os.Bundle;
    86 import android.os.Handler;
    87 import android.os.PowerManager;
    88 import android.os.StrictMode;
    89 import android.provider.ContactsContract;
    90 import android.provider.MediaStore.Images.Media;
    91 import android.telephony.CellLocation;
    92 import android.telephony.NeighboringCellInfo;
    93 import android.telephony.PhoneStateListener;
    94 import android.telephony.SignalStrength;
    95 import android.telephony.TelephonyManager;
    96 import android.telephony.gsm.GsmCellLocation;
    97 import android.text.TextUtils;
    98 import android.util.AttributeSet;
    99 import android.util.Base64;
   100 import android.util.Log;
   101 import android.util.SparseBooleanArray;
   102 import android.view.Gravity;
   103 import android.view.KeyEvent;
   104 import android.view.Menu;
   105 import android.view.MenuInflater;
   106 import android.view.MenuItem;
   107 import android.view.MotionEvent;
   108 import android.view.OrientationEventListener;
   109 import android.view.SurfaceHolder;
   110 import android.view.SurfaceView;
   111 import android.view.TextureView;
   112 import android.view.View;
   113 import android.view.ViewGroup;
   114 import android.view.ViewStub;
   115 import android.view.Window;
   116 import android.view.WindowManager;
   117 import android.widget.AbsoluteLayout;
   118 import android.widget.FrameLayout;
   119 import android.widget.ListView;
   120 import android.widget.RelativeLayout;
   121 import android.widget.SimpleAdapter;
   122 import android.widget.TextView;
   123 import android.widget.Toast;
   125 public abstract class GeckoApp
   126     extends GeckoActivity
   127     implements
   128     ContextGetter,
   129     GeckoAppShell.GeckoInterface,
   130     GeckoEventListener,
   131     GeckoMenu.Callback,
   132     GeckoMenu.MenuPresenter,
   133     LocationListener,
   134     SensorEventListener,
   135     Tabs.OnTabsChangedListener
   136 {
   137     private static final String LOGTAG = "GeckoApp";
   138     private static final int ONE_DAY_MS = 1000*60*60*24;
   140     private static enum StartupAction {
   141         NORMAL,     /* normal application start */
   142         URL,        /* launched with a passed URL */
   143         PREFETCH    /* launched with a passed URL that we prefetch */
   144     }
   146     public static final String ACTION_ALERT_CALLBACK       = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
   147     public static final String ACTION_BOOKMARK             = "org.mozilla.gecko.BOOKMARK";
   148     public static final String ACTION_DEBUG                = "org.mozilla.gecko.DEBUG";
   149     public static final String ACTION_LAUNCH_SETTINGS      = "org.mozilla.gecko.SETTINGS";
   150     public static final String ACTION_LOAD                 = "org.mozilla.gecko.LOAD";
   151     public static final String ACTION_INIT_PW              = "org.mozilla.gecko.INIT_PW";
   152     public static final String ACTION_WEBAPP_PREFIX        = "org.mozilla.gecko.WEBAPP";
   154     public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
   156     public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
   157     public static final String PREFS_OOM_EXCEPTION         = "OOMException";
   158     public static final String PREFS_VERSION_CODE          = "versionCode";
   159     public static final String PREFS_WAS_STOPPED           = "wasStopped";
   160     public static final String PREFS_CRASHED               = "crashed";
   161     public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
   163     public static final String SAVED_STATE_IN_BACKGROUND   = "inBackground";
   164     public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
   166     static private final String LOCATION_URL = "https://location.services.mozilla.com/v1/submit";
   168     // Delay before running one-time "cleanup" tasks that may be needed
   169     // after a version upgrade.
   170     private static final int CLEANUP_DEFERRAL_SECONDS = 15;
   172     protected RelativeLayout mMainLayout;
   173     protected RelativeLayout mGeckoLayout;
   174     public View getView() { return mGeckoLayout; }
   175     private View mCameraView;
   176     private OrientationEventListener mCameraOrientationEventListener;
   177     public List<GeckoAppShell.AppStateListener> mAppStateListeners;
   178     protected MenuPanel mMenuPanel;
   179     protected Menu mMenu;
   180     protected GeckoProfile mProfile;
   181     protected boolean mIsRestoringActivity;
   183     private ContactService mContactService;
   184     private PromptService mPromptService;
   185     private TextSelection mTextSelection;
   187     protected DoorHangerPopup mDoorHangerPopup;
   188     protected FormAssistPopup mFormAssistPopup;
   189     protected ButtonToast mToast;
   191     protected LayerView mLayerView;
   192     private AbsoluteLayout mPluginContainer;
   194     private FullScreenHolder mFullScreenPluginContainer;
   195     private View mFullScreenPluginView;
   197     private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
   199     protected boolean mShouldRestore;
   200     protected boolean mInitialized = false;
   201     private Telemetry.Timer mJavaUiStartupTimer;
   202     private Telemetry.Timer mGeckoReadyStartupTimer;
   204     private String mPrivateBrowsingSession;
   206     private volatile HealthRecorder mHealthRecorder = null;
   208     private int mSignalStrenth;
   209     private PhoneStateListener mPhoneStateListener = null;
   210     private boolean mShouldReportGeoData;
   211     private EventListener mWebappEventListener;
   213     abstract public int getLayout();
   214     abstract public boolean hasTabsSideBar();
   215     abstract protected String getDefaultProfileName() throws NoMozillaDirectoryException;
   217     private static final String RESTARTER_ACTION = "org.mozilla.gecko.restart";
   218     private static final String RESTARTER_CLASS = "org.mozilla.gecko.Restarter";
   220     @SuppressWarnings("serial")
   221     class SessionRestoreException extends Exception {
   222         public SessionRestoreException(Exception e) {
   223             super(e);
   224         }
   226         public SessionRestoreException(String message) {
   227             super(message);
   228         }
   229     }
   231     void toggleChrome(final boolean aShow) { }
   233     void focusChrome() { }
   235     @Override
   236     public Context getContext() {
   237         return this;
   238     }
   240     @Override
   241     public SharedPreferences getSharedPreferences() {
   242         return GeckoSharedPrefs.forApp(this);
   243     }
   245     public Activity getActivity() {
   246         return this;
   247     }
   249     public LocationListener getLocationListener() {
   250         if (mShouldReportGeoData && mPhoneStateListener == null) {
   251             mPhoneStateListener = new PhoneStateListener() {
   252                 public void onSignalStrengthsChanged(SignalStrength signalStrength) {
   253                     setCurrentSignalStrenth(signalStrength);
   254                 }
   255             };
   256             TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
   257             tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
   258         }
   259         return this;
   260     }
   262     public SensorEventListener getSensorEventListener() {
   263         return this;
   264     }
   266     public View getCameraView() {
   267         return mCameraView;
   268     }
   270     public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
   271         mAppStateListeners.add(listener);
   272     }
   274     public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
   275         mAppStateListeners.remove(listener);
   276     }
   278     public FormAssistPopup getFormAssistPopup() {
   279         return mFormAssistPopup;
   280     }
   282     @Override
   283     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
   284         // When a tab is closed, it is always unselected first.
   285         // When a tab is unselected, another tab is always selected first.
   286         switch(msg) {
   287             case UNSELECTED:
   288                 hidePlugins(tab);
   289                 break;
   291             case LOCATION_CHANGE:
   292                 // We only care about location change for the selected tab.
   293                 if (!Tabs.getInstance().isSelectedTab(tab))
   294                     break;
   295                 // Fall through...
   296             case SELECTED:
   297                 invalidateOptionsMenu();
   298                 if (mFormAssistPopup != null)
   299                     mFormAssistPopup.hide();
   300                 break;
   302             case LOADED:
   303                 // Sync up the layer view and the tab if the tab is
   304                 // currently displayed.
   305                 LayerView layerView = mLayerView;
   306                 if (layerView != null && Tabs.getInstance().isSelectedTab(tab))
   307                     layerView.setBackgroundColor(tab.getBackgroundColor());
   308                 break;
   310             case DESKTOP_MODE_CHANGE:
   311                 if (Tabs.getInstance().isSelectedTab(tab))
   312                     invalidateOptionsMenu();
   313                 break;
   314         }
   315     }
   317     public void refreshChrome() { }
   319     @Override
   320     public void invalidateOptionsMenu() {
   321         if (mMenu == null)
   322             return;
   324         onPrepareOptionsMenu(mMenu);
   326         if (Build.VERSION.SDK_INT >= 11)
   327             super.invalidateOptionsMenu();
   328     }
   330     @Override
   331     public boolean onCreateOptionsMenu(Menu menu) {
   332         mMenu = menu;
   334         MenuInflater inflater = getMenuInflater();
   335         inflater.inflate(R.menu.gecko_app_menu, mMenu);
   336         return true;
   337     }
   339     @Override
   340     public MenuInflater getMenuInflater() {
   341         if (Build.VERSION.SDK_INT >= 11)
   342             return new GeckoMenuInflater(this);
   343         else
   344             return super.getMenuInflater();
   345     }
   347     public MenuPanel getMenuPanel() {
   348         if (mMenuPanel == null) {
   349             onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
   350             invalidateOptionsMenu();
   351         }
   352         return mMenuPanel;
   353     }
   355     @Override
   356     public boolean onMenuItemSelected(MenuItem item) {
   357         return onOptionsItemSelected(item);
   358     }
   360     @Override
   361     public void openMenu() {
   362         openOptionsMenu();
   363     }
   365     @Override
   366     public void showMenu(final View menu) {
   367         // On devices using the custom menu, focus is cleared from the menu when its tapped.
   368         // Close and then reshow it to avoid these issues. See bug 794581 and bug 968182.
   369         closeMenu();
   371         // Post the reshow code back to the UI thread to avoid some optimizations Android
   372         // has put in place for menus that hide/show themselves quickly. See bug 985400.
   373         ThreadUtils.postToUiThread(new Runnable() {
   374             @Override
   375             public void run() {
   376                 mMenuPanel.removeAllViews();
   377                 mMenuPanel.addView(menu);
   378                 openOptionsMenu();
   379             }
   380         });
   381     }
   383     @Override
   384     public void closeMenu() {
   385         closeOptionsMenu();
   386     }
   388     @Override
   389     public View onCreatePanelView(int featureId) {
   390         if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
   391             if (mMenuPanel == null) {
   392                 mMenuPanel = new MenuPanel(this, null);
   393             } else {
   394                 // Prepare the panel everytime before showing the menu.
   395                 onPreparePanel(featureId, mMenuPanel, mMenu);
   396             }
   398             return mMenuPanel; 
   399         }
   401         return super.onCreatePanelView(featureId);
   402     }
   404     @Override
   405     public boolean onCreatePanelMenu(int featureId, Menu menu) {
   406         if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
   407             if (mMenuPanel == null) {
   408                 mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
   409             }
   411             GeckoMenu gMenu = new GeckoMenu(this, null);
   412             gMenu.setCallback(this);
   413             gMenu.setMenuPresenter(this);
   414             menu = gMenu;
   415             mMenuPanel.addView(gMenu);
   417             return onCreateOptionsMenu(menu);
   418         }
   420         return super.onCreatePanelMenu(featureId, menu);
   421     }
   423     @Override
   424     public boolean onPreparePanel(int featureId, View view, Menu menu) {
   425         if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL)
   426             return onPrepareOptionsMenu(menu);
   428         return super.onPreparePanel(featureId, view, menu);
   429     }
   431     @Override
   432     public boolean onMenuOpened(int featureId, Menu menu) {
   433         // exit full-screen mode whenever the menu is opened
   434         if (mLayerView != null && mLayerView.isFullScreen()) {
   435             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
   436         }
   438         if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
   439             if (mMenu == null) {
   440                 // getMenuPanel() will force the creation of the menu as well
   441                 MenuPanel panel = getMenuPanel();
   442                 onPreparePanel(featureId, panel, mMenu);
   443             }
   445             // Scroll custom menu to the top
   446             if (mMenuPanel != null)
   447                 mMenuPanel.scrollTo(0, 0);
   449             return true;
   450         }
   452         return super.onMenuOpened(featureId, menu);
   453     }
   455     @Override
   456     public boolean onOptionsItemSelected(MenuItem item) {
   457         if (item.getItemId() == R.id.quit) {
   458             if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.GeckoRunning, GeckoThread.LaunchState.GeckoExiting)) {
   459                 GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent("Browser:Quit", null));
   460             } else {
   461                 GeckoAppShell.systemExit();
   462             }
   463             return true;
   464         }
   466         return super.onOptionsItemSelected(item);
   467     }
   469     @Override
   470     public void onOptionsMenuClosed(Menu menu) {
   471         if (Build.VERSION.SDK_INT >= 11) {
   472             mMenuPanel.removeAllViews();
   473             mMenuPanel.addView((GeckoMenu) mMenu);
   474         }
   475     }
   477     @Override
   478     public boolean onKeyDown(int keyCode, KeyEvent event) {
   479         // Handle hardware menu key presses separately so that we can show a custom menu in some cases.
   480         if (keyCode == KeyEvent.KEYCODE_MENU) {
   481             openOptionsMenu();
   482             return true;
   483         }
   485         return super.onKeyDown(keyCode, event);
   486     }
   488     @Override
   489     protected void onSaveInstanceState(Bundle outState) {
   490         super.onSaveInstanceState(outState);
   492         if (mToast != null) {
   493             mToast.onSaveInstanceState(outState);
   494         }
   496         outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
   497         outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
   498     }
   500     void handleFaviconRequest(final String url) {
   501         (new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
   502             @Override
   503             public String doInBackground(Void... params) {
   504                 return Favicons.getFaviconURLForPageURL(url);
   505             }
   507             @Override
   508             public void onPostExecute(String faviconUrl) {
   509                 JSONObject args = new JSONObject();
   511                 if (faviconUrl != null) {
   512                     try {
   513                         args.put("url", url);
   514                         args.put("faviconUrl", faviconUrl);
   515                     } catch (JSONException e) {
   516                         Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
   517                     }
   518                 }
   520                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
   521             }
   522         }).execute();
   523     }
   525     void handleClearHistory() {
   526         BrowserDB.clearHistory(getContentResolver());
   527     }
   529     public void addTab() { }
   531     public void addPrivateTab() { }
   533     public void showNormalTabs() { }
   535     public void showPrivateTabs() { }
   537     public void hideTabs() { }
   539     /**
   540      * Close the tab UI indirectly (not as the result of a direct user
   541      * action).  This does not force the UI to close; for example in Firefox
   542      * tablet mode it will remain open unless the user explicitly closes it.
   543      *
   544      * @return True if the tab UI was hidden.
   545      */
   546     public boolean autoHideTabs() { return false; }
   548     public boolean areTabsShown() { return false; }
   550     @Override
   551     public void handleMessage(String event, JSONObject message) {
   552         try {
   553             if (event.equals("Toast:Show")) {
   554                 final String msg = message.getString("message");
   555                 final JSONObject button = message.optJSONObject("button");
   556                 if (button != null) {
   557                     final String label = button.optString("label");
   558                     final String icon = button.optString("icon");
   559                     final String id = button.optString("id");
   560                     showButtonToast(msg, label, icon, id);
   561                 } else {
   562                     final String duration = message.getString("duration");
   563                     showNormalToast(msg, duration);
   564                 }
   565             } else if (event.equals("log")) {
   566                 // generic log listener
   567                 final String msg = message.getString("msg");
   568                 Log.d(LOGTAG, "Log: " + msg);
   569             } else if (event.equals("Reader:FaviconRequest")) {
   570                 final String url = message.getString("url");
   571                 handleFaviconRequest(url);
   572             } else if (event.equals("Gecko:DelayedStartup")) {
   573                 ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this));
   574             } else if (event.equals("Gecko:Ready")) {
   575                 mGeckoReadyStartupTimer.stop();
   576                 geckoConnected();
   578                 // This method is already running on the background thread, so we
   579                 // know that mHealthRecorder will exist. That doesn't stop us being
   580                 // paranoid.
   581                 // This method is cheap, so don't spawn a new runnable.
   582                 final HealthRecorder rec = mHealthRecorder;
   583                 if (rec != null) {
   584                   rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
   585                 }
   586             } else if (event.equals("ToggleChrome:Hide")) {
   587                 toggleChrome(false);
   588             } else if (event.equals("ToggleChrome:Show")) {
   589                 toggleChrome(true);
   590             } else if (event.equals("ToggleChrome:Focus")) {
   591                 focusChrome();
   592             } else if (event.equals("DOMFullScreen:Start")) {
   593                 // Local ref to layerView for thread safety
   594                 LayerView layerView = mLayerView;
   595                 if (layerView != null) {
   596                     layerView.setFullScreen(true);
   597                 }
   598             } else if (event.equals("DOMFullScreen:Stop")) {
   599                 // Local ref to layerView for thread safety
   600                 LayerView layerView = mLayerView;
   601                 if (layerView != null) {
   602                     layerView.setFullScreen(false);
   603                 }
   604             } else if (event.equals("Permissions:Data")) {
   605                 String host = message.getString("host");
   606                 JSONArray permissions = message.getJSONArray("permissions");
   607                 showSiteSettingsDialog(host, permissions);
   608             } else if (event.equals("Session:StatePurged")) {
   609                 onStatePurged();
   610             } else if (event.equals("Bookmark:Insert")) {
   611                 final String url = message.getString("url");
   612                 final String title = message.getString("title");
   613                 final Context context = this;
   614                 ThreadUtils.postToUiThread(new Runnable() {
   615                     @Override
   616                     public void run() {
   617                         Toast.makeText(context, R.string.bookmark_added, Toast.LENGTH_SHORT).show();
   618                         ThreadUtils.postToBackgroundThread(new Runnable() {
   619                             @Override
   620                             public void run() {
   621                                 BrowserDB.addBookmark(getContentResolver(), title, url);
   622                             }
   623                         });
   624                     }
   625                 });
   626             } else if (event.equals("Accessibility:Event")) {
   627                 GeckoAccessibility.sendAccessibilityEvent(message);
   628             } else if (event.equals("Accessibility:Ready")) {
   629                 GeckoAccessibility.updateAccessibilitySettings(this);
   630             } else if (event.equals("Shortcut:Remove")) {
   631                 final String url = message.getString("url");
   632                 final String origin = message.getString("origin");
   633                 final String title = message.getString("title");
   634                 final String type = message.getString("shortcutType");
   635                 GeckoAppShell.removeShortcut(title, url, origin, type);
   636             } else if (event.equals("Share:Text")) {
   637                 String text = message.getString("text");
   638                 GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, "");
   640                 // Context: Sharing via chrome list (no explicit session is active)
   641                 Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
   642             } else if (event.equals("Image:SetAs")) {
   643                 String src = message.getString("url");
   644                 setImageAs(src);
   645             } else if (event.equals("Sanitize:ClearHistory")) {
   646                 handleClearHistory();
   647             } else if (event.equals("Update:Check")) {
   648                 startService(new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class));
   649             } else if (event.equals("Update:Download")) {
   650                 startService(new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE, null, this, UpdateService.class));
   651             } else if (event.equals("Update:Install")) {
   652                 startService(new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE, null, this, UpdateService.class));
   653             } else if (event.equals("PrivateBrowsing:Data")) {
   654                 // null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
   655                 if (message.isNull("session")) {
   656                     mPrivateBrowsingSession = null;
   657                 } else {
   658                     mPrivateBrowsingSession = message.getString("session");
   659                 }
   660             } else if (event.equals("Contact:Add")) {                
   661                 if (!message.isNull("email")) {
   662                     Uri contactUri = Uri.parse(message.getString("email"));       
   663                     Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
   664                     startActivity(i);
   665                 } else if (!message.isNull("phone")) {
   666                     Uri contactUri = Uri.parse(message.getString("phone"));       
   667                     Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
   668                     startActivity(i);
   669                 } else {
   670                     // something went wrong.
   671                     Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number");
   672                 }                
   673             } else if (event.equals("Intent:GetHandlers")) {
   674                 Intent intent = GeckoAppShell.getOpenURIIntent((Context) this, message.optString("url"),
   675                     message.optString("mime"), message.optString("action"), message.optString("title"));
   676                 String[] handlers = GeckoAppShell.getHandlersForIntent(intent);
   677                 List<String> appList = Arrays.asList(handlers);
   678                 JSONObject handlersJSON = new JSONObject();
   679                 handlersJSON.put("apps", new JSONArray(appList));
   680                 EventDispatcher.sendResponse(message, handlersJSON);
   681             } else if (event.equals("Intent:Open")) {
   682                 GeckoAppShell.openUriExternal(message.optString("url"),
   683                     message.optString("mime"), message.optString("packageName"),
   684                     message.optString("className"), message.optString("action"), message.optString("title"));
   685             } else if (event.equals("Intent:OpenForResult")) {
   686                 Intent intent = GeckoAppShell.getOpenURIIntent(this,
   687                                                                message.optString("url"),
   688                                                                message.optString("mime"),
   689                                                                message.optString("action"),
   690                                                                message.optString("title"));
   691                 intent.setClassName(message.optString("packageName"), message.optString("className"));
   693                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
   695                 final JSONObject originalMessage = message;
   696                 ActivityHandlerHelper.startIntentForActivity(this,
   697                                                              intent,
   698                         new ActivityResultHandler() {
   699                             @Override
   700                             public void onActivityResult (int resultCode, Intent data) {
   701                                 JSONObject response = new JSONObject();
   703                                 try {
   704                                     if (data != null) {
   705                                         response.put("extras", bundleToJSON(data.getExtras()));
   706                                     }
   707                                     response.put("resultCode", resultCode);
   708                                 } catch (JSONException e) {
   709                                     Log.w(LOGTAG, "Error building JSON response.", e);
   710                                 }
   712                                 EventDispatcher.sendResponse(originalMessage, response);
   713                             }
   714                         });
   715             } else if (event.equals("Locale:Set")) {
   716                 setLocale(message.getString("locale"));
   717             } else if (event.equals("NativeApp:IsDebuggable")) {
   718                 JSONObject ret = new JSONObject();
   719                 ret.put("isDebuggable", getIsDebuggable());
   720                 EventDispatcher.sendResponse(message, ret);
   721             } else if (event.equals("SystemUI:Visibility")) {
   722                 setSystemUiVisible(message.getBoolean("visible"));
   723             }
   724         } catch (Exception e) {
   725             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
   726         }
   727     }
   729     void onStatePurged() { }
   731     /**
   732      * @param aPermissions
   733      *        Array of JSON objects to represent site permissions.
   734      *        Example: { type: "offline-app", setting: "Store Offline Data", value: "Allow" }
   735      */
   736     private void showSiteSettingsDialog(String aHost, JSONArray aPermissions) {
   737         final AlertDialog.Builder builder = new AlertDialog.Builder(this);
   739         View customTitleView = getLayoutInflater().inflate(R.layout.site_setting_title, null);
   740         ((TextView) customTitleView.findViewById(R.id.title)).setText(R.string.site_settings_title);
   741         ((TextView) customTitleView.findViewById(R.id.host)).setText(aHost);
   742         builder.setCustomTitle(customTitleView);
   744         // If there are no permissions to clear, show the user a message about that.
   745         // In the future, we want to disable the menu item if there are no permissions to clear.
   746         if (aPermissions.length() == 0) {
   747             builder.setMessage(R.string.site_settings_no_settings);
   748         } else {
   750             ArrayList <HashMap<String, String>> itemList = new ArrayList <HashMap<String, String>>();
   751             for (int i = 0; i < aPermissions.length(); i++) {
   752                 try {
   753                     JSONObject permObj = aPermissions.getJSONObject(i);
   754                     HashMap<String, String> map = new HashMap<String, String>();
   755                     map.put("setting", permObj.getString("setting"));
   756                     map.put("value", permObj.getString("value"));
   757                     itemList.add(map);
   758                 } catch (JSONException e) {
   759                     Log.w(LOGTAG, "Exception populating settings items.", e);
   760                 }
   761             }
   763             // setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with
   764             // setSingleChoiceItems and changing the choiceMode below when we create the dialog
   765             builder.setSingleChoiceItems(new SimpleAdapter(
   766                 GeckoApp.this,
   767                 itemList,
   768                 R.layout.site_setting_item,
   769                 new String[] { "setting", "value" },
   770                 new int[] { R.id.setting, R.id.value }
   771                 ), -1, new DialogInterface.OnClickListener() {
   772                     @Override
   773                     public void onClick(DialogInterface dialog, int id) { }
   774                 });
   776             builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
   777                 @Override
   778                 public void onClick(DialogInterface dialog, int id) {
   779                     ListView listView = ((AlertDialog) dialog).getListView();
   780                     SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions();
   782                     // An array of the indices of the permissions we want to clear
   783                     JSONArray permissionsToClear = new JSONArray();
   784                     for (int i = 0; i < checkedItemPositions.size(); i++)
   785                         if (checkedItemPositions.get(i))
   786                             permissionsToClear.put(i);
   788                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
   789                         "Permissions:Clear", permissionsToClear.toString()));
   790                 }
   791             });
   792         }
   794         builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener(){
   795             @Override
   796             public void onClick(DialogInterface dialog, int id) {
   797                 dialog.cancel();
   798             }
   799         });
   801         ThreadUtils.postToUiThread(new Runnable() {
   802             @Override
   803             public void run() {
   804                 Dialog dialog = builder.create();
   805                 dialog.show();
   807                 ListView listView = ((AlertDialog) dialog).getListView();
   808                 if (listView != null) {
   809                     listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
   810                     int listSize = listView.getAdapter().getCount();
   811                     for (int i = 0; i < listSize; i++)
   812                         listView.setItemChecked(i, true);
   813                 }
   814             }
   815         });
   816     }
   818     public void showToast(final int resId, final int duration) {
   819         ThreadUtils.postToUiThread(new Runnable() {
   820             @Override
   821             public void run() {
   822                 Toast.makeText(GeckoApp.this, resId, duration).show();
   823             }
   824         });
   825     }
   827     public void showNormalToast(final String message, final String duration) {
   828         ThreadUtils.postToUiThread(new Runnable() {
   829             @Override
   830             public void run() {
   831                 Toast toast;
   832                 if (duration.equals("long")) {
   833                     toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_LONG);
   834                 } else {
   835                     toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_SHORT);
   836                 }
   837                 toast.show();
   838             }
   839         });
   840     }
   842     protected ButtonToast getButtonToast() {
   843         if (mToast != null) {
   844             return mToast;
   845         }
   847         ViewStub toastStub = (ViewStub) findViewById(R.id.toast_stub);
   848         mToast = new ButtonToast(toastStub.inflate());
   850         return mToast;
   851     }
   853     void showButtonToast(final String message, final String buttonText,
   854                          final String buttonIcon, final String buttonId) {
   855         BitmapUtils.getDrawable(GeckoApp.this, buttonIcon, new BitmapUtils.BitmapLoader() {
   856             @Override
   857             public void onBitmapFound(final Drawable d) {
   858                 getButtonToast().show(false, message, buttonText, d, new ButtonToast.ToastListener() {
   859                     @Override
   860                     public void onButtonClicked() {
   861                         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Click", buttonId));
   862                     }
   864                     @Override
   865                     public void onToastHidden(ButtonToast.ReasonHidden reason) {
   866                         if (reason == ButtonToast.ReasonHidden.TIMEOUT) {
   867                             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Hidden", buttonId));
   868                         }
   869                     }
   870                 });
   871             }
   872         });
   873     }
   875     private JSONObject bundleToJSON(Bundle bundle) {
   876         JSONObject json = new JSONObject();
   877         if (bundle == null) {
   878             return json;
   879         }
   881         for (String key : bundle.keySet()) {
   882             try {
   883                 json.put(key, bundle.get(key));
   884             } catch (JSONException e) {
   885                 Log.w(LOGTAG, "Error building JSON response.", e);
   886             }
   887         }
   889         return json;
   890     }
   892     private void addFullScreenPluginView(View view) {
   893         if (mFullScreenPluginView != null) {
   894             Log.w(LOGTAG, "Already have a fullscreen plugin view");
   895             return;
   896         }
   898         setFullScreen(true);
   900         view.setWillNotDraw(false);
   901         if (view instanceof SurfaceView) {
   902             ((SurfaceView) view).setZOrderOnTop(true);
   903         }
   905         mFullScreenPluginContainer = new FullScreenHolder(this);
   907         FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
   908                             ViewGroup.LayoutParams.FILL_PARENT,
   909                             ViewGroup.LayoutParams.FILL_PARENT,
   910                             Gravity.CENTER);
   911         mFullScreenPluginContainer.addView(view, layoutParams);
   914         FrameLayout decor = (FrameLayout)getWindow().getDecorView();
   915         decor.addView(mFullScreenPluginContainer, layoutParams);
   917         mFullScreenPluginView = view;
   918     }
   920     public void addPluginView(final View view, final RectF rect, final boolean isFullScreen) {
   921         ThreadUtils.postToUiThread(new Runnable() {
   922             @Override
   923             public void run() {
   924                 Tabs tabs = Tabs.getInstance();
   925                 Tab tab = tabs.getSelectedTab();
   927                 if (isFullScreen) {
   928                     addFullScreenPluginView(view);
   929                     return;
   930                 }
   932                 PluginLayer layer = (PluginLayer) tab.getPluginLayer(view);
   933                 if (layer == null) {
   934                     layer = new PluginLayer(view, rect, mLayerView.getRenderer().getMaxTextureSize());
   935                     tab.addPluginLayer(view, layer);
   936                 } else {
   937                     layer.reset(rect);
   938                     layer.setVisible(true);
   939                 }
   941                 mLayerView.addLayer(layer);
   942             }
   943         });
   944     }
   946     private void removeFullScreenPluginView(View view) {
   947         if (mFullScreenPluginView == null) {
   948             Log.w(LOGTAG, "Don't have a fullscreen plugin view");
   949             return;
   950         }
   952         if (mFullScreenPluginView != view) {
   953             Log.w(LOGTAG, "Passed view is not the current full screen view");
   954             return;
   955         }
   957         mFullScreenPluginContainer.removeView(mFullScreenPluginView);
   959         // We need do do this on the next iteration in order to avoid
   960         // a deadlock, see comment below in FullScreenHolder
   961         ThreadUtils.postToUiThread(new Runnable() {
   962             @Override
   963             public void run() {
   964                 mLayerView.showSurface();
   965             }
   966         });
   968         FrameLayout decor = (FrameLayout)getWindow().getDecorView();
   969         decor.removeView(mFullScreenPluginContainer);
   971         mFullScreenPluginView = null;
   973         GeckoScreenOrientation.getInstance().unlock();
   974         setFullScreen(false);
   975     }
   977     public void removePluginView(final View view, final boolean isFullScreen) {
   978         ThreadUtils.postToUiThread(new Runnable() {
   979             @Override
   980             public void run() {
   981                 Tabs tabs = Tabs.getInstance();
   982                 Tab tab = tabs.getSelectedTab();
   984                 if (isFullScreen) {
   985                     removeFullScreenPluginView(view);
   986                     return;
   987                 }
   989                 PluginLayer layer = (PluginLayer) tab.removePluginLayer(view);
   990                 if (layer != null) {
   991                     layer.destroy();
   992                 }
   993             }
   994         });
   995     }
   997     // This method starts downloading an image synchronously and displays the Chooser activity to set the image as wallpaper.
   998     private void setImageAs(final String aSrc) {
   999         boolean isDataURI = aSrc.startsWith("data:");
  1000         Bitmap image = null;
  1001         InputStream is = null;
  1002         ByteArrayOutputStream os = null;
  1003         try {
  1004             if (isDataURI) {
  1005                 int dataStart = aSrc.indexOf(",");
  1006                 byte[] buf = Base64.decode(aSrc.substring(dataStart+1), Base64.DEFAULT);
  1007                 image = BitmapUtils.decodeByteArray(buf);
  1008             } else {
  1009                 int byteRead;
  1010                 byte[] buf = new byte[4192];
  1011                 os = new ByteArrayOutputStream();
  1012                 URL url = new URL(aSrc);
  1013                 is = url.openStream();
  1015                 // Cannot read from same stream twice. Also, InputStream from
  1016                 // URL does not support reset. So converting to byte array.
  1018                 while((byteRead = is.read(buf)) != -1) {
  1019                     os.write(buf, 0, byteRead);
  1021                 byte[] imgBuffer = os.toByteArray();
  1022                 image = BitmapUtils.decodeByteArray(imgBuffer);
  1024             if (image != null) {
  1025                 String path = Media.insertImage(getContentResolver(),image, null, null);
  1026                 final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
  1027                 intent.addCategory(Intent.CATEGORY_DEFAULT);
  1028                 intent.setData(Uri.parse(path));
  1030                 // Removes the image from storage once the chooser activity ends.
  1031                 Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title));
  1032                 ActivityResultHandler handler = new ActivityResultHandler() {
  1033                     @Override
  1034                     public void onActivityResult (int resultCode, Intent data) {
  1035                         getContentResolver().delete(intent.getData(), null, null);
  1037                 };
  1038                 ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
  1039             } else {
  1040                 Toast.makeText((Context) this, R.string.set_image_fail, Toast.LENGTH_SHORT).show();
  1042         } catch(OutOfMemoryError ome) {
  1043             Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
  1044         } catch(IOException ioe) {
  1045             Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
  1046         } finally {
  1047             if (is != null) {
  1048                 try {
  1049                     is.close();
  1050                 } catch(IOException ioe) {
  1051                     Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
  1054             if (os != null) {
  1055                 try {
  1056                     os.close();
  1057                 } catch(IOException ioe) {
  1058                     Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
  1064     private int getBitmapSampleSize(BitmapFactory.Options options, int idealWidth, int idealHeight) {
  1065         int width = options.outWidth;
  1066         int height = options.outHeight;
  1067         int inSampleSize = 1;
  1068         if (height > idealHeight || width > idealWidth) {
  1069             if (width > height) {
  1070                 inSampleSize = Math.round((float)height / (float)idealHeight);
  1071             } else {
  1072                 inSampleSize = Math.round((float)width / (float)idealWidth);
  1075         return inSampleSize;
  1078     private void hidePluginLayer(Layer layer) {
  1079         LayerView layerView = mLayerView;
  1080         layerView.removeLayer(layer);
  1081         layerView.requestRender();
  1084     private void showPluginLayer(Layer layer) {
  1085         LayerView layerView = mLayerView;
  1086         layerView.addLayer(layer);
  1087         layerView.requestRender();
  1090     public void requestRender() {
  1091         mLayerView.requestRender();
  1094     public void hidePlugins(Tab tab) {
  1095         for (Layer layer : tab.getPluginLayers()) {
  1096             if (layer instanceof PluginLayer) {
  1097                 ((PluginLayer) layer).setVisible(false);
  1100             hidePluginLayer(layer);
  1103         requestRender();
  1106     public void showPlugins() {
  1107         Tabs tabs = Tabs.getInstance();
  1108         Tab tab = tabs.getSelectedTab();
  1110         showPlugins(tab);
  1113     public void showPlugins(Tab tab) {
  1114         for (Layer layer : tab.getPluginLayers()) {
  1115             showPluginLayer(layer);
  1117             if (layer instanceof PluginLayer) {
  1118                 ((PluginLayer) layer).setVisible(true);
  1122         requestRender();
  1125     public void setFullScreen(final boolean fullscreen) {
  1126         ThreadUtils.postToUiThread(new Runnable() {
  1127             @Override
  1128             public void run() {
  1129                 // Hide/show the system notification bar
  1130                 Window window = getWindow();
  1131                 window.setFlags(fullscreen ?
  1132                                 WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
  1133                                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
  1135                 if (Build.VERSION.SDK_INT >= 11)
  1136                     window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0);
  1138         });
  1141     /**
  1142      * Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified
  1143      **/
  1144     protected void earlyStartJavaSampler(Intent intent)
  1146         String env = intent.getStringExtra("env0");
  1147         for (int i = 1; env != null; i++) {
  1148             if (env.startsWith("MOZ_PROFILER_STARTUP=")) {
  1149                 if (!env.endsWith("=")) {
  1150                     GeckoJavaSampler.start(10, 1000);
  1151                     Log.d(LOGTAG, "Profiling Java on startup");
  1153                 break;
  1155             env = intent.getStringExtra("env" + i);
  1159     /**
  1160      * Called when the activity is first created.
  1162      * Here we initialize all of our profile settings, Firefox Health Report,
  1163      * and other one-shot constructions.
  1164      **/
  1165     @Override
  1166     public void onCreate(Bundle savedInstanceState)
  1168         GeckoAppShell.registerGlobalExceptionHandler();
  1170         // Enable Android Strict Mode for developers' local builds (the "default" channel).
  1171         if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
  1172             enableStrictMode();
  1175         // The clock starts...now. Better hurry!
  1176         mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
  1177         mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
  1179         final Intent intent = getIntent();
  1180         final String args = intent.getStringExtra("args");
  1182         earlyStartJavaSampler(intent);
  1184         // GeckoLoader wants to dig some environment variables out of the
  1185         // incoming intent, so pass it in here. GeckoLoader will do its
  1186         // business later and dispose of the reference.
  1187         GeckoLoader.setLastIntent(intent);
  1189         if (mProfile == null) {
  1190             String profileName = null;
  1191             String profilePath = null;
  1192             if (args != null) {
  1193                 if (args.contains("-P")) {
  1194                     Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
  1195                     Matcher m = p.matcher(args);
  1196                     if (m.find()) {
  1197                         profileName = m.group(1);
  1201                 if (args.contains("-profile")) {
  1202                     Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
  1203                     Matcher m = p.matcher(args);
  1204                     if (m.find()) {
  1205                         profilePath =  m.group(1);
  1207                     if (profileName == null) {
  1208                         try {
  1209                             profileName = getDefaultProfileName();
  1210                         } catch (NoMozillaDirectoryException e) {
  1211                             Log.wtf(LOGTAG, "Unable to fetch default profile name!", e);
  1212                             // There's nothing at all we can do now. If the Mozilla directory
  1213                             // didn't exist, then we're screwed.
  1214                             // Crash here so we can fix the bug.
  1215                             throw new RuntimeException(e);
  1217                         if (profileName == null)
  1218                             profileName = GeckoProfile.DEFAULT_PROFILE;
  1220                     GeckoProfile.sIsUsingCustomProfile = true;
  1223                 if (profileName != null || profilePath != null) {
  1224                     mProfile = GeckoProfile.get(this, profileName, profilePath);
  1229         BrowserDB.initialize(getProfile().getName());
  1231         // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
  1232         try {
  1233             Class.forName("android.os.AsyncTask");
  1234         } catch (ClassNotFoundException e) {}
  1236         MemoryMonitor.getInstance().init(getApplicationContext());
  1238         // GeckoAppShell is tightly coupled to us, rather than
  1239         // the app context, because various parts of Fennec (e.g.,
  1240         // GeckoScreenOrientation) use GAS to access the Activity in
  1241         // the guise of fetching a Context.
  1242         // When that's fixed, `this` can change to
  1243         // `(GeckoApplication) getApplication()` here.
  1244         GeckoAppShell.setContextGetter(this);
  1245         GeckoAppShell.setGeckoInterface(this);
  1247         ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
  1249         Tabs.getInstance().attachToContext(this);
  1250         try {
  1251             Favicons.attachToContext(this);
  1252         } catch (Exception e) {
  1253             Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
  1256         // Did the OS locale change while we were backgrounded? If so,
  1257         // we need to die so that Gecko will re-init add-ons that touch
  1258         // the UI.
  1259         // This is using a sledgehammer to crack a nut, but it'll do for
  1260         // now.
  1261         if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
  1262             Log.i(LOGTAG, "System locale changed. Restarting.");
  1263             doRestart();
  1264             GeckoAppShell.systemExit();
  1265             return;
  1268         if (GeckoThread.isCreated()) {
  1269             // This happens when the GeckoApp activity is destroyed by Android
  1270             // without killing the entire application (see Bug 769269).
  1271             mIsRestoringActivity = true;
  1272             Telemetry.HistogramAdd("FENNEC_RESTORING_ACTIVITY", 1);
  1275         // Fix for Bug 830557 on Tegra boards running Froyo.
  1276         // This fix must be done before doing layout.
  1277         // Assume the bug is fixed in Gingerbread and up.
  1278         if (Build.VERSION.SDK_INT < 9) {
  1279             try {
  1280                 Class<?> inputBindResultClass =
  1281                     Class.forName("com.android.internal.view.InputBindResult");
  1282                 java.lang.reflect.Field creatorField =
  1283                     inputBindResultClass.getField("CREATOR");
  1284                 Log.i(LOGTAG, "froyo startup fix: " + String.valueOf(creatorField.get(null)));
  1285             } catch (Exception e) {
  1286                 Log.w(LOGTAG, "froyo startup fix failed", e);
  1290         Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE);
  1291         if (stateBundle != null) {
  1292             // Use the state bundle if it was given as an intent extra. This is
  1293             // only intended to be used internally via Robocop, so a boolean
  1294             // is read from a private shared pref to prevent other apps from
  1295             // injecting states.
  1296             final SharedPreferences prefs = getSharedPreferences();
  1297             if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) {
  1298                 Log.i(LOGTAG, "Restoring state from intent bundle");
  1299                 prefs.edit().remove(PREFS_ALLOW_STATE_BUNDLE).commit();
  1300                 savedInstanceState = stateBundle;
  1302         } else if (savedInstanceState != null) {
  1303             // Bug 896992 - This intent has already been handled; reset the intent.
  1304             setIntent(new Intent(Intent.ACTION_MAIN));
  1307         super.onCreate(savedInstanceState);
  1309         GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
  1311         setContentView(getLayout());
  1313         // Set up Gecko layout.
  1314         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
  1315         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
  1317         // Determine whether we should restore tabs.
  1318         mShouldRestore = getSessionRestoreState(savedInstanceState);
  1319         if (mShouldRestore && savedInstanceState != null) {
  1320             boolean wasInBackground =
  1321                 savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
  1323             // Don't log OOM-kills if only one activity was destroyed. (For example
  1324             // from "Don't keep activities" on ICS)
  1325             if (!wasInBackground && !mIsRestoringActivity) {
  1326                 Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
  1329             mPrivateBrowsingSession = savedInstanceState.getString(SAVED_STATE_PRIVATE_SESSION);
  1332         // Perform background initialization.
  1333         ThreadUtils.postToBackgroundThread(new Runnable() {
  1334             @Override
  1335             public void run() {
  1336                 final SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  1338                 // Wait until now to set this, because we'd rather throw an exception than 
  1339                 // have a caller of BrowserLocaleManager regress startup.
  1340                 BrowserLocaleManager.getInstance().initialize(getApplicationContext());
  1342                 SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
  1343                 if (previousSession.wasKilled()) {
  1344                     Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
  1347                 SharedPreferences.Editor editor = prefs.edit();
  1348                 editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
  1350                 // Put a flag to check if we got a normal `onSaveInstanceState`
  1351                 // on exit, or if we were suddenly killed (crash or native OOM).
  1352                 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
  1354                 editor.commit();
  1356                 // The lifecycle of mHealthRecorder is "shortly after onCreate"
  1357                 // through "onDestroy" -- essentially the same as the lifecycle
  1358                 // of the activity itself.
  1359                 final String profilePath = getProfile().getDir().getAbsolutePath();
  1360                 final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
  1361                 Log.i(LOGTAG, "Creating HealthRecorder.");
  1363                 final String osLocale = Locale.getDefault().toString();
  1364                 String appLocale = BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(GeckoApp.this);
  1365                 Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
  1367                 if (appLocale == null) {
  1368                     appLocale = osLocale;
  1371                 mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
  1372                                                                      profilePath,
  1373                                                                      dispatcher,
  1374                                                                      osLocale,
  1375                                                                      appLocale,
  1376                                                                      previousSession);
  1378                 final String uiLocale = appLocale;
  1379                 ThreadUtils.postToUiThread(new Runnable() {
  1380                     @Override
  1381                     public void run() {
  1382                         GeckoApp.this.onLocaleReady(uiLocale);
  1384                 });
  1386         });
  1388         GeckoAppShell.setNotificationClient(makeNotificationClient());
  1389         NotificationHelper.init(getApplicationContext());
  1392     /**
  1393      * At this point, the resource system and the rest of the browser are
  1394      * aware of the locale.
  1396      * Now we can display strings!
  1398      * You can think of this as being something like a second phase of onCreate,
  1399      * where you can do string-related operations. Use this in place of embedding
  1400      * strings in view XML.
  1402      * By contrast, onConfigurationChanged does some locale operations, but is in
  1403      * response to device changes.
  1404      */
  1405     @Override
  1406     public void onLocaleReady(final String locale) {
  1407         if (!ThreadUtils.isOnUiThread()) {
  1408             throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
  1411         // The URL bar hint needs to be populated.
  1412         TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
  1413         if (urlBar != null) {
  1414             final String hint = getResources().getString(R.string.url_bar_default_text);
  1415             urlBar.setHint(hint);
  1416         } else {
  1417             Log.d(LOGTAG, "No URL bar in GeckoApp. Not loading localized hint string.");
  1420         // Allow onConfigurationChanged to take care of the rest.
  1421         onConfigurationChanged(getResources().getConfiguration());
  1424     protected void initializeChrome() {
  1425         mDoorHangerPopup = new DoorHangerPopup(this);
  1426         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
  1427         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
  1429         if (mCameraView == null) {
  1430             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  1431                 mCameraView = new SurfaceView(this);
  1432                 ((SurfaceView)mCameraView).getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  1433             } else {
  1434                 mCameraView = new TextureView(this);
  1438         if (mLayerView == null) {
  1439             LayerView layerView = (LayerView) findViewById(R.id.layer_view);
  1440             layerView.initializeView(GeckoAppShell.getEventDispatcher());
  1441             mLayerView = layerView;
  1442             GeckoAppShell.setLayerView(layerView);
  1443             // bind the GeckoEditable instance to the new LayerView
  1444             GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", "");
  1448     /**
  1449      * Loads the initial tab at Fennec startup.
  1451      * If Fennec was opened with an external URL, that URL will be loaded.
  1452      * Otherwise, unless there was a session restore, the default URL
  1453      * (about:home) be loaded.
  1455      * @param url External URL to load, or null to load the default URL
  1456      */
  1457     protected void loadStartupTab(String url) {
  1458         if (url == null) {
  1459             if (!mShouldRestore) {
  1460                 // Show about:home if we aren't restoring previous session and
  1461                 // there's no external URL.
  1462                 Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
  1464         } else {
  1465             // If given an external URL, load it
  1466             int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
  1467             Tabs.getInstance().loadUrl(url, flags);
  1471     private void initialize() {
  1472         mInitialized = true;
  1474         Intent intent = getIntent();
  1475         String action = intent.getAction();
  1477         String passedUri = null;
  1478         final String uri = getURIFromIntent(intent);
  1479         if (!TextUtils.isEmpty(uri)) {
  1480             passedUri = uri;
  1483         final boolean isExternalURL = passedUri != null &&
  1484                                       !AboutPages.isAboutHome(passedUri);
  1485         StartupAction startupAction;
  1486         if (isExternalURL) {
  1487             startupAction = StartupAction.URL;
  1488         } else {
  1489             startupAction = StartupAction.NORMAL;
  1492         // Start migrating as early as possible, can do this in
  1493         // parallel with Gecko load.
  1494         checkMigrateProfile();
  1496         Uri data = intent.getData();
  1497         if (data != null && "http".equals(data.getScheme())) {
  1498             startupAction = StartupAction.PREFETCH;
  1499             ThreadUtils.postToBackgroundThread(new PrefetchRunnable(data.toString()));
  1502         Tabs.registerOnTabsChangedListener(this);
  1504         initializeChrome();
  1506         // If we are doing a restore, read the session data and send it to Gecko
  1507         if (!mIsRestoringActivity) {
  1508             String restoreMessage = null;
  1509             if (mShouldRestore) {
  1510                 try {
  1511                     // restoreSessionTabs() will create simple tab stubs with the
  1512                     // URL and title for each page, but we also need to restore
  1513                     // session history. restoreSessionTabs() will inject the IDs
  1514                     // of the tab stubs into the JSON data (which holds the session
  1515                     // history). This JSON data is then sent to Gecko so session
  1516                     // history can be restored for each tab.
  1517                     restoreMessage = restoreSessionTabs(isExternalURL);
  1518                 } catch (SessionRestoreException e) {
  1519                     // If restore failed, do a normal startup
  1520                     Log.e(LOGTAG, "An error occurred during restore", e);
  1521                     mShouldRestore = false;
  1525             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Session:Restore", restoreMessage));
  1528         // External URLs should always be loaded regardless of whether Gecko is
  1529         // already running.
  1530         if (isExternalURL) {
  1531             loadStartupTab(passedUri);
  1532         } else if (!mIsRestoringActivity) {
  1533             loadStartupTab(null);
  1536         // We now have tab stubs from the last session. Any future tabs should
  1537         // be animated.
  1538         Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
  1540         // If we're not restoring, move the session file so it can be read for
  1541         // the last tabs section.
  1542         if (!mShouldRestore) {
  1543             getProfile().moveSessionFile();
  1546         Telemetry.HistogramAdd("FENNEC_STARTUP_GECKOAPP_ACTION", startupAction.ordinal());
  1548         if (!mIsRestoringActivity) {
  1549             GeckoThread.setArgs(intent.getStringExtra("args"));
  1550             GeckoThread.setAction(intent.getAction());
  1551             GeckoThread.setUri(passedUri);
  1553         if (!ACTION_DEBUG.equals(action) &&
  1554             GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
  1555             GeckoThread.createAndStart();
  1556         } else if (ACTION_DEBUG.equals(action) &&
  1557             GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.WaitForDebugger)) {
  1558             ThreadUtils.getUiHandler().postDelayed(new Runnable() {
  1559                 @Override
  1560                 public void run() {
  1561                     GeckoThread.setLaunchState(GeckoThread.LaunchState.Launching);
  1562                     GeckoThread.createAndStart();
  1564             }, 1000 * 5 /* 5 seconds */);
  1567         // Check if launched from data reporting notification.
  1568         if (ACTION_LAUNCH_SETTINGS.equals(action)) {
  1569             Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
  1570             // Copy extras.
  1571             settingsIntent.putExtras(intent);
  1572             startActivity(settingsIntent);
  1575         //app state callbacks
  1576         mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
  1578         //register for events
  1579         registerEventListener("log");
  1580         registerEventListener("Reader:ListStatusRequest");
  1581         registerEventListener("Reader:Added");
  1582         registerEventListener("Reader:Removed");
  1583         registerEventListener("Reader:Share");
  1584         registerEventListener("Reader:FaviconRequest");
  1585         registerEventListener("onCameraCapture");
  1586         registerEventListener("Gecko:Ready");
  1587         registerEventListener("Gecko:DelayedStartup");
  1588         registerEventListener("Toast:Show");
  1589         registerEventListener("DOMFullScreen:Start");
  1590         registerEventListener("DOMFullScreen:Stop");
  1591         registerEventListener("ToggleChrome:Hide");
  1592         registerEventListener("ToggleChrome:Show");
  1593         registerEventListener("ToggleChrome:Focus");
  1594         registerEventListener("Permissions:Data");
  1595         registerEventListener("Session:StatePurged");
  1596         registerEventListener("Bookmark:Insert");
  1597         registerEventListener("Accessibility:Event");
  1598         registerEventListener("Accessibility:Ready");
  1599         registerEventListener("Shortcut:Remove");
  1600         registerEventListener("Share:Text");
  1601         registerEventListener("Image:SetAs");
  1602         registerEventListener("Sanitize:ClearHistory");
  1603         registerEventListener("Update:Check");
  1604         registerEventListener("Update:Download");
  1605         registerEventListener("Update:Install");
  1606         registerEventListener("PrivateBrowsing:Data");
  1607         registerEventListener("Contact:Add");
  1608         registerEventListener("Intent:Open");
  1609         registerEventListener("Intent:OpenForResult");
  1610         registerEventListener("Intent:GetHandlers");
  1611         registerEventListener("Locale:Set");
  1612         registerEventListener("NativeApp:IsDebuggable");
  1613         registerEventListener("SystemUI:Visibility");
  1615         if (mWebappEventListener == null) {
  1616             mWebappEventListener = new EventListener();
  1617             mWebappEventListener.registerEvents();
  1620         if (SmsManager.getInstance() != null) {
  1621             SmsManager.getInstance().start();
  1624         mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
  1626         mPromptService = new PromptService(this);
  1628         mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle),
  1629                                            (TextSelectionHandle) findViewById(R.id.middle_handle),
  1630                                            (TextSelectionHandle) findViewById(R.id.end_handle),
  1631                                            GeckoAppShell.getEventDispatcher(),
  1632                                            this);
  1634         PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
  1635             @Override public void prefValue(String pref, String value) {
  1636                 UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
  1638         });
  1640         PrefsHelper.getPref("app.geo.reportdata", new PrefsHelper.PrefHandlerBase() {
  1641             @Override public void prefValue(String pref, int value) {
  1642                 if (value == 1)
  1643                     mShouldReportGeoData = true;
  1644                 else
  1645                     mShouldReportGeoData = false;
  1647         });
  1649         // Trigger the completion of the telemetry timer that wraps activity startup,
  1650         // then grab the duration to give to FHR.
  1651         mJavaUiStartupTimer.stop();
  1652         final long javaDuration = mJavaUiStartupTimer.getElapsed();
  1654         ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
  1655             @Override
  1656             public void run() {
  1657                 final HealthRecorder rec = mHealthRecorder;
  1658                 if (rec != null) {
  1659                     rec.recordJavaStartupTime(javaDuration);
  1662                 // Record our launch time for the announcements service
  1663                 // to use in assessing inactivity.
  1664                 final Context context = GeckoApp.this;
  1665                 AnnouncementsBroadcastService.recordLastLaunch(context);
  1667                 // Kick off our background services. We do this by invoking the broadcast
  1668                 // receiver, which uses the system alarm infrastructure to perform tasks at
  1669                 // intervals.
  1670                 GeckoPreferences.broadcastAnnouncementsPref(context);
  1671                 GeckoPreferences.broadcastHealthReportUploadPref(context);
  1672                 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
  1673                     return;
  1676         }, 50);
  1678         if (mIsRestoringActivity) {
  1679             GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
  1680             Tab selectedTab = Tabs.getInstance().getSelectedTab();
  1681             if (selectedTab != null)
  1682                 Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
  1683             geckoConnected();
  1684             GeckoAppShell.setLayerClient(mLayerView.getLayerClientObject());
  1685             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
  1688         if (ACTION_ALERT_CALLBACK.equals(action)) {
  1689             processAlertCallback(intent);
  1693     private String restoreSessionTabs(final boolean isExternalURL) throws SessionRestoreException {
  1694         try {
  1695             String sessionString = getProfile().readSessionFile(false);
  1696             if (sessionString == null) {
  1697                 throw new SessionRestoreException("Could not read from session file");
  1700             // If we are doing an OOM restore, parse the session data and
  1701             // stub the restored tabs immediately. This allows the UI to be
  1702             // updated before Gecko has restored.
  1703             if (mShouldRestore) {
  1704                 final JSONArray tabs = new JSONArray();
  1705                 SessionParser parser = new SessionParser() {
  1706                     @Override
  1707                     public void onTabRead(SessionTab sessionTab) {
  1708                         JSONObject tabObject = sessionTab.getTabObject();
  1710                         int flags = Tabs.LOADURL_NEW_TAB;
  1711                         flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
  1712                         flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
  1713                         flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
  1715                         Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
  1716                         tab.updateTitle(sessionTab.getTitle());
  1718                         try {
  1719                             tabObject.put("tabId", tab.getId());
  1720                         } catch (JSONException e) {
  1721                             Log.e(LOGTAG, "JSON error", e);
  1723                         tabs.put(tabObject);
  1725                 };
  1727                 if (mPrivateBrowsingSession == null) {
  1728                     parser.parse(sessionString);
  1729                 } else {
  1730                     parser.parse(sessionString, mPrivateBrowsingSession);
  1733                 if (tabs.length() > 0) {
  1734                     sessionString = new JSONObject().put("windows", new JSONArray().put(new JSONObject().put("tabs", tabs))).toString();
  1735                 } else {
  1736                     throw new SessionRestoreException("No tabs could be read from session file");
  1740             JSONObject restoreData = new JSONObject();
  1741             restoreData.put("sessionString", sessionString);
  1742             return restoreData.toString();
  1744         } catch (JSONException e) {
  1745             throw new SessionRestoreException(e);
  1749     public GeckoProfile getProfile() {
  1750         // fall back to default profile if we didn't load a specific one
  1751         if (mProfile == null) {
  1752             mProfile = GeckoProfile.get(this);
  1754         return mProfile;
  1757     /**
  1758      * Determine whether the session should be restored.
  1760      * @param savedInstanceState Saved instance state given to the activity
  1761      * @return                   Whether to restore
  1762      */
  1763     protected boolean getSessionRestoreState(Bundle savedInstanceState) {
  1764         final SharedPreferences prefs = getSharedPreferences();
  1765         boolean shouldRestore = false;
  1767         final int versionCode = getVersionCode();
  1768         if (prefs.getInt(PREFS_VERSION_CODE, 0) != versionCode) {
  1769             // If the version has changed, the user has done an upgrade, so restore
  1770             // previous tabs.
  1771             ThreadUtils.postToBackgroundThread(new Runnable() {
  1772                 @Override
  1773                 public void run() {
  1774                     prefs.edit()
  1775                          .putInt(PREFS_VERSION_CODE, versionCode)
  1776                          .commit();
  1778             });
  1780             shouldRestore = true;
  1781         } else if (savedInstanceState != null ||
  1782                    getSessionRestorePreference().equals("always") ||
  1783                    getRestartFromIntent()) {
  1784             // We're coming back from a background kill by the OS, the user
  1785             // has chosen to always restore, or we restarted.
  1786             shouldRestore = true;
  1787         } else if (prefs.getBoolean(GeckoApp.PREFS_CRASHED, false)) {
  1788             ThreadUtils.postToBackgroundThread(new Runnable() {
  1789                 @Override
  1790                 public void run() {
  1791                     prefs.edit().putBoolean(PREFS_CRASHED, false).commit();
  1793             });
  1794             shouldRestore = true;
  1797         return shouldRestore;
  1800     private String getSessionRestorePreference() {
  1801         return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
  1804     private boolean getRestartFromIntent() {
  1805         return getIntent().getBooleanExtra("didRestart", false);
  1808     /**
  1809      * Enable Android StrictMode checks (for supported OS versions).
  1810      * http://developer.android.com/reference/android/os/StrictMode.html
  1811      */
  1812     private void enableStrictMode() {
  1813         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
  1814             return;
  1817         Log.d(LOGTAG, "Enabling Android StrictMode");
  1819         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
  1820                                   .detectAll()
  1821                                   .penaltyLog()
  1822                                   .build());
  1824         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
  1825                                .detectAll()
  1826                                .penaltyLog()
  1827                                .build());
  1830     public void enableCameraView() {
  1831         // Start listening for orientation events
  1832         mCameraOrientationEventListener = new OrientationEventListener(this) {
  1833             @Override
  1834             public void onOrientationChanged(int orientation) {
  1835                 if (mAppStateListeners != null) {
  1836                     for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
  1837                         listener.onOrientationChanged();
  1841         };
  1842         mCameraOrientationEventListener.enable();
  1844         // Try to make it fully transparent.
  1845         if (mCameraView instanceof SurfaceView) {
  1846             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  1847                 mCameraView.setAlpha(0.0f);
  1849         } else if (mCameraView instanceof TextureView) {
  1850             mCameraView.setAlpha(0.0f);
  1852         ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
  1853         // Some phones (eg. nexus S) need at least a 8x16 preview size
  1854         mCameraLayout.addView(mCameraView,
  1855                               new AbsoluteLayout.LayoutParams(8, 16, 0, 0));
  1858     public void disableCameraView() {
  1859         if (mCameraOrientationEventListener != null) {
  1860             mCameraOrientationEventListener.disable();
  1861             mCameraOrientationEventListener = null;
  1863         ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
  1864         mCameraLayout.removeView(mCameraView);
  1867     public String getDefaultUAString() {
  1868         return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET :
  1869                                           AppConstants.USER_AGENT_FENNEC_MOBILE;
  1872     public String getUAStringForHost(String host) {
  1873         // With our standard UA String, we get a 200 response code and
  1874         // client-side redirect from t.co. This bot-like UA gives us a
  1875         // 301 response code
  1876         if ("t.co".equals(host)) {
  1877             return AppConstants.USER_AGENT_BOT_LIKE;
  1879         return getDefaultUAString();
  1882     class PrefetchRunnable implements Runnable {
  1883         private String mPrefetchUrl;
  1885         PrefetchRunnable(String prefetchUrl) {
  1886             mPrefetchUrl = prefetchUrl;
  1889         @Override
  1890         public void run() {
  1891             HttpURLConnection connection = null;
  1892             try {
  1893                 URL url = new URL(mPrefetchUrl);
  1894                 // data url should have an http scheme
  1895                 connection = (HttpURLConnection) url.openConnection();
  1896                 connection.setRequestProperty("User-Agent", getUAStringForHost(url.getHost()));
  1897                 connection.setInstanceFollowRedirects(false);
  1898                 connection.setRequestMethod("GET");
  1899                 connection.connect();
  1900             } catch (Exception e) {
  1901                 Log.e(LOGTAG, "Exception prefetching URL", e);
  1902             } finally {
  1903                 if (connection != null)
  1904                     connection.disconnect();
  1909     private void processAlertCallback(Intent intent) {
  1910         String alertName = "";
  1911         String alertCookie = "";
  1912         Uri data = intent.getData();
  1913         if (data != null) {
  1914             alertName = data.getQueryParameter("name");
  1915             if (alertName == null)
  1916                 alertName = "";
  1917             alertCookie = data.getQueryParameter("cookie");
  1918             if (alertCookie == null)
  1919                 alertCookie = "";
  1921         handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
  1924     @Override
  1925     protected void onNewIntent(Intent intent) {
  1926         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
  1927             // We're exiting and shouldn't try to do anything else. In the case
  1928             // where we are hung while exiting, we should force the process to exit.
  1929             GeckoAppShell.systemExit();
  1930             return;
  1933         // if we were previously OOM killed, we can end up here when launching
  1934         // from external shortcuts, so set this as the intent for initialization
  1935         if (!mInitialized) {
  1936             setIntent(intent);
  1937             return;
  1940         final String action = intent.getAction();
  1942         if (ACTION_LOAD.equals(action)) {
  1943             String uri = intent.getDataString();
  1944             Tabs.getInstance().loadUrl(uri);
  1945         } else if (Intent.ACTION_VIEW.equals(action)) {
  1946             String uri = intent.getDataString();
  1947             Tabs.getInstance().loadUrl(uri, Tabs.LOADURL_NEW_TAB |
  1948                                             Tabs.LOADURL_USER_ENTERED |
  1949                                             Tabs.LOADURL_EXTERNAL);
  1950         } else if (action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) {
  1951             // A lightweight mechanism for loading a web page as a webapp
  1952             // without installing the app natively nor registering it in the DOM
  1953             // application registry.
  1954             String uri = getURIFromIntent(intent);
  1955             GeckoAppShell.sendEventToGecko(GeckoEvent.createWebappLoadEvent(uri));
  1956         } else if (ACTION_BOOKMARK.equals(action)) {
  1957             String uri = getURIFromIntent(intent);
  1958             GeckoAppShell.sendEventToGecko(GeckoEvent.createBookmarkLoadEvent(uri));
  1959         } else if (Intent.ACTION_SEARCH.equals(action)) {
  1960             String uri = getURIFromIntent(intent);
  1961             GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
  1962         } else if (ACTION_ALERT_CALLBACK.equals(action)) {
  1963             processAlertCallback(intent);
  1964         } else if (ACTION_LAUNCH_SETTINGS.equals(action)) {
  1965             // Check if launched from data reporting notification.
  1966             Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
  1967             // Copy extras.
  1968             settingsIntent.putExtras(intent);
  1969             startActivity(settingsIntent);
  1973     /*
  1974      * Handles getting a uri from and intent in a way that is backwards
  1975      * compatable with our previous implementations
  1976      */
  1977     protected String getURIFromIntent(Intent intent) {
  1978         final String action = intent.getAction();
  1979         if (ACTION_ALERT_CALLBACK.equals(action))
  1980             return null;
  1982         String uri = intent.getDataString();
  1983         if (uri != null)
  1984             return uri;
  1986         if ((action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) || ACTION_BOOKMARK.equals(action)) {
  1987             uri = intent.getStringExtra("args");
  1988             if (uri != null && uri.startsWith("--url=")) {
  1989                 uri.replace("--url=", "");
  1992         return uri;
  1995     protected int getOrientation() {
  1996         return GeckoScreenOrientation.getInstance().getAndroidOrientation();
  1999     @Override
  2000     public void onResume()
  2002         // After an onPause, the activity is back in the foreground.
  2003         // Undo whatever we did in onPause.
  2004         super.onResume();
  2006         int newOrientation = getResources().getConfiguration().orientation;
  2007         if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
  2008             refreshChrome();
  2011         // User may have enabled/disabled accessibility.
  2012         GeckoAccessibility.updateAccessibilitySettings(this);
  2014         if (mAppStateListeners != null) {
  2015             for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
  2016                 listener.onResume();
  2020         // We use two times: a pseudo-unique wall-clock time to identify the
  2021         // current session across power cycles, and the elapsed realtime to
  2022         // track the duration of the session.
  2023         final long now = System.currentTimeMillis();
  2024         final long realTime = android.os.SystemClock.elapsedRealtime();
  2026         ThreadUtils.postToBackgroundThread(new Runnable() {
  2027             @Override
  2028             public void run() {
  2029                 // Now construct the new session on HealthRecorder's behalf. We do this here
  2030                 // so it can benefit from a single near-startup prefs commit.
  2031                 SessionInformation currentSession = new SessionInformation(now, realTime);
  2033                 SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  2034                 SharedPreferences.Editor editor = prefs.edit();
  2035                 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
  2036                 currentSession.recordBegin(editor);
  2037                 editor.commit();
  2039                 final HealthRecorder rec = mHealthRecorder;
  2040                 if (rec != null) {
  2041                     rec.setCurrentSession(currentSession);
  2042                 } else {
  2043                     Log.w(LOGTAG, "Can't record session: rec is null.");
  2046          });
  2049     @Override
  2050     public void onWindowFocusChanged(boolean hasFocus) {
  2051         super.onWindowFocusChanged(hasFocus);
  2053         if (!mInitialized && hasFocus) {
  2054             initialize();
  2055             getWindow().setBackgroundDrawable(null);
  2059     @Override
  2060     public void onPause()
  2062         final HealthRecorder rec = mHealthRecorder;
  2063         final Context context = this;
  2065         // In some way it's sad that Android will trigger StrictMode warnings
  2066         // here as the whole point is to save to disk while the activity is not
  2067         // interacting with the user.
  2068         ThreadUtils.postToBackgroundThread(new Runnable() {
  2069             @Override
  2070             public void run() {
  2071                 SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  2072                 SharedPreferences.Editor editor = prefs.edit();
  2073                 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
  2074                 if (rec != null) {
  2075                     rec.recordSessionEnd("P", editor);
  2078                 // If we haven't done it before, cleanup any old files in our old temp dir
  2079                 if (prefs.getBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, true)) {
  2080                     File tempDir = GeckoLoader.getGREDir(GeckoApp.this);
  2081                     FileUtils.delTree(tempDir, new FileUtils.NameAndAgeFilter(null, ONE_DAY_MS), false);
  2083                     editor.putBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, false);
  2086                 editor.commit();
  2088                 // In theory, the first browser session will not run long enough that we need to
  2089                 // prune during it and we'd rather run it when the browser is inactive so we wait
  2090                 // until here to register the prune service.
  2091                 GeckoPreferences.broadcastHealthReportPrune(context);
  2093         });
  2095         if (mAppStateListeners != null) {
  2096             for(GeckoAppShell.AppStateListener listener: mAppStateListeners) {
  2097                 listener.onPause();
  2101         super.onPause();
  2104     @Override
  2105     public void onRestart()
  2107         ThreadUtils.postToBackgroundThread(new Runnable() {
  2108             @Override
  2109             public void run() {
  2110                 SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  2111                 SharedPreferences.Editor editor = prefs.edit();
  2112                 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
  2113                 editor.commit();
  2115         });
  2117         super.onRestart();
  2120     @Override
  2121     public void onDestroy()
  2123         unregisterEventListener("log");
  2124         unregisterEventListener("Reader:ListStatusRequest");
  2125         unregisterEventListener("Reader:Added");
  2126         unregisterEventListener("Reader:Removed");
  2127         unregisterEventListener("Reader:Share");
  2128         unregisterEventListener("Reader:FaviconRequest");
  2129         unregisterEventListener("onCameraCapture");
  2130         unregisterEventListener("Gecko:Ready");
  2131         unregisterEventListener("Gecko:DelayedStartup");
  2132         unregisterEventListener("Toast:Show");
  2133         unregisterEventListener("DOMFullScreen:Start");
  2134         unregisterEventListener("DOMFullScreen:Stop");
  2135         unregisterEventListener("ToggleChrome:Hide");
  2136         unregisterEventListener("ToggleChrome:Show");
  2137         unregisterEventListener("ToggleChrome:Focus");
  2138         unregisterEventListener("Permissions:Data");
  2139         unregisterEventListener("Session:StatePurged");
  2140         unregisterEventListener("Bookmark:Insert");
  2141         unregisterEventListener("Accessibility:Event");
  2142         unregisterEventListener("Accessibility:Ready");
  2143         unregisterEventListener("Shortcut:Remove");
  2144         unregisterEventListener("Share:Text");
  2145         unregisterEventListener("Image:SetAs");
  2146         unregisterEventListener("Sanitize:ClearHistory");
  2147         unregisterEventListener("Update:Check");
  2148         unregisterEventListener("Update:Download");
  2149         unregisterEventListener("Update:Install");
  2150         unregisterEventListener("PrivateBrowsing:Data");
  2151         unregisterEventListener("Contact:Add");
  2152         unregisterEventListener("Intent:Open");
  2153         unregisterEventListener("Intent:GetHandlers");
  2154         unregisterEventListener("Locale:Set");
  2155         unregisterEventListener("NativeApp:IsDebuggable");
  2156         unregisterEventListener("SystemUI:Visibility");
  2158         if (mWebappEventListener != null) {
  2159             mWebappEventListener.unregisterEvents();
  2160             mWebappEventListener = null;
  2163         deleteTempFiles();
  2165         if (mLayerView != null)
  2166             mLayerView.destroy();
  2167         if (mDoorHangerPopup != null)
  2168             mDoorHangerPopup.destroy();
  2169         if (mFormAssistPopup != null)
  2170             mFormAssistPopup.destroy();
  2171         if (mContactService != null)
  2172             mContactService.destroy();
  2173         if (mPromptService != null)
  2174             mPromptService.destroy();
  2175         if (mTextSelection != null)
  2176             mTextSelection.destroy();
  2177         NotificationHelper.destroy();
  2179         if (SmsManager.getInstance() != null) {
  2180             SmsManager.getInstance().stop();
  2181             if (isFinishing())
  2182                 SmsManager.getInstance().shutdown();
  2185         final HealthRecorder rec = mHealthRecorder;
  2186         mHealthRecorder = null;
  2187         if (rec != null && rec.isEnabled()) {
  2188             // Closing a BrowserHealthRecorder could incur a write.
  2189             ThreadUtils.postToBackgroundThread(new Runnable() {
  2190                 @Override
  2191                 public void run() {
  2192                     rec.close();
  2194             });
  2197         Favicons.close();
  2199         super.onDestroy();
  2201         Tabs.unregisterOnTabsChangedListener(this);
  2204     protected void registerEventListener(String event) {
  2205         GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
  2208     protected void unregisterEventListener(String event) {
  2209         GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
  2212     // Get a temporary directory, may return null
  2213     public static File getTempDirectory() {
  2214         File dir = GeckoApplication.get().getExternalFilesDir("temp");
  2215         return dir;
  2218     // Delete any files in our temporary directory
  2219     public static void deleteTempFiles() {
  2220         File dir = getTempDirectory();
  2221         if (dir == null)
  2222             return;
  2223         File[] files = dir.listFiles();
  2224         if (files == null)
  2225             return;
  2226         for (File file : files) {
  2227             file.delete();
  2231     @Override
  2232     public void onConfigurationChanged(Configuration newConfig) {
  2233         Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
  2234         BrowserLocaleManager.getInstance().correctLocale(this, getResources(), newConfig);
  2236         // onConfigurationChanged is not called for 180 degree orientation changes,
  2237         // we will miss such rotations and the screen orientation will not be
  2238         // updated.
  2239         if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) {
  2240             if (mFormAssistPopup != null)
  2241                 mFormAssistPopup.hide();
  2242             refreshChrome();
  2244         super.onConfigurationChanged(newConfig);
  2247     public String getContentProcessName() {
  2248         return AppConstants.MOZ_CHILD_PROCESS_NAME;
  2251     public void addEnvToIntent(Intent intent) {
  2252         Map<String,String> envMap = System.getenv();
  2253         Set<Map.Entry<String,String>> envSet = envMap.entrySet();
  2254         Iterator<Map.Entry<String,String>> envIter = envSet.iterator();
  2255         int c = 0;
  2256         while (envIter.hasNext()) {
  2257             Map.Entry<String,String> entry = envIter.next();
  2258             intent.putExtra("env" + c, entry.getKey() + "="
  2259                             + entry.getValue());
  2260             c++;
  2264     public void doRestart() {
  2265         doRestart(RESTARTER_ACTION, null);
  2268     public void doRestart(String args) {
  2269         doRestart(RESTARTER_ACTION, args);
  2272     public void doRestart(String action, String args) {
  2273         Log.d(LOGTAG, "doRestart(\"" + action + "\")");
  2274         try {
  2275             Intent intent = new Intent(action);
  2276             intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, RESTARTER_CLASS);
  2277             /* TODO: addEnvToIntent(intent); */
  2278             if (args != null)
  2279                 intent.putExtra("args", args);
  2280             intent.putExtra("didRestart", true);
  2281             Log.d(LOGTAG, "Restart intent: " + intent.toString());
  2282             GeckoAppShell.killAnyZombies();
  2283             startActivity(intent);
  2284         } catch (Exception e) {
  2285             Log.e(LOGTAG, "Error effecting restart.", e);
  2288         finish();
  2289         // Give the restart process time to start before we die
  2290         GeckoAppShell.waitForAnotherGeckoProc();
  2293     public void handleNotification(String action, String alertName, String alertCookie) {
  2294         // If Gecko isn't running yet, we ignore the notification. Note that
  2295         // even if Gecko is running but it was restarted since the notification
  2296         // was created, the notification won't be handled (bug 849653).
  2297         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
  2298             GeckoAppShell.handleNotification(action, alertName, alertCookie);
  2302     private void checkMigrateProfile() {
  2303         final File profileDir = getProfile().getDir();
  2305         if (profileDir != null) {
  2306             ThreadUtils.postToBackgroundThread(new Runnable() {
  2307                 @Override
  2308                 public void run() {
  2309                     Handler handler = new Handler();
  2310                     handler.postDelayed(new DeferredCleanupTask(), CLEANUP_DEFERRAL_SECONDS * 1000);
  2312             });
  2316     private class DeferredCleanupTask implements Runnable {
  2317         // The cleanup-version setting is recorded to avoid repeating the same
  2318         // tasks on subsequent startups; CURRENT_CLEANUP_VERSION may be updated
  2319         // if we need to do additional cleanup for future Gecko versions.
  2321         private static final String CLEANUP_VERSION = "cleanup-version";
  2322         private static final int CURRENT_CLEANUP_VERSION = 1;
  2324         @Override
  2325         public void run() {
  2326             long cleanupVersion = getSharedPreferences().getInt(CLEANUP_VERSION, 0);
  2328             if (cleanupVersion < 1) {
  2329                 // Reduce device storage footprint by removing .ttf files from
  2330                 // the res/fonts directory: we no longer need to copy our
  2331                 // bundled fonts out of the APK in order to use them.
  2332                 // See https://bugzilla.mozilla.org/show_bug.cgi?id=878674.
  2333                 File dir = new File("res/fonts");
  2334                 if (dir.exists() && dir.isDirectory()) {
  2335                     for (File file : dir.listFiles()) {
  2336                         if (file.isFile() && file.getName().endsWith(".ttf")) {
  2337                             Log.i(LOGTAG, "deleting " + file.toString());
  2338                             file.delete();
  2341                     if (!dir.delete()) {
  2342                         Log.w(LOGTAG, "unable to delete res/fonts directory (not empty?)");
  2343                     } else {
  2344                         Log.i(LOGTAG, "res/fonts directory deleted");
  2349             // Additional cleanup needed for future versions would go here
  2351             if (cleanupVersion != CURRENT_CLEANUP_VERSION) {
  2352                 SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit();
  2353                 editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION);
  2354                 editor.commit();
  2359     public PromptService getPromptService() {
  2360         return mPromptService;
  2363     @Override
  2364     public void onBackPressed() {
  2365         if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
  2366             super.onBackPressed();
  2367             return;
  2370         if (autoHideTabs()) {
  2371             return;
  2374         if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) {
  2375             mDoorHangerPopup.dismiss();
  2376             return;
  2379         if (mFullScreenPluginView != null) {
  2380             GeckoAppShell.onFullScreenPluginHidden(mFullScreenPluginView);
  2381             removeFullScreenPluginView(mFullScreenPluginView);
  2382             return;
  2385         if (mLayerView != null && mLayerView.isFullScreen()) {
  2386             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
  2387             return;
  2390         Tabs tabs = Tabs.getInstance();
  2391         Tab tab = tabs.getSelectedTab();
  2392         if (tab == null) {
  2393             moveTaskToBack(true);
  2394             return;
  2397         if (tab.doBack())
  2398             return;
  2400         if (tab.isExternal()) {
  2401             moveTaskToBack(true);
  2402             tabs.closeTab(tab);
  2403             return;
  2406         int parentId = tab.getParentId();
  2407         Tab parent = tabs.getTab(parentId);
  2408         if (parent != null) {
  2409             // The back button should always return to the parent (not a sibling).
  2410             tabs.closeTab(tab, parent);
  2411             return;
  2414         moveTaskToBack(true);
  2417     @Override
  2418     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  2419         if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) {
  2420             super.onActivityResult(requestCode, resultCode, data);
  2424     public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
  2426     // Accelerometer.
  2427     @Override
  2428     public void onAccuracyChanged(Sensor sensor, int accuracy) {
  2431     @Override
  2432     public void onSensorChanged(SensorEvent event) {
  2433         GeckoAppShell.sendEventToGecko(GeckoEvent.createSensorEvent(event));
  2436     // Geolocation.
  2437     @Override
  2438     public void onLocationChanged(Location location) {
  2439         // No logging here: user-identifying information.
  2440         GeckoAppShell.sendEventToGecko(GeckoEvent.createLocationEvent(location));
  2441         if (mShouldReportGeoData)
  2442             collectAndReportLocInfo(location);
  2445     public void setCurrentSignalStrenth(SignalStrength ss) {
  2446         if (ss.isGsm())
  2447             mSignalStrenth = ss.getGsmSignalStrength();
  2450     private int getCellInfo(JSONArray cellInfo) {
  2451         TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
  2452         if (tm == null)
  2453             return TelephonyManager.PHONE_TYPE_NONE;
  2454         List<NeighboringCellInfo> cells = tm.getNeighboringCellInfo();
  2455         CellLocation cl = tm.getCellLocation();
  2456         String mcc = "", mnc = "";
  2457         if (cl instanceof GsmCellLocation) {
  2458             JSONObject obj = new JSONObject();
  2459             GsmCellLocation gcl = (GsmCellLocation)cl;
  2460             try {
  2461                 obj.put("lac", gcl.getLac());
  2462                 obj.put("cid", gcl.getCid());
  2464                 int psc = (Build.VERSION.SDK_INT >= 9) ? gcl.getPsc() : -1;
  2465                 obj.put("psc", psc);
  2467                 switch(tm.getNetworkType()) {
  2468                 case TelephonyManager.NETWORK_TYPE_GPRS:
  2469                 case TelephonyManager.NETWORK_TYPE_EDGE:
  2470                     obj.put("radio", "gsm");
  2471                     break;
  2472                 case TelephonyManager.NETWORK_TYPE_UMTS:
  2473                 case TelephonyManager.NETWORK_TYPE_HSDPA:
  2474                 case TelephonyManager.NETWORK_TYPE_HSUPA:
  2475                 case TelephonyManager.NETWORK_TYPE_HSPA:
  2476                 case TelephonyManager.NETWORK_TYPE_HSPAP:
  2477                     obj.put("radio", "umts");
  2478                     break;
  2480                 String mcc_mnc = tm.getNetworkOperator();
  2481                 if (mcc_mnc.length() > 3) {
  2482                     mcc = mcc_mnc.substring(0, 3);
  2483                     mnc = mcc_mnc.substring(3);
  2484                     obj.put("mcc", mcc);
  2485                     obj.put("mnc", mnc);
  2487                 obj.put("asu", mSignalStrenth);
  2488             } catch(JSONException jsonex) {}
  2489             cellInfo.put(obj);
  2491         if (cells != null) {
  2492             for (NeighboringCellInfo nci : cells) {
  2493                 try {
  2494                     JSONObject obj = new JSONObject();
  2495                     obj.put("lac", nci.getLac());
  2496                     obj.put("cid", nci.getCid());
  2497                     obj.put("psc", nci.getPsc());
  2498                     obj.put("mcc", mcc);
  2499                     obj.put("mnc", mnc);
  2501                     int dbm;
  2502                     switch(nci.getNetworkType()) {
  2503                     case TelephonyManager.NETWORK_TYPE_GPRS:
  2504                     case TelephonyManager.NETWORK_TYPE_EDGE:
  2505                         obj.put("radio", "gsm");
  2506                         break;
  2507                     case TelephonyManager.NETWORK_TYPE_UMTS:
  2508                     case TelephonyManager.NETWORK_TYPE_HSDPA:
  2509                     case TelephonyManager.NETWORK_TYPE_HSUPA:
  2510                     case TelephonyManager.NETWORK_TYPE_HSPA:
  2511                     case TelephonyManager.NETWORK_TYPE_HSPAP:
  2512                         obj.put("radio", "umts");
  2513                         break;
  2516                     obj.put("asu", nci.getRssi());
  2517                     cellInfo.put(obj);
  2518                 } catch(JSONException jsonex) {}
  2521         return tm.getPhoneType();
  2524     private static boolean shouldLog(final ScanResult sr) {
  2525         return sr.SSID == null || !sr.SSID.endsWith("_nomap");
  2528     private void collectAndReportLocInfo(Location location) {
  2529         final JSONObject locInfo = new JSONObject();
  2530         WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
  2531         wm.startScan();
  2532         try {
  2533             JSONArray cellInfo = new JSONArray();
  2535             String radioType = getRadioTypeName(getCellInfo(cellInfo));
  2536             if (radioType != null) {
  2537                 locInfo.put("radio", radioType);
  2540             locInfo.put("lon", location.getLongitude());
  2541             locInfo.put("lat", location.getLatitude());
  2543             // If we have an accuracy, round it up to the next meter.
  2544             if (location.hasAccuracy()) {
  2545                 locInfo.put("accuracy", (int) Math.ceil(location.getAccuracy()));
  2548             // If we have an altitude, round it to the nearest meter.
  2549             if (location.hasAltitude()) {
  2550                 locInfo.put("altitude", Math.round(location.getAltitude()));
  2553             // Reduce timestamp precision so as to expose less PII.
  2554             DateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
  2555             locInfo.put("time", df.format(new Date(location.getTime())));
  2556             locInfo.put("cell", cellInfo);
  2558             JSONArray wifiInfo = new JSONArray();
  2559             List<ScanResult> aps = wm.getScanResults();
  2560             if (aps != null) {
  2561                 for (ScanResult ap : aps) {
  2562                     if (!shouldLog(ap))
  2563                         continue;
  2565                     JSONObject obj = new JSONObject();
  2566                     obj.put("key", ap.BSSID);
  2567                     obj.put("frequency", ap.frequency);
  2568                     obj.put("signal", ap.level);
  2569                     wifiInfo.put(obj);
  2572             locInfo.put("wifi", wifiInfo);
  2573         } catch (JSONException jsonex) {
  2574             Log.w(LOGTAG, "json exception", jsonex);
  2575             return;
  2578         ThreadUtils.postToBackgroundThread(new Runnable() {
  2579             public void run() {
  2580                 try {
  2581                     URL url = new URL(LOCATION_URL);
  2582                     HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
  2583                     try {
  2584                         urlConnection.setDoOutput(true);
  2586                         // Workaround for a bug in Android HttpURLConnection. When the library
  2587                         // reuses a stale connection, the connection may fail with an EOFException.
  2588                         if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT <= 18) {
  2589                             urlConnection.setRequestProperty("Connection", "Close");
  2592                         JSONArray batch = new JSONArray();
  2593                         batch.put(locInfo);
  2594                         JSONObject wrapper = new JSONObject();
  2595                         wrapper.put("items", batch);
  2596                         byte[] bytes = wrapper.toString().getBytes();
  2597                         urlConnection.setFixedLengthStreamingMode(bytes.length);
  2598                         OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
  2599                         out.write(bytes);
  2600                         out.flush();
  2601                     } catch (JSONException jsonex) {
  2602                         Log.e(LOGTAG, "error wrapping data as a batch", jsonex);
  2603                     } catch (IOException ioex) {
  2604                         Log.e(LOGTAG, "error submitting data", ioex);
  2605                     } finally {
  2606                         urlConnection.disconnect();
  2608                 } catch (IOException ioex) {
  2609                     Log.e(LOGTAG, "error submitting data", ioex);
  2612         });
  2615     private static String getRadioTypeName(int phoneType) {
  2616         switch (phoneType) {
  2617             case TelephonyManager.PHONE_TYPE_CDMA:
  2618                 return "cdma";
  2620             case TelephonyManager.PHONE_TYPE_GSM:
  2621                 return "gsm";
  2623             case TelephonyManager.PHONE_TYPE_NONE:
  2624             case TelephonyManager.PHONE_TYPE_SIP:
  2625                 // These devices have no radio.
  2626                 return null;
  2628             default:
  2629                 Log.e(LOGTAG, "", new IllegalArgumentException("Unexpected PHONE_TYPE: " + phoneType));
  2630                 return null;
  2634     @Override
  2635     public void onProviderDisabled(String provider)
  2639     @Override
  2640     public void onProviderEnabled(String provider)
  2644     @Override
  2645     public void onStatusChanged(String provider, int status, Bundle extras)
  2649     // Called when a Gecko Hal WakeLock is changed
  2650     public void notifyWakeLockChanged(String topic, String state) {
  2651         PowerManager.WakeLock wl = mWakeLocks.get(topic);
  2652         if (state.equals("locked-foreground") && wl == null) {
  2653             PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
  2654             wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, topic);
  2655             wl.acquire();
  2656             mWakeLocks.put(topic, wl);
  2657         } else if (!state.equals("locked-foreground") && wl != null) {
  2658             wl.release();
  2659             mWakeLocks.remove(topic);
  2663     public void notifyCheckUpdateResult(String result) {
  2664         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Update:CheckResult", result));
  2667     protected void geckoConnected() {
  2668         mLayerView.geckoConnected();
  2669         mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
  2672     public void setAccessibilityEnabled(boolean enabled) {
  2675     public static class MainLayout extends RelativeLayout {
  2676         private TouchEventInterceptor mTouchEventInterceptor;
  2677         private MotionEventInterceptor mMotionEventInterceptor;
  2679         public MainLayout(Context context, AttributeSet attrs) {
  2680             super(context, attrs);
  2683         public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
  2684             mTouchEventInterceptor = interceptor;
  2687         public void setMotionEventInterceptor(MotionEventInterceptor interceptor) {
  2688             mMotionEventInterceptor = interceptor;
  2691         @Override
  2692         public boolean onInterceptTouchEvent(MotionEvent event) {
  2693             if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) {
  2694                 return true;
  2696             return super.onInterceptTouchEvent(event);
  2699         @Override
  2700         public boolean onTouchEvent(MotionEvent event) {
  2701             if (mTouchEventInterceptor != null && mTouchEventInterceptor.onTouch(this, event)) {
  2702                 return true;
  2704             return super.onTouchEvent(event);
  2707         @Override
  2708         public boolean onGenericMotionEvent(MotionEvent event) {
  2709             if (mMotionEventInterceptor != null && mMotionEventInterceptor.onInterceptMotionEvent(this, event)) {
  2710                 return true;
  2712             return super.onGenericMotionEvent(event);
  2715         @Override
  2716         public void setDrawingCacheEnabled(boolean enabled) {
  2717             // Instead of setting drawing cache in the view itself, we simply
  2718             // enable drawing caching on its children. This is mainly used in
  2719             // animations (see PropertyAnimator)
  2720             super.setChildrenDrawnWithCacheEnabled(enabled);
  2724     private class FullScreenHolder extends FrameLayout {
  2726         public FullScreenHolder(Context ctx) {
  2727             super(ctx);
  2730         @Override
  2731         public void addView(View view, int index) {
  2732             /**
  2733              * This normally gets called when Flash adds a separate SurfaceView
  2734              * for the video. It is unhappy if we have the LayerView underneath
  2735              * it for some reason so we need to hide that. Hiding the LayerView causes
  2736              * its surface to be destroyed, which causes a pause composition
  2737              * event to be sent to Gecko. We synchronously wait for that to be
  2738              * processed. Simultaneously, however, Flash is waiting on a mutex so
  2739              * the post() below is an attempt to avoid a deadlock.
  2740              */
  2741             super.addView(view, index);
  2743             ThreadUtils.postToUiThread(new Runnable() {
  2744                 @Override
  2745                 public void run() {
  2746                     mLayerView.hideSurface();
  2748             });
  2751         /**
  2752          * The methods below are simply copied from what Android WebKit does.
  2753          * It wasn't ever called in my testing, but might as well
  2754          * keep it in case it is for some reason. The methods
  2755          * all return true because we don't want any events
  2756          * leaking out from the fullscreen view.
  2757          */
  2758         @Override
  2759         public boolean onKeyDown(int keyCode, KeyEvent event) {
  2760             if (event.isSystem()) {
  2761                 return super.onKeyDown(keyCode, event);
  2763             mFullScreenPluginView.onKeyDown(keyCode, event);
  2764             return true;
  2767         @Override
  2768         public boolean onKeyUp(int keyCode, KeyEvent event) {
  2769             if (event.isSystem()) {
  2770                 return super.onKeyUp(keyCode, event);
  2772             mFullScreenPluginView.onKeyUp(keyCode, event);
  2773             return true;
  2776         @Override
  2777         public boolean onTouchEvent(MotionEvent event) {
  2778             return true;
  2781         @Override
  2782         public boolean onTrackballEvent(MotionEvent event) {
  2783             mFullScreenPluginView.onTrackballEvent(event);
  2784             return true;
  2788     protected NotificationClient makeNotificationClient() {
  2789         // Don't use a notification service; we may be killed in the background
  2790         // during downloads.
  2791         return new AppNotificationClient(getApplicationContext());
  2794     private int getVersionCode() {
  2795         int versionCode = 0;
  2796         try {
  2797             versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
  2798         } catch (NameNotFoundException e) {
  2799             Log.wtf(LOGTAG, getPackageName() + " not found", e);
  2801         return versionCode;
  2804     protected boolean getIsDebuggable() {
  2805         // Return false so Fennec doesn't appear to be debuggable.  WebappImpl
  2806         // then overrides this and returns the value of android:debuggable for
  2807         // the webapp APK, so webapps get the behavior supported by this method
  2808         // (i.e. automatic configuration and enabling of the remote debugger).
  2809         return false;
  2811         // If we ever want to expose this for Fennec, here's how we would do it:
  2812         // int flags = 0;
  2813         // try {
  2814         //     flags = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.flags;
  2815         // } catch (NameNotFoundException e) {
  2816         //     Log.wtf(LOGTAG, getPackageName() + " not found", e);
  2817         // }
  2818         // return (flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0;
  2821     // FHR reason code for a session end prior to a restart for a
  2822     // locale change.
  2823     private static final String SESSION_END_LOCALE_CHANGED = "L";
  2825     /**
  2826      * Use BrowserLocaleManager to change our persisted and current locales,
  2827      * and poke HealthRecorder to tell it of our changed state.
  2828      */
  2829     private void setLocale(final String locale) {
  2830         if (locale == null) {
  2831             return;
  2833         final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale);
  2834         if (resultant == null) {
  2835             return;
  2838         final boolean startNewSession = true;
  2839         final boolean shouldRestart = false;
  2841         // If the HealthRecorder is not yet initialized (unlikely), the locale change won't
  2842         // trigger a session transition and subsequent events will be recorded in an environment
  2843         // with the wrong locale.
  2844         final HealthRecorder rec = mHealthRecorder;
  2845         if (rec != null) {
  2846             rec.onAppLocaleChanged(resultant);
  2847             rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
  2850         if (!shouldRestart) {
  2851             ThreadUtils.postToUiThread(new Runnable() {
  2852                 @Override
  2853                 public void run() {
  2854                     GeckoApp.this.onLocaleReady(resultant);
  2856             });
  2857             return;
  2860         // Do this in the background so that the health recorder has its
  2861         // time to finish.
  2862         ThreadUtils.postToBackgroundThread(new Runnable() {
  2863             @Override
  2864             public void run() {
  2865                 GeckoApp.this.doRestart();
  2866                 GeckoApp.this.finish();
  2868         });
  2871     private void setSystemUiVisible(final boolean visible) {
  2872         if (Build.VERSION.SDK_INT < 14) {
  2873             return;
  2876         ThreadUtils.postToUiThread(new Runnable() {
  2877             @Override
  2878             public void run() {
  2879                 if (visible) {
  2880                     mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
  2881                 } else {
  2882                     mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
  2885         });
  2888     protected HealthRecorder createHealthRecorder(final Context context,
  2889                                                   final String profilePath,
  2890                                                   final EventDispatcher dispatcher,
  2891                                                   final String osLocale,
  2892                                                   final String appLocale,
  2893                                                   final SessionInformation previousSession) {
  2894         // GeckoApp does not need to record any health information - return a stub.
  2895         return new StubbedHealthRecorder();

mercurial