mobile/android/base/GeckoApp.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/GeckoApp.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2897 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko;
    1.10 +
    1.11 +import java.io.BufferedOutputStream;
    1.12 +import java.io.ByteArrayOutputStream;
    1.13 +import java.io.File;
    1.14 +import java.io.IOException;
    1.15 +import java.io.InputStream;
    1.16 +import java.io.OutputStream;
    1.17 +import java.net.HttpURLConnection;
    1.18 +import java.net.URL;
    1.19 +import java.text.DateFormat;
    1.20 +import java.text.SimpleDateFormat;
    1.21 +import java.util.ArrayList;
    1.22 +import java.util.Arrays;
    1.23 +import java.util.Date;
    1.24 +import java.util.HashMap;
    1.25 +import java.util.Iterator;
    1.26 +import java.util.LinkedList;
    1.27 +import java.util.List;
    1.28 +import java.util.Locale;
    1.29 +import java.util.Map;
    1.30 +import java.util.Set;
    1.31 +import java.util.regex.Matcher;
    1.32 +import java.util.regex.Pattern;
    1.33 +
    1.34 +import org.json.JSONArray;
    1.35 +import org.json.JSONException;
    1.36 +import org.json.JSONObject;
    1.37 +import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
    1.38 +import org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService;
    1.39 +import org.mozilla.gecko.db.BrowserDB;
    1.40 +import org.mozilla.gecko.favicons.Favicons;
    1.41 +import org.mozilla.gecko.gfx.BitmapUtils;
    1.42 +import org.mozilla.gecko.gfx.Layer;
    1.43 +import org.mozilla.gecko.gfx.LayerView;
    1.44 +import org.mozilla.gecko.gfx.PluginLayer;
    1.45 +import org.mozilla.gecko.health.HealthRecorder;
    1.46 +import org.mozilla.gecko.health.SessionInformation;
    1.47 +import org.mozilla.gecko.health.StubbedHealthRecorder;
    1.48 +import org.mozilla.gecko.menu.GeckoMenu;
    1.49 +import org.mozilla.gecko.menu.GeckoMenuInflater;
    1.50 +import org.mozilla.gecko.menu.MenuPanel;
    1.51 +import org.mozilla.gecko.mozglue.GeckoLoader;
    1.52 +import org.mozilla.gecko.preferences.GeckoPreferences;
    1.53 +import org.mozilla.gecko.prompts.PromptService;
    1.54 +import org.mozilla.gecko.updater.UpdateService;
    1.55 +import org.mozilla.gecko.updater.UpdateServiceHelper;
    1.56 +import org.mozilla.gecko.util.ActivityResultHandler;
    1.57 +import org.mozilla.gecko.util.FileUtils;
    1.58 +import org.mozilla.gecko.util.GeckoEventListener;
    1.59 +import org.mozilla.gecko.util.HardwareUtils;
    1.60 +import org.mozilla.gecko.util.ThreadUtils;
    1.61 +import org.mozilla.gecko.util.UiAsyncTask;
    1.62 +import org.mozilla.gecko.webapp.EventListener;
    1.63 +import org.mozilla.gecko.webapp.UninstallListener;
    1.64 +import org.mozilla.gecko.widget.ButtonToast;
    1.65 +
    1.66 +import android.app.Activity;
    1.67 +import android.app.AlertDialog;
    1.68 +import android.app.Dialog;
    1.69 +import android.content.Context;
    1.70 +import android.content.DialogInterface;
    1.71 +import android.content.Intent;
    1.72 +import android.content.SharedPreferences;
    1.73 +import android.content.pm.PackageManager.NameNotFoundException;
    1.74 +import android.content.res.Configuration;
    1.75 +import android.graphics.Bitmap;
    1.76 +import android.graphics.BitmapFactory;
    1.77 +import android.graphics.RectF;
    1.78 +import android.graphics.drawable.Drawable;
    1.79 +import android.hardware.Sensor;
    1.80 +import android.hardware.SensorEvent;
    1.81 +import android.hardware.SensorEventListener;
    1.82 +import android.location.Location;
    1.83 +import android.location.LocationListener;
    1.84 +import android.net.Uri;
    1.85 +import android.net.wifi.ScanResult;
    1.86 +import android.net.wifi.WifiManager;
    1.87 +import android.os.Build;
    1.88 +import android.os.Bundle;
    1.89 +import android.os.Handler;
    1.90 +import android.os.PowerManager;
    1.91 +import android.os.StrictMode;
    1.92 +import android.provider.ContactsContract;
    1.93 +import android.provider.MediaStore.Images.Media;
    1.94 +import android.telephony.CellLocation;
    1.95 +import android.telephony.NeighboringCellInfo;
    1.96 +import android.telephony.PhoneStateListener;
    1.97 +import android.telephony.SignalStrength;
    1.98 +import android.telephony.TelephonyManager;
    1.99 +import android.telephony.gsm.GsmCellLocation;
   1.100 +import android.text.TextUtils;
   1.101 +import android.util.AttributeSet;
   1.102 +import android.util.Base64;
   1.103 +import android.util.Log;
   1.104 +import android.util.SparseBooleanArray;
   1.105 +import android.view.Gravity;
   1.106 +import android.view.KeyEvent;
   1.107 +import android.view.Menu;
   1.108 +import android.view.MenuInflater;
   1.109 +import android.view.MenuItem;
   1.110 +import android.view.MotionEvent;
   1.111 +import android.view.OrientationEventListener;
   1.112 +import android.view.SurfaceHolder;
   1.113 +import android.view.SurfaceView;
   1.114 +import android.view.TextureView;
   1.115 +import android.view.View;
   1.116 +import android.view.ViewGroup;
   1.117 +import android.view.ViewStub;
   1.118 +import android.view.Window;
   1.119 +import android.view.WindowManager;
   1.120 +import android.widget.AbsoluteLayout;
   1.121 +import android.widget.FrameLayout;
   1.122 +import android.widget.ListView;
   1.123 +import android.widget.RelativeLayout;
   1.124 +import android.widget.SimpleAdapter;
   1.125 +import android.widget.TextView;
   1.126 +import android.widget.Toast;
   1.127 +
   1.128 +public abstract class GeckoApp
   1.129 +    extends GeckoActivity
   1.130 +    implements
   1.131 +    ContextGetter,
   1.132 +    GeckoAppShell.GeckoInterface,
   1.133 +    GeckoEventListener,
   1.134 +    GeckoMenu.Callback,
   1.135 +    GeckoMenu.MenuPresenter,
   1.136 +    LocationListener,
   1.137 +    SensorEventListener,
   1.138 +    Tabs.OnTabsChangedListener
   1.139 +{
   1.140 +    private static final String LOGTAG = "GeckoApp";
   1.141 +    private static final int ONE_DAY_MS = 1000*60*60*24;
   1.142 +
   1.143 +    private static enum StartupAction {
   1.144 +        NORMAL,     /* normal application start */
   1.145 +        URL,        /* launched with a passed URL */
   1.146 +        PREFETCH    /* launched with a passed URL that we prefetch */
   1.147 +    }
   1.148 +
   1.149 +    public static final String ACTION_ALERT_CALLBACK       = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
   1.150 +    public static final String ACTION_BOOKMARK             = "org.mozilla.gecko.BOOKMARK";
   1.151 +    public static final String ACTION_DEBUG                = "org.mozilla.gecko.DEBUG";
   1.152 +    public static final String ACTION_LAUNCH_SETTINGS      = "org.mozilla.gecko.SETTINGS";
   1.153 +    public static final String ACTION_LOAD                 = "org.mozilla.gecko.LOAD";
   1.154 +    public static final String ACTION_INIT_PW              = "org.mozilla.gecko.INIT_PW";
   1.155 +    public static final String ACTION_WEBAPP_PREFIX        = "org.mozilla.gecko.WEBAPP";
   1.156 +
   1.157 +    public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
   1.158 +
   1.159 +    public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
   1.160 +    public static final String PREFS_OOM_EXCEPTION         = "OOMException";
   1.161 +    public static final String PREFS_VERSION_CODE          = "versionCode";
   1.162 +    public static final String PREFS_WAS_STOPPED           = "wasStopped";
   1.163 +    public static final String PREFS_CRASHED               = "crashed";
   1.164 +    public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
   1.165 +
   1.166 +    public static final String SAVED_STATE_IN_BACKGROUND   = "inBackground";
   1.167 +    public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
   1.168 +
   1.169 +    static private final String LOCATION_URL = "https://location.services.mozilla.com/v1/submit";
   1.170 +
   1.171 +    // Delay before running one-time "cleanup" tasks that may be needed
   1.172 +    // after a version upgrade.
   1.173 +    private static final int CLEANUP_DEFERRAL_SECONDS = 15;
   1.174 +
   1.175 +    protected RelativeLayout mMainLayout;
   1.176 +    protected RelativeLayout mGeckoLayout;
   1.177 +    public View getView() { return mGeckoLayout; }
   1.178 +    private View mCameraView;
   1.179 +    private OrientationEventListener mCameraOrientationEventListener;
   1.180 +    public List<GeckoAppShell.AppStateListener> mAppStateListeners;
   1.181 +    protected MenuPanel mMenuPanel;
   1.182 +    protected Menu mMenu;
   1.183 +    protected GeckoProfile mProfile;
   1.184 +    protected boolean mIsRestoringActivity;
   1.185 +
   1.186 +    private ContactService mContactService;
   1.187 +    private PromptService mPromptService;
   1.188 +    private TextSelection mTextSelection;
   1.189 +
   1.190 +    protected DoorHangerPopup mDoorHangerPopup;
   1.191 +    protected FormAssistPopup mFormAssistPopup;
   1.192 +    protected ButtonToast mToast;
   1.193 +
   1.194 +    protected LayerView mLayerView;
   1.195 +    private AbsoluteLayout mPluginContainer;
   1.196 +
   1.197 +    private FullScreenHolder mFullScreenPluginContainer;
   1.198 +    private View mFullScreenPluginView;
   1.199 +
   1.200 +    private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
   1.201 +
   1.202 +    protected boolean mShouldRestore;
   1.203 +    protected boolean mInitialized = false;
   1.204 +    private Telemetry.Timer mJavaUiStartupTimer;
   1.205 +    private Telemetry.Timer mGeckoReadyStartupTimer;
   1.206 +
   1.207 +    private String mPrivateBrowsingSession;
   1.208 +
   1.209 +    private volatile HealthRecorder mHealthRecorder = null;
   1.210 +
   1.211 +    private int mSignalStrenth;
   1.212 +    private PhoneStateListener mPhoneStateListener = null;
   1.213 +    private boolean mShouldReportGeoData;
   1.214 +    private EventListener mWebappEventListener;
   1.215 +
   1.216 +    abstract public int getLayout();
   1.217 +    abstract public boolean hasTabsSideBar();
   1.218 +    abstract protected String getDefaultProfileName() throws NoMozillaDirectoryException;
   1.219 +
   1.220 +    private static final String RESTARTER_ACTION = "org.mozilla.gecko.restart";
   1.221 +    private static final String RESTARTER_CLASS = "org.mozilla.gecko.Restarter";
   1.222 +
   1.223 +    @SuppressWarnings("serial")
   1.224 +    class SessionRestoreException extends Exception {
   1.225 +        public SessionRestoreException(Exception e) {
   1.226 +            super(e);
   1.227 +        }
   1.228 +
   1.229 +        public SessionRestoreException(String message) {
   1.230 +            super(message);
   1.231 +        }
   1.232 +    }
   1.233 +
   1.234 +    void toggleChrome(final boolean aShow) { }
   1.235 +
   1.236 +    void focusChrome() { }
   1.237 +
   1.238 +    @Override
   1.239 +    public Context getContext() {
   1.240 +        return this;
   1.241 +    }
   1.242 +
   1.243 +    @Override
   1.244 +    public SharedPreferences getSharedPreferences() {
   1.245 +        return GeckoSharedPrefs.forApp(this);
   1.246 +    }
   1.247 +
   1.248 +    public Activity getActivity() {
   1.249 +        return this;
   1.250 +    }
   1.251 +
   1.252 +    public LocationListener getLocationListener() {
   1.253 +        if (mShouldReportGeoData && mPhoneStateListener == null) {
   1.254 +            mPhoneStateListener = new PhoneStateListener() {
   1.255 +                public void onSignalStrengthsChanged(SignalStrength signalStrength) {
   1.256 +                    setCurrentSignalStrenth(signalStrength);
   1.257 +                }
   1.258 +            };
   1.259 +            TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
   1.260 +            tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
   1.261 +        }
   1.262 +        return this;
   1.263 +    }
   1.264 +
   1.265 +    public SensorEventListener getSensorEventListener() {
   1.266 +        return this;
   1.267 +    }
   1.268 +
   1.269 +    public View getCameraView() {
   1.270 +        return mCameraView;
   1.271 +    }
   1.272 +
   1.273 +    public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
   1.274 +        mAppStateListeners.add(listener);
   1.275 +    }
   1.276 +
   1.277 +    public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
   1.278 +        mAppStateListeners.remove(listener);
   1.279 +    }
   1.280 +
   1.281 +    public FormAssistPopup getFormAssistPopup() {
   1.282 +        return mFormAssistPopup;
   1.283 +    }
   1.284 +
   1.285 +    @Override
   1.286 +    public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
   1.287 +        // When a tab is closed, it is always unselected first.
   1.288 +        // When a tab is unselected, another tab is always selected first.
   1.289 +        switch(msg) {
   1.290 +            case UNSELECTED:
   1.291 +                hidePlugins(tab);
   1.292 +                break;
   1.293 +
   1.294 +            case LOCATION_CHANGE:
   1.295 +                // We only care about location change for the selected tab.
   1.296 +                if (!Tabs.getInstance().isSelectedTab(tab))
   1.297 +                    break;
   1.298 +                // Fall through...
   1.299 +            case SELECTED:
   1.300 +                invalidateOptionsMenu();
   1.301 +                if (mFormAssistPopup != null)
   1.302 +                    mFormAssistPopup.hide();
   1.303 +                break;
   1.304 +
   1.305 +            case LOADED:
   1.306 +                // Sync up the layer view and the tab if the tab is
   1.307 +                // currently displayed.
   1.308 +                LayerView layerView = mLayerView;
   1.309 +                if (layerView != null && Tabs.getInstance().isSelectedTab(tab))
   1.310 +                    layerView.setBackgroundColor(tab.getBackgroundColor());
   1.311 +                break;
   1.312 +
   1.313 +            case DESKTOP_MODE_CHANGE:
   1.314 +                if (Tabs.getInstance().isSelectedTab(tab))
   1.315 +                    invalidateOptionsMenu();
   1.316 +                break;
   1.317 +        }
   1.318 +    }
   1.319 +
   1.320 +    public void refreshChrome() { }
   1.321 +
   1.322 +    @Override
   1.323 +    public void invalidateOptionsMenu() {
   1.324 +        if (mMenu == null)
   1.325 +            return;
   1.326 +
   1.327 +        onPrepareOptionsMenu(mMenu);
   1.328 +
   1.329 +        if (Build.VERSION.SDK_INT >= 11)
   1.330 +            super.invalidateOptionsMenu();
   1.331 +    }
   1.332 +
   1.333 +    @Override
   1.334 +    public boolean onCreateOptionsMenu(Menu menu) {
   1.335 +        mMenu = menu;
   1.336 +
   1.337 +        MenuInflater inflater = getMenuInflater();
   1.338 +        inflater.inflate(R.menu.gecko_app_menu, mMenu);
   1.339 +        return true;
   1.340 +    }
   1.341 +
   1.342 +    @Override
   1.343 +    public MenuInflater getMenuInflater() {
   1.344 +        if (Build.VERSION.SDK_INT >= 11)
   1.345 +            return new GeckoMenuInflater(this);
   1.346 +        else
   1.347 +            return super.getMenuInflater();
   1.348 +    }
   1.349 +
   1.350 +    public MenuPanel getMenuPanel() {
   1.351 +        if (mMenuPanel == null) {
   1.352 +            onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
   1.353 +            invalidateOptionsMenu();
   1.354 +        }
   1.355 +        return mMenuPanel;
   1.356 +    }
   1.357 +
   1.358 +    @Override
   1.359 +    public boolean onMenuItemSelected(MenuItem item) {
   1.360 +        return onOptionsItemSelected(item);
   1.361 +    }
   1.362 +
   1.363 +    @Override
   1.364 +    public void openMenu() {
   1.365 +        openOptionsMenu();
   1.366 +    }
   1.367 +
   1.368 +    @Override
   1.369 +    public void showMenu(final View menu) {
   1.370 +        // On devices using the custom menu, focus is cleared from the menu when its tapped.
   1.371 +        // Close and then reshow it to avoid these issues. See bug 794581 and bug 968182.
   1.372 +        closeMenu();
   1.373 +
   1.374 +        // Post the reshow code back to the UI thread to avoid some optimizations Android
   1.375 +        // has put in place for menus that hide/show themselves quickly. See bug 985400.
   1.376 +        ThreadUtils.postToUiThread(new Runnable() {
   1.377 +            @Override
   1.378 +            public void run() {
   1.379 +                mMenuPanel.removeAllViews();
   1.380 +                mMenuPanel.addView(menu);
   1.381 +                openOptionsMenu();
   1.382 +            }
   1.383 +        });
   1.384 +    }
   1.385 +
   1.386 +    @Override
   1.387 +    public void closeMenu() {
   1.388 +        closeOptionsMenu();
   1.389 +    }
   1.390 +
   1.391 +    @Override
   1.392 +    public View onCreatePanelView(int featureId) {
   1.393 +        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
   1.394 +            if (mMenuPanel == null) {
   1.395 +                mMenuPanel = new MenuPanel(this, null);
   1.396 +            } else {
   1.397 +                // Prepare the panel everytime before showing the menu.
   1.398 +                onPreparePanel(featureId, mMenuPanel, mMenu);
   1.399 +            }
   1.400 +
   1.401 +            return mMenuPanel; 
   1.402 +        }
   1.403 +  
   1.404 +        return super.onCreatePanelView(featureId);
   1.405 +    }
   1.406 +
   1.407 +    @Override
   1.408 +    public boolean onCreatePanelMenu(int featureId, Menu menu) {
   1.409 +        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
   1.410 +            if (mMenuPanel == null) {
   1.411 +                mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
   1.412 +            }
   1.413 +
   1.414 +            GeckoMenu gMenu = new GeckoMenu(this, null);
   1.415 +            gMenu.setCallback(this);
   1.416 +            gMenu.setMenuPresenter(this);
   1.417 +            menu = gMenu;
   1.418 +            mMenuPanel.addView(gMenu);
   1.419 +
   1.420 +            return onCreateOptionsMenu(menu);
   1.421 +        }
   1.422 +
   1.423 +        return super.onCreatePanelMenu(featureId, menu);
   1.424 +    }
   1.425 +
   1.426 +    @Override
   1.427 +    public boolean onPreparePanel(int featureId, View view, Menu menu) {
   1.428 +        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL)
   1.429 +            return onPrepareOptionsMenu(menu);
   1.430 +
   1.431 +        return super.onPreparePanel(featureId, view, menu);
   1.432 +    }
   1.433 +
   1.434 +    @Override
   1.435 +    public boolean onMenuOpened(int featureId, Menu menu) {
   1.436 +        // exit full-screen mode whenever the menu is opened
   1.437 +        if (mLayerView != null && mLayerView.isFullScreen()) {
   1.438 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
   1.439 +        }
   1.440 +
   1.441 +        if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
   1.442 +            if (mMenu == null) {
   1.443 +                // getMenuPanel() will force the creation of the menu as well
   1.444 +                MenuPanel panel = getMenuPanel();
   1.445 +                onPreparePanel(featureId, panel, mMenu);
   1.446 +            }
   1.447 +
   1.448 +            // Scroll custom menu to the top
   1.449 +            if (mMenuPanel != null)
   1.450 +                mMenuPanel.scrollTo(0, 0);
   1.451 +
   1.452 +            return true;
   1.453 +        }
   1.454 +
   1.455 +        return super.onMenuOpened(featureId, menu);
   1.456 +    }
   1.457 +
   1.458 +    @Override
   1.459 +    public boolean onOptionsItemSelected(MenuItem item) {
   1.460 +        if (item.getItemId() == R.id.quit) {
   1.461 +            if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.GeckoRunning, GeckoThread.LaunchState.GeckoExiting)) {
   1.462 +                GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent("Browser:Quit", null));
   1.463 +            } else {
   1.464 +                GeckoAppShell.systemExit();
   1.465 +            }
   1.466 +            return true;
   1.467 +        }
   1.468 +
   1.469 +        return super.onOptionsItemSelected(item);
   1.470 +    }
   1.471 +
   1.472 +    @Override
   1.473 +    public void onOptionsMenuClosed(Menu menu) {
   1.474 +        if (Build.VERSION.SDK_INT >= 11) {
   1.475 +            mMenuPanel.removeAllViews();
   1.476 +            mMenuPanel.addView((GeckoMenu) mMenu);
   1.477 +        }
   1.478 +    }
   1.479 + 
   1.480 +    @Override
   1.481 +    public boolean onKeyDown(int keyCode, KeyEvent event) {
   1.482 +        // Handle hardware menu key presses separately so that we can show a custom menu in some cases.
   1.483 +        if (keyCode == KeyEvent.KEYCODE_MENU) {
   1.484 +            openOptionsMenu();
   1.485 +            return true;
   1.486 +        }
   1.487 +
   1.488 +        return super.onKeyDown(keyCode, event);
   1.489 +    }
   1.490 +
   1.491 +    @Override
   1.492 +    protected void onSaveInstanceState(Bundle outState) {
   1.493 +        super.onSaveInstanceState(outState);
   1.494 +
   1.495 +        if (mToast != null) {
   1.496 +            mToast.onSaveInstanceState(outState);
   1.497 +        }
   1.498 +
   1.499 +        outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
   1.500 +        outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
   1.501 +    }
   1.502 +
   1.503 +    void handleFaviconRequest(final String url) {
   1.504 +        (new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
   1.505 +            @Override
   1.506 +            public String doInBackground(Void... params) {
   1.507 +                return Favicons.getFaviconURLForPageURL(url);
   1.508 +            }
   1.509 +
   1.510 +            @Override
   1.511 +            public void onPostExecute(String faviconUrl) {
   1.512 +                JSONObject args = new JSONObject();
   1.513 +
   1.514 +                if (faviconUrl != null) {
   1.515 +                    try {
   1.516 +                        args.put("url", url);
   1.517 +                        args.put("faviconUrl", faviconUrl);
   1.518 +                    } catch (JSONException e) {
   1.519 +                        Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
   1.520 +                    }
   1.521 +                }
   1.522 +
   1.523 +                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
   1.524 +            }
   1.525 +        }).execute();
   1.526 +    }
   1.527 +
   1.528 +    void handleClearHistory() {
   1.529 +        BrowserDB.clearHistory(getContentResolver());
   1.530 +    }
   1.531 +
   1.532 +    public void addTab() { }
   1.533 +
   1.534 +    public void addPrivateTab() { }
   1.535 +
   1.536 +    public void showNormalTabs() { }
   1.537 +
   1.538 +    public void showPrivateTabs() { }
   1.539 +
   1.540 +    public void hideTabs() { }
   1.541 +
   1.542 +    /**
   1.543 +     * Close the tab UI indirectly (not as the result of a direct user
   1.544 +     * action).  This does not force the UI to close; for example in Firefox
   1.545 +     * tablet mode it will remain open unless the user explicitly closes it.
   1.546 +     *
   1.547 +     * @return True if the tab UI was hidden.
   1.548 +     */
   1.549 +    public boolean autoHideTabs() { return false; }
   1.550 +
   1.551 +    public boolean areTabsShown() { return false; }
   1.552 +
   1.553 +    @Override
   1.554 +    public void handleMessage(String event, JSONObject message) {
   1.555 +        try {
   1.556 +            if (event.equals("Toast:Show")) {
   1.557 +                final String msg = message.getString("message");
   1.558 +                final JSONObject button = message.optJSONObject("button");
   1.559 +                if (button != null) {
   1.560 +                    final String label = button.optString("label");
   1.561 +                    final String icon = button.optString("icon");
   1.562 +                    final String id = button.optString("id");
   1.563 +                    showButtonToast(msg, label, icon, id);
   1.564 +                } else {
   1.565 +                    final String duration = message.getString("duration");
   1.566 +                    showNormalToast(msg, duration);
   1.567 +                }
   1.568 +            } else if (event.equals("log")) {
   1.569 +                // generic log listener
   1.570 +                final String msg = message.getString("msg");
   1.571 +                Log.d(LOGTAG, "Log: " + msg);
   1.572 +            } else if (event.equals("Reader:FaviconRequest")) {
   1.573 +                final String url = message.getString("url");
   1.574 +                handleFaviconRequest(url);
   1.575 +            } else if (event.equals("Gecko:DelayedStartup")) {
   1.576 +                ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this));
   1.577 +            } else if (event.equals("Gecko:Ready")) {
   1.578 +                mGeckoReadyStartupTimer.stop();
   1.579 +                geckoConnected();
   1.580 +
   1.581 +                // This method is already running on the background thread, so we
   1.582 +                // know that mHealthRecorder will exist. That doesn't stop us being
   1.583 +                // paranoid.
   1.584 +                // This method is cheap, so don't spawn a new runnable.
   1.585 +                final HealthRecorder rec = mHealthRecorder;
   1.586 +                if (rec != null) {
   1.587 +                  rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
   1.588 +                }
   1.589 +            } else if (event.equals("ToggleChrome:Hide")) {
   1.590 +                toggleChrome(false);
   1.591 +            } else if (event.equals("ToggleChrome:Show")) {
   1.592 +                toggleChrome(true);
   1.593 +            } else if (event.equals("ToggleChrome:Focus")) {
   1.594 +                focusChrome();
   1.595 +            } else if (event.equals("DOMFullScreen:Start")) {
   1.596 +                // Local ref to layerView for thread safety
   1.597 +                LayerView layerView = mLayerView;
   1.598 +                if (layerView != null) {
   1.599 +                    layerView.setFullScreen(true);
   1.600 +                }
   1.601 +            } else if (event.equals("DOMFullScreen:Stop")) {
   1.602 +                // Local ref to layerView for thread safety
   1.603 +                LayerView layerView = mLayerView;
   1.604 +                if (layerView != null) {
   1.605 +                    layerView.setFullScreen(false);
   1.606 +                }
   1.607 +            } else if (event.equals("Permissions:Data")) {
   1.608 +                String host = message.getString("host");
   1.609 +                JSONArray permissions = message.getJSONArray("permissions");
   1.610 +                showSiteSettingsDialog(host, permissions);
   1.611 +            } else if (event.equals("Session:StatePurged")) {
   1.612 +                onStatePurged();
   1.613 +            } else if (event.equals("Bookmark:Insert")) {
   1.614 +                final String url = message.getString("url");
   1.615 +                final String title = message.getString("title");
   1.616 +                final Context context = this;
   1.617 +                ThreadUtils.postToUiThread(new Runnable() {
   1.618 +                    @Override
   1.619 +                    public void run() {
   1.620 +                        Toast.makeText(context, R.string.bookmark_added, Toast.LENGTH_SHORT).show();
   1.621 +                        ThreadUtils.postToBackgroundThread(new Runnable() {
   1.622 +                            @Override
   1.623 +                            public void run() {
   1.624 +                                BrowserDB.addBookmark(getContentResolver(), title, url);
   1.625 +                            }
   1.626 +                        });
   1.627 +                    }
   1.628 +                });
   1.629 +            } else if (event.equals("Accessibility:Event")) {
   1.630 +                GeckoAccessibility.sendAccessibilityEvent(message);
   1.631 +            } else if (event.equals("Accessibility:Ready")) {
   1.632 +                GeckoAccessibility.updateAccessibilitySettings(this);
   1.633 +            } else if (event.equals("Shortcut:Remove")) {
   1.634 +                final String url = message.getString("url");
   1.635 +                final String origin = message.getString("origin");
   1.636 +                final String title = message.getString("title");
   1.637 +                final String type = message.getString("shortcutType");
   1.638 +                GeckoAppShell.removeShortcut(title, url, origin, type);
   1.639 +            } else if (event.equals("Share:Text")) {
   1.640 +                String text = message.getString("text");
   1.641 +                GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, "");
   1.642 +
   1.643 +                // Context: Sharing via chrome list (no explicit session is active)
   1.644 +                Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
   1.645 +            } else if (event.equals("Image:SetAs")) {
   1.646 +                String src = message.getString("url");
   1.647 +                setImageAs(src);
   1.648 +            } else if (event.equals("Sanitize:ClearHistory")) {
   1.649 +                handleClearHistory();
   1.650 +            } else if (event.equals("Update:Check")) {
   1.651 +                startService(new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class));
   1.652 +            } else if (event.equals("Update:Download")) {
   1.653 +                startService(new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE, null, this, UpdateService.class));
   1.654 +            } else if (event.equals("Update:Install")) {
   1.655 +                startService(new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE, null, this, UpdateService.class));
   1.656 +            } else if (event.equals("PrivateBrowsing:Data")) {
   1.657 +                // null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
   1.658 +                if (message.isNull("session")) {
   1.659 +                    mPrivateBrowsingSession = null;
   1.660 +                } else {
   1.661 +                    mPrivateBrowsingSession = message.getString("session");
   1.662 +                }
   1.663 +            } else if (event.equals("Contact:Add")) {                
   1.664 +                if (!message.isNull("email")) {
   1.665 +                    Uri contactUri = Uri.parse(message.getString("email"));       
   1.666 +                    Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
   1.667 +                    startActivity(i);
   1.668 +                } else if (!message.isNull("phone")) {
   1.669 +                    Uri contactUri = Uri.parse(message.getString("phone"));       
   1.670 +                    Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
   1.671 +                    startActivity(i);
   1.672 +                } else {
   1.673 +                    // something went wrong.
   1.674 +                    Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number");
   1.675 +                }                
   1.676 +            } else if (event.equals("Intent:GetHandlers")) {
   1.677 +                Intent intent = GeckoAppShell.getOpenURIIntent((Context) this, message.optString("url"),
   1.678 +                    message.optString("mime"), message.optString("action"), message.optString("title"));
   1.679 +                String[] handlers = GeckoAppShell.getHandlersForIntent(intent);
   1.680 +                List<String> appList = Arrays.asList(handlers);
   1.681 +                JSONObject handlersJSON = new JSONObject();
   1.682 +                handlersJSON.put("apps", new JSONArray(appList));
   1.683 +                EventDispatcher.sendResponse(message, handlersJSON);
   1.684 +            } else if (event.equals("Intent:Open")) {
   1.685 +                GeckoAppShell.openUriExternal(message.optString("url"),
   1.686 +                    message.optString("mime"), message.optString("packageName"),
   1.687 +                    message.optString("className"), message.optString("action"), message.optString("title"));
   1.688 +            } else if (event.equals("Intent:OpenForResult")) {
   1.689 +                Intent intent = GeckoAppShell.getOpenURIIntent(this,
   1.690 +                                                               message.optString("url"),
   1.691 +                                                               message.optString("mime"),
   1.692 +                                                               message.optString("action"),
   1.693 +                                                               message.optString("title"));
   1.694 +                intent.setClassName(message.optString("packageName"), message.optString("className"));
   1.695 +
   1.696 +                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
   1.697 +
   1.698 +                final JSONObject originalMessage = message;
   1.699 +                ActivityHandlerHelper.startIntentForActivity(this,
   1.700 +                                                             intent,
   1.701 +                        new ActivityResultHandler() {
   1.702 +                            @Override
   1.703 +                            public void onActivityResult (int resultCode, Intent data) {
   1.704 +                                JSONObject response = new JSONObject();
   1.705 +
   1.706 +                                try {
   1.707 +                                    if (data != null) {
   1.708 +                                        response.put("extras", bundleToJSON(data.getExtras()));
   1.709 +                                    }
   1.710 +                                    response.put("resultCode", resultCode);
   1.711 +                                } catch (JSONException e) {
   1.712 +                                    Log.w(LOGTAG, "Error building JSON response.", e);
   1.713 +                                }
   1.714 +
   1.715 +                                EventDispatcher.sendResponse(originalMessage, response);
   1.716 +                            }
   1.717 +                        });
   1.718 +            } else if (event.equals("Locale:Set")) {
   1.719 +                setLocale(message.getString("locale"));
   1.720 +            } else if (event.equals("NativeApp:IsDebuggable")) {
   1.721 +                JSONObject ret = new JSONObject();
   1.722 +                ret.put("isDebuggable", getIsDebuggable());
   1.723 +                EventDispatcher.sendResponse(message, ret);
   1.724 +            } else if (event.equals("SystemUI:Visibility")) {
   1.725 +                setSystemUiVisible(message.getBoolean("visible"));
   1.726 +            }
   1.727 +        } catch (Exception e) {
   1.728 +            Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
   1.729 +        }
   1.730 +    }
   1.731 +
   1.732 +    void onStatePurged() { }
   1.733 +
   1.734 +    /**
   1.735 +     * @param aPermissions
   1.736 +     *        Array of JSON objects to represent site permissions.
   1.737 +     *        Example: { type: "offline-app", setting: "Store Offline Data", value: "Allow" }
   1.738 +     */
   1.739 +    private void showSiteSettingsDialog(String aHost, JSONArray aPermissions) {
   1.740 +        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
   1.741 +
   1.742 +        View customTitleView = getLayoutInflater().inflate(R.layout.site_setting_title, null);
   1.743 +        ((TextView) customTitleView.findViewById(R.id.title)).setText(R.string.site_settings_title);
   1.744 +        ((TextView) customTitleView.findViewById(R.id.host)).setText(aHost);
   1.745 +        builder.setCustomTitle(customTitleView);
   1.746 +
   1.747 +        // If there are no permissions to clear, show the user a message about that.
   1.748 +        // In the future, we want to disable the menu item if there are no permissions to clear.
   1.749 +        if (aPermissions.length() == 0) {
   1.750 +            builder.setMessage(R.string.site_settings_no_settings);
   1.751 +        } else {
   1.752 +
   1.753 +            ArrayList <HashMap<String, String>> itemList = new ArrayList <HashMap<String, String>>();
   1.754 +            for (int i = 0; i < aPermissions.length(); i++) {
   1.755 +                try {
   1.756 +                    JSONObject permObj = aPermissions.getJSONObject(i);
   1.757 +                    HashMap<String, String> map = new HashMap<String, String>();
   1.758 +                    map.put("setting", permObj.getString("setting"));
   1.759 +                    map.put("value", permObj.getString("value"));
   1.760 +                    itemList.add(map);
   1.761 +                } catch (JSONException e) {
   1.762 +                    Log.w(LOGTAG, "Exception populating settings items.", e);
   1.763 +                }
   1.764 +            }
   1.765 +
   1.766 +            // setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with
   1.767 +            // setSingleChoiceItems and changing the choiceMode below when we create the dialog
   1.768 +            builder.setSingleChoiceItems(new SimpleAdapter(
   1.769 +                GeckoApp.this,
   1.770 +                itemList,
   1.771 +                R.layout.site_setting_item,
   1.772 +                new String[] { "setting", "value" },
   1.773 +                new int[] { R.id.setting, R.id.value }
   1.774 +                ), -1, new DialogInterface.OnClickListener() {
   1.775 +                    @Override
   1.776 +                    public void onClick(DialogInterface dialog, int id) { }
   1.777 +                });
   1.778 +
   1.779 +            builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
   1.780 +                @Override
   1.781 +                public void onClick(DialogInterface dialog, int id) {
   1.782 +                    ListView listView = ((AlertDialog) dialog).getListView();
   1.783 +                    SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions();
   1.784 +
   1.785 +                    // An array of the indices of the permissions we want to clear
   1.786 +                    JSONArray permissionsToClear = new JSONArray();
   1.787 +                    for (int i = 0; i < checkedItemPositions.size(); i++)
   1.788 +                        if (checkedItemPositions.get(i))
   1.789 +                            permissionsToClear.put(i);
   1.790 +
   1.791 +                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
   1.792 +                        "Permissions:Clear", permissionsToClear.toString()));
   1.793 +                }
   1.794 +            });
   1.795 +        }
   1.796 +
   1.797 +        builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener(){
   1.798 +            @Override
   1.799 +            public void onClick(DialogInterface dialog, int id) {
   1.800 +                dialog.cancel();
   1.801 +            }
   1.802 +        });
   1.803 +
   1.804 +        ThreadUtils.postToUiThread(new Runnable() {
   1.805 +            @Override
   1.806 +            public void run() {
   1.807 +                Dialog dialog = builder.create();
   1.808 +                dialog.show();
   1.809 +
   1.810 +                ListView listView = ((AlertDialog) dialog).getListView();
   1.811 +                if (listView != null) {
   1.812 +                    listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
   1.813 +                    int listSize = listView.getAdapter().getCount();
   1.814 +                    for (int i = 0; i < listSize; i++)
   1.815 +                        listView.setItemChecked(i, true);
   1.816 +                }
   1.817 +            }
   1.818 +        });
   1.819 +    }
   1.820 +
   1.821 +    public void showToast(final int resId, final int duration) {
   1.822 +        ThreadUtils.postToUiThread(new Runnable() {
   1.823 +            @Override
   1.824 +            public void run() {
   1.825 +                Toast.makeText(GeckoApp.this, resId, duration).show();
   1.826 +            }
   1.827 +        });
   1.828 +    }
   1.829 +
   1.830 +    public void showNormalToast(final String message, final String duration) {
   1.831 +        ThreadUtils.postToUiThread(new Runnable() {
   1.832 +            @Override
   1.833 +            public void run() {
   1.834 +                Toast toast;
   1.835 +                if (duration.equals("long")) {
   1.836 +                    toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_LONG);
   1.837 +                } else {
   1.838 +                    toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_SHORT);
   1.839 +                }
   1.840 +                toast.show();
   1.841 +            }
   1.842 +        });
   1.843 +    }
   1.844 +
   1.845 +    protected ButtonToast getButtonToast() {
   1.846 +        if (mToast != null) {
   1.847 +            return mToast;
   1.848 +        }
   1.849 +
   1.850 +        ViewStub toastStub = (ViewStub) findViewById(R.id.toast_stub);
   1.851 +        mToast = new ButtonToast(toastStub.inflate());
   1.852 +
   1.853 +        return mToast;
   1.854 +    }
   1.855 +
   1.856 +    void showButtonToast(final String message, final String buttonText,
   1.857 +                         final String buttonIcon, final String buttonId) {
   1.858 +        BitmapUtils.getDrawable(GeckoApp.this, buttonIcon, new BitmapUtils.BitmapLoader() {
   1.859 +            @Override
   1.860 +            public void onBitmapFound(final Drawable d) {
   1.861 +                getButtonToast().show(false, message, buttonText, d, new ButtonToast.ToastListener() {
   1.862 +                    @Override
   1.863 +                    public void onButtonClicked() {
   1.864 +                        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Click", buttonId));
   1.865 +                    }
   1.866 +
   1.867 +                    @Override
   1.868 +                    public void onToastHidden(ButtonToast.ReasonHidden reason) {
   1.869 +                        if (reason == ButtonToast.ReasonHidden.TIMEOUT) {
   1.870 +                            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Hidden", buttonId));
   1.871 +                        }
   1.872 +                    }
   1.873 +                });
   1.874 +            }
   1.875 +        });
   1.876 +    }
   1.877 +
   1.878 +    private JSONObject bundleToJSON(Bundle bundle) {
   1.879 +        JSONObject json = new JSONObject();
   1.880 +        if (bundle == null) {
   1.881 +            return json;
   1.882 +        }
   1.883 +
   1.884 +        for (String key : bundle.keySet()) {
   1.885 +            try {
   1.886 +                json.put(key, bundle.get(key));
   1.887 +            } catch (JSONException e) {
   1.888 +                Log.w(LOGTAG, "Error building JSON response.", e);
   1.889 +            }
   1.890 +        }
   1.891 +
   1.892 +        return json;
   1.893 +    }
   1.894 +
   1.895 +    private void addFullScreenPluginView(View view) {
   1.896 +        if (mFullScreenPluginView != null) {
   1.897 +            Log.w(LOGTAG, "Already have a fullscreen plugin view");
   1.898 +            return;
   1.899 +        }
   1.900 +
   1.901 +        setFullScreen(true);
   1.902 +
   1.903 +        view.setWillNotDraw(false);
   1.904 +        if (view instanceof SurfaceView) {
   1.905 +            ((SurfaceView) view).setZOrderOnTop(true);
   1.906 +        }
   1.907 +
   1.908 +        mFullScreenPluginContainer = new FullScreenHolder(this);
   1.909 +
   1.910 +        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
   1.911 +                            ViewGroup.LayoutParams.FILL_PARENT,
   1.912 +                            ViewGroup.LayoutParams.FILL_PARENT,
   1.913 +                            Gravity.CENTER);
   1.914 +        mFullScreenPluginContainer.addView(view, layoutParams);
   1.915 +
   1.916 +
   1.917 +        FrameLayout decor = (FrameLayout)getWindow().getDecorView();
   1.918 +        decor.addView(mFullScreenPluginContainer, layoutParams);
   1.919 +
   1.920 +        mFullScreenPluginView = view;
   1.921 +    }
   1.922 +
   1.923 +    public void addPluginView(final View view, final RectF rect, final boolean isFullScreen) {
   1.924 +        ThreadUtils.postToUiThread(new Runnable() {
   1.925 +            @Override
   1.926 +            public void run() {
   1.927 +                Tabs tabs = Tabs.getInstance();
   1.928 +                Tab tab = tabs.getSelectedTab();
   1.929 +
   1.930 +                if (isFullScreen) {
   1.931 +                    addFullScreenPluginView(view);
   1.932 +                    return;
   1.933 +                }
   1.934 +
   1.935 +                PluginLayer layer = (PluginLayer) tab.getPluginLayer(view);
   1.936 +                if (layer == null) {
   1.937 +                    layer = new PluginLayer(view, rect, mLayerView.getRenderer().getMaxTextureSize());
   1.938 +                    tab.addPluginLayer(view, layer);
   1.939 +                } else {
   1.940 +                    layer.reset(rect);
   1.941 +                    layer.setVisible(true);
   1.942 +                }
   1.943 +
   1.944 +                mLayerView.addLayer(layer);
   1.945 +            }
   1.946 +        });
   1.947 +    }
   1.948 +
   1.949 +    private void removeFullScreenPluginView(View view) {
   1.950 +        if (mFullScreenPluginView == null) {
   1.951 +            Log.w(LOGTAG, "Don't have a fullscreen plugin view");
   1.952 +            return;
   1.953 +        }
   1.954 +
   1.955 +        if (mFullScreenPluginView != view) {
   1.956 +            Log.w(LOGTAG, "Passed view is not the current full screen view");
   1.957 +            return;
   1.958 +        }
   1.959 +
   1.960 +        mFullScreenPluginContainer.removeView(mFullScreenPluginView);
   1.961 +
   1.962 +        // We need do do this on the next iteration in order to avoid
   1.963 +        // a deadlock, see comment below in FullScreenHolder
   1.964 +        ThreadUtils.postToUiThread(new Runnable() {
   1.965 +            @Override
   1.966 +            public void run() {
   1.967 +                mLayerView.showSurface();
   1.968 +            }
   1.969 +        });
   1.970 +
   1.971 +        FrameLayout decor = (FrameLayout)getWindow().getDecorView();
   1.972 +        decor.removeView(mFullScreenPluginContainer);
   1.973 +
   1.974 +        mFullScreenPluginView = null;
   1.975 +
   1.976 +        GeckoScreenOrientation.getInstance().unlock();
   1.977 +        setFullScreen(false);
   1.978 +    }
   1.979 +
   1.980 +    public void removePluginView(final View view, final boolean isFullScreen) {
   1.981 +        ThreadUtils.postToUiThread(new Runnable() {
   1.982 +            @Override
   1.983 +            public void run() {
   1.984 +                Tabs tabs = Tabs.getInstance();
   1.985 +                Tab tab = tabs.getSelectedTab();
   1.986 +
   1.987 +                if (isFullScreen) {
   1.988 +                    removeFullScreenPluginView(view);
   1.989 +                    return;
   1.990 +                }
   1.991 +
   1.992 +                PluginLayer layer = (PluginLayer) tab.removePluginLayer(view);
   1.993 +                if (layer != null) {
   1.994 +                    layer.destroy();
   1.995 +                }
   1.996 +            }
   1.997 +        });
   1.998 +    }
   1.999 +
  1.1000 +    // This method starts downloading an image synchronously and displays the Chooser activity to set the image as wallpaper.
  1.1001 +    private void setImageAs(final String aSrc) {
  1.1002 +        boolean isDataURI = aSrc.startsWith("data:");
  1.1003 +        Bitmap image = null;
  1.1004 +        InputStream is = null;
  1.1005 +        ByteArrayOutputStream os = null;
  1.1006 +        try {
  1.1007 +            if (isDataURI) {
  1.1008 +                int dataStart = aSrc.indexOf(",");
  1.1009 +                byte[] buf = Base64.decode(aSrc.substring(dataStart+1), Base64.DEFAULT);
  1.1010 +                image = BitmapUtils.decodeByteArray(buf);
  1.1011 +            } else {
  1.1012 +                int byteRead;
  1.1013 +                byte[] buf = new byte[4192];
  1.1014 +                os = new ByteArrayOutputStream();
  1.1015 +                URL url = new URL(aSrc);
  1.1016 +                is = url.openStream();
  1.1017 +
  1.1018 +                // Cannot read from same stream twice. Also, InputStream from
  1.1019 +                // URL does not support reset. So converting to byte array.
  1.1020 +
  1.1021 +                while((byteRead = is.read(buf)) != -1) {
  1.1022 +                    os.write(buf, 0, byteRead);
  1.1023 +                }
  1.1024 +                byte[] imgBuffer = os.toByteArray();
  1.1025 +                image = BitmapUtils.decodeByteArray(imgBuffer);
  1.1026 +            }
  1.1027 +            if (image != null) {
  1.1028 +                String path = Media.insertImage(getContentResolver(),image, null, null);
  1.1029 +                final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
  1.1030 +                intent.addCategory(Intent.CATEGORY_DEFAULT);
  1.1031 +                intent.setData(Uri.parse(path));
  1.1032 +
  1.1033 +                // Removes the image from storage once the chooser activity ends.
  1.1034 +                Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title));
  1.1035 +                ActivityResultHandler handler = new ActivityResultHandler() {
  1.1036 +                    @Override
  1.1037 +                    public void onActivityResult (int resultCode, Intent data) {
  1.1038 +                        getContentResolver().delete(intent.getData(), null, null);
  1.1039 +                    }
  1.1040 +                };
  1.1041 +                ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
  1.1042 +            } else {
  1.1043 +                Toast.makeText((Context) this, R.string.set_image_fail, Toast.LENGTH_SHORT).show();
  1.1044 +            }
  1.1045 +        } catch(OutOfMemoryError ome) {
  1.1046 +            Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
  1.1047 +        } catch(IOException ioe) {
  1.1048 +            Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
  1.1049 +        } finally {
  1.1050 +            if (is != null) {
  1.1051 +                try {
  1.1052 +                    is.close();
  1.1053 +                } catch(IOException ioe) {
  1.1054 +                    Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
  1.1055 +                }
  1.1056 +            }
  1.1057 +            if (os != null) {
  1.1058 +                try {
  1.1059 +                    os.close();
  1.1060 +                } catch(IOException ioe) {
  1.1061 +                    Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
  1.1062 +                }
  1.1063 +            }
  1.1064 +        }
  1.1065 +    }
  1.1066 +
  1.1067 +    private int getBitmapSampleSize(BitmapFactory.Options options, int idealWidth, int idealHeight) {
  1.1068 +        int width = options.outWidth;
  1.1069 +        int height = options.outHeight;
  1.1070 +        int inSampleSize = 1;
  1.1071 +        if (height > idealHeight || width > idealWidth) {
  1.1072 +            if (width > height) {
  1.1073 +                inSampleSize = Math.round((float)height / (float)idealHeight);
  1.1074 +            } else {
  1.1075 +                inSampleSize = Math.round((float)width / (float)idealWidth);
  1.1076 +            }
  1.1077 +        }
  1.1078 +        return inSampleSize;
  1.1079 +    }
  1.1080 +
  1.1081 +    private void hidePluginLayer(Layer layer) {
  1.1082 +        LayerView layerView = mLayerView;
  1.1083 +        layerView.removeLayer(layer);
  1.1084 +        layerView.requestRender();
  1.1085 +    }
  1.1086 +
  1.1087 +    private void showPluginLayer(Layer layer) {
  1.1088 +        LayerView layerView = mLayerView;
  1.1089 +        layerView.addLayer(layer);
  1.1090 +        layerView.requestRender();
  1.1091 +    }
  1.1092 +
  1.1093 +    public void requestRender() {
  1.1094 +        mLayerView.requestRender();
  1.1095 +    }
  1.1096 +    
  1.1097 +    public void hidePlugins(Tab tab) {
  1.1098 +        for (Layer layer : tab.getPluginLayers()) {
  1.1099 +            if (layer instanceof PluginLayer) {
  1.1100 +                ((PluginLayer) layer).setVisible(false);
  1.1101 +            }
  1.1102 +
  1.1103 +            hidePluginLayer(layer);
  1.1104 +        }
  1.1105 +
  1.1106 +        requestRender();
  1.1107 +    }
  1.1108 +
  1.1109 +    public void showPlugins() {
  1.1110 +        Tabs tabs = Tabs.getInstance();
  1.1111 +        Tab tab = tabs.getSelectedTab();
  1.1112 +
  1.1113 +        showPlugins(tab);
  1.1114 +    }
  1.1115 +
  1.1116 +    public void showPlugins(Tab tab) {
  1.1117 +        for (Layer layer : tab.getPluginLayers()) {
  1.1118 +            showPluginLayer(layer);
  1.1119 +
  1.1120 +            if (layer instanceof PluginLayer) {
  1.1121 +                ((PluginLayer) layer).setVisible(true);
  1.1122 +            }
  1.1123 +        }
  1.1124 +
  1.1125 +        requestRender();
  1.1126 +    }
  1.1127 +
  1.1128 +    public void setFullScreen(final boolean fullscreen) {
  1.1129 +        ThreadUtils.postToUiThread(new Runnable() {
  1.1130 +            @Override
  1.1131 +            public void run() {
  1.1132 +                // Hide/show the system notification bar
  1.1133 +                Window window = getWindow();
  1.1134 +                window.setFlags(fullscreen ?
  1.1135 +                                WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
  1.1136 +                                WindowManager.LayoutParams.FLAG_FULLSCREEN);
  1.1137 +
  1.1138 +                if (Build.VERSION.SDK_INT >= 11)
  1.1139 +                    window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0);
  1.1140 +            }
  1.1141 +        });
  1.1142 +    }
  1.1143 +
  1.1144 +    /**
  1.1145 +     * Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified
  1.1146 +     **/
  1.1147 +    protected void earlyStartJavaSampler(Intent intent)
  1.1148 +    {
  1.1149 +        String env = intent.getStringExtra("env0");
  1.1150 +        for (int i = 1; env != null; i++) {
  1.1151 +            if (env.startsWith("MOZ_PROFILER_STARTUP=")) {
  1.1152 +                if (!env.endsWith("=")) {
  1.1153 +                    GeckoJavaSampler.start(10, 1000);
  1.1154 +                    Log.d(LOGTAG, "Profiling Java on startup");
  1.1155 +                }
  1.1156 +                break;
  1.1157 +            }
  1.1158 +            env = intent.getStringExtra("env" + i);
  1.1159 +        }
  1.1160 +    }
  1.1161 +
  1.1162 +    /**
  1.1163 +     * Called when the activity is first created.
  1.1164 +     *
  1.1165 +     * Here we initialize all of our profile settings, Firefox Health Report,
  1.1166 +     * and other one-shot constructions.
  1.1167 +     **/
  1.1168 +    @Override
  1.1169 +    public void onCreate(Bundle savedInstanceState)
  1.1170 +    {
  1.1171 +        GeckoAppShell.registerGlobalExceptionHandler();
  1.1172 +
  1.1173 +        // Enable Android Strict Mode for developers' local builds (the "default" channel).
  1.1174 +        if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
  1.1175 +            enableStrictMode();
  1.1176 +        }
  1.1177 +
  1.1178 +        // The clock starts...now. Better hurry!
  1.1179 +        mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
  1.1180 +        mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
  1.1181 +
  1.1182 +        final Intent intent = getIntent();
  1.1183 +        final String args = intent.getStringExtra("args");
  1.1184 +
  1.1185 +        earlyStartJavaSampler(intent);
  1.1186 +
  1.1187 +        // GeckoLoader wants to dig some environment variables out of the
  1.1188 +        // incoming intent, so pass it in here. GeckoLoader will do its
  1.1189 +        // business later and dispose of the reference.
  1.1190 +        GeckoLoader.setLastIntent(intent);
  1.1191 +
  1.1192 +        if (mProfile == null) {
  1.1193 +            String profileName = null;
  1.1194 +            String profilePath = null;
  1.1195 +            if (args != null) {
  1.1196 +                if (args.contains("-P")) {
  1.1197 +                    Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
  1.1198 +                    Matcher m = p.matcher(args);
  1.1199 +                    if (m.find()) {
  1.1200 +                        profileName = m.group(1);
  1.1201 +                    }
  1.1202 +                }
  1.1203 +
  1.1204 +                if (args.contains("-profile")) {
  1.1205 +                    Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
  1.1206 +                    Matcher m = p.matcher(args);
  1.1207 +                    if (m.find()) {
  1.1208 +                        profilePath =  m.group(1);
  1.1209 +                    }
  1.1210 +                    if (profileName == null) {
  1.1211 +                        try {
  1.1212 +                            profileName = getDefaultProfileName();
  1.1213 +                        } catch (NoMozillaDirectoryException e) {
  1.1214 +                            Log.wtf(LOGTAG, "Unable to fetch default profile name!", e);
  1.1215 +                            // There's nothing at all we can do now. If the Mozilla directory
  1.1216 +                            // didn't exist, then we're screwed.
  1.1217 +                            // Crash here so we can fix the bug.
  1.1218 +                            throw new RuntimeException(e);
  1.1219 +                        }
  1.1220 +                        if (profileName == null)
  1.1221 +                            profileName = GeckoProfile.DEFAULT_PROFILE;
  1.1222 +                    }
  1.1223 +                    GeckoProfile.sIsUsingCustomProfile = true;
  1.1224 +                }
  1.1225 +
  1.1226 +                if (profileName != null || profilePath != null) {
  1.1227 +                    mProfile = GeckoProfile.get(this, profileName, profilePath);
  1.1228 +                }
  1.1229 +            }
  1.1230 +        }
  1.1231 +
  1.1232 +        BrowserDB.initialize(getProfile().getName());
  1.1233 +
  1.1234 +        // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
  1.1235 +        try {
  1.1236 +            Class.forName("android.os.AsyncTask");
  1.1237 +        } catch (ClassNotFoundException e) {}
  1.1238 +
  1.1239 +        MemoryMonitor.getInstance().init(getApplicationContext());
  1.1240 +
  1.1241 +        // GeckoAppShell is tightly coupled to us, rather than
  1.1242 +        // the app context, because various parts of Fennec (e.g.,
  1.1243 +        // GeckoScreenOrientation) use GAS to access the Activity in
  1.1244 +        // the guise of fetching a Context.
  1.1245 +        // When that's fixed, `this` can change to
  1.1246 +        // `(GeckoApplication) getApplication()` here.
  1.1247 +        GeckoAppShell.setContextGetter(this);
  1.1248 +        GeckoAppShell.setGeckoInterface(this);
  1.1249 +
  1.1250 +        ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
  1.1251 +
  1.1252 +        Tabs.getInstance().attachToContext(this);
  1.1253 +        try {
  1.1254 +            Favicons.attachToContext(this);
  1.1255 +        } catch (Exception e) {
  1.1256 +            Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
  1.1257 +        }
  1.1258 +
  1.1259 +        // Did the OS locale change while we were backgrounded? If so,
  1.1260 +        // we need to die so that Gecko will re-init add-ons that touch
  1.1261 +        // the UI.
  1.1262 +        // This is using a sledgehammer to crack a nut, but it'll do for
  1.1263 +        // now.
  1.1264 +        if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
  1.1265 +            Log.i(LOGTAG, "System locale changed. Restarting.");
  1.1266 +            doRestart();
  1.1267 +            GeckoAppShell.systemExit();
  1.1268 +            return;
  1.1269 +        }
  1.1270 +
  1.1271 +        if (GeckoThread.isCreated()) {
  1.1272 +            // This happens when the GeckoApp activity is destroyed by Android
  1.1273 +            // without killing the entire application (see Bug 769269).
  1.1274 +            mIsRestoringActivity = true;
  1.1275 +            Telemetry.HistogramAdd("FENNEC_RESTORING_ACTIVITY", 1);
  1.1276 +        }
  1.1277 +
  1.1278 +        // Fix for Bug 830557 on Tegra boards running Froyo.
  1.1279 +        // This fix must be done before doing layout.
  1.1280 +        // Assume the bug is fixed in Gingerbread and up.
  1.1281 +        if (Build.VERSION.SDK_INT < 9) {
  1.1282 +            try {
  1.1283 +                Class<?> inputBindResultClass =
  1.1284 +                    Class.forName("com.android.internal.view.InputBindResult");
  1.1285 +                java.lang.reflect.Field creatorField =
  1.1286 +                    inputBindResultClass.getField("CREATOR");
  1.1287 +                Log.i(LOGTAG, "froyo startup fix: " + String.valueOf(creatorField.get(null)));
  1.1288 +            } catch (Exception e) {
  1.1289 +                Log.w(LOGTAG, "froyo startup fix failed", e);
  1.1290 +            }
  1.1291 +        }
  1.1292 +
  1.1293 +        Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE);
  1.1294 +        if (stateBundle != null) {
  1.1295 +            // Use the state bundle if it was given as an intent extra. This is
  1.1296 +            // only intended to be used internally via Robocop, so a boolean
  1.1297 +            // is read from a private shared pref to prevent other apps from
  1.1298 +            // injecting states.
  1.1299 +            final SharedPreferences prefs = getSharedPreferences();
  1.1300 +            if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) {
  1.1301 +                Log.i(LOGTAG, "Restoring state from intent bundle");
  1.1302 +                prefs.edit().remove(PREFS_ALLOW_STATE_BUNDLE).commit();
  1.1303 +                savedInstanceState = stateBundle;
  1.1304 +            }
  1.1305 +        } else if (savedInstanceState != null) {
  1.1306 +            // Bug 896992 - This intent has already been handled; reset the intent.
  1.1307 +            setIntent(new Intent(Intent.ACTION_MAIN));
  1.1308 +        }
  1.1309 +
  1.1310 +        super.onCreate(savedInstanceState);
  1.1311 +
  1.1312 +        GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
  1.1313 +
  1.1314 +        setContentView(getLayout());
  1.1315 +
  1.1316 +        // Set up Gecko layout.
  1.1317 +        mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
  1.1318 +        mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
  1.1319 +
  1.1320 +        // Determine whether we should restore tabs.
  1.1321 +        mShouldRestore = getSessionRestoreState(savedInstanceState);
  1.1322 +        if (mShouldRestore && savedInstanceState != null) {
  1.1323 +            boolean wasInBackground =
  1.1324 +                savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
  1.1325 +
  1.1326 +            // Don't log OOM-kills if only one activity was destroyed. (For example
  1.1327 +            // from "Don't keep activities" on ICS)
  1.1328 +            if (!wasInBackground && !mIsRestoringActivity) {
  1.1329 +                Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
  1.1330 +            }
  1.1331 +
  1.1332 +            mPrivateBrowsingSession = savedInstanceState.getString(SAVED_STATE_PRIVATE_SESSION);
  1.1333 +        }
  1.1334 +
  1.1335 +        // Perform background initialization.
  1.1336 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.1337 +            @Override
  1.1338 +            public void run() {
  1.1339 +                final SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  1.1340 +
  1.1341 +                // Wait until now to set this, because we'd rather throw an exception than 
  1.1342 +                // have a caller of BrowserLocaleManager regress startup.
  1.1343 +                BrowserLocaleManager.getInstance().initialize(getApplicationContext());
  1.1344 +
  1.1345 +                SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
  1.1346 +                if (previousSession.wasKilled()) {
  1.1347 +                    Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
  1.1348 +                }
  1.1349 +
  1.1350 +                SharedPreferences.Editor editor = prefs.edit();
  1.1351 +                editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
  1.1352 +
  1.1353 +                // Put a flag to check if we got a normal `onSaveInstanceState`
  1.1354 +                // on exit, or if we were suddenly killed (crash or native OOM).
  1.1355 +                editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
  1.1356 +
  1.1357 +                editor.commit();
  1.1358 +
  1.1359 +                // The lifecycle of mHealthRecorder is "shortly after onCreate"
  1.1360 +                // through "onDestroy" -- essentially the same as the lifecycle
  1.1361 +                // of the activity itself.
  1.1362 +                final String profilePath = getProfile().getDir().getAbsolutePath();
  1.1363 +                final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
  1.1364 +                Log.i(LOGTAG, "Creating HealthRecorder.");
  1.1365 +
  1.1366 +                final String osLocale = Locale.getDefault().toString();
  1.1367 +                String appLocale = BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(GeckoApp.this);
  1.1368 +                Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
  1.1369 +
  1.1370 +                if (appLocale == null) {
  1.1371 +                    appLocale = osLocale;
  1.1372 +                }
  1.1373 +
  1.1374 +                mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
  1.1375 +                                                                     profilePath,
  1.1376 +                                                                     dispatcher,
  1.1377 +                                                                     osLocale,
  1.1378 +                                                                     appLocale,
  1.1379 +                                                                     previousSession);
  1.1380 +
  1.1381 +                final String uiLocale = appLocale;
  1.1382 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1383 +                    @Override
  1.1384 +                    public void run() {
  1.1385 +                        GeckoApp.this.onLocaleReady(uiLocale);
  1.1386 +                    }
  1.1387 +                });
  1.1388 +            }
  1.1389 +        });
  1.1390 +
  1.1391 +        GeckoAppShell.setNotificationClient(makeNotificationClient());
  1.1392 +        NotificationHelper.init(getApplicationContext());
  1.1393 +    }
  1.1394 +
  1.1395 +    /**
  1.1396 +     * At this point, the resource system and the rest of the browser are
  1.1397 +     * aware of the locale.
  1.1398 +     *
  1.1399 +     * Now we can display strings!
  1.1400 +     *
  1.1401 +     * You can think of this as being something like a second phase of onCreate,
  1.1402 +     * where you can do string-related operations. Use this in place of embedding
  1.1403 +     * strings in view XML.
  1.1404 +     *
  1.1405 +     * By contrast, onConfigurationChanged does some locale operations, but is in
  1.1406 +     * response to device changes.
  1.1407 +     */
  1.1408 +    @Override
  1.1409 +    public void onLocaleReady(final String locale) {
  1.1410 +        if (!ThreadUtils.isOnUiThread()) {
  1.1411 +            throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
  1.1412 +        }
  1.1413 +
  1.1414 +        // The URL bar hint needs to be populated.
  1.1415 +        TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
  1.1416 +        if (urlBar != null) {
  1.1417 +            final String hint = getResources().getString(R.string.url_bar_default_text);
  1.1418 +            urlBar.setHint(hint);
  1.1419 +        } else {
  1.1420 +            Log.d(LOGTAG, "No URL bar in GeckoApp. Not loading localized hint string.");
  1.1421 +        }
  1.1422 +
  1.1423 +        // Allow onConfigurationChanged to take care of the rest.
  1.1424 +        onConfigurationChanged(getResources().getConfiguration());
  1.1425 +    }
  1.1426 +
  1.1427 +    protected void initializeChrome() {
  1.1428 +        mDoorHangerPopup = new DoorHangerPopup(this);
  1.1429 +        mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
  1.1430 +        mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
  1.1431 +
  1.1432 +        if (mCameraView == null) {
  1.1433 +            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  1.1434 +                mCameraView = new SurfaceView(this);
  1.1435 +                ((SurfaceView)mCameraView).getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  1.1436 +            } else {
  1.1437 +                mCameraView = new TextureView(this);
  1.1438 +            }
  1.1439 +        }
  1.1440 +
  1.1441 +        if (mLayerView == null) {
  1.1442 +            LayerView layerView = (LayerView) findViewById(R.id.layer_view);
  1.1443 +            layerView.initializeView(GeckoAppShell.getEventDispatcher());
  1.1444 +            mLayerView = layerView;
  1.1445 +            GeckoAppShell.setLayerView(layerView);
  1.1446 +            // bind the GeckoEditable instance to the new LayerView
  1.1447 +            GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", "");
  1.1448 +        }
  1.1449 +    }
  1.1450 +
  1.1451 +    /**
  1.1452 +     * Loads the initial tab at Fennec startup.
  1.1453 +     *
  1.1454 +     * If Fennec was opened with an external URL, that URL will be loaded.
  1.1455 +     * Otherwise, unless there was a session restore, the default URL
  1.1456 +     * (about:home) be loaded.
  1.1457 +     *
  1.1458 +     * @param url External URL to load, or null to load the default URL
  1.1459 +     */
  1.1460 +    protected void loadStartupTab(String url) {
  1.1461 +        if (url == null) {
  1.1462 +            if (!mShouldRestore) {
  1.1463 +                // Show about:home if we aren't restoring previous session and
  1.1464 +                // there's no external URL.
  1.1465 +                Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
  1.1466 +            }
  1.1467 +        } else {
  1.1468 +            // If given an external URL, load it
  1.1469 +            int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
  1.1470 +            Tabs.getInstance().loadUrl(url, flags);
  1.1471 +        }
  1.1472 +    }
  1.1473 +
  1.1474 +    private void initialize() {
  1.1475 +        mInitialized = true;
  1.1476 +
  1.1477 +        Intent intent = getIntent();
  1.1478 +        String action = intent.getAction();
  1.1479 +
  1.1480 +        String passedUri = null;
  1.1481 +        final String uri = getURIFromIntent(intent);
  1.1482 +        if (!TextUtils.isEmpty(uri)) {
  1.1483 +            passedUri = uri;
  1.1484 +        }
  1.1485 +
  1.1486 +        final boolean isExternalURL = passedUri != null &&
  1.1487 +                                      !AboutPages.isAboutHome(passedUri);
  1.1488 +        StartupAction startupAction;
  1.1489 +        if (isExternalURL) {
  1.1490 +            startupAction = StartupAction.URL;
  1.1491 +        } else {
  1.1492 +            startupAction = StartupAction.NORMAL;
  1.1493 +        }
  1.1494 +
  1.1495 +        // Start migrating as early as possible, can do this in
  1.1496 +        // parallel with Gecko load.
  1.1497 +        checkMigrateProfile();
  1.1498 +
  1.1499 +        Uri data = intent.getData();
  1.1500 +        if (data != null && "http".equals(data.getScheme())) {
  1.1501 +            startupAction = StartupAction.PREFETCH;
  1.1502 +            ThreadUtils.postToBackgroundThread(new PrefetchRunnable(data.toString()));
  1.1503 +        }
  1.1504 +
  1.1505 +        Tabs.registerOnTabsChangedListener(this);
  1.1506 +
  1.1507 +        initializeChrome();
  1.1508 +
  1.1509 +        // If we are doing a restore, read the session data and send it to Gecko
  1.1510 +        if (!mIsRestoringActivity) {
  1.1511 +            String restoreMessage = null;
  1.1512 +            if (mShouldRestore) {
  1.1513 +                try {
  1.1514 +                    // restoreSessionTabs() will create simple tab stubs with the
  1.1515 +                    // URL and title for each page, but we also need to restore
  1.1516 +                    // session history. restoreSessionTabs() will inject the IDs
  1.1517 +                    // of the tab stubs into the JSON data (which holds the session
  1.1518 +                    // history). This JSON data is then sent to Gecko so session
  1.1519 +                    // history can be restored for each tab.
  1.1520 +                    restoreMessage = restoreSessionTabs(isExternalURL);
  1.1521 +                } catch (SessionRestoreException e) {
  1.1522 +                    // If restore failed, do a normal startup
  1.1523 +                    Log.e(LOGTAG, "An error occurred during restore", e);
  1.1524 +                    mShouldRestore = false;
  1.1525 +                }
  1.1526 +            }
  1.1527 +
  1.1528 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Session:Restore", restoreMessage));
  1.1529 +        }
  1.1530 +
  1.1531 +        // External URLs should always be loaded regardless of whether Gecko is
  1.1532 +        // already running.
  1.1533 +        if (isExternalURL) {
  1.1534 +            loadStartupTab(passedUri);
  1.1535 +        } else if (!mIsRestoringActivity) {
  1.1536 +            loadStartupTab(null);
  1.1537 +        }
  1.1538 +
  1.1539 +        // We now have tab stubs from the last session. Any future tabs should
  1.1540 +        // be animated.
  1.1541 +        Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
  1.1542 +
  1.1543 +        // If we're not restoring, move the session file so it can be read for
  1.1544 +        // the last tabs section.
  1.1545 +        if (!mShouldRestore) {
  1.1546 +            getProfile().moveSessionFile();
  1.1547 +        }
  1.1548 +
  1.1549 +        Telemetry.HistogramAdd("FENNEC_STARTUP_GECKOAPP_ACTION", startupAction.ordinal());
  1.1550 +
  1.1551 +        if (!mIsRestoringActivity) {
  1.1552 +            GeckoThread.setArgs(intent.getStringExtra("args"));
  1.1553 +            GeckoThread.setAction(intent.getAction());
  1.1554 +            GeckoThread.setUri(passedUri);
  1.1555 +        }
  1.1556 +        if (!ACTION_DEBUG.equals(action) &&
  1.1557 +            GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
  1.1558 +            GeckoThread.createAndStart();
  1.1559 +        } else if (ACTION_DEBUG.equals(action) &&
  1.1560 +            GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.WaitForDebugger)) {
  1.1561 +            ThreadUtils.getUiHandler().postDelayed(new Runnable() {
  1.1562 +                @Override
  1.1563 +                public void run() {
  1.1564 +                    GeckoThread.setLaunchState(GeckoThread.LaunchState.Launching);
  1.1565 +                    GeckoThread.createAndStart();
  1.1566 +                }
  1.1567 +            }, 1000 * 5 /* 5 seconds */);
  1.1568 +        }
  1.1569 +
  1.1570 +        // Check if launched from data reporting notification.
  1.1571 +        if (ACTION_LAUNCH_SETTINGS.equals(action)) {
  1.1572 +            Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
  1.1573 +            // Copy extras.
  1.1574 +            settingsIntent.putExtras(intent);
  1.1575 +            startActivity(settingsIntent);
  1.1576 +        }
  1.1577 +
  1.1578 +        //app state callbacks
  1.1579 +        mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
  1.1580 +
  1.1581 +        //register for events
  1.1582 +        registerEventListener("log");
  1.1583 +        registerEventListener("Reader:ListStatusRequest");
  1.1584 +        registerEventListener("Reader:Added");
  1.1585 +        registerEventListener("Reader:Removed");
  1.1586 +        registerEventListener("Reader:Share");
  1.1587 +        registerEventListener("Reader:FaviconRequest");
  1.1588 +        registerEventListener("onCameraCapture");
  1.1589 +        registerEventListener("Gecko:Ready");
  1.1590 +        registerEventListener("Gecko:DelayedStartup");
  1.1591 +        registerEventListener("Toast:Show");
  1.1592 +        registerEventListener("DOMFullScreen:Start");
  1.1593 +        registerEventListener("DOMFullScreen:Stop");
  1.1594 +        registerEventListener("ToggleChrome:Hide");
  1.1595 +        registerEventListener("ToggleChrome:Show");
  1.1596 +        registerEventListener("ToggleChrome:Focus");
  1.1597 +        registerEventListener("Permissions:Data");
  1.1598 +        registerEventListener("Session:StatePurged");
  1.1599 +        registerEventListener("Bookmark:Insert");
  1.1600 +        registerEventListener("Accessibility:Event");
  1.1601 +        registerEventListener("Accessibility:Ready");
  1.1602 +        registerEventListener("Shortcut:Remove");
  1.1603 +        registerEventListener("Share:Text");
  1.1604 +        registerEventListener("Image:SetAs");
  1.1605 +        registerEventListener("Sanitize:ClearHistory");
  1.1606 +        registerEventListener("Update:Check");
  1.1607 +        registerEventListener("Update:Download");
  1.1608 +        registerEventListener("Update:Install");
  1.1609 +        registerEventListener("PrivateBrowsing:Data");
  1.1610 +        registerEventListener("Contact:Add");
  1.1611 +        registerEventListener("Intent:Open");
  1.1612 +        registerEventListener("Intent:OpenForResult");
  1.1613 +        registerEventListener("Intent:GetHandlers");
  1.1614 +        registerEventListener("Locale:Set");
  1.1615 +        registerEventListener("NativeApp:IsDebuggable");
  1.1616 +        registerEventListener("SystemUI:Visibility");
  1.1617 +
  1.1618 +        if (mWebappEventListener == null) {
  1.1619 +            mWebappEventListener = new EventListener();
  1.1620 +            mWebappEventListener.registerEvents();
  1.1621 +        }
  1.1622 +
  1.1623 +        if (SmsManager.getInstance() != null) {
  1.1624 +            SmsManager.getInstance().start();
  1.1625 +        }
  1.1626 +
  1.1627 +        mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
  1.1628 +
  1.1629 +        mPromptService = new PromptService(this);
  1.1630 +
  1.1631 +        mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle),
  1.1632 +                                           (TextSelectionHandle) findViewById(R.id.middle_handle),
  1.1633 +                                           (TextSelectionHandle) findViewById(R.id.end_handle),
  1.1634 +                                           GeckoAppShell.getEventDispatcher(),
  1.1635 +                                           this);
  1.1636 +
  1.1637 +        PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
  1.1638 +            @Override public void prefValue(String pref, String value) {
  1.1639 +                UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
  1.1640 +            }
  1.1641 +        });
  1.1642 +
  1.1643 +        PrefsHelper.getPref("app.geo.reportdata", new PrefsHelper.PrefHandlerBase() {
  1.1644 +            @Override public void prefValue(String pref, int value) {
  1.1645 +                if (value == 1)
  1.1646 +                    mShouldReportGeoData = true;
  1.1647 +                else
  1.1648 +                    mShouldReportGeoData = false;
  1.1649 +            }
  1.1650 +        });
  1.1651 +
  1.1652 +        // Trigger the completion of the telemetry timer that wraps activity startup,
  1.1653 +        // then grab the duration to give to FHR.
  1.1654 +        mJavaUiStartupTimer.stop();
  1.1655 +        final long javaDuration = mJavaUiStartupTimer.getElapsed();
  1.1656 +
  1.1657 +        ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
  1.1658 +            @Override
  1.1659 +            public void run() {
  1.1660 +                final HealthRecorder rec = mHealthRecorder;
  1.1661 +                if (rec != null) {
  1.1662 +                    rec.recordJavaStartupTime(javaDuration);
  1.1663 +                }
  1.1664 +
  1.1665 +                // Record our launch time for the announcements service
  1.1666 +                // to use in assessing inactivity.
  1.1667 +                final Context context = GeckoApp.this;
  1.1668 +                AnnouncementsBroadcastService.recordLastLaunch(context);
  1.1669 +
  1.1670 +                // Kick off our background services. We do this by invoking the broadcast
  1.1671 +                // receiver, which uses the system alarm infrastructure to perform tasks at
  1.1672 +                // intervals.
  1.1673 +                GeckoPreferences.broadcastAnnouncementsPref(context);
  1.1674 +                GeckoPreferences.broadcastHealthReportUploadPref(context);
  1.1675 +                if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
  1.1676 +                    return;
  1.1677 +                }
  1.1678 +            }
  1.1679 +        }, 50);
  1.1680 +
  1.1681 +        if (mIsRestoringActivity) {
  1.1682 +            GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
  1.1683 +            Tab selectedTab = Tabs.getInstance().getSelectedTab();
  1.1684 +            if (selectedTab != null)
  1.1685 +                Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
  1.1686 +            geckoConnected();
  1.1687 +            GeckoAppShell.setLayerClient(mLayerView.getLayerClientObject());
  1.1688 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
  1.1689 +        }
  1.1690 +
  1.1691 +        if (ACTION_ALERT_CALLBACK.equals(action)) {
  1.1692 +            processAlertCallback(intent);
  1.1693 +        }
  1.1694 +    }
  1.1695 +
  1.1696 +    private String restoreSessionTabs(final boolean isExternalURL) throws SessionRestoreException {
  1.1697 +        try {
  1.1698 +            String sessionString = getProfile().readSessionFile(false);
  1.1699 +            if (sessionString == null) {
  1.1700 +                throw new SessionRestoreException("Could not read from session file");
  1.1701 +            }
  1.1702 +
  1.1703 +            // If we are doing an OOM restore, parse the session data and
  1.1704 +            // stub the restored tabs immediately. This allows the UI to be
  1.1705 +            // updated before Gecko has restored.
  1.1706 +            if (mShouldRestore) {
  1.1707 +                final JSONArray tabs = new JSONArray();
  1.1708 +                SessionParser parser = new SessionParser() {
  1.1709 +                    @Override
  1.1710 +                    public void onTabRead(SessionTab sessionTab) {
  1.1711 +                        JSONObject tabObject = sessionTab.getTabObject();
  1.1712 +
  1.1713 +                        int flags = Tabs.LOADURL_NEW_TAB;
  1.1714 +                        flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
  1.1715 +                        flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
  1.1716 +                        flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
  1.1717 +
  1.1718 +                        Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
  1.1719 +                        tab.updateTitle(sessionTab.getTitle());
  1.1720 +
  1.1721 +                        try {
  1.1722 +                            tabObject.put("tabId", tab.getId());
  1.1723 +                        } catch (JSONException e) {
  1.1724 +                            Log.e(LOGTAG, "JSON error", e);
  1.1725 +                        }
  1.1726 +                        tabs.put(tabObject);
  1.1727 +                    }
  1.1728 +                };
  1.1729 +
  1.1730 +                if (mPrivateBrowsingSession == null) {
  1.1731 +                    parser.parse(sessionString);
  1.1732 +                } else {
  1.1733 +                    parser.parse(sessionString, mPrivateBrowsingSession);
  1.1734 +                }
  1.1735 +
  1.1736 +                if (tabs.length() > 0) {
  1.1737 +                    sessionString = new JSONObject().put("windows", new JSONArray().put(new JSONObject().put("tabs", tabs))).toString();
  1.1738 +                } else {
  1.1739 +                    throw new SessionRestoreException("No tabs could be read from session file");
  1.1740 +                }
  1.1741 +            }
  1.1742 +
  1.1743 +            JSONObject restoreData = new JSONObject();
  1.1744 +            restoreData.put("sessionString", sessionString);
  1.1745 +            return restoreData.toString();
  1.1746 +
  1.1747 +        } catch (JSONException e) {
  1.1748 +            throw new SessionRestoreException(e);
  1.1749 +        }
  1.1750 +    }
  1.1751 +
  1.1752 +    public GeckoProfile getProfile() {
  1.1753 +        // fall back to default profile if we didn't load a specific one
  1.1754 +        if (mProfile == null) {
  1.1755 +            mProfile = GeckoProfile.get(this);
  1.1756 +        }
  1.1757 +        return mProfile;
  1.1758 +    }
  1.1759 +
  1.1760 +    /**
  1.1761 +     * Determine whether the session should be restored.
  1.1762 +     *
  1.1763 +     * @param savedInstanceState Saved instance state given to the activity
  1.1764 +     * @return                   Whether to restore
  1.1765 +     */
  1.1766 +    protected boolean getSessionRestoreState(Bundle savedInstanceState) {
  1.1767 +        final SharedPreferences prefs = getSharedPreferences();
  1.1768 +        boolean shouldRestore = false;
  1.1769 +
  1.1770 +        final int versionCode = getVersionCode();
  1.1771 +        if (prefs.getInt(PREFS_VERSION_CODE, 0) != versionCode) {
  1.1772 +            // If the version has changed, the user has done an upgrade, so restore
  1.1773 +            // previous tabs.
  1.1774 +            ThreadUtils.postToBackgroundThread(new Runnable() {
  1.1775 +                @Override
  1.1776 +                public void run() {
  1.1777 +                    prefs.edit()
  1.1778 +                         .putInt(PREFS_VERSION_CODE, versionCode)
  1.1779 +                         .commit();
  1.1780 +                }
  1.1781 +            });
  1.1782 +
  1.1783 +            shouldRestore = true;
  1.1784 +        } else if (savedInstanceState != null ||
  1.1785 +                   getSessionRestorePreference().equals("always") ||
  1.1786 +                   getRestartFromIntent()) {
  1.1787 +            // We're coming back from a background kill by the OS, the user
  1.1788 +            // has chosen to always restore, or we restarted.
  1.1789 +            shouldRestore = true;
  1.1790 +        } else if (prefs.getBoolean(GeckoApp.PREFS_CRASHED, false)) {
  1.1791 +            ThreadUtils.postToBackgroundThread(new Runnable() {
  1.1792 +                @Override
  1.1793 +                public void run() {
  1.1794 +                    prefs.edit().putBoolean(PREFS_CRASHED, false).commit();
  1.1795 +                }
  1.1796 +            });
  1.1797 +            shouldRestore = true;
  1.1798 +        }
  1.1799 +
  1.1800 +        return shouldRestore;
  1.1801 +    }
  1.1802 +
  1.1803 +    private String getSessionRestorePreference() {
  1.1804 +        return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
  1.1805 +    }
  1.1806 +
  1.1807 +    private boolean getRestartFromIntent() {
  1.1808 +        return getIntent().getBooleanExtra("didRestart", false);
  1.1809 +    }
  1.1810 +
  1.1811 +    /**
  1.1812 +     * Enable Android StrictMode checks (for supported OS versions).
  1.1813 +     * http://developer.android.com/reference/android/os/StrictMode.html
  1.1814 +     */
  1.1815 +    private void enableStrictMode() {
  1.1816 +        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
  1.1817 +            return;
  1.1818 +        }
  1.1819 +
  1.1820 +        Log.d(LOGTAG, "Enabling Android StrictMode");
  1.1821 +
  1.1822 +        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
  1.1823 +                                  .detectAll()
  1.1824 +                                  .penaltyLog()
  1.1825 +                                  .build());
  1.1826 +
  1.1827 +        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
  1.1828 +                               .detectAll()
  1.1829 +                               .penaltyLog()
  1.1830 +                               .build());
  1.1831 +    }
  1.1832 +
  1.1833 +    public void enableCameraView() {
  1.1834 +        // Start listening for orientation events
  1.1835 +        mCameraOrientationEventListener = new OrientationEventListener(this) {
  1.1836 +            @Override
  1.1837 +            public void onOrientationChanged(int orientation) {
  1.1838 +                if (mAppStateListeners != null) {
  1.1839 +                    for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
  1.1840 +                        listener.onOrientationChanged();
  1.1841 +                    }
  1.1842 +                }
  1.1843 +            }
  1.1844 +        };
  1.1845 +        mCameraOrientationEventListener.enable();
  1.1846 +
  1.1847 +        // Try to make it fully transparent.
  1.1848 +        if (mCameraView instanceof SurfaceView) {
  1.1849 +            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  1.1850 +                mCameraView.setAlpha(0.0f);
  1.1851 +            }
  1.1852 +        } else if (mCameraView instanceof TextureView) {
  1.1853 +            mCameraView.setAlpha(0.0f);
  1.1854 +        }
  1.1855 +        ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
  1.1856 +        // Some phones (eg. nexus S) need at least a 8x16 preview size
  1.1857 +        mCameraLayout.addView(mCameraView,
  1.1858 +                              new AbsoluteLayout.LayoutParams(8, 16, 0, 0));
  1.1859 +    }
  1.1860 +
  1.1861 +    public void disableCameraView() {
  1.1862 +        if (mCameraOrientationEventListener != null) {
  1.1863 +            mCameraOrientationEventListener.disable();
  1.1864 +            mCameraOrientationEventListener = null;
  1.1865 +        }
  1.1866 +        ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
  1.1867 +        mCameraLayout.removeView(mCameraView);
  1.1868 +    }
  1.1869 +
  1.1870 +    public String getDefaultUAString() {
  1.1871 +        return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET :
  1.1872 +                                          AppConstants.USER_AGENT_FENNEC_MOBILE;
  1.1873 +    }
  1.1874 +
  1.1875 +    public String getUAStringForHost(String host) {
  1.1876 +        // With our standard UA String, we get a 200 response code and
  1.1877 +        // client-side redirect from t.co. This bot-like UA gives us a
  1.1878 +        // 301 response code
  1.1879 +        if ("t.co".equals(host)) {
  1.1880 +            return AppConstants.USER_AGENT_BOT_LIKE;
  1.1881 +        }
  1.1882 +        return getDefaultUAString();
  1.1883 +    }
  1.1884 +
  1.1885 +    class PrefetchRunnable implements Runnable {
  1.1886 +        private String mPrefetchUrl;
  1.1887 +
  1.1888 +        PrefetchRunnable(String prefetchUrl) {
  1.1889 +            mPrefetchUrl = prefetchUrl;
  1.1890 +        }
  1.1891 +
  1.1892 +        @Override
  1.1893 +        public void run() {
  1.1894 +            HttpURLConnection connection = null;
  1.1895 +            try {
  1.1896 +                URL url = new URL(mPrefetchUrl);
  1.1897 +                // data url should have an http scheme
  1.1898 +                connection = (HttpURLConnection) url.openConnection();
  1.1899 +                connection.setRequestProperty("User-Agent", getUAStringForHost(url.getHost()));
  1.1900 +                connection.setInstanceFollowRedirects(false);
  1.1901 +                connection.setRequestMethod("GET");
  1.1902 +                connection.connect();
  1.1903 +            } catch (Exception e) {
  1.1904 +                Log.e(LOGTAG, "Exception prefetching URL", e);
  1.1905 +            } finally {
  1.1906 +                if (connection != null)
  1.1907 +                    connection.disconnect();
  1.1908 +            }
  1.1909 +        }
  1.1910 +    }
  1.1911 +
  1.1912 +    private void processAlertCallback(Intent intent) {
  1.1913 +        String alertName = "";
  1.1914 +        String alertCookie = "";
  1.1915 +        Uri data = intent.getData();
  1.1916 +        if (data != null) {
  1.1917 +            alertName = data.getQueryParameter("name");
  1.1918 +            if (alertName == null)
  1.1919 +                alertName = "";
  1.1920 +            alertCookie = data.getQueryParameter("cookie");
  1.1921 +            if (alertCookie == null)
  1.1922 +                alertCookie = "";
  1.1923 +        }
  1.1924 +        handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
  1.1925 +    }
  1.1926 +
  1.1927 +    @Override
  1.1928 +    protected void onNewIntent(Intent intent) {
  1.1929 +        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
  1.1930 +            // We're exiting and shouldn't try to do anything else. In the case
  1.1931 +            // where we are hung while exiting, we should force the process to exit.
  1.1932 +            GeckoAppShell.systemExit();
  1.1933 +            return;
  1.1934 +        }
  1.1935 +
  1.1936 +        // if we were previously OOM killed, we can end up here when launching
  1.1937 +        // from external shortcuts, so set this as the intent for initialization
  1.1938 +        if (!mInitialized) {
  1.1939 +            setIntent(intent);
  1.1940 +            return;
  1.1941 +        }
  1.1942 +
  1.1943 +        final String action = intent.getAction();
  1.1944 +
  1.1945 +        if (ACTION_LOAD.equals(action)) {
  1.1946 +            String uri = intent.getDataString();
  1.1947 +            Tabs.getInstance().loadUrl(uri);
  1.1948 +        } else if (Intent.ACTION_VIEW.equals(action)) {
  1.1949 +            String uri = intent.getDataString();
  1.1950 +            Tabs.getInstance().loadUrl(uri, Tabs.LOADURL_NEW_TAB |
  1.1951 +                                            Tabs.LOADURL_USER_ENTERED |
  1.1952 +                                            Tabs.LOADURL_EXTERNAL);
  1.1953 +        } else if (action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) {
  1.1954 +            // A lightweight mechanism for loading a web page as a webapp
  1.1955 +            // without installing the app natively nor registering it in the DOM
  1.1956 +            // application registry.
  1.1957 +            String uri = getURIFromIntent(intent);
  1.1958 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createWebappLoadEvent(uri));
  1.1959 +        } else if (ACTION_BOOKMARK.equals(action)) {
  1.1960 +            String uri = getURIFromIntent(intent);
  1.1961 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBookmarkLoadEvent(uri));
  1.1962 +        } else if (Intent.ACTION_SEARCH.equals(action)) {
  1.1963 +            String uri = getURIFromIntent(intent);
  1.1964 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
  1.1965 +        } else if (ACTION_ALERT_CALLBACK.equals(action)) {
  1.1966 +            processAlertCallback(intent);
  1.1967 +        } else if (ACTION_LAUNCH_SETTINGS.equals(action)) {
  1.1968 +            // Check if launched from data reporting notification.
  1.1969 +            Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
  1.1970 +            // Copy extras.
  1.1971 +            settingsIntent.putExtras(intent);
  1.1972 +            startActivity(settingsIntent);
  1.1973 +        }
  1.1974 +    }
  1.1975 +
  1.1976 +    /*
  1.1977 +     * Handles getting a uri from and intent in a way that is backwards
  1.1978 +     * compatable with our previous implementations
  1.1979 +     */
  1.1980 +    protected String getURIFromIntent(Intent intent) {
  1.1981 +        final String action = intent.getAction();
  1.1982 +        if (ACTION_ALERT_CALLBACK.equals(action))
  1.1983 +            return null;
  1.1984 +
  1.1985 +        String uri = intent.getDataString();
  1.1986 +        if (uri != null)
  1.1987 +            return uri;
  1.1988 +
  1.1989 +        if ((action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) || ACTION_BOOKMARK.equals(action)) {
  1.1990 +            uri = intent.getStringExtra("args");
  1.1991 +            if (uri != null && uri.startsWith("--url=")) {
  1.1992 +                uri.replace("--url=", "");
  1.1993 +            }
  1.1994 +        }
  1.1995 +        return uri;
  1.1996 +    }
  1.1997 +
  1.1998 +    protected int getOrientation() {
  1.1999 +        return GeckoScreenOrientation.getInstance().getAndroidOrientation();
  1.2000 +    }
  1.2001 +
  1.2002 +    @Override
  1.2003 +    public void onResume()
  1.2004 +    {
  1.2005 +        // After an onPause, the activity is back in the foreground.
  1.2006 +        // Undo whatever we did in onPause.
  1.2007 +        super.onResume();
  1.2008 +
  1.2009 +        int newOrientation = getResources().getConfiguration().orientation;
  1.2010 +        if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
  1.2011 +            refreshChrome();
  1.2012 +        }
  1.2013 +
  1.2014 +        // User may have enabled/disabled accessibility.
  1.2015 +        GeckoAccessibility.updateAccessibilitySettings(this);
  1.2016 +
  1.2017 +        if (mAppStateListeners != null) {
  1.2018 +            for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
  1.2019 +                listener.onResume();
  1.2020 +            }
  1.2021 +        }
  1.2022 +
  1.2023 +        // We use two times: a pseudo-unique wall-clock time to identify the
  1.2024 +        // current session across power cycles, and the elapsed realtime to
  1.2025 +        // track the duration of the session.
  1.2026 +        final long now = System.currentTimeMillis();
  1.2027 +        final long realTime = android.os.SystemClock.elapsedRealtime();
  1.2028 +
  1.2029 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2030 +            @Override
  1.2031 +            public void run() {
  1.2032 +                // Now construct the new session on HealthRecorder's behalf. We do this here
  1.2033 +                // so it can benefit from a single near-startup prefs commit.
  1.2034 +                SessionInformation currentSession = new SessionInformation(now, realTime);
  1.2035 +
  1.2036 +                SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  1.2037 +                SharedPreferences.Editor editor = prefs.edit();
  1.2038 +                editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
  1.2039 +                currentSession.recordBegin(editor);
  1.2040 +                editor.commit();
  1.2041 +
  1.2042 +                final HealthRecorder rec = mHealthRecorder;
  1.2043 +                if (rec != null) {
  1.2044 +                    rec.setCurrentSession(currentSession);
  1.2045 +                } else {
  1.2046 +                    Log.w(LOGTAG, "Can't record session: rec is null.");
  1.2047 +                }
  1.2048 +            }
  1.2049 +         });
  1.2050 +    }
  1.2051 +
  1.2052 +    @Override
  1.2053 +    public void onWindowFocusChanged(boolean hasFocus) {
  1.2054 +        super.onWindowFocusChanged(hasFocus);
  1.2055 +
  1.2056 +        if (!mInitialized && hasFocus) {
  1.2057 +            initialize();
  1.2058 +            getWindow().setBackgroundDrawable(null);
  1.2059 +        }
  1.2060 +    }
  1.2061 +
  1.2062 +    @Override
  1.2063 +    public void onPause()
  1.2064 +    {
  1.2065 +        final HealthRecorder rec = mHealthRecorder;
  1.2066 +        final Context context = this;
  1.2067 +
  1.2068 +        // In some way it's sad that Android will trigger StrictMode warnings
  1.2069 +        // here as the whole point is to save to disk while the activity is not
  1.2070 +        // interacting with the user.
  1.2071 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2072 +            @Override
  1.2073 +            public void run() {
  1.2074 +                SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  1.2075 +                SharedPreferences.Editor editor = prefs.edit();
  1.2076 +                editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
  1.2077 +                if (rec != null) {
  1.2078 +                    rec.recordSessionEnd("P", editor);
  1.2079 +                }
  1.2080 +
  1.2081 +                // If we haven't done it before, cleanup any old files in our old temp dir
  1.2082 +                if (prefs.getBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, true)) {
  1.2083 +                    File tempDir = GeckoLoader.getGREDir(GeckoApp.this);
  1.2084 +                    FileUtils.delTree(tempDir, new FileUtils.NameAndAgeFilter(null, ONE_DAY_MS), false);
  1.2085 +
  1.2086 +                    editor.putBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, false);
  1.2087 +                }
  1.2088 +
  1.2089 +                editor.commit();
  1.2090 +
  1.2091 +                // In theory, the first browser session will not run long enough that we need to
  1.2092 +                // prune during it and we'd rather run it when the browser is inactive so we wait
  1.2093 +                // until here to register the prune service.
  1.2094 +                GeckoPreferences.broadcastHealthReportPrune(context);
  1.2095 +            }
  1.2096 +        });
  1.2097 +
  1.2098 +        if (mAppStateListeners != null) {
  1.2099 +            for(GeckoAppShell.AppStateListener listener: mAppStateListeners) {
  1.2100 +                listener.onPause();
  1.2101 +            }
  1.2102 +        }
  1.2103 +
  1.2104 +        super.onPause();
  1.2105 +    }
  1.2106 +
  1.2107 +    @Override
  1.2108 +    public void onRestart()
  1.2109 +    {
  1.2110 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2111 +            @Override
  1.2112 +            public void run() {
  1.2113 +                SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
  1.2114 +                SharedPreferences.Editor editor = prefs.edit();
  1.2115 +                editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
  1.2116 +                editor.commit();
  1.2117 +            }
  1.2118 +        });
  1.2119 +
  1.2120 +        super.onRestart();
  1.2121 +    }
  1.2122 +
  1.2123 +    @Override
  1.2124 +    public void onDestroy()
  1.2125 +    {
  1.2126 +        unregisterEventListener("log");
  1.2127 +        unregisterEventListener("Reader:ListStatusRequest");
  1.2128 +        unregisterEventListener("Reader:Added");
  1.2129 +        unregisterEventListener("Reader:Removed");
  1.2130 +        unregisterEventListener("Reader:Share");
  1.2131 +        unregisterEventListener("Reader:FaviconRequest");
  1.2132 +        unregisterEventListener("onCameraCapture");
  1.2133 +        unregisterEventListener("Gecko:Ready");
  1.2134 +        unregisterEventListener("Gecko:DelayedStartup");
  1.2135 +        unregisterEventListener("Toast:Show");
  1.2136 +        unregisterEventListener("DOMFullScreen:Start");
  1.2137 +        unregisterEventListener("DOMFullScreen:Stop");
  1.2138 +        unregisterEventListener("ToggleChrome:Hide");
  1.2139 +        unregisterEventListener("ToggleChrome:Show");
  1.2140 +        unregisterEventListener("ToggleChrome:Focus");
  1.2141 +        unregisterEventListener("Permissions:Data");
  1.2142 +        unregisterEventListener("Session:StatePurged");
  1.2143 +        unregisterEventListener("Bookmark:Insert");
  1.2144 +        unregisterEventListener("Accessibility:Event");
  1.2145 +        unregisterEventListener("Accessibility:Ready");
  1.2146 +        unregisterEventListener("Shortcut:Remove");
  1.2147 +        unregisterEventListener("Share:Text");
  1.2148 +        unregisterEventListener("Image:SetAs");
  1.2149 +        unregisterEventListener("Sanitize:ClearHistory");
  1.2150 +        unregisterEventListener("Update:Check");
  1.2151 +        unregisterEventListener("Update:Download");
  1.2152 +        unregisterEventListener("Update:Install");
  1.2153 +        unregisterEventListener("PrivateBrowsing:Data");
  1.2154 +        unregisterEventListener("Contact:Add");
  1.2155 +        unregisterEventListener("Intent:Open");
  1.2156 +        unregisterEventListener("Intent:GetHandlers");
  1.2157 +        unregisterEventListener("Locale:Set");
  1.2158 +        unregisterEventListener("NativeApp:IsDebuggable");
  1.2159 +        unregisterEventListener("SystemUI:Visibility");
  1.2160 +
  1.2161 +        if (mWebappEventListener != null) {
  1.2162 +            mWebappEventListener.unregisterEvents();
  1.2163 +            mWebappEventListener = null;
  1.2164 +        }
  1.2165 +
  1.2166 +        deleteTempFiles();
  1.2167 +
  1.2168 +        if (mLayerView != null)
  1.2169 +            mLayerView.destroy();
  1.2170 +        if (mDoorHangerPopup != null)
  1.2171 +            mDoorHangerPopup.destroy();
  1.2172 +        if (mFormAssistPopup != null)
  1.2173 +            mFormAssistPopup.destroy();
  1.2174 +        if (mContactService != null)
  1.2175 +            mContactService.destroy();
  1.2176 +        if (mPromptService != null)
  1.2177 +            mPromptService.destroy();
  1.2178 +        if (mTextSelection != null)
  1.2179 +            mTextSelection.destroy();
  1.2180 +        NotificationHelper.destroy();
  1.2181 +
  1.2182 +        if (SmsManager.getInstance() != null) {
  1.2183 +            SmsManager.getInstance().stop();
  1.2184 +            if (isFinishing())
  1.2185 +                SmsManager.getInstance().shutdown();
  1.2186 +        }
  1.2187 +
  1.2188 +        final HealthRecorder rec = mHealthRecorder;
  1.2189 +        mHealthRecorder = null;
  1.2190 +        if (rec != null && rec.isEnabled()) {
  1.2191 +            // Closing a BrowserHealthRecorder could incur a write.
  1.2192 +            ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2193 +                @Override
  1.2194 +                public void run() {
  1.2195 +                    rec.close();
  1.2196 +                }
  1.2197 +            });
  1.2198 +        }
  1.2199 +
  1.2200 +        Favicons.close();
  1.2201 +
  1.2202 +        super.onDestroy();
  1.2203 +
  1.2204 +        Tabs.unregisterOnTabsChangedListener(this);
  1.2205 +    }
  1.2206 +
  1.2207 +    protected void registerEventListener(String event) {
  1.2208 +        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
  1.2209 +    }
  1.2210 +
  1.2211 +    protected void unregisterEventListener(String event) {
  1.2212 +        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
  1.2213 +    }
  1.2214 +
  1.2215 +    // Get a temporary directory, may return null
  1.2216 +    public static File getTempDirectory() {
  1.2217 +        File dir = GeckoApplication.get().getExternalFilesDir("temp");
  1.2218 +        return dir;
  1.2219 +    }
  1.2220 +
  1.2221 +    // Delete any files in our temporary directory
  1.2222 +    public static void deleteTempFiles() {
  1.2223 +        File dir = getTempDirectory();
  1.2224 +        if (dir == null)
  1.2225 +            return;
  1.2226 +        File[] files = dir.listFiles();
  1.2227 +        if (files == null)
  1.2228 +            return;
  1.2229 +        for (File file : files) {
  1.2230 +            file.delete();
  1.2231 +        }
  1.2232 +    }
  1.2233 +
  1.2234 +    @Override
  1.2235 +    public void onConfigurationChanged(Configuration newConfig) {
  1.2236 +        Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
  1.2237 +        BrowserLocaleManager.getInstance().correctLocale(this, getResources(), newConfig);
  1.2238 +
  1.2239 +        // onConfigurationChanged is not called for 180 degree orientation changes,
  1.2240 +        // we will miss such rotations and the screen orientation will not be
  1.2241 +        // updated.
  1.2242 +        if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) {
  1.2243 +            if (mFormAssistPopup != null)
  1.2244 +                mFormAssistPopup.hide();
  1.2245 +            refreshChrome();
  1.2246 +        }
  1.2247 +        super.onConfigurationChanged(newConfig);
  1.2248 +    }
  1.2249 +
  1.2250 +    public String getContentProcessName() {
  1.2251 +        return AppConstants.MOZ_CHILD_PROCESS_NAME;
  1.2252 +    }
  1.2253 +
  1.2254 +    public void addEnvToIntent(Intent intent) {
  1.2255 +        Map<String,String> envMap = System.getenv();
  1.2256 +        Set<Map.Entry<String,String>> envSet = envMap.entrySet();
  1.2257 +        Iterator<Map.Entry<String,String>> envIter = envSet.iterator();
  1.2258 +        int c = 0;
  1.2259 +        while (envIter.hasNext()) {
  1.2260 +            Map.Entry<String,String> entry = envIter.next();
  1.2261 +            intent.putExtra("env" + c, entry.getKey() + "="
  1.2262 +                            + entry.getValue());
  1.2263 +            c++;
  1.2264 +        }
  1.2265 +    }
  1.2266 +
  1.2267 +    public void doRestart() {
  1.2268 +        doRestart(RESTARTER_ACTION, null);
  1.2269 +    }
  1.2270 +
  1.2271 +    public void doRestart(String args) {
  1.2272 +        doRestart(RESTARTER_ACTION, args);
  1.2273 +    }
  1.2274 +
  1.2275 +    public void doRestart(String action, String args) {
  1.2276 +        Log.d(LOGTAG, "doRestart(\"" + action + "\")");
  1.2277 +        try {
  1.2278 +            Intent intent = new Intent(action);
  1.2279 +            intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, RESTARTER_CLASS);
  1.2280 +            /* TODO: addEnvToIntent(intent); */
  1.2281 +            if (args != null)
  1.2282 +                intent.putExtra("args", args);
  1.2283 +            intent.putExtra("didRestart", true);
  1.2284 +            Log.d(LOGTAG, "Restart intent: " + intent.toString());
  1.2285 +            GeckoAppShell.killAnyZombies();
  1.2286 +            startActivity(intent);
  1.2287 +        } catch (Exception e) {
  1.2288 +            Log.e(LOGTAG, "Error effecting restart.", e);
  1.2289 +        }
  1.2290 +
  1.2291 +        finish();
  1.2292 +        // Give the restart process time to start before we die
  1.2293 +        GeckoAppShell.waitForAnotherGeckoProc();
  1.2294 +    }
  1.2295 +
  1.2296 +    public void handleNotification(String action, String alertName, String alertCookie) {
  1.2297 +        // If Gecko isn't running yet, we ignore the notification. Note that
  1.2298 +        // even if Gecko is running but it was restarted since the notification
  1.2299 +        // was created, the notification won't be handled (bug 849653).
  1.2300 +        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
  1.2301 +            GeckoAppShell.handleNotification(action, alertName, alertCookie);
  1.2302 +        }
  1.2303 +    }
  1.2304 +
  1.2305 +    private void checkMigrateProfile() {
  1.2306 +        final File profileDir = getProfile().getDir();
  1.2307 +
  1.2308 +        if (profileDir != null) {
  1.2309 +            ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2310 +                @Override
  1.2311 +                public void run() {
  1.2312 +                    Handler handler = new Handler();
  1.2313 +                    handler.postDelayed(new DeferredCleanupTask(), CLEANUP_DEFERRAL_SECONDS * 1000);
  1.2314 +                }
  1.2315 +            });
  1.2316 +        }
  1.2317 +    }
  1.2318 +
  1.2319 +    private class DeferredCleanupTask implements Runnable {
  1.2320 +        // The cleanup-version setting is recorded to avoid repeating the same
  1.2321 +        // tasks on subsequent startups; CURRENT_CLEANUP_VERSION may be updated
  1.2322 +        // if we need to do additional cleanup for future Gecko versions.
  1.2323 +
  1.2324 +        private static final String CLEANUP_VERSION = "cleanup-version";
  1.2325 +        private static final int CURRENT_CLEANUP_VERSION = 1;
  1.2326 +
  1.2327 +        @Override
  1.2328 +        public void run() {
  1.2329 +            long cleanupVersion = getSharedPreferences().getInt(CLEANUP_VERSION, 0);
  1.2330 +
  1.2331 +            if (cleanupVersion < 1) {
  1.2332 +                // Reduce device storage footprint by removing .ttf files from
  1.2333 +                // the res/fonts directory: we no longer need to copy our
  1.2334 +                // bundled fonts out of the APK in order to use them.
  1.2335 +                // See https://bugzilla.mozilla.org/show_bug.cgi?id=878674.
  1.2336 +                File dir = new File("res/fonts");
  1.2337 +                if (dir.exists() && dir.isDirectory()) {
  1.2338 +                    for (File file : dir.listFiles()) {
  1.2339 +                        if (file.isFile() && file.getName().endsWith(".ttf")) {
  1.2340 +                            Log.i(LOGTAG, "deleting " + file.toString());
  1.2341 +                            file.delete();
  1.2342 +                        }
  1.2343 +                    }
  1.2344 +                    if (!dir.delete()) {
  1.2345 +                        Log.w(LOGTAG, "unable to delete res/fonts directory (not empty?)");
  1.2346 +                    } else {
  1.2347 +                        Log.i(LOGTAG, "res/fonts directory deleted");
  1.2348 +                    }
  1.2349 +                }
  1.2350 +            }
  1.2351 +
  1.2352 +            // Additional cleanup needed for future versions would go here
  1.2353 +
  1.2354 +            if (cleanupVersion != CURRENT_CLEANUP_VERSION) {
  1.2355 +                SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit();
  1.2356 +                editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION);
  1.2357 +                editor.commit();
  1.2358 +            }
  1.2359 +        }
  1.2360 +    }
  1.2361 +
  1.2362 +    public PromptService getPromptService() {
  1.2363 +        return mPromptService;
  1.2364 +    }
  1.2365 +
  1.2366 +    @Override
  1.2367 +    public void onBackPressed() {
  1.2368 +        if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
  1.2369 +            super.onBackPressed();
  1.2370 +            return;
  1.2371 +        }
  1.2372 +
  1.2373 +        if (autoHideTabs()) {
  1.2374 +            return;
  1.2375 +        }
  1.2376 +
  1.2377 +        if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) {
  1.2378 +            mDoorHangerPopup.dismiss();
  1.2379 +            return;
  1.2380 +        }
  1.2381 +
  1.2382 +        if (mFullScreenPluginView != null) {
  1.2383 +            GeckoAppShell.onFullScreenPluginHidden(mFullScreenPluginView);
  1.2384 +            removeFullScreenPluginView(mFullScreenPluginView);
  1.2385 +            return;
  1.2386 +        }
  1.2387 +
  1.2388 +        if (mLayerView != null && mLayerView.isFullScreen()) {
  1.2389 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
  1.2390 +            return;
  1.2391 +        }
  1.2392 +
  1.2393 +        Tabs tabs = Tabs.getInstance();
  1.2394 +        Tab tab = tabs.getSelectedTab();
  1.2395 +        if (tab == null) {
  1.2396 +            moveTaskToBack(true);
  1.2397 +            return;
  1.2398 +        }
  1.2399 +
  1.2400 +        if (tab.doBack())
  1.2401 +            return;
  1.2402 +
  1.2403 +        if (tab.isExternal()) {
  1.2404 +            moveTaskToBack(true);
  1.2405 +            tabs.closeTab(tab);
  1.2406 +            return;
  1.2407 +        }
  1.2408 +
  1.2409 +        int parentId = tab.getParentId();
  1.2410 +        Tab parent = tabs.getTab(parentId);
  1.2411 +        if (parent != null) {
  1.2412 +            // The back button should always return to the parent (not a sibling).
  1.2413 +            tabs.closeTab(tab, parent);
  1.2414 +            return;
  1.2415 +        }
  1.2416 +
  1.2417 +        moveTaskToBack(true);
  1.2418 +    }
  1.2419 +
  1.2420 +    @Override
  1.2421 +    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  1.2422 +        if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) {
  1.2423 +            super.onActivityResult(requestCode, resultCode, data);
  1.2424 +        }
  1.2425 +    }
  1.2426 +
  1.2427 +    public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
  1.2428 +
  1.2429 +    // Accelerometer.
  1.2430 +    @Override
  1.2431 +    public void onAccuracyChanged(Sensor sensor, int accuracy) {
  1.2432 +    }
  1.2433 +
  1.2434 +    @Override
  1.2435 +    public void onSensorChanged(SensorEvent event) {
  1.2436 +        GeckoAppShell.sendEventToGecko(GeckoEvent.createSensorEvent(event));
  1.2437 +    }
  1.2438 +
  1.2439 +    // Geolocation.
  1.2440 +    @Override
  1.2441 +    public void onLocationChanged(Location location) {
  1.2442 +        // No logging here: user-identifying information.
  1.2443 +        GeckoAppShell.sendEventToGecko(GeckoEvent.createLocationEvent(location));
  1.2444 +        if (mShouldReportGeoData)
  1.2445 +            collectAndReportLocInfo(location);
  1.2446 +    }
  1.2447 +
  1.2448 +    public void setCurrentSignalStrenth(SignalStrength ss) {
  1.2449 +        if (ss.isGsm())
  1.2450 +            mSignalStrenth = ss.getGsmSignalStrength();
  1.2451 +    }
  1.2452 +
  1.2453 +    private int getCellInfo(JSONArray cellInfo) {
  1.2454 +        TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
  1.2455 +        if (tm == null)
  1.2456 +            return TelephonyManager.PHONE_TYPE_NONE;
  1.2457 +        List<NeighboringCellInfo> cells = tm.getNeighboringCellInfo();
  1.2458 +        CellLocation cl = tm.getCellLocation();
  1.2459 +        String mcc = "", mnc = "";
  1.2460 +        if (cl instanceof GsmCellLocation) {
  1.2461 +            JSONObject obj = new JSONObject();
  1.2462 +            GsmCellLocation gcl = (GsmCellLocation)cl;
  1.2463 +            try {
  1.2464 +                obj.put("lac", gcl.getLac());
  1.2465 +                obj.put("cid", gcl.getCid());
  1.2466 +
  1.2467 +                int psc = (Build.VERSION.SDK_INT >= 9) ? gcl.getPsc() : -1;
  1.2468 +                obj.put("psc", psc);
  1.2469 +
  1.2470 +                switch(tm.getNetworkType()) {
  1.2471 +                case TelephonyManager.NETWORK_TYPE_GPRS:
  1.2472 +                case TelephonyManager.NETWORK_TYPE_EDGE:
  1.2473 +                    obj.put("radio", "gsm");
  1.2474 +                    break;
  1.2475 +                case TelephonyManager.NETWORK_TYPE_UMTS:
  1.2476 +                case TelephonyManager.NETWORK_TYPE_HSDPA:
  1.2477 +                case TelephonyManager.NETWORK_TYPE_HSUPA:
  1.2478 +                case TelephonyManager.NETWORK_TYPE_HSPA:
  1.2479 +                case TelephonyManager.NETWORK_TYPE_HSPAP:
  1.2480 +                    obj.put("radio", "umts");
  1.2481 +                    break;
  1.2482 +                }
  1.2483 +                String mcc_mnc = tm.getNetworkOperator();
  1.2484 +                if (mcc_mnc.length() > 3) {
  1.2485 +                    mcc = mcc_mnc.substring(0, 3);
  1.2486 +                    mnc = mcc_mnc.substring(3);
  1.2487 +                    obj.put("mcc", mcc);
  1.2488 +                    obj.put("mnc", mnc);
  1.2489 +                }
  1.2490 +                obj.put("asu", mSignalStrenth);
  1.2491 +            } catch(JSONException jsonex) {}
  1.2492 +            cellInfo.put(obj);
  1.2493 +        }
  1.2494 +        if (cells != null) {
  1.2495 +            for (NeighboringCellInfo nci : cells) {
  1.2496 +                try {
  1.2497 +                    JSONObject obj = new JSONObject();
  1.2498 +                    obj.put("lac", nci.getLac());
  1.2499 +                    obj.put("cid", nci.getCid());
  1.2500 +                    obj.put("psc", nci.getPsc());
  1.2501 +                    obj.put("mcc", mcc);
  1.2502 +                    obj.put("mnc", mnc);
  1.2503 +
  1.2504 +                    int dbm;
  1.2505 +                    switch(nci.getNetworkType()) {
  1.2506 +                    case TelephonyManager.NETWORK_TYPE_GPRS:
  1.2507 +                    case TelephonyManager.NETWORK_TYPE_EDGE:
  1.2508 +                        obj.put("radio", "gsm");
  1.2509 +                        break;
  1.2510 +                    case TelephonyManager.NETWORK_TYPE_UMTS:
  1.2511 +                    case TelephonyManager.NETWORK_TYPE_HSDPA:
  1.2512 +                    case TelephonyManager.NETWORK_TYPE_HSUPA:
  1.2513 +                    case TelephonyManager.NETWORK_TYPE_HSPA:
  1.2514 +                    case TelephonyManager.NETWORK_TYPE_HSPAP:
  1.2515 +                        obj.put("radio", "umts");
  1.2516 +                        break;
  1.2517 +                    }
  1.2518 +
  1.2519 +                    obj.put("asu", nci.getRssi());
  1.2520 +                    cellInfo.put(obj);
  1.2521 +                } catch(JSONException jsonex) {}
  1.2522 +            }
  1.2523 +        }
  1.2524 +        return tm.getPhoneType();
  1.2525 +    }
  1.2526 +
  1.2527 +    private static boolean shouldLog(final ScanResult sr) {
  1.2528 +        return sr.SSID == null || !sr.SSID.endsWith("_nomap");
  1.2529 +    }
  1.2530 +
  1.2531 +    private void collectAndReportLocInfo(Location location) {
  1.2532 +        final JSONObject locInfo = new JSONObject();
  1.2533 +        WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
  1.2534 +        wm.startScan();
  1.2535 +        try {
  1.2536 +            JSONArray cellInfo = new JSONArray();
  1.2537 +
  1.2538 +            String radioType = getRadioTypeName(getCellInfo(cellInfo));
  1.2539 +            if (radioType != null) {
  1.2540 +                locInfo.put("radio", radioType);
  1.2541 +            }
  1.2542 +
  1.2543 +            locInfo.put("lon", location.getLongitude());
  1.2544 +            locInfo.put("lat", location.getLatitude());
  1.2545 +
  1.2546 +            // If we have an accuracy, round it up to the next meter.
  1.2547 +            if (location.hasAccuracy()) {
  1.2548 +                locInfo.put("accuracy", (int) Math.ceil(location.getAccuracy()));
  1.2549 +            }
  1.2550 +
  1.2551 +            // If we have an altitude, round it to the nearest meter.
  1.2552 +            if (location.hasAltitude()) {
  1.2553 +                locInfo.put("altitude", Math.round(location.getAltitude()));
  1.2554 +            }
  1.2555 +
  1.2556 +            // Reduce timestamp precision so as to expose less PII.
  1.2557 +            DateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
  1.2558 +            locInfo.put("time", df.format(new Date(location.getTime())));
  1.2559 +            locInfo.put("cell", cellInfo);
  1.2560 +
  1.2561 +            JSONArray wifiInfo = new JSONArray();
  1.2562 +            List<ScanResult> aps = wm.getScanResults();
  1.2563 +            if (aps != null) {
  1.2564 +                for (ScanResult ap : aps) {
  1.2565 +                    if (!shouldLog(ap))
  1.2566 +                        continue;
  1.2567 +
  1.2568 +                    JSONObject obj = new JSONObject();
  1.2569 +                    obj.put("key", ap.BSSID);
  1.2570 +                    obj.put("frequency", ap.frequency);
  1.2571 +                    obj.put("signal", ap.level);
  1.2572 +                    wifiInfo.put(obj);
  1.2573 +                }
  1.2574 +            }
  1.2575 +            locInfo.put("wifi", wifiInfo);
  1.2576 +        } catch (JSONException jsonex) {
  1.2577 +            Log.w(LOGTAG, "json exception", jsonex);
  1.2578 +            return;
  1.2579 +        }
  1.2580 +
  1.2581 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2582 +            public void run() {
  1.2583 +                try {
  1.2584 +                    URL url = new URL(LOCATION_URL);
  1.2585 +                    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
  1.2586 +                    try {
  1.2587 +                        urlConnection.setDoOutput(true);
  1.2588 +
  1.2589 +                        // Workaround for a bug in Android HttpURLConnection. When the library
  1.2590 +                        // reuses a stale connection, the connection may fail with an EOFException.
  1.2591 +                        if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT <= 18) {
  1.2592 +                            urlConnection.setRequestProperty("Connection", "Close");
  1.2593 +                        }
  1.2594 +
  1.2595 +                        JSONArray batch = new JSONArray();
  1.2596 +                        batch.put(locInfo);
  1.2597 +                        JSONObject wrapper = new JSONObject();
  1.2598 +                        wrapper.put("items", batch);
  1.2599 +                        byte[] bytes = wrapper.toString().getBytes();
  1.2600 +                        urlConnection.setFixedLengthStreamingMode(bytes.length);
  1.2601 +                        OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
  1.2602 +                        out.write(bytes);
  1.2603 +                        out.flush();
  1.2604 +                    } catch (JSONException jsonex) {
  1.2605 +                        Log.e(LOGTAG, "error wrapping data as a batch", jsonex);
  1.2606 +                    } catch (IOException ioex) {
  1.2607 +                        Log.e(LOGTAG, "error submitting data", ioex);
  1.2608 +                    } finally {
  1.2609 +                        urlConnection.disconnect();
  1.2610 +                    }
  1.2611 +                } catch (IOException ioex) {
  1.2612 +                    Log.e(LOGTAG, "error submitting data", ioex);
  1.2613 +                }
  1.2614 +            }
  1.2615 +        });
  1.2616 +    }
  1.2617 +
  1.2618 +    private static String getRadioTypeName(int phoneType) {
  1.2619 +        switch (phoneType) {
  1.2620 +            case TelephonyManager.PHONE_TYPE_CDMA:
  1.2621 +                return "cdma";
  1.2622 +
  1.2623 +            case TelephonyManager.PHONE_TYPE_GSM:
  1.2624 +                return "gsm";
  1.2625 +
  1.2626 +            case TelephonyManager.PHONE_TYPE_NONE:
  1.2627 +            case TelephonyManager.PHONE_TYPE_SIP:
  1.2628 +                // These devices have no radio.
  1.2629 +                return null;
  1.2630 +
  1.2631 +            default:
  1.2632 +                Log.e(LOGTAG, "", new IllegalArgumentException("Unexpected PHONE_TYPE: " + phoneType));
  1.2633 +                return null;
  1.2634 +        }
  1.2635 +    }
  1.2636 +
  1.2637 +    @Override
  1.2638 +    public void onProviderDisabled(String provider)
  1.2639 +    {
  1.2640 +    }
  1.2641 +
  1.2642 +    @Override
  1.2643 +    public void onProviderEnabled(String provider)
  1.2644 +    {
  1.2645 +    }
  1.2646 +
  1.2647 +    @Override
  1.2648 +    public void onStatusChanged(String provider, int status, Bundle extras)
  1.2649 +    {
  1.2650 +    }
  1.2651 +
  1.2652 +    // Called when a Gecko Hal WakeLock is changed
  1.2653 +    public void notifyWakeLockChanged(String topic, String state) {
  1.2654 +        PowerManager.WakeLock wl = mWakeLocks.get(topic);
  1.2655 +        if (state.equals("locked-foreground") && wl == null) {
  1.2656 +            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
  1.2657 +            wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, topic);
  1.2658 +            wl.acquire();
  1.2659 +            mWakeLocks.put(topic, wl);
  1.2660 +        } else if (!state.equals("locked-foreground") && wl != null) {
  1.2661 +            wl.release();
  1.2662 +            mWakeLocks.remove(topic);
  1.2663 +        }
  1.2664 +    }
  1.2665 +
  1.2666 +    public void notifyCheckUpdateResult(String result) {
  1.2667 +        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Update:CheckResult", result));
  1.2668 +    }
  1.2669 +
  1.2670 +    protected void geckoConnected() {
  1.2671 +        mLayerView.geckoConnected();
  1.2672 +        mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
  1.2673 +    }
  1.2674 +
  1.2675 +    public void setAccessibilityEnabled(boolean enabled) {
  1.2676 +    }
  1.2677 +
  1.2678 +    public static class MainLayout extends RelativeLayout {
  1.2679 +        private TouchEventInterceptor mTouchEventInterceptor;
  1.2680 +        private MotionEventInterceptor mMotionEventInterceptor;
  1.2681 +
  1.2682 +        public MainLayout(Context context, AttributeSet attrs) {
  1.2683 +            super(context, attrs);
  1.2684 +        }
  1.2685 +
  1.2686 +        public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
  1.2687 +            mTouchEventInterceptor = interceptor;
  1.2688 +        }
  1.2689 +
  1.2690 +        public void setMotionEventInterceptor(MotionEventInterceptor interceptor) {
  1.2691 +            mMotionEventInterceptor = interceptor;
  1.2692 +        }
  1.2693 +
  1.2694 +        @Override
  1.2695 +        public boolean onInterceptTouchEvent(MotionEvent event) {
  1.2696 +            if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) {
  1.2697 +                return true;
  1.2698 +            }
  1.2699 +            return super.onInterceptTouchEvent(event);
  1.2700 +        }
  1.2701 +
  1.2702 +        @Override
  1.2703 +        public boolean onTouchEvent(MotionEvent event) {
  1.2704 +            if (mTouchEventInterceptor != null && mTouchEventInterceptor.onTouch(this, event)) {
  1.2705 +                return true;
  1.2706 +            }
  1.2707 +            return super.onTouchEvent(event);
  1.2708 +        }
  1.2709 +
  1.2710 +        @Override
  1.2711 +        public boolean onGenericMotionEvent(MotionEvent event) {
  1.2712 +            if (mMotionEventInterceptor != null && mMotionEventInterceptor.onInterceptMotionEvent(this, event)) {
  1.2713 +                return true;
  1.2714 +            }
  1.2715 +            return super.onGenericMotionEvent(event);
  1.2716 +        }
  1.2717 +
  1.2718 +        @Override
  1.2719 +        public void setDrawingCacheEnabled(boolean enabled) {
  1.2720 +            // Instead of setting drawing cache in the view itself, we simply
  1.2721 +            // enable drawing caching on its children. This is mainly used in
  1.2722 +            // animations (see PropertyAnimator)
  1.2723 +            super.setChildrenDrawnWithCacheEnabled(enabled);
  1.2724 +        }
  1.2725 +    }
  1.2726 +
  1.2727 +    private class FullScreenHolder extends FrameLayout {
  1.2728 +
  1.2729 +        public FullScreenHolder(Context ctx) {
  1.2730 +            super(ctx);
  1.2731 +        }
  1.2732 +
  1.2733 +        @Override
  1.2734 +        public void addView(View view, int index) {
  1.2735 +            /**
  1.2736 +             * This normally gets called when Flash adds a separate SurfaceView
  1.2737 +             * for the video. It is unhappy if we have the LayerView underneath
  1.2738 +             * it for some reason so we need to hide that. Hiding the LayerView causes
  1.2739 +             * its surface to be destroyed, which causes a pause composition
  1.2740 +             * event to be sent to Gecko. We synchronously wait for that to be
  1.2741 +             * processed. Simultaneously, however, Flash is waiting on a mutex so
  1.2742 +             * the post() below is an attempt to avoid a deadlock.
  1.2743 +             */
  1.2744 +            super.addView(view, index);
  1.2745 +
  1.2746 +            ThreadUtils.postToUiThread(new Runnable() {
  1.2747 +                @Override
  1.2748 +                public void run() {
  1.2749 +                    mLayerView.hideSurface();
  1.2750 +                }
  1.2751 +            });
  1.2752 +        }
  1.2753 +
  1.2754 +        /**
  1.2755 +         * The methods below are simply copied from what Android WebKit does.
  1.2756 +         * It wasn't ever called in my testing, but might as well
  1.2757 +         * keep it in case it is for some reason. The methods
  1.2758 +         * all return true because we don't want any events
  1.2759 +         * leaking out from the fullscreen view.
  1.2760 +         */
  1.2761 +        @Override
  1.2762 +        public boolean onKeyDown(int keyCode, KeyEvent event) {
  1.2763 +            if (event.isSystem()) {
  1.2764 +                return super.onKeyDown(keyCode, event);
  1.2765 +            }
  1.2766 +            mFullScreenPluginView.onKeyDown(keyCode, event);
  1.2767 +            return true;
  1.2768 +        }
  1.2769 +
  1.2770 +        @Override
  1.2771 +        public boolean onKeyUp(int keyCode, KeyEvent event) {
  1.2772 +            if (event.isSystem()) {
  1.2773 +                return super.onKeyUp(keyCode, event);
  1.2774 +            }
  1.2775 +            mFullScreenPluginView.onKeyUp(keyCode, event);
  1.2776 +            return true;
  1.2777 +        }
  1.2778 +
  1.2779 +        @Override
  1.2780 +        public boolean onTouchEvent(MotionEvent event) {
  1.2781 +            return true;
  1.2782 +        }
  1.2783 +
  1.2784 +        @Override
  1.2785 +        public boolean onTrackballEvent(MotionEvent event) {
  1.2786 +            mFullScreenPluginView.onTrackballEvent(event);
  1.2787 +            return true;
  1.2788 +        }
  1.2789 +    }
  1.2790 +
  1.2791 +    protected NotificationClient makeNotificationClient() {
  1.2792 +        // Don't use a notification service; we may be killed in the background
  1.2793 +        // during downloads.
  1.2794 +        return new AppNotificationClient(getApplicationContext());
  1.2795 +    }
  1.2796 +
  1.2797 +    private int getVersionCode() {
  1.2798 +        int versionCode = 0;
  1.2799 +        try {
  1.2800 +            versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
  1.2801 +        } catch (NameNotFoundException e) {
  1.2802 +            Log.wtf(LOGTAG, getPackageName() + " not found", e);
  1.2803 +        }
  1.2804 +        return versionCode;
  1.2805 +    }
  1.2806 +
  1.2807 +    protected boolean getIsDebuggable() {
  1.2808 +        // Return false so Fennec doesn't appear to be debuggable.  WebappImpl
  1.2809 +        // then overrides this and returns the value of android:debuggable for
  1.2810 +        // the webapp APK, so webapps get the behavior supported by this method
  1.2811 +        // (i.e. automatic configuration and enabling of the remote debugger).
  1.2812 +        return false;
  1.2813 +
  1.2814 +        // If we ever want to expose this for Fennec, here's how we would do it:
  1.2815 +        // int flags = 0;
  1.2816 +        // try {
  1.2817 +        //     flags = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.flags;
  1.2818 +        // } catch (NameNotFoundException e) {
  1.2819 +        //     Log.wtf(LOGTAG, getPackageName() + " not found", e);
  1.2820 +        // }
  1.2821 +        // return (flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0;
  1.2822 +    }
  1.2823 +
  1.2824 +    // FHR reason code for a session end prior to a restart for a
  1.2825 +    // locale change.
  1.2826 +    private static final String SESSION_END_LOCALE_CHANGED = "L";
  1.2827 +
  1.2828 +    /**
  1.2829 +     * Use BrowserLocaleManager to change our persisted and current locales,
  1.2830 +     * and poke HealthRecorder to tell it of our changed state.
  1.2831 +     */
  1.2832 +    private void setLocale(final String locale) {
  1.2833 +        if (locale == null) {
  1.2834 +            return;
  1.2835 +        }
  1.2836 +        final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale);
  1.2837 +        if (resultant == null) {
  1.2838 +            return;
  1.2839 +        }
  1.2840 +
  1.2841 +        final boolean startNewSession = true;
  1.2842 +        final boolean shouldRestart = false;
  1.2843 +
  1.2844 +        // If the HealthRecorder is not yet initialized (unlikely), the locale change won't
  1.2845 +        // trigger a session transition and subsequent events will be recorded in an environment
  1.2846 +        // with the wrong locale.
  1.2847 +        final HealthRecorder rec = mHealthRecorder;
  1.2848 +        if (rec != null) {
  1.2849 +            rec.onAppLocaleChanged(resultant);
  1.2850 +            rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
  1.2851 +        }
  1.2852 +
  1.2853 +        if (!shouldRestart) {
  1.2854 +            ThreadUtils.postToUiThread(new Runnable() {
  1.2855 +                @Override
  1.2856 +                public void run() {
  1.2857 +                    GeckoApp.this.onLocaleReady(resultant);
  1.2858 +                }
  1.2859 +            });
  1.2860 +            return;
  1.2861 +        }
  1.2862 +
  1.2863 +        // Do this in the background so that the health recorder has its
  1.2864 +        // time to finish.
  1.2865 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2866 +            @Override
  1.2867 +            public void run() {
  1.2868 +                GeckoApp.this.doRestart();
  1.2869 +                GeckoApp.this.finish();
  1.2870 +            }
  1.2871 +        });
  1.2872 +    }
  1.2873 +
  1.2874 +    private void setSystemUiVisible(final boolean visible) {
  1.2875 +        if (Build.VERSION.SDK_INT < 14) {
  1.2876 +            return;
  1.2877 +        }
  1.2878 +
  1.2879 +        ThreadUtils.postToUiThread(new Runnable() {
  1.2880 +            @Override
  1.2881 +            public void run() {
  1.2882 +                if (visible) {
  1.2883 +                    mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
  1.2884 +                } else {
  1.2885 +                    mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
  1.2886 +                }
  1.2887 +            }
  1.2888 +        });
  1.2889 +    }
  1.2890 +
  1.2891 +    protected HealthRecorder createHealthRecorder(final Context context,
  1.2892 +                                                  final String profilePath,
  1.2893 +                                                  final EventDispatcher dispatcher,
  1.2894 +                                                  final String osLocale,
  1.2895 +                                                  final String appLocale,
  1.2896 +                                                  final SessionInformation previousSession) {
  1.2897 +        // GeckoApp does not need to record any health information - return a stub.
  1.2898 +        return new StubbedHealthRecorder();
  1.2899 +    }
  1.2900 +}

mercurial