mobile/android/base/BrowserApp.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/BrowserApp.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2748 @@
     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.File;
    1.12 +import java.io.FileNotFoundException;
    1.13 +import java.net.URLEncoder;
    1.14 +import java.util.EnumSet;
    1.15 +import java.util.Vector;
    1.16 +
    1.17 +import org.json.JSONArray;
    1.18 +import org.json.JSONException;
    1.19 +import org.json.JSONObject;
    1.20 +
    1.21 +import org.mozilla.gecko.DynamicToolbar.PinReason;
    1.22 +import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
    1.23 +import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
    1.24 +import org.mozilla.gecko.Telemetry;
    1.25 +import org.mozilla.gecko.TelemetryContract;
    1.26 +import org.mozilla.gecko.animation.PropertyAnimator;
    1.27 +import org.mozilla.gecko.animation.ViewHelper;
    1.28 +import org.mozilla.gecko.db.BrowserContract.Combined;
    1.29 +import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
    1.30 +import org.mozilla.gecko.db.BrowserDB;
    1.31 +import org.mozilla.gecko.favicons.Favicons;
    1.32 +import org.mozilla.gecko.favicons.LoadFaviconTask;
    1.33 +import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
    1.34 +import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
    1.35 +import org.mozilla.gecko.fxa.FirefoxAccounts;
    1.36 +import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
    1.37 +import org.mozilla.gecko.gfx.BitmapUtils;
    1.38 +import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
    1.39 +import org.mozilla.gecko.gfx.LayerMarginsAnimator;
    1.40 +import org.mozilla.gecko.gfx.LayerView;
    1.41 +import org.mozilla.gecko.health.BrowserHealthRecorder;
    1.42 +import org.mozilla.gecko.health.BrowserHealthReporter;
    1.43 +import org.mozilla.gecko.health.HealthRecorder;
    1.44 +import org.mozilla.gecko.health.SessionInformation;
    1.45 +import org.mozilla.gecko.home.BrowserSearch;
    1.46 +import org.mozilla.gecko.home.HomeBanner;
    1.47 +import org.mozilla.gecko.home.HomePanelsManager;
    1.48 +import org.mozilla.gecko.home.HomePager;
    1.49 +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
    1.50 +import org.mozilla.gecko.home.SearchEngine;
    1.51 +import org.mozilla.gecko.menu.GeckoMenu;
    1.52 +import org.mozilla.gecko.menu.GeckoMenuItem;
    1.53 +import org.mozilla.gecko.preferences.GeckoPreferences;
    1.54 +import org.mozilla.gecko.prompts.Prompt;
    1.55 +import org.mozilla.gecko.prompts.PromptListItem;
    1.56 +import org.mozilla.gecko.sync.setup.SyncAccounts;
    1.57 +import org.mozilla.gecko.toolbar.AutocompleteHandler;
    1.58 +import org.mozilla.gecko.toolbar.BrowserToolbar;
    1.59 +import org.mozilla.gecko.toolbar.ToolbarProgressView;
    1.60 +import org.mozilla.gecko.util.Clipboard;
    1.61 +import org.mozilla.gecko.util.GamepadUtils;
    1.62 +import org.mozilla.gecko.util.HardwareUtils;
    1.63 +import org.mozilla.gecko.util.MenuUtils;
    1.64 +import org.mozilla.gecko.util.StringUtils;
    1.65 +import org.mozilla.gecko.util.ThreadUtils;
    1.66 +import org.mozilla.gecko.util.UiAsyncTask;
    1.67 +import org.mozilla.gecko.widget.ButtonToast;
    1.68 +import org.mozilla.gecko.widget.GeckoActionProvider;
    1.69 +
    1.70 +import android.app.Activity;
    1.71 +import android.app.AlertDialog;
    1.72 +import android.content.ContentValues;
    1.73 +import android.content.Context;
    1.74 +import android.content.DialogInterface;
    1.75 +import android.content.Intent;
    1.76 +import android.content.SharedPreferences;
    1.77 +import android.content.res.Configuration;
    1.78 +import android.content.res.Resources;
    1.79 +import android.database.Cursor;
    1.80 +import android.graphics.Bitmap;
    1.81 +import android.graphics.Rect;
    1.82 +import android.graphics.drawable.BitmapDrawable;
    1.83 +import android.graphics.drawable.Drawable;
    1.84 +import android.net.Uri;
    1.85 +import android.nfc.NdefMessage;
    1.86 +import android.nfc.NdefRecord;
    1.87 +import android.nfc.NfcAdapter;
    1.88 +import android.nfc.NfcEvent;
    1.89 +import android.os.Build;
    1.90 +import android.os.Bundle;
    1.91 +import android.support.v4.app.FragmentManager;
    1.92 +import android.text.TextUtils;
    1.93 +import android.util.Log;
    1.94 +import android.view.InputDevice;
    1.95 +import android.view.KeyEvent;
    1.96 +import android.view.Menu;
    1.97 +import android.view.MenuInflater;
    1.98 +import android.view.MenuItem;
    1.99 +import android.view.MotionEvent;
   1.100 +import android.view.SubMenu;
   1.101 +import android.view.View;
   1.102 +import android.view.ViewGroup;
   1.103 +import android.view.ViewStub;
   1.104 +import android.view.ViewTreeObserver;
   1.105 +import android.view.Window;
   1.106 +import android.view.animation.Interpolator;
   1.107 +import android.widget.RelativeLayout;
   1.108 +import android.widget.ListView;
   1.109 +import android.widget.Toast;
   1.110 +import android.widget.ViewFlipper;
   1.111 +
   1.112 +abstract public class BrowserApp extends GeckoApp
   1.113 +                                 implements TabsPanel.TabsLayoutChangeListener,
   1.114 +                                            PropertyAnimator.PropertyAnimationListener,
   1.115 +                                            View.OnKeyListener,
   1.116 +                                            LayerView.OnMetricsChangedListener,
   1.117 +                                            BrowserSearch.OnSearchListener,
   1.118 +                                            BrowserSearch.OnEditSuggestionListener,
   1.119 +                                            HomePager.OnNewTabsListener,
   1.120 +                                            OnUrlOpenListener,
   1.121 +                                            ActionModeCompat.Presenter {
   1.122 +    private static final String LOGTAG = "GeckoBrowserApp";
   1.123 +
   1.124 +    private static final int TABS_ANIMATION_DURATION = 450;
   1.125 +
   1.126 +    private static final int READER_ADD_SUCCESS = 0;
   1.127 +    private static final int READER_ADD_FAILED = 1;
   1.128 +    private static final int READER_ADD_DUPLICATE = 2;
   1.129 +
   1.130 +    private static final String ADD_SHORTCUT_TOAST = "add_shortcut_toast";
   1.131 +    public static final String GUEST_BROWSING_ARG = "--guest";
   1.132 +
   1.133 +    private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding";
   1.134 +
   1.135 +    private static final String BROWSER_SEARCH_TAG = "browser_search";
   1.136 +    private BrowserSearch mBrowserSearch;
   1.137 +    private View mBrowserSearchContainer;
   1.138 +
   1.139 +    public ViewFlipper mViewFlipper;
   1.140 +    public ActionModeCompatView mActionBar;
   1.141 +    private BrowserToolbar mBrowserToolbar;
   1.142 +    private ToolbarProgressView mProgressView;
   1.143 +    private HomePager mHomePager;
   1.144 +    private TabsPanel mTabsPanel;
   1.145 +    private ViewGroup mHomePagerContainer;
   1.146 +    protected Telemetry.Timer mAboutHomeStartupTimer = null;
   1.147 +    private ActionModeCompat mActionMode;
   1.148 +    private boolean mShowActionModeEndAnimation = false;
   1.149 +
   1.150 +    private static final int GECKO_TOOLS_MENU = -1;
   1.151 +    private static final int ADDON_MENU_OFFSET = 1000;
   1.152 +    private static class MenuItemInfo {
   1.153 +        public int id;
   1.154 +        public String label;
   1.155 +        public String icon;
   1.156 +        public boolean checkable = false;
   1.157 +        public boolean checked = false;
   1.158 +        public boolean enabled = true;
   1.159 +        public boolean visible = true;
   1.160 +        public int parent;
   1.161 +        public boolean added = false;    // So we can re-add after a locale change.
   1.162 +    }
   1.163 +
   1.164 +    // The types of guest mdoe dialogs we show
   1.165 +    private static enum GuestModeDialog {
   1.166 +        ENTERING,
   1.167 +        LEAVING
   1.168 +    }
   1.169 +
   1.170 +    private Vector<MenuItemInfo> mAddonMenuItemsCache;
   1.171 +    private PropertyAnimator mMainLayoutAnimator;
   1.172 +
   1.173 +    private static final Interpolator sTabsInterpolator = new Interpolator() {
   1.174 +        @Override
   1.175 +        public float getInterpolation(float t) {
   1.176 +            t -= 1.0f;
   1.177 +            return t * t * t * t * t + 1.0f;
   1.178 +        }
   1.179 +    };
   1.180 +
   1.181 +    private FindInPageBar mFindInPageBar;
   1.182 +    private MediaCastingBar mMediaCastingBar;
   1.183 +
   1.184 +    // We'll ask for feedback after the user launches the app this many times.
   1.185 +    private static final int FEEDBACK_LAUNCH_COUNT = 15;
   1.186 +
   1.187 +    // Stored value of the toolbar height, so we know when it's changed.
   1.188 +    private int mToolbarHeight = 0;
   1.189 +
   1.190 +    // Stored value of whether the last metrics change allowed for toolbar
   1.191 +    // scrolling.
   1.192 +    private boolean mDynamicToolbarCanScroll = false;
   1.193 +
   1.194 +    private SharedPreferencesHelper mSharedPreferencesHelper;
   1.195 +
   1.196 +    private OrderedBroadcastHelper mOrderedBroadcastHelper;
   1.197 +
   1.198 +    private BrowserHealthReporter mBrowserHealthReporter;
   1.199 +
   1.200 +    // The tab to be selected on editing mode exit.
   1.201 +    private Integer mTargetTabForEditingMode = null;
   1.202 +
   1.203 +    // The animator used to toggle HomePager visibility has a race where if the HomePager is shown
   1.204 +    // (starting the animation), the HomePager is hidden, and the HomePager animation completes,
   1.205 +    // both the web content and the HomePager will be hidden. This flag is used to prevent the
   1.206 +    // race by determining if the web content should be hidden at the animation's end.
   1.207 +    private boolean mHideWebContentOnAnimationEnd = false;
   1.208 +
   1.209 +    private DynamicToolbar mDynamicToolbar = new DynamicToolbar();
   1.210 +
   1.211 +    @Override
   1.212 +    public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
   1.213 +        if (tab == null) {
   1.214 +            // Only RESTORED is allowed a null tab: it's the only event that
   1.215 +            // isn't tied to a specific tab.
   1.216 +            if (msg != Tabs.TabEvents.RESTORED) {
   1.217 +                throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
   1.218 +            }
   1.219 +            return;
   1.220 +        }
   1.221 +
   1.222 +        Log.d(LOGTAG, "BrowserApp.onTabChanged: " + tab.getId() + ": " + msg);
   1.223 +        switch(msg) {
   1.224 +            case LOCATION_CHANGE:
   1.225 +                if (Tabs.getInstance().isSelectedTab(tab)) {
   1.226 +                    maybeCancelFaviconLoad(tab);
   1.227 +                }
   1.228 +                // fall through
   1.229 +            case SELECTED:
   1.230 +                if (Tabs.getInstance().isSelectedTab(tab)) {
   1.231 +                    updateHomePagerForTab(tab);
   1.232 +
   1.233 +                    final TabsPanel.Panel panel = tab.isPrivate()
   1.234 +                                                ? TabsPanel.Panel.PRIVATE_TABS
   1.235 +                                                : TabsPanel.Panel.NORMAL_TABS;
   1.236 +
   1.237 +                    if (areTabsShown() && mTabsPanel.getCurrentPanel() != panel) {
   1.238 +                        showTabs(panel);
   1.239 +                    }
   1.240 +                }
   1.241 +                break;
   1.242 +            case START:
   1.243 +                if (Tabs.getInstance().isSelectedTab(tab)) {
   1.244 +                    invalidateOptionsMenu();
   1.245 +
   1.246 +                    if (mDynamicToolbar.isEnabled()) {
   1.247 +                        mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   1.248 +                    }
   1.249 +                }
   1.250 +                break;
   1.251 +            case LOAD_ERROR:
   1.252 +            case STOP:
   1.253 +            case MENU_UPDATED:
   1.254 +                if (Tabs.getInstance().isSelectedTab(tab)) {
   1.255 +                    invalidateOptionsMenu();
   1.256 +                }
   1.257 +                break;
   1.258 +            case PAGE_SHOW:
   1.259 +                loadFavicon(tab);
   1.260 +                break;
   1.261 +            case LINK_FAVICON:
   1.262 +                // If tab is not loading and the favicon is updated, we
   1.263 +                // want to load the image straight away. If tab is still
   1.264 +                // loading, we only load the favicon once the page's content
   1.265 +                // is fully loaded.
   1.266 +                if (tab.getState() != Tab.STATE_LOADING) {
   1.267 +                    loadFavicon(tab);
   1.268 +                }
   1.269 +                break;
   1.270 +        }
   1.271 +        super.onTabChanged(tab, msg, data);
   1.272 +    }
   1.273 +
   1.274 +    @Override
   1.275 +    public boolean onKey(View v, int keyCode, KeyEvent event) {
   1.276 +        // Global onKey handler. This is called if the focused UI doesn't
   1.277 +        // handle the key event, and before Gecko swallows the events.
   1.278 +        if (event.getAction() != KeyEvent.ACTION_DOWN) {
   1.279 +            return false;
   1.280 +        }
   1.281 +
   1.282 +        // Gamepad support only exists in API-level >= 9
   1.283 +        if (Build.VERSION.SDK_INT >= 9 &&
   1.284 +            (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
   1.285 +            switch (keyCode) {
   1.286 +                case KeyEvent.KEYCODE_BUTTON_Y:
   1.287 +                    // Toggle/focus the address bar on gamepad-y button.
   1.288 +                    if (mViewFlipper.getVisibility() == View.VISIBLE) {
   1.289 +                        if (mDynamicToolbar.isEnabled() && !isHomePagerVisible()) {
   1.290 +                            mDynamicToolbar.setVisible(false, VisibilityTransition.ANIMATE);
   1.291 +                            if (mLayerView != null) {
   1.292 +                                mLayerView.requestFocus();
   1.293 +                            }
   1.294 +                        } else {
   1.295 +                            // Just focus the address bar when about:home is visible
   1.296 +                            // or when the dynamic toolbar isn't enabled.
   1.297 +                            mBrowserToolbar.requestFocusFromTouch();
   1.298 +                        }
   1.299 +                    } else {
   1.300 +                        mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   1.301 +                        mBrowserToolbar.requestFocusFromTouch();
   1.302 +                    }
   1.303 +                    return true;
   1.304 +                case KeyEvent.KEYCODE_BUTTON_L1:
   1.305 +                    // Go back on L1
   1.306 +                    Tabs.getInstance().getSelectedTab().doBack();
   1.307 +                    return true;
   1.308 +                case KeyEvent.KEYCODE_BUTTON_R1:
   1.309 +                    // Go forward on R1
   1.310 +                    Tabs.getInstance().getSelectedTab().doForward();
   1.311 +                    return true;
   1.312 +            }
   1.313 +        }
   1.314 +
   1.315 +        // Check if this was a shortcut. Meta keys exists only on 11+.
   1.316 +        final Tab tab = Tabs.getInstance().getSelectedTab();
   1.317 +        if (Build.VERSION.SDK_INT >= 11 && tab != null && event.isCtrlPressed()) {
   1.318 +            switch (keyCode) {
   1.319 +                case KeyEvent.KEYCODE_LEFT_BRACKET:
   1.320 +                    tab.doBack();
   1.321 +                    return true;
   1.322 +
   1.323 +                case KeyEvent.KEYCODE_RIGHT_BRACKET:
   1.324 +                    tab.doForward();
   1.325 +                    return true;
   1.326 +
   1.327 +                case KeyEvent.KEYCODE_R:
   1.328 +                    tab.doReload();
   1.329 +                    return true;
   1.330 +
   1.331 +                case KeyEvent.KEYCODE_PERIOD:
   1.332 +                    tab.doStop();
   1.333 +                    return true;
   1.334 +
   1.335 +                case KeyEvent.KEYCODE_T:
   1.336 +                    addTab();
   1.337 +                    return true;
   1.338 +
   1.339 +                case KeyEvent.KEYCODE_W:
   1.340 +                    Tabs.getInstance().closeTab(tab);
   1.341 +                    return true;
   1.342 +
   1.343 +                case KeyEvent.KEYCODE_F:
   1.344 +                    mFindInPageBar.show();
   1.345 +                return true;
   1.346 +            }
   1.347 +        }
   1.348 +
   1.349 +        return false;
   1.350 +    }
   1.351 +
   1.352 +    @Override
   1.353 +    public boolean onKeyDown(int keyCode, KeyEvent event) {
   1.354 +        if (!mBrowserToolbar.isEditing() && onKey(null, keyCode, event)) {
   1.355 +            return true;
   1.356 +        }
   1.357 +        return super.onKeyDown(keyCode, event);
   1.358 +    }
   1.359 +
   1.360 +    void handleReaderListStatusRequest(final String url) {
   1.361 +        ThreadUtils.postToBackgroundThread(new Runnable() {
   1.362 +            @Override
   1.363 +            public void run() {
   1.364 +                final int inReadingList = BrowserDB.isReadingListItem(getContentResolver(), url) ? 1 : 0;
   1.365 +
   1.366 +                final JSONObject json = new JSONObject();
   1.367 +                try {
   1.368 +                    json.put("url", url);
   1.369 +                    json.put("inReadingList", inReadingList);
   1.370 +                } catch (JSONException e) {
   1.371 +                    Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
   1.372 +                    return;
   1.373 +                }
   1.374 +
   1.375 +                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
   1.376 +            }
   1.377 +        });
   1.378 +    }
   1.379 +
   1.380 +    private void handleReaderAdded(int result, final ContentValues values) {
   1.381 +        if (result != READER_ADD_SUCCESS) {
   1.382 +            if (result == READER_ADD_FAILED) {
   1.383 +                showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
   1.384 +            } else if (result == READER_ADD_DUPLICATE) {
   1.385 +                showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
   1.386 +            }
   1.387 +
   1.388 +            return;
   1.389 +        }
   1.390 +
   1.391 +        ThreadUtils.postToBackgroundThread(new Runnable() {
   1.392 +            @Override
   1.393 +            public void run() {
   1.394 +                BrowserDB.addReadingListItem(getContentResolver(), values);
   1.395 +                showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
   1.396 +            }
   1.397 +        });
   1.398 +    }
   1.399 +
   1.400 +    private ContentValues messageToReadingListContentValues(JSONObject message) {
   1.401 +        final ContentValues values = new ContentValues();
   1.402 +        values.put(ReadingListItems.URL, message.optString("url"));
   1.403 +        values.put(ReadingListItems.TITLE, message.optString("title"));
   1.404 +        values.put(ReadingListItems.LENGTH, message.optInt("length"));
   1.405 +        values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
   1.406 +        return values;
   1.407 +    }
   1.408 +
   1.409 +    void handleReaderRemoved(final String url) {
   1.410 +        ThreadUtils.postToBackgroundThread(new Runnable() {
   1.411 +            @Override
   1.412 +            public void run() {
   1.413 +                BrowserDB.removeReadingListItemWithURL(getContentResolver(), url);
   1.414 +                showToast(R.string.reading_list_removed, Toast.LENGTH_SHORT);
   1.415 +
   1.416 +                final int count = BrowserDB.getReadingListCount(getContentResolver());
   1.417 +                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(count)));
   1.418 +            }
   1.419 +        });
   1.420 +    }
   1.421 +
   1.422 +    @Override
   1.423 +    public void onCreate(Bundle savedInstanceState) {
   1.424 +        mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
   1.425 +
   1.426 +        final Intent intent = getIntent();
   1.427 +
   1.428 +        String args = intent.getStringExtra("args");
   1.429 +        if (args != null && args.contains(GUEST_BROWSING_ARG)) {
   1.430 +            mProfile = GeckoProfile.createGuestProfile(this);
   1.431 +        } else {
   1.432 +            GeckoProfile.maybeCleanupGuestProfile(this);
   1.433 +        }
   1.434 +
   1.435 +        // This has to be prepared prior to calling GeckoApp.onCreate, because
   1.436 +        // widget code and BrowserToolbar need it, and they're created by the
   1.437 +        // layout, which GeckoApp takes care of.
   1.438 +        ((GeckoApplication) getApplication()).prepareLightweightTheme();
   1.439 +        super.onCreate(savedInstanceState);
   1.440 +
   1.441 +        mViewFlipper = (ViewFlipper) findViewById(R.id.browser_actionbar);
   1.442 +        mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
   1.443 +
   1.444 +        mBrowserToolbar = (BrowserToolbar) findViewById(R.id.browser_toolbar);
   1.445 +        mProgressView = (ToolbarProgressView) findViewById(R.id.progress);
   1.446 +        mBrowserToolbar.setProgressBar(mProgressView);
   1.447 +        if (Intent.ACTION_VIEW.equals(intent.getAction())) {
   1.448 +            // Show the target URL immediately in the toolbar.
   1.449 +            mBrowserToolbar.setTitle(intent.getDataString());
   1.450 +
   1.451 +            Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
   1.452 +        }
   1.453 +
   1.454 +        ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener());
   1.455 +        ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
   1.456 +            @Override
   1.457 +            public boolean onInterceptMotionEvent(View view, MotionEvent event) {
   1.458 +                // If we get a gamepad panning MotionEvent while the focus is not on the layerview,
   1.459 +                // put the focus on the layerview and carry on
   1.460 +                if (mLayerView != null && !mLayerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
   1.461 +                    if (mHomePager == null) {
   1.462 +                        return false;
   1.463 +                    }
   1.464 +
   1.465 +                    if (isHomePagerVisible()) {
   1.466 +                        mLayerView.requestFocus();
   1.467 +                    } else {
   1.468 +                        mHomePager.requestFocus();
   1.469 +                    }
   1.470 +                }
   1.471 +                return false;
   1.472 +            }
   1.473 +        });
   1.474 +
   1.475 +        mHomePagerContainer = (ViewGroup) findViewById(R.id.home_pager_container);
   1.476 +
   1.477 +        mBrowserSearchContainer = findViewById(R.id.search_container);
   1.478 +        mBrowserSearch = (BrowserSearch) getSupportFragmentManager().findFragmentByTag(BROWSER_SEARCH_TAG);
   1.479 +        if (mBrowserSearch == null) {
   1.480 +            mBrowserSearch = BrowserSearch.newInstance();
   1.481 +            mBrowserSearch.setUserVisibleHint(false);
   1.482 +        }
   1.483 +
   1.484 +        setBrowserToolbarListeners();
   1.485 +
   1.486 +        mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
   1.487 +        mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
   1.488 +
   1.489 +        registerEventListener("CharEncoding:Data");
   1.490 +        registerEventListener("CharEncoding:State");
   1.491 +        registerEventListener("Feedback:LastUrl");
   1.492 +        registerEventListener("Feedback:OpenPlayStore");
   1.493 +        registerEventListener("Feedback:MaybeLater");
   1.494 +        registerEventListener("Telemetry:Gather");
   1.495 +        registerEventListener("Settings:Show");
   1.496 +        registerEventListener("Updater:Launch");
   1.497 +        registerEventListener("Menu:Add");
   1.498 +        registerEventListener("Menu:Remove");
   1.499 +        registerEventListener("Menu:Update");
   1.500 +        registerEventListener("Accounts:Create");
   1.501 +        registerEventListener("Accounts:Exist");
   1.502 +        registerEventListener("Prompt:ShowTop");
   1.503 +
   1.504 +        Distribution.init(this);
   1.505 +        JavaAddonManager.getInstance().init(getApplicationContext());
   1.506 +        mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
   1.507 +        mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
   1.508 +        mBrowserHealthReporter = new BrowserHealthReporter();
   1.509 +
   1.510 +        if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
   1.511 +            NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
   1.512 +            if (nfc != null) {
   1.513 +                nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
   1.514 +                    @Override
   1.515 +                    public NdefMessage createNdefMessage(NfcEvent event) {
   1.516 +                        Tab tab = Tabs.getInstance().getSelectedTab();
   1.517 +                        if (tab == null || tab.isPrivate()) {
   1.518 +                            return null;
   1.519 +                        }
   1.520 +                        return new NdefMessage(new NdefRecord[] { NdefRecord.createUri(tab.getURL()) });
   1.521 +                    }
   1.522 +                }, this);
   1.523 +            }
   1.524 +        }
   1.525 +
   1.526 +        if (savedInstanceState != null) {
   1.527 +            mDynamicToolbar.onRestoreInstanceState(savedInstanceState);
   1.528 +            mHomePagerContainer.setPadding(0, savedInstanceState.getInt(STATE_ABOUT_HOME_TOP_PADDING), 0, 0);
   1.529 +        }
   1.530 +
   1.531 +        mDynamicToolbar.setEnabledChangedListener(new DynamicToolbar.OnEnabledChangedListener() {
   1.532 +            @Override
   1.533 +            public void onEnabledChanged(boolean enabled) {
   1.534 +                setDynamicToolbarEnabled(enabled);
   1.535 +            }
   1.536 +        });
   1.537 +
   1.538 +        // Set the maximum bits-per-pixel the favicon system cares about.
   1.539 +        IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
   1.540 +    }
   1.541 +
   1.542 +    @Override
   1.543 +    public void onBackPressed() {
   1.544 +        if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
   1.545 +            super.onBackPressed();
   1.546 +            return;
   1.547 +        }
   1.548 +
   1.549 +        if (mBrowserToolbar.onBackPressed()) {
   1.550 +            return;
   1.551 +        }
   1.552 +
   1.553 +        if (mActionMode != null) {
   1.554 +            endActionModeCompat();
   1.555 +            return;
   1.556 +        }
   1.557 +
   1.558 +        super.onBackPressed();
   1.559 +    }
   1.560 +
   1.561 +    @Override
   1.562 +    public void onResume() {
   1.563 +        super.onResume();
   1.564 +        unregisterEventListener("Prompt:ShowTop");
   1.565 +    }
   1.566 +
   1.567 +    @Override
   1.568 +    public void onPause() {
   1.569 +        super.onPause();
   1.570 +        // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
   1.571 +        registerEventListener("Prompt:ShowTop");
   1.572 +    }
   1.573 +
   1.574 +    private void setBrowserToolbarListeners() {
   1.575 +        mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() {
   1.576 +            public void onActivate() {
   1.577 +                enterEditingMode();
   1.578 +            }
   1.579 +        });
   1.580 +
   1.581 +        mBrowserToolbar.setOnCommitListener(new BrowserToolbar.OnCommitListener() {
   1.582 +            public void onCommit() {
   1.583 +                commitEditingMode();
   1.584 +            }
   1.585 +        });
   1.586 +
   1.587 +        mBrowserToolbar.setOnDismissListener(new BrowserToolbar.OnDismissListener() {
   1.588 +            public void onDismiss() {
   1.589 +                mBrowserToolbar.cancelEdit();
   1.590 +            }
   1.591 +        });
   1.592 +
   1.593 +        mBrowserToolbar.setOnFilterListener(new BrowserToolbar.OnFilterListener() {
   1.594 +            public void onFilter(String searchText, AutocompleteHandler handler) {
   1.595 +                filterEditingMode(searchText, handler);
   1.596 +            }
   1.597 +        });
   1.598 +
   1.599 +        mBrowserToolbar.setOnFocusChangeListener(new View.OnFocusChangeListener() {
   1.600 +            @Override
   1.601 +            public void onFocusChange(View v, boolean hasFocus) {
   1.602 +                if (isHomePagerVisible()) {
   1.603 +                    mHomePager.onToolbarFocusChange(hasFocus);
   1.604 +                }
   1.605 +            }
   1.606 +        });
   1.607 +
   1.608 +        mBrowserToolbar.setOnStartEditingListener(new BrowserToolbar.OnStartEditingListener() {
   1.609 +            public void onStartEditing() {
   1.610 +                // Temporarily disable doorhanger notifications.
   1.611 +                mDoorHangerPopup.disable();
   1.612 +            }
   1.613 +        });
   1.614 +
   1.615 +        mBrowserToolbar.setOnStopEditingListener(new BrowserToolbar.OnStopEditingListener() {
   1.616 +            public void onStopEditing() {
   1.617 +                selectTargetTabForEditingMode();
   1.618 +
   1.619 +                // Since the underlying LayerView is set visible in hideHomePager, we would
   1.620 +                // ordinarily want to call it first. However, hideBrowserSearch changes the
   1.621 +                // visibility of the HomePager and hideHomePager will take no action if the
   1.622 +                // HomePager is hidden, so we want to call hideBrowserSearch to restore the
   1.623 +                // HomePager visibility first.
   1.624 +                hideBrowserSearch();
   1.625 +                hideHomePager();
   1.626 +
   1.627 +                // Re-enable doorhanger notifications. They may trigger on the selected tab above.
   1.628 +                mDoorHangerPopup.enable();
   1.629 +            }
   1.630 +        });
   1.631 +
   1.632 +        // Intercept key events for gamepad shortcuts
   1.633 +        mBrowserToolbar.setOnKeyListener(this);
   1.634 +    }
   1.635 +
   1.636 +    private void showBookmarkDialog() {
   1.637 +        final Tab tab = Tabs.getInstance().getSelectedTab();
   1.638 +        final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
   1.639 +            @Override
   1.640 +            public void onPromptFinished(String result) {
   1.641 +                int itemId = -1;
   1.642 +                try {
   1.643 +                  itemId = new JSONObject(result).getInt("button");
   1.644 +                } catch(JSONException ex) {
   1.645 +                    Log.e(LOGTAG, "Exception reading bookmark prompt result", ex);
   1.646 +                }
   1.647 +
   1.648 +                if (tab == null)
   1.649 +                    return;
   1.650 +
   1.651 +                if (itemId == 0) {
   1.652 +                    new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
   1.653 +                } else if (itemId == 1) {
   1.654 +                    String url = tab.getURL();
   1.655 +                    String title = tab.getDisplayTitle();
   1.656 +                    Bitmap favicon = tab.getFavicon();
   1.657 +                    if (url != null && title != null) {
   1.658 +                        GeckoAppShell.createShortcut(title, url, url, favicon, "");
   1.659 +                    }
   1.660 +                }
   1.661 +            }
   1.662 +        });
   1.663 +
   1.664 +        final PromptListItem[] items = new PromptListItem[2];
   1.665 +        Resources res = getResources();
   1.666 +        items[0] = new PromptListItem(res.getString(R.string.contextmenu_edit_bookmark));
   1.667 +        items[1] = new PromptListItem(res.getString(R.string.contextmenu_add_to_launcher));
   1.668 +
   1.669 +        ps.show("", "", items, ListView.CHOICE_MODE_NONE);
   1.670 +    }
   1.671 +
   1.672 +    private void setDynamicToolbarEnabled(boolean enabled) {
   1.673 +        ThreadUtils.assertOnUiThread();
   1.674 +
   1.675 +        if (enabled) {
   1.676 +            if (mLayerView != null) {
   1.677 +                mLayerView.setOnMetricsChangedListener(this);
   1.678 +            }
   1.679 +            setToolbarMargin(0);
   1.680 +            mHomePagerContainer.setPadding(0, mViewFlipper.getHeight(), 0, 0);
   1.681 +        } else {
   1.682 +            // Immediately show the toolbar when disabling the dynamic
   1.683 +            // toolbar.
   1.684 +            if (mLayerView != null) {
   1.685 +                mLayerView.setOnMetricsChangedListener(null);
   1.686 +            }
   1.687 +            mHomePagerContainer.setPadding(0, 0, 0, 0);
   1.688 +            if (mViewFlipper != null) {
   1.689 +                ViewHelper.setTranslationY(mViewFlipper, 0);
   1.690 +            }
   1.691 +        }
   1.692 +
   1.693 +        refreshToolbarHeight();
   1.694 +    }
   1.695 +
   1.696 +    private static boolean isAboutHome(final Tab tab) {
   1.697 +        return AboutPages.isAboutHome(tab.getURL());
   1.698 +    }
   1.699 +
   1.700 +    @Override
   1.701 +    public boolean onSearchRequested() {
   1.702 +        enterEditingMode();
   1.703 +        return true;
   1.704 +    }
   1.705 +
   1.706 +    @Override
   1.707 +    public boolean onContextItemSelected(MenuItem item) {
   1.708 +        final int itemId = item.getItemId();
   1.709 +        if (itemId == R.id.pasteandgo) {
   1.710 +            String text = Clipboard.getText();
   1.711 +            if (!TextUtils.isEmpty(text)) {
   1.712 +                Tabs.getInstance().loadUrl(text);
   1.713 +                Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
   1.714 +                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "pasteandgo");
   1.715 +            }
   1.716 +            return true;
   1.717 +        }
   1.718 +
   1.719 +        if (itemId == R.id.site_settings) {
   1.720 +            // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
   1.721 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Permissions:Get", null));
   1.722 +            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
   1.723 +                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "site_settings");
   1.724 +            }
   1.725 +            return true;
   1.726 +        }
   1.727 +
   1.728 +        if (itemId == R.id.paste) {
   1.729 +            String text = Clipboard.getText();
   1.730 +            if (!TextUtils.isEmpty(text)) {
   1.731 +                enterEditingMode(text);
   1.732 +                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "paste");
   1.733 +            }
   1.734 +            return true;
   1.735 +        }
   1.736 +
   1.737 +        if (itemId == R.id.share) {
   1.738 +            shareCurrentUrl();
   1.739 +            return true;
   1.740 +        }
   1.741 +
   1.742 +        if (itemId == R.id.subscribe) {
   1.743 +            // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
   1.744 +            Tab tab = Tabs.getInstance().getSelectedTab();
   1.745 +            if (tab != null && tab.hasFeeds()) {
   1.746 +                JSONObject args = new JSONObject();
   1.747 +                try {
   1.748 +                    args.put("tabId", tab.getId());
   1.749 +                } catch (JSONException e) {
   1.750 +                    Log.e(LOGTAG, "error building json arguments");
   1.751 +                }
   1.752 +                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
   1.753 +                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
   1.754 +                    Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "subscribe");
   1.755 +                }
   1.756 +            }
   1.757 +            return true;
   1.758 +        }
   1.759 +
   1.760 +        if (itemId == R.id.add_search_engine) {
   1.761 +            // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
   1.762 +            Tab tab = Tabs.getInstance().getSelectedTab();
   1.763 +            if (tab != null && tab.hasOpenSearch()) {
   1.764 +                JSONObject args = new JSONObject();
   1.765 +                try {
   1.766 +                    args.put("tabId", tab.getId());
   1.767 +                } catch (JSONException e) {
   1.768 +                    Log.e(LOGTAG, "error building json arguments");
   1.769 +                    return true;
   1.770 +                }
   1.771 +                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Add", args.toString()));
   1.772 +
   1.773 +                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
   1.774 +                    Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "add_search_engine");
   1.775 +                }
   1.776 +            }
   1.777 +            return true;
   1.778 +        }
   1.779 +
   1.780 +        if (itemId == R.id.copyurl) {
   1.781 +            Tab tab = Tabs.getInstance().getSelectedTab();
   1.782 +            if (tab != null) {
   1.783 +                String url = tab.getURL();
   1.784 +                if (url != null) {
   1.785 +                    Clipboard.setText(url);
   1.786 +                    Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "copyurl");
   1.787 +                }
   1.788 +            }
   1.789 +            return true;
   1.790 +        }
   1.791 +
   1.792 +        if (itemId == R.id.add_to_launcher) {
   1.793 +            Tab tab = Tabs.getInstance().getSelectedTab();
   1.794 +            if (tab == null) {
   1.795 +                return true;
   1.796 +            }
   1.797 +
   1.798 +            final String url = tab.getURL();
   1.799 +            final String title = tab.getDisplayTitle();
   1.800 +            if (url == null || title == null) {
   1.801 +                return true;
   1.802 +            }
   1.803 +
   1.804 +            final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
   1.805 +            Favicons.getSizedFavicon(url,
   1.806 +                    tab.getFaviconURL(),
   1.807 +                    Integer.MAX_VALUE,
   1.808 +                    LoadFaviconTask.FLAG_PERSIST,
   1.809 +                    listener);
   1.810 +            return true;
   1.811 +        }
   1.812 +
   1.813 +        return false;
   1.814 +    }
   1.815 +
   1.816 +    @Override
   1.817 +    public void setAccessibilityEnabled(boolean enabled) {
   1.818 +        mDynamicToolbar.setAccessibilityEnabled(enabled);
   1.819 +    }
   1.820 +
   1.821 +    @Override
   1.822 +    public void onDestroy() {
   1.823 +        mDynamicToolbar.destroy();
   1.824 +
   1.825 +        if (mBrowserToolbar != null)
   1.826 +            mBrowserToolbar.onDestroy();
   1.827 +
   1.828 +        if (mFindInPageBar != null) {
   1.829 +            mFindInPageBar.onDestroy();
   1.830 +            mFindInPageBar = null;
   1.831 +        }
   1.832 +
   1.833 +        if (mMediaCastingBar != null) {
   1.834 +            mMediaCastingBar.onDestroy();
   1.835 +            mMediaCastingBar = null;
   1.836 +        }
   1.837 +
   1.838 +        if (mSharedPreferencesHelper != null) {
   1.839 +            mSharedPreferencesHelper.uninit();
   1.840 +            mSharedPreferencesHelper = null;
   1.841 +        }
   1.842 +
   1.843 +        if (mOrderedBroadcastHelper != null) {
   1.844 +            mOrderedBroadcastHelper.uninit();
   1.845 +            mOrderedBroadcastHelper = null;
   1.846 +        }
   1.847 +
   1.848 +        if (mBrowserHealthReporter != null) {
   1.849 +            mBrowserHealthReporter.uninit();
   1.850 +            mBrowserHealthReporter = null;
   1.851 +        }
   1.852 +
   1.853 +        unregisterEventListener("CharEncoding:Data");
   1.854 +        unregisterEventListener("CharEncoding:State");
   1.855 +        unregisterEventListener("Feedback:LastUrl");
   1.856 +        unregisterEventListener("Feedback:OpenPlayStore");
   1.857 +        unregisterEventListener("Feedback:MaybeLater");
   1.858 +        unregisterEventListener("Telemetry:Gather");
   1.859 +        unregisterEventListener("Settings:Show");
   1.860 +        unregisterEventListener("Updater:Launch");
   1.861 +        unregisterEventListener("Menu:Add");
   1.862 +        unregisterEventListener("Menu:Remove");
   1.863 +        unregisterEventListener("Menu:Update");
   1.864 +        unregisterEventListener("Accounts:Create");
   1.865 +        unregisterEventListener("Accounts:Exist");
   1.866 +
   1.867 +        if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
   1.868 +            NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
   1.869 +            if (nfc != null) {
   1.870 +                // null this out even though the docs say it's not needed,
   1.871 +                // because the source code looks like it will only do this
   1.872 +                // automatically on API 14+
   1.873 +                nfc.setNdefPushMessageCallback(null, this);
   1.874 +            }
   1.875 +        }
   1.876 +
   1.877 +        super.onDestroy();
   1.878 +    }
   1.879 +
   1.880 +    @Override
   1.881 +    protected void initializeChrome() {
   1.882 +        super.initializeChrome();
   1.883 +
   1.884 +        mDoorHangerPopup.setAnchor(mBrowserToolbar.getDoorHangerAnchor());
   1.885 +
   1.886 +        mDynamicToolbar.setLayerView(mLayerView);
   1.887 +        setDynamicToolbarEnabled(mDynamicToolbar.isEnabled());
   1.888 +
   1.889 +        // Intercept key events for gamepad shortcuts
   1.890 +        mLayerView.setOnKeyListener(this);
   1.891 +
   1.892 +        // Initialize the actionbar menu items on startup for both large and small tablets
   1.893 +        if (HardwareUtils.isTablet()) {
   1.894 +            onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
   1.895 +            invalidateOptionsMenu();
   1.896 +        }
   1.897 +    }
   1.898 +
   1.899 +    private void shareCurrentUrl() {
   1.900 +        Tab tab = Tabs.getInstance().getSelectedTab();
   1.901 +        if (tab == null) {
   1.902 +            return;
   1.903 +        }
   1.904 +
   1.905 +        String url = tab.getURL();
   1.906 +        if (url == null) {
   1.907 +            return;
   1.908 +        }
   1.909 +
   1.910 +        if (AboutPages.isAboutReader(url)) {
   1.911 +            url = ReaderModeUtils.getUrlFromAboutReader(url);
   1.912 +        }
   1.913 +
   1.914 +        GeckoAppShell.openUriExternal(url, "text/plain", "", "",
   1.915 +                                      Intent.ACTION_SEND, tab.getDisplayTitle());
   1.916 +
   1.917 +        // Context: Sharing via chrome list (no explicit session is active)
   1.918 +        Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
   1.919 +    }
   1.920 +
   1.921 +    @Override
   1.922 +    protected void loadStartupTab(String url) {
   1.923 +        // We aren't showing about:home, so cancel the telemetry timer
   1.924 +        if (url != null || mShouldRestore) {
   1.925 +            mAboutHomeStartupTimer.cancel();
   1.926 +        }
   1.927 +
   1.928 +        super.loadStartupTab(url);
   1.929 +    }
   1.930 +
   1.931 +    private void setToolbarMargin(int margin) {
   1.932 +        ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
   1.933 +        mGeckoLayout.requestLayout();
   1.934 +    }
   1.935 +
   1.936 +    @Override
   1.937 +    public void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
   1.938 +        if (isHomePagerVisible() || mViewFlipper == null) {
   1.939 +            return;
   1.940 +        }
   1.941 +
   1.942 +        // If the page has shrunk so that the toolbar no longer scrolls, make
   1.943 +        // sure the toolbar is visible.
   1.944 +        if (aMetrics.getPageHeight() <= aMetrics.getHeight()) {
   1.945 +            if (mDynamicToolbarCanScroll) {
   1.946 +                mDynamicToolbarCanScroll = false;
   1.947 +                if (mViewFlipper.getVisibility() != View.VISIBLE) {
   1.948 +                    ThreadUtils.postToUiThread(new Runnable() {
   1.949 +                        public void run() {
   1.950 +                            mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   1.951 +                        }
   1.952 +                    });
   1.953 +                }
   1.954 +            }
   1.955 +        } else {
   1.956 +            mDynamicToolbarCanScroll = true;
   1.957 +        }
   1.958 +
   1.959 +        final View toolbarLayout = mViewFlipper;
   1.960 +        final int marginTop = Math.round(aMetrics.marginTop);
   1.961 +        ThreadUtils.postToUiThread(new Runnable() {
   1.962 +            public void run() {
   1.963 +                final float translationY = marginTop - toolbarLayout.getHeight();
   1.964 +                ViewHelper.setTranslationY(toolbarLayout, translationY);
   1.965 +                ViewHelper.setTranslationY(mProgressView, translationY);
   1.966 +
   1.967 +                if (mDoorHangerPopup.isShowing()) {
   1.968 +                    mDoorHangerPopup.updatePopup();
   1.969 +                }
   1.970 +            }
   1.971 +        });
   1.972 +
   1.973 +        if (mFormAssistPopup != null)
   1.974 +            mFormAssistPopup.onMetricsChanged(aMetrics);
   1.975 +    }
   1.976 +
   1.977 +    @Override
   1.978 +    public void onPanZoomStopped() {
   1.979 +        if (!mDynamicToolbar.isEnabled() || isHomePagerVisible()) {
   1.980 +            return;
   1.981 +        }
   1.982 +
   1.983 +        // Make sure the toolbar is fully hidden or fully shown when the user
   1.984 +        // lifts their finger. If the page is shorter than the viewport, the
   1.985 +        // toolbar is always shown.
   1.986 +        ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
   1.987 +        if (metrics.getPageHeight() < metrics.getHeight()
   1.988 +              || metrics.marginTop >= mToolbarHeight / 2) {
   1.989 +            mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   1.990 +        } else {
   1.991 +            mDynamicToolbar.setVisible(false, VisibilityTransition.ANIMATE);
   1.992 +        }
   1.993 +    }
   1.994 +
   1.995 +    public void refreshToolbarHeight() {
   1.996 +        ThreadUtils.assertOnUiThread();
   1.997 +
   1.998 +        int height = 0;
   1.999 +        if (mViewFlipper != null) {
  1.1000 +            height = mViewFlipper.getHeight();
  1.1001 +        }
  1.1002 +
  1.1003 +        if (!mDynamicToolbar.isEnabled() || isHomePagerVisible()) {
  1.1004 +            // Use aVisibleHeight here so that when the dynamic toolbar is
  1.1005 +            // enabled, the padding will animate with the toolbar becoming
  1.1006 +            // visible.
  1.1007 +            if (mDynamicToolbar.isEnabled()) {
  1.1008 +                // When the dynamic toolbar is enabled, set the padding on the
  1.1009 +                // about:home widget directly - this is to avoid resizing the
  1.1010 +                // LayerView, which can cause visible artifacts.
  1.1011 +                mHomePagerContainer.setPadding(0, height, 0, 0);
  1.1012 +            } else {
  1.1013 +                setToolbarMargin(height);
  1.1014 +                height = 0;
  1.1015 +            }
  1.1016 +        } else {
  1.1017 +            setToolbarMargin(0);
  1.1018 +        }
  1.1019 +
  1.1020 +        if (mLayerView != null && height != mToolbarHeight) {
  1.1021 +            mToolbarHeight = height;
  1.1022 +            mLayerView.getLayerMarginsAnimator().setMaxMargins(0, height, 0, 0);
  1.1023 +            mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
  1.1024 +        }
  1.1025 +    }
  1.1026 +
  1.1027 +    @Override
  1.1028 +    void toggleChrome(final boolean aShow) {
  1.1029 +        ThreadUtils.postToUiThread(new Runnable() {
  1.1030 +            @Override
  1.1031 +            public void run() {
  1.1032 +                if (aShow) {
  1.1033 +                    mViewFlipper.setVisibility(View.VISIBLE);
  1.1034 +                } else {
  1.1035 +                    mViewFlipper.setVisibility(View.GONE);
  1.1036 +                    if (hasTabsSideBar()) {
  1.1037 +                        hideTabs();
  1.1038 +                    }
  1.1039 +                }
  1.1040 +            }
  1.1041 +        });
  1.1042 +
  1.1043 +        super.toggleChrome(aShow);
  1.1044 +    }
  1.1045 +
  1.1046 +    @Override
  1.1047 +    void focusChrome() {
  1.1048 +        ThreadUtils.postToUiThread(new Runnable() {
  1.1049 +            @Override
  1.1050 +            public void run() {
  1.1051 +                mViewFlipper.setVisibility(View.VISIBLE);
  1.1052 +                mViewFlipper.requestFocusFromTouch();
  1.1053 +            }
  1.1054 +        });
  1.1055 +    }
  1.1056 +
  1.1057 +    @Override
  1.1058 +    public void refreshChrome() {
  1.1059 +        invalidateOptionsMenu();
  1.1060 +
  1.1061 +        if (mTabsPanel != null) {
  1.1062 +            updateSideBarState();
  1.1063 +            mTabsPanel.refresh();
  1.1064 +        }
  1.1065 +
  1.1066 +        mBrowserToolbar.refresh();
  1.1067 +    }
  1.1068 +
  1.1069 +    @Override
  1.1070 +    public boolean hasTabsSideBar() {
  1.1071 +        return (mTabsPanel != null && mTabsPanel.isSideBar());
  1.1072 +    }
  1.1073 +
  1.1074 +    private void updateSideBarState() {
  1.1075 +        if (mMainLayoutAnimator != null)
  1.1076 +            mMainLayoutAnimator.stop();
  1.1077 +
  1.1078 +        boolean isSideBar = (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
  1.1079 +        final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width);
  1.1080 +
  1.1081 +        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams();
  1.1082 +        lp.width = (isSideBar ? sidebarWidth : ViewGroup.LayoutParams.FILL_PARENT);
  1.1083 +        mTabsPanel.requestLayout();
  1.1084 +
  1.1085 +        final boolean sidebarIsShown = (isSideBar && mTabsPanel.isShown());
  1.1086 +        final int mainLayoutScrollX = (sidebarIsShown ? -sidebarWidth : 0);
  1.1087 +        mMainLayout.scrollTo(mainLayoutScrollX, 0);
  1.1088 +
  1.1089 +        mTabsPanel.setIsSideBar(isSideBar);
  1.1090 +    }
  1.1091 +
  1.1092 +    @Override
  1.1093 +    public void handleMessage(String event, JSONObject message) {
  1.1094 +        try {
  1.1095 +            if (event.equals("Menu:Add")) {
  1.1096 +                MenuItemInfo info = new MenuItemInfo();
  1.1097 +                info.label = message.getString("name");
  1.1098 +                info.id = message.getInt("id") + ADDON_MENU_OFFSET;
  1.1099 +                info.icon = message.optString("icon", null);
  1.1100 +                info.checked = message.optBoolean("checked", false);
  1.1101 +                info.enabled = message.optBoolean("enabled", true);
  1.1102 +                info.visible = message.optBoolean("visible", true);
  1.1103 +                info.checkable = message.optBoolean("checkable", false);
  1.1104 +                int parent = message.optInt("parent", 0);
  1.1105 +                info.parent = parent <= 0 ? parent : parent + ADDON_MENU_OFFSET;
  1.1106 +                final MenuItemInfo menuItemInfo = info;
  1.1107 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1108 +                    @Override
  1.1109 +                    public void run() {
  1.1110 +                        addAddonMenuItem(menuItemInfo);
  1.1111 +                    }
  1.1112 +                });
  1.1113 +            } else if (event.equals("Menu:Remove")) {
  1.1114 +                final int id = message.getInt("id") + ADDON_MENU_OFFSET;
  1.1115 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1116 +                    @Override
  1.1117 +                    public void run() {
  1.1118 +                        removeAddonMenuItem(id);
  1.1119 +                    }
  1.1120 +                });
  1.1121 +            } else if (event.equals("Menu:Update")) {
  1.1122 +                final int id = message.getInt("id") + ADDON_MENU_OFFSET;
  1.1123 +                final JSONObject options = message.getJSONObject("options");
  1.1124 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1125 +                    @Override
  1.1126 +                    public void run() {
  1.1127 +                        updateAddonMenuItem(id, options);
  1.1128 +                    }
  1.1129 +                });
  1.1130 +            } else if (event.equals("CharEncoding:Data")) {
  1.1131 +                final JSONArray charsets = message.getJSONArray("charsets");
  1.1132 +                int selected = message.getInt("selected");
  1.1133 +
  1.1134 +                final int len = charsets.length();
  1.1135 +                final String[] titleArray = new String[len];
  1.1136 +                for (int i = 0; i < len; i++) {
  1.1137 +                    JSONObject charset = charsets.getJSONObject(i);
  1.1138 +                    titleArray[i] = charset.getString("title");
  1.1139 +                }
  1.1140 +
  1.1141 +                final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
  1.1142 +                dialogBuilder.setSingleChoiceItems(titleArray, selected, new AlertDialog.OnClickListener() {
  1.1143 +                    @Override
  1.1144 +                    public void onClick(DialogInterface dialog, int which) {
  1.1145 +                        try {
  1.1146 +                            JSONObject charset = charsets.getJSONObject(which);
  1.1147 +                            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("CharEncoding:Set", charset.getString("code")));
  1.1148 +                            dialog.dismiss();
  1.1149 +                        } catch (JSONException e) {
  1.1150 +                            Log.e(LOGTAG, "error parsing json", e);
  1.1151 +                        }
  1.1152 +                    }
  1.1153 +                });
  1.1154 +                dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
  1.1155 +                    @Override
  1.1156 +                    public void onClick(DialogInterface dialog, int which) {
  1.1157 +                        dialog.dismiss();
  1.1158 +                    }
  1.1159 +                });
  1.1160 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1161 +                    @Override
  1.1162 +                    public void run() {
  1.1163 +                        dialogBuilder.show();
  1.1164 +                    }
  1.1165 +                });
  1.1166 +            } else if (event.equals("CharEncoding:State")) {
  1.1167 +                final boolean visible = message.getString("visible").equals("true");
  1.1168 +                GeckoPreferences.setCharEncodingState(visible);
  1.1169 +                final Menu menu = mMenu;
  1.1170 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1171 +                    @Override
  1.1172 +                    public void run() {
  1.1173 +                        if (menu != null)
  1.1174 +                            menu.findItem(R.id.char_encoding).setVisible(visible);
  1.1175 +                    }
  1.1176 +                });
  1.1177 +            } else if (event.equals("Feedback:OpenPlayStore")) {
  1.1178 +                Intent intent = new Intent(Intent.ACTION_VIEW);
  1.1179 +                intent.setData(Uri.parse("market://details?id=" + getPackageName()));
  1.1180 +                startActivity(intent);
  1.1181 +            } else if (event.equals("Feedback:MaybeLater")) {
  1.1182 +                resetFeedbackLaunchCount();
  1.1183 +            } else if (event.equals("Feedback:LastUrl")) {
  1.1184 +                getLastUrl();
  1.1185 +            } else if (event.equals("Gecko:DelayedStartup")) {
  1.1186 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1187 +                    @Override
  1.1188 +                    public void run() {
  1.1189 +                        // Force tabs panel inflation once the initial
  1.1190 +                        // pageload is finished.
  1.1191 +                        ensureTabsPanelExists();
  1.1192 +                    }
  1.1193 +                });
  1.1194 +
  1.1195 +                super.handleMessage(event, message);
  1.1196 +            } else if (event.equals("Gecko:Ready")) {
  1.1197 +                // Handle this message in GeckoApp, but also enable the Settings
  1.1198 +                // menuitem, which is specific to BrowserApp.
  1.1199 +                super.handleMessage(event, message);
  1.1200 +                final Menu menu = mMenu;
  1.1201 +                ThreadUtils.postToUiThread(new Runnable() {
  1.1202 +                    @Override
  1.1203 +                    public void run() {
  1.1204 +                        if (menu != null)
  1.1205 +                            menu.findItem(R.id.settings).setEnabled(true);
  1.1206 +                    }
  1.1207 +                });
  1.1208 +
  1.1209 +                // Display notification for Mozilla data reporting, if data should be collected.
  1.1210 +                if (AppConstants.MOZ_DATA_REPORTING) {
  1.1211 +                    DataReportingNotification.checkAndNotifyPolicy(GeckoAppShell.getContext());
  1.1212 +                }
  1.1213 +
  1.1214 +            } else if (event.equals("Telemetry:Gather")) {
  1.1215 +                Telemetry.HistogramAdd("PLACES_PAGES_COUNT", BrowserDB.getCount(getContentResolver(), "history"));
  1.1216 +                Telemetry.HistogramAdd("PLACES_BOOKMARKS_COUNT", BrowserDB.getCount(getContentResolver(), "bookmarks"));
  1.1217 +                Telemetry.HistogramAdd("FENNEC_FAVICONS_COUNT", BrowserDB.getCount(getContentResolver(), "favicons"));
  1.1218 +                Telemetry.HistogramAdd("FENNEC_THUMBNAILS_COUNT", BrowserDB.getCount(getContentResolver(), "thumbnails"));
  1.1219 +            } else if (event.equals("Reader:ListStatusRequest")) {
  1.1220 +                handleReaderListStatusRequest(message.getString("url"));
  1.1221 +            } else if (event.equals("Reader:Added")) {
  1.1222 +                final int result = message.getInt("result");
  1.1223 +                handleReaderAdded(result, messageToReadingListContentValues(message));
  1.1224 +            } else if (event.equals("Reader:Removed")) {
  1.1225 +                final String url = message.getString("url");
  1.1226 +                handleReaderRemoved(url);
  1.1227 +            } else if (event.equals("Reader:Share")) {
  1.1228 +                final String title = message.getString("title");
  1.1229 +                final String url = message.getString("url");
  1.1230 +                GeckoAppShell.openUriExternal(url, "text/plain", "", "",
  1.1231 +                                              Intent.ACTION_SEND, title);
  1.1232 +            } else if (event.equals("Settings:Show")) {
  1.1233 +                // null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
  1.1234 +                String resource = null;
  1.1235 +                if (!message.isNull(GeckoPreferences.INTENT_EXTRA_RESOURCES)) {
  1.1236 +                    resource = message.getString(GeckoPreferences.INTENT_EXTRA_RESOURCES);
  1.1237 +                }
  1.1238 +                Intent settingsIntent = new Intent(this, GeckoPreferences.class);
  1.1239 +                GeckoPreferences.setResourceToOpen(settingsIntent, resource);
  1.1240 +                startActivity(settingsIntent);
  1.1241 +            } else if (event.equals("Updater:Launch")) {
  1.1242 +                handleUpdaterLaunch();
  1.1243 +            } else if (event.equals("Prompt:ShowTop")) {
  1.1244 +                // Bring this activity to front so the prompt is visible..
  1.1245 +                Intent bringToFrontIntent = new Intent();
  1.1246 +                bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
  1.1247 +                bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
  1.1248 +                startActivity(bringToFrontIntent);
  1.1249 +            } else if (event.equals("Accounts:Create")) {
  1.1250 +                // Do exactly the same thing as if you tapped 'Sync'
  1.1251 +                // in Settings.
  1.1252 +                final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
  1.1253 +                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  1.1254 +                getContext().startActivity(intent);
  1.1255 +            } else if (event.equals("Accounts:Exist")) {
  1.1256 +                final String kind = message.getString("kind");
  1.1257 +                final JSONObject response = new JSONObject();
  1.1258 +
  1.1259 +                if ("any".equals(kind)) {
  1.1260 +                    response.put("exists", SyncAccounts.syncAccountsExist(getContext()) ||
  1.1261 +                                           FirefoxAccounts.firefoxAccountsExist(getContext()));
  1.1262 +                    EventDispatcher.sendResponse(message, response);
  1.1263 +                } else if ("fxa".equals(kind)) {
  1.1264 +                    response.put("exists", FirefoxAccounts.firefoxAccountsExist(getContext()));
  1.1265 +                    EventDispatcher.sendResponse(message, response);
  1.1266 +                } else if ("sync11".equals(kind)) {
  1.1267 +                    response.put("exists", SyncAccounts.syncAccountsExist(getContext()));
  1.1268 +                    EventDispatcher.sendResponse(message, response);
  1.1269 +                } else {
  1.1270 +                    response.put("error", "Unknown kind");
  1.1271 +                    EventDispatcher.sendError(message, response);
  1.1272 +                }
  1.1273 +            } else {
  1.1274 +                super.handleMessage(event, message);
  1.1275 +            }
  1.1276 +        } catch (Exception e) {
  1.1277 +            Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
  1.1278 +        }
  1.1279 +    }
  1.1280 +
  1.1281 +    @Override
  1.1282 +    public void addTab() {
  1.1283 +        // Always load about:home when opening a new tab.
  1.1284 +        Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
  1.1285 +    }
  1.1286 +
  1.1287 +    @Override
  1.1288 +    public void addPrivateTab() {
  1.1289 +        Tabs.getInstance().loadUrl(AboutPages.PRIVATEBROWSING, Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_PRIVATE);
  1.1290 +    }
  1.1291 +
  1.1292 +    @Override
  1.1293 +    public void showNormalTabs() {
  1.1294 +        showTabs(TabsPanel.Panel.NORMAL_TABS);
  1.1295 +    }
  1.1296 +
  1.1297 +    @Override
  1.1298 +    public void showPrivateTabs() {
  1.1299 +        showTabs(TabsPanel.Panel.PRIVATE_TABS);
  1.1300 +    }
  1.1301 +    /**
  1.1302 +    * Ensure the TabsPanel view is properly inflated and returns
  1.1303 +    * true when the view has been inflated, false otherwise.
  1.1304 +    */
  1.1305 +    private boolean ensureTabsPanelExists() {
  1.1306 +        if (mTabsPanel != null) {
  1.1307 +            return false;
  1.1308 +        }
  1.1309 +
  1.1310 +        ViewStub tabsPanelStub = (ViewStub) findViewById(R.id.tabs_panel);
  1.1311 +        mTabsPanel = (TabsPanel) tabsPanelStub.inflate();
  1.1312 +
  1.1313 +        mTabsPanel.setTabsLayoutChangeListener(this);
  1.1314 +        updateSideBarState();
  1.1315 +
  1.1316 +        return true;
  1.1317 +    }
  1.1318 +
  1.1319 +    private void showTabs(final TabsPanel.Panel panel) {
  1.1320 +        if (Tabs.getInstance().getDisplayCount() == 0)
  1.1321 +            return;
  1.1322 +
  1.1323 +        if (ensureTabsPanelExists()) {
  1.1324 +            // If we've just inflated the tabs panel, only show it once the current
  1.1325 +            // layout pass is done to avoid displayed temporary UI states during
  1.1326 +            // relayout.
  1.1327 +            ViewTreeObserver vto = mTabsPanel.getViewTreeObserver();
  1.1328 +            if (vto.isAlive()) {
  1.1329 +                vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  1.1330 +                    @Override
  1.1331 +                    public void onGlobalLayout() {
  1.1332 +                        mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
  1.1333 +                        mTabsPanel.show(panel);
  1.1334 +                    }
  1.1335 +                });
  1.1336 +            }
  1.1337 +        } else {
  1.1338 +            mTabsPanel.show(panel);
  1.1339 +        }
  1.1340 +    }
  1.1341 +
  1.1342 +    @Override
  1.1343 +    public void hideTabs() {
  1.1344 +        mTabsPanel.hide();
  1.1345 +    }
  1.1346 +
  1.1347 +    @Override
  1.1348 +    public boolean autoHideTabs() {
  1.1349 +        if (areTabsShown()) {
  1.1350 +            hideTabs();
  1.1351 +            return true;
  1.1352 +        }
  1.1353 +        return false;
  1.1354 +    }
  1.1355 +
  1.1356 +    @Override
  1.1357 +    public boolean areTabsShown() {
  1.1358 +        return (mTabsPanel != null && mTabsPanel.isShown());
  1.1359 +    }
  1.1360 +
  1.1361 +    @Override
  1.1362 +    public void onTabsLayoutChange(int width, int height) {
  1.1363 +        int animationLength = TABS_ANIMATION_DURATION;
  1.1364 +
  1.1365 +        if (mMainLayoutAnimator != null) {
  1.1366 +            animationLength = Math.max(1, animationLength - (int)mMainLayoutAnimator.getRemainingTime());
  1.1367 +            mMainLayoutAnimator.stop(false);
  1.1368 +        }
  1.1369 +
  1.1370 +        if (areTabsShown()) {
  1.1371 +            mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  1.1372 +        }
  1.1373 +
  1.1374 +        mMainLayoutAnimator = new PropertyAnimator(animationLength, sTabsInterpolator);
  1.1375 +        mMainLayoutAnimator.addPropertyAnimationListener(this);
  1.1376 +
  1.1377 +        if (hasTabsSideBar()) {
  1.1378 +            mMainLayoutAnimator.attach(mMainLayout,
  1.1379 +                                       PropertyAnimator.Property.SCROLL_X,
  1.1380 +                                       -width);
  1.1381 +        } else {
  1.1382 +            mMainLayoutAnimator.attach(mMainLayout,
  1.1383 +                                       PropertyAnimator.Property.SCROLL_Y,
  1.1384 +                                       -height);
  1.1385 +        }
  1.1386 +
  1.1387 +        mTabsPanel.prepareTabsAnimation(mMainLayoutAnimator);
  1.1388 +        mBrowserToolbar.prepareTabsAnimation(mMainLayoutAnimator, areTabsShown());
  1.1389 +
  1.1390 +        // If the tabs layout is animating onto the screen, pin the dynamic
  1.1391 +        // toolbar.
  1.1392 +        if (mDynamicToolbar.isEnabled()) {
  1.1393 +            if (width > 0 && height > 0) {
  1.1394 +                mDynamicToolbar.setPinned(true, PinReason.RELAYOUT);
  1.1395 +                mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  1.1396 +            } else {
  1.1397 +                mDynamicToolbar.setPinned(false, PinReason.RELAYOUT);
  1.1398 +            }
  1.1399 +        }
  1.1400 +
  1.1401 +        mMainLayoutAnimator.start();
  1.1402 +    }
  1.1403 +
  1.1404 +    @Override
  1.1405 +    public void onPropertyAnimationStart() {
  1.1406 +    }
  1.1407 +
  1.1408 +    @Override
  1.1409 +    public void onPropertyAnimationEnd() {
  1.1410 +        if (!areTabsShown()) {
  1.1411 +            mTabsPanel.setVisibility(View.INVISIBLE);
  1.1412 +            mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
  1.1413 +        }
  1.1414 +
  1.1415 +        mTabsPanel.finishTabsAnimation();
  1.1416 +
  1.1417 +        mMainLayoutAnimator = null;
  1.1418 +    }
  1.1419 +
  1.1420 +    @Override
  1.1421 +    public void onSaveInstanceState(Bundle outState) {
  1.1422 +        super.onSaveInstanceState(outState);
  1.1423 +        mDynamicToolbar.onSaveInstanceState(outState);
  1.1424 +        outState.putInt(STATE_ABOUT_HOME_TOP_PADDING, mHomePagerContainer.getPaddingTop());
  1.1425 +    }
  1.1426 +
  1.1427 +    /**
  1.1428 +     * Attempts to switch to an open tab with the given URL.
  1.1429 +     *
  1.1430 +     * @return true if we successfully switched to a tab, false otherwise.
  1.1431 +     */
  1.1432 +    private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
  1.1433 +        if (!flags.contains(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)) {
  1.1434 +            return false;
  1.1435 +        }
  1.1436 +
  1.1437 +        final Tabs tabs = Tabs.getInstance();
  1.1438 +        final Tab tab = tabs.getFirstTabForUrl(url, tabs.getSelectedTab().isPrivate());
  1.1439 +        if (tab == null) {
  1.1440 +            return false;
  1.1441 +        }
  1.1442 +
  1.1443 +        // Set the target tab to null so it does not get selected (on editing
  1.1444 +        // mode exit) in lieu of the tab we are about to select.
  1.1445 +        mTargetTabForEditingMode = null;
  1.1446 +        tabs.selectTab(tab.getId());
  1.1447 +
  1.1448 +        mBrowserToolbar.cancelEdit();
  1.1449 +
  1.1450 +        return true;
  1.1451 +    }
  1.1452 +
  1.1453 +    private void openUrlAndStopEditing(String url) {
  1.1454 +        openUrlAndStopEditing(url, null, false);
  1.1455 +    }
  1.1456 +
  1.1457 +    private void openUrlAndStopEditing(String url, boolean newTab) {
  1.1458 +        openUrlAndStopEditing(url, null, newTab);
  1.1459 +    }
  1.1460 +
  1.1461 +    private void openUrlAndStopEditing(String url, String searchEngine) {
  1.1462 +        openUrlAndStopEditing(url, searchEngine, false);
  1.1463 +    }
  1.1464 +
  1.1465 +    private void openUrlAndStopEditing(String url, String searchEngine, boolean newTab) {
  1.1466 +        int flags = Tabs.LOADURL_NONE;
  1.1467 +        if (newTab) {
  1.1468 +            flags |= Tabs.LOADURL_NEW_TAB;
  1.1469 +        }
  1.1470 +
  1.1471 +        Tabs.getInstance().loadUrl(url, searchEngine, -1, flags);
  1.1472 +
  1.1473 +        mBrowserToolbar.cancelEdit();
  1.1474 +    }
  1.1475 +
  1.1476 +    private boolean isHomePagerVisible() {
  1.1477 +        return (mHomePager != null && mHomePager.isVisible()
  1.1478 +            && mHomePagerContainer != null && mHomePagerContainer.getVisibility() == View.VISIBLE);
  1.1479 +    }
  1.1480 +
  1.1481 +    /* Favicon stuff. */
  1.1482 +    private static OnFaviconLoadedListener sFaviconLoadedListener = new OnFaviconLoadedListener() {
  1.1483 +        @Override
  1.1484 +        public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
  1.1485 +            // If we failed to load a favicon, we use the default favicon instead.
  1.1486 +            Tabs.getInstance()
  1.1487 +                .updateFaviconForURL(pageUrl,
  1.1488 +                                     (favicon == null) ? Favicons.defaultFavicon : favicon);
  1.1489 +        }
  1.1490 +    };
  1.1491 +
  1.1492 +    private void loadFavicon(final Tab tab) {
  1.1493 +        maybeCancelFaviconLoad(tab);
  1.1494 +
  1.1495 +        final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
  1.1496 +
  1.1497 +        int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
  1.1498 +        int id = Favicons.getSizedFavicon(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags, sFaviconLoadedListener);
  1.1499 +
  1.1500 +        tab.setFaviconLoadId(id);
  1.1501 +    }
  1.1502 +
  1.1503 +    private void maybeCancelFaviconLoad(Tab tab) {
  1.1504 +        int faviconLoadId = tab.getFaviconLoadId();
  1.1505 +
  1.1506 +        if (Favicons.NOT_LOADING == faviconLoadId) {
  1.1507 +            return;
  1.1508 +        }
  1.1509 +
  1.1510 +        // Cancel load task and reset favicon load state if it wasn't already
  1.1511 +        // in NOT_LOADING state.
  1.1512 +        Favicons.cancelFaviconLoad(faviconLoadId);
  1.1513 +        tab.setFaviconLoadId(Favicons.NOT_LOADING);
  1.1514 +    }
  1.1515 +
  1.1516 +    /**
  1.1517 +     * Enters editing mode with the current tab's URL. There might be no
  1.1518 +     * tabs loaded by the time the user enters editing mode e.g. just after
  1.1519 +     * the app starts. In this case, we simply fallback to an empty URL.
  1.1520 +     */
  1.1521 +    private void enterEditingMode() {
  1.1522 +        String url = "";
  1.1523 +
  1.1524 +        final Tab tab = Tabs.getInstance().getSelectedTab();
  1.1525 +        if (tab != null) {
  1.1526 +            final String userSearch = tab.getUserSearch();
  1.1527 +
  1.1528 +            // Check to see if there's a user-entered search term,
  1.1529 +            // which we save whenever the user performs a search.
  1.1530 +            url = (TextUtils.isEmpty(userSearch) ? tab.getURL() : userSearch);
  1.1531 +        }
  1.1532 +
  1.1533 +        enterEditingMode(url);
  1.1534 +    }
  1.1535 +
  1.1536 +    /**
  1.1537 +     * Enters editing mode with the specified URL. This method will
  1.1538 +     * always open the HISTORY page on about:home.
  1.1539 +     */
  1.1540 +    private void enterEditingMode(String url) {
  1.1541 +        if (url == null) {
  1.1542 +            throw new IllegalArgumentException("Cannot handle null URLs in enterEditingMode");
  1.1543 +        }
  1.1544 +
  1.1545 +        if (mBrowserToolbar.isEditing() || mBrowserToolbar.isAnimating()) {
  1.1546 +            return;
  1.1547 +        }
  1.1548 +
  1.1549 +        final Tab selectedTab = Tabs.getInstance().getSelectedTab();
  1.1550 +        mTargetTabForEditingMode = (selectedTab != null ? selectedTab.getId() : null);
  1.1551 +
  1.1552 +        final PropertyAnimator animator = new PropertyAnimator(250);
  1.1553 +        animator.setUseHardwareLayer(false);
  1.1554 +
  1.1555 +        mBrowserToolbar.startEditing(url, animator);
  1.1556 +
  1.1557 +        final String panelId = selectedTab.getMostRecentHomePanel();
  1.1558 +        showHomePagerWithAnimator(panelId, animator);
  1.1559 +
  1.1560 +        animator.start();
  1.1561 +        Telemetry.startUISession(TelemetryContract.Session.AWESOMESCREEN);
  1.1562 +    }
  1.1563 +
  1.1564 +    private void commitEditingMode() {
  1.1565 +        if (!mBrowserToolbar.isEditing()) {
  1.1566 +            return;
  1.1567 +        }
  1.1568 +
  1.1569 +        Telemetry.stopUISession(TelemetryContract.Session.AWESOMESCREEN,
  1.1570 +                                TelemetryContract.Reason.COMMIT);
  1.1571 +
  1.1572 +        final String url = mBrowserToolbar.commitEdit();
  1.1573 +
  1.1574 +        // HACK: We don't know the url that will be loaded when hideHomePager is initially called
  1.1575 +        // in BrowserToolbar's onStopEditing listener so on the awesomescreen, hideHomePager will
  1.1576 +        // use the url "about:home" and return without taking any action. hideBrowserSearch is
  1.1577 +        // then called, but since hideHomePager changes both HomePager and LayerView visibility
  1.1578 +        // and exited without taking an action, no Views are displayed and graphical corruption is
  1.1579 +        // visible instead.
  1.1580 +        //
  1.1581 +        // Here we call hideHomePager for the second time with the URL to be loaded so that
  1.1582 +        // hideHomePager is called with the correct state for the upcoming page load.
  1.1583 +        //
  1.1584 +        // Expected to be fixed by bug 915825.
  1.1585 +        hideHomePager(url);
  1.1586 +
  1.1587 +        // Don't do anything if the user entered an empty URL.
  1.1588 +        if (TextUtils.isEmpty(url)) {
  1.1589 +            return;
  1.1590 +        }
  1.1591 +
  1.1592 +        // If the URL doesn't look like a search query, just load it.
  1.1593 +        if (!StringUtils.isSearchQuery(url, true)) {
  1.1594 +            Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED);
  1.1595 +
  1.1596 +            Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
  1.1597 +            return;
  1.1598 +        }
  1.1599 +
  1.1600 +        // Otherwise, check for a bookmark keyword.
  1.1601 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.1602 +            @Override
  1.1603 +            public void run() {
  1.1604 +                final String keyword;
  1.1605 +                final String keywordSearch;
  1.1606 +
  1.1607 +                final int index = url.indexOf(" ");
  1.1608 +                if (index == -1) {
  1.1609 +                    keyword = url;
  1.1610 +                    keywordSearch = "";
  1.1611 +                } else {
  1.1612 +                    keyword = url.substring(0, index);
  1.1613 +                    keywordSearch = url.substring(index + 1);
  1.1614 +                }
  1.1615 +
  1.1616 +                final String keywordUrl = BrowserDB.getUrlForKeyword(getContentResolver(), keyword);
  1.1617 +
  1.1618 +                // If there isn't a bookmark keyword, load the url. This may result in a query
  1.1619 +                // using the default search engine.
  1.1620 +                if (TextUtils.isEmpty(keywordUrl)) {
  1.1621 +                    Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED);
  1.1622 +                    Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
  1.1623 +                    return;
  1.1624 +                }
  1.1625 +
  1.1626 +                recordSearch(null, "barkeyword");
  1.1627 +
  1.1628 +                // Otherwise, construct a search query from the bookmark keyword.
  1.1629 +                final String searchUrl = keywordUrl.replace("%s", URLEncoder.encode(keywordSearch));
  1.1630 +                Tabs.getInstance().loadUrl(searchUrl, Tabs.LOADURL_USER_ENTERED);
  1.1631 +                Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, "", "keyword");
  1.1632 +            }
  1.1633 +        });
  1.1634 +    }
  1.1635 +
  1.1636 +    /**
  1.1637 +     * Record in Health Report that a search has occurred.
  1.1638 +     *
  1.1639 +     * @param engine
  1.1640 +     *        a search engine instance. Can be null.
  1.1641 +     * @param where
  1.1642 +     *        where the search was initialized; one of the values in
  1.1643 +     *        {@link BrowserHealthRecorder#SEARCH_LOCATIONS}.
  1.1644 +     */
  1.1645 +    private static void recordSearch(SearchEngine engine, String where) {
  1.1646 +        Log.i(LOGTAG, "Recording search: " +
  1.1647 +                      ((engine == null) ? "null" : engine.name) +
  1.1648 +                      ", " + where);
  1.1649 +        try {
  1.1650 +            String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
  1.1651 +            JSONObject message = new JSONObject();
  1.1652 +            message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
  1.1653 +            message.put("location", where);
  1.1654 +            message.put("identifier", identifier);
  1.1655 +            GeckoAppShell.getEventDispatcher().dispatchEvent(message, null);
  1.1656 +        } catch (Exception e) {
  1.1657 +            Log.w(LOGTAG, "Error recording search.", e);
  1.1658 +        }
  1.1659 +    }
  1.1660 +
  1.1661 +    void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
  1.1662 +        if (TextUtils.isEmpty(searchTerm)) {
  1.1663 +            hideBrowserSearch();
  1.1664 +        } else {
  1.1665 +            showBrowserSearch();
  1.1666 +            mBrowserSearch.filter(searchTerm, handler);
  1.1667 +        }
  1.1668 +    }
  1.1669 +
  1.1670 +    /**
  1.1671 +     * Selects the target tab for editing mode. This is expected to be the tab selected on editing
  1.1672 +     * mode entry, unless it is subsequently overridden.
  1.1673 +     *
  1.1674 +     * A background tab may be selected while editing mode is active (e.g. popups), causing the
  1.1675 +     * new url to load in the newly selected tab. Call this method on editing mode exit to
  1.1676 +     * mitigate this.
  1.1677 +     */
  1.1678 +    private void selectTargetTabForEditingMode() {
  1.1679 +        if (mTargetTabForEditingMode != null) {
  1.1680 +            Tabs.getInstance().selectTab(mTargetTabForEditingMode);
  1.1681 +        }
  1.1682 +
  1.1683 +        mTargetTabForEditingMode = null;
  1.1684 +    }
  1.1685 +
  1.1686 +    /**
  1.1687 +     * Shows or hides the home pager for the given tab.
  1.1688 +     */
  1.1689 +    private void updateHomePagerForTab(Tab tab) {
  1.1690 +        // Don't change the visibility of the home pager if we're in editing mode.
  1.1691 +        if (mBrowserToolbar.isEditing()) {
  1.1692 +            return;
  1.1693 +        }
  1.1694 +
  1.1695 +        if (isAboutHome(tab)) {
  1.1696 +            String panelId = AboutPages.getPanelIdFromAboutHomeUrl(tab.getURL());
  1.1697 +            if (panelId == null) {
  1.1698 +                // No panel was specified in the URL. Try loading the most recent
  1.1699 +                // home panel for this tab.
  1.1700 +                panelId = tab.getMostRecentHomePanel();
  1.1701 +            }
  1.1702 +            showHomePager(panelId);
  1.1703 +
  1.1704 +            if (mDynamicToolbar.isEnabled()) {
  1.1705 +                mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  1.1706 +            }
  1.1707 +        } else {
  1.1708 +            hideHomePager();
  1.1709 +        }
  1.1710 +    }
  1.1711 +
  1.1712 +    @Override
  1.1713 +    public void onLocaleReady(final String locale) {
  1.1714 +        super.onLocaleReady(locale);
  1.1715 +
  1.1716 +        HomePanelsManager.getInstance().onLocaleReady(locale);
  1.1717 +
  1.1718 +        if (mMenu != null) {
  1.1719 +            mMenu.clear();
  1.1720 +            onCreateOptionsMenu(mMenu);
  1.1721 +        }
  1.1722 +    }
  1.1723 +
  1.1724 +    private void showHomePager(String panelId) {
  1.1725 +        showHomePagerWithAnimator(panelId, null);
  1.1726 +    }
  1.1727 +
  1.1728 +    private void showHomePagerWithAnimator(String panelId, PropertyAnimator animator) {
  1.1729 +        if (isHomePagerVisible()) {
  1.1730 +            // Home pager already visible, make sure it shows the correct panel.
  1.1731 +            mHomePager.showPanel(panelId);
  1.1732 +            return;
  1.1733 +        }
  1.1734 +
  1.1735 +        // Refresh toolbar height to possibly restore the toolbar padding
  1.1736 +        refreshToolbarHeight();
  1.1737 +
  1.1738 +        // Show the toolbar before hiding about:home so the
  1.1739 +        // onMetricsChanged callback still works.
  1.1740 +        if (mDynamicToolbar.isEnabled()) {
  1.1741 +            mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
  1.1742 +        }
  1.1743 +
  1.1744 +        if (mHomePager == null) {
  1.1745 +            final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
  1.1746 +            mHomePager = (HomePager) homePagerStub.inflate();
  1.1747 +
  1.1748 +            mHomePager.setOnPanelChangeListener(new HomePager.OnPanelChangeListener() {
  1.1749 +                @Override
  1.1750 +                public void onPanelSelected(String panelId) {
  1.1751 +                    final Tab currentTab = Tabs.getInstance().getSelectedTab();
  1.1752 +                    if (currentTab != null) {
  1.1753 +                        currentTab.setMostRecentHomePanel(panelId);
  1.1754 +                    }
  1.1755 +                }
  1.1756 +            });
  1.1757 +
  1.1758 +            // Don't show the banner in guest mode.
  1.1759 +            if (!getProfile().inGuestMode()) {
  1.1760 +                final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub);
  1.1761 +                final HomeBanner homeBanner = (HomeBanner) homeBannerStub.inflate();
  1.1762 +                mHomePager.setBanner(homeBanner);
  1.1763 +
  1.1764 +                // Remove the banner from the view hierarchy if it is dismissed.
  1.1765 +                homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
  1.1766 +                    @Override
  1.1767 +                    public void onDismiss() {
  1.1768 +                        mHomePager.setBanner(null);
  1.1769 +                        mHomePagerContainer.removeView(homeBanner);
  1.1770 +                    }
  1.1771 +                });
  1.1772 +            }
  1.1773 +        }
  1.1774 +
  1.1775 +        mHomePagerContainer.setVisibility(View.VISIBLE);
  1.1776 +        mHomePager.load(getSupportLoaderManager(),
  1.1777 +                        getSupportFragmentManager(),
  1.1778 +                        panelId, animator);
  1.1779 +
  1.1780 +        // Hide the web content so it cannot be focused by screen readers.
  1.1781 +        hideWebContentOnPropertyAnimationEnd(animator);
  1.1782 +    }
  1.1783 +
  1.1784 +    private void hideWebContentOnPropertyAnimationEnd(final PropertyAnimator animator) {
  1.1785 +        if (animator == null) {
  1.1786 +            hideWebContent();
  1.1787 +            return;
  1.1788 +        }
  1.1789 +
  1.1790 +        animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
  1.1791 +            @Override
  1.1792 +            public void onPropertyAnimationStart() {
  1.1793 +                mHideWebContentOnAnimationEnd = true;
  1.1794 +            }
  1.1795 +
  1.1796 +            @Override
  1.1797 +            public void onPropertyAnimationEnd() {
  1.1798 +                if (mHideWebContentOnAnimationEnd) {
  1.1799 +                    hideWebContent();
  1.1800 +                }
  1.1801 +            }
  1.1802 +        });
  1.1803 +    }
  1.1804 +
  1.1805 +    private void hideWebContent() {
  1.1806 +        // The view is set to INVISIBLE, rather than GONE, to avoid
  1.1807 +        // the additional requestLayout() call.
  1.1808 +        mLayerView.setVisibility(View.INVISIBLE);
  1.1809 +    }
  1.1810 +
  1.1811 +    /**
  1.1812 +     * Hides the HomePager, using the url of the currently selected tab as the url to be
  1.1813 +     * loaded.
  1.1814 +     */
  1.1815 +    private void hideHomePager() {
  1.1816 +        final Tab selectedTab = Tabs.getInstance().getSelectedTab();
  1.1817 +        final String url = (selectedTab != null) ? selectedTab.getURL() : null;
  1.1818 +
  1.1819 +        hideHomePager(url);
  1.1820 +    }
  1.1821 +
  1.1822 +    /**
  1.1823 +     * Hides the HomePager. The given url should be the url of the page to be loaded, or null
  1.1824 +     * if a new page is not being loaded.
  1.1825 +     */
  1.1826 +    private void hideHomePager(final String url) {
  1.1827 +        if (!isHomePagerVisible() || AboutPages.isAboutHome(url)) {
  1.1828 +            return;
  1.1829 +        }
  1.1830 +
  1.1831 +        // Prevent race in hiding web content - see declaration for more info.
  1.1832 +        mHideWebContentOnAnimationEnd = false;
  1.1833 +
  1.1834 +        // Display the previously hidden web content (which prevented screen reader access).
  1.1835 +        mLayerView.setVisibility(View.VISIBLE);
  1.1836 +        mHomePagerContainer.setVisibility(View.GONE);
  1.1837 +
  1.1838 +        if (mHomePager != null) {
  1.1839 +            mHomePager.unload();
  1.1840 +        }
  1.1841 +
  1.1842 +        mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
  1.1843 +
  1.1844 +        // Refresh toolbar height to possibly restore the toolbar padding
  1.1845 +        refreshToolbarHeight();
  1.1846 +    }
  1.1847 +
  1.1848 +    private void showBrowserSearch() {
  1.1849 +        if (mBrowserSearch.getUserVisibleHint()) {
  1.1850 +            return;
  1.1851 +        }
  1.1852 +
  1.1853 +        mBrowserSearchContainer.setVisibility(View.VISIBLE);
  1.1854 +
  1.1855 +        // Prevent overdraw by hiding the underlying HomePager View.
  1.1856 +        mHomePager.setVisibility(View.INVISIBLE);
  1.1857 +
  1.1858 +        final FragmentManager fm = getSupportFragmentManager();
  1.1859 +
  1.1860 +        // In certain situations, showBrowserSearch() can be called immediately after hideBrowserSearch()
  1.1861 +        // (see bug 925012). Because of an Android bug (http://code.google.com/p/android/issues/detail?id=61179),
  1.1862 +        // calling FragmentTransaction#add immediately after FragmentTransaction#remove won't add the fragment's
  1.1863 +        // view to the layout. Calling FragmentManager#executePendingTransactions before re-adding the fragment
  1.1864 +        // prevents this issue.
  1.1865 +        fm.executePendingTransactions();
  1.1866 +
  1.1867 +        fm.beginTransaction().add(R.id.search_container, mBrowserSearch, BROWSER_SEARCH_TAG).commitAllowingStateLoss();
  1.1868 +        mBrowserSearch.setUserVisibleHint(true);
  1.1869 +    }
  1.1870 +
  1.1871 +    private void hideBrowserSearch() {
  1.1872 +        if (!mBrowserSearch.getUserVisibleHint()) {
  1.1873 +            return;
  1.1874 +        }
  1.1875 +
  1.1876 +        // To prevent overdraw, the HomePager is hidden when BrowserSearch is displayed:
  1.1877 +        // reverse that.
  1.1878 +        mHomePager.setVisibility(View.VISIBLE);
  1.1879 +
  1.1880 +        mBrowserSearchContainer.setVisibility(View.INVISIBLE);
  1.1881 +
  1.1882 +        getSupportFragmentManager().beginTransaction()
  1.1883 +                .remove(mBrowserSearch).commitAllowingStateLoss();
  1.1884 +        mBrowserSearch.setUserVisibleHint(false);
  1.1885 +    }
  1.1886 +
  1.1887 +    private class HideTabsTouchListener implements TouchEventInterceptor {
  1.1888 +        private boolean mIsHidingTabs = false;
  1.1889 +
  1.1890 +        @Override
  1.1891 +        public boolean onInterceptTouchEvent(View view, MotionEvent event) {
  1.1892 +            // We need to account for scroll state for the touched view otherwise
  1.1893 +            // tapping on an "empty" part of the view will still be considered a
  1.1894 +            // valid touch event.
  1.1895 +            if (view.getScrollX() != 0 || view.getScrollY() != 0) {
  1.1896 +                Rect rect = new Rect();
  1.1897 +                view.getHitRect(rect);
  1.1898 +                rect.offset(-view.getScrollX(), -view.getScrollY());
  1.1899 +
  1.1900 +                int[] viewCoords = new int[2];
  1.1901 +                view.getLocationOnScreen(viewCoords);
  1.1902 +
  1.1903 +                int x = (int) event.getRawX() - viewCoords[0];
  1.1904 +                int y = (int) event.getRawY() - viewCoords[1];
  1.1905 +
  1.1906 +                if (!rect.contains(x, y))
  1.1907 +                    return false;
  1.1908 +            }
  1.1909 +
  1.1910 +            // If the tab tray is showing, hide the tab tray and don't send the event to content.
  1.1911 +            if (event.getActionMasked() == MotionEvent.ACTION_DOWN && autoHideTabs()) {
  1.1912 +                mIsHidingTabs = true;
  1.1913 +                return true;
  1.1914 +            }
  1.1915 +            return false;
  1.1916 +        }
  1.1917 +
  1.1918 +        @Override
  1.1919 +        public boolean onTouch(View view, MotionEvent event) {
  1.1920 +            if (mIsHidingTabs) {
  1.1921 +                // Keep consuming events until the gesture finishes.
  1.1922 +                int action = event.getActionMasked();
  1.1923 +                if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
  1.1924 +                    mIsHidingTabs = false;
  1.1925 +                }
  1.1926 +                return true;
  1.1927 +            }
  1.1928 +            return false;
  1.1929 +        }
  1.1930 +    }
  1.1931 +
  1.1932 +    private static Menu findParentMenu(Menu menu, MenuItem item) {
  1.1933 +        final int itemId = item.getItemId();
  1.1934 +
  1.1935 +        final int count = (menu != null) ? menu.size() : 0;
  1.1936 +        for (int i = 0; i < count; i++) {
  1.1937 +            MenuItem menuItem = menu.getItem(i);
  1.1938 +            if (menuItem.getItemId() == itemId) {
  1.1939 +                return menu;
  1.1940 +            }
  1.1941 +            if (menuItem.hasSubMenu()) {
  1.1942 +                Menu parent = findParentMenu(menuItem.getSubMenu(), item);
  1.1943 +                if (parent != null) {
  1.1944 +                    return parent;
  1.1945 +                }
  1.1946 +            }
  1.1947 +        }
  1.1948 +
  1.1949 +        return null;
  1.1950 +    }
  1.1951 +
  1.1952 +    /**
  1.1953 +     * Add the provided item to the provided menu, which should be
  1.1954 +     * the root (mMenu).
  1.1955 +     */
  1.1956 +    private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
  1.1957 +        info.added = true;
  1.1958 +
  1.1959 +        final Menu destination;
  1.1960 +        if (info.parent == 0) {
  1.1961 +            destination = menu;
  1.1962 +        } else if (info.parent == GECKO_TOOLS_MENU) {
  1.1963 +            MenuItem tools = menu.findItem(R.id.tools);
  1.1964 +            destination = tools != null ? tools.getSubMenu() : menu;
  1.1965 +        } else {
  1.1966 +            MenuItem parent = menu.findItem(info.parent);
  1.1967 +            if (parent == null) {
  1.1968 +                return;
  1.1969 +            }
  1.1970 +
  1.1971 +            Menu parentMenu = findParentMenu(menu, parent);
  1.1972 +
  1.1973 +            if (!parent.hasSubMenu()) {
  1.1974 +                parentMenu.removeItem(parent.getItemId());
  1.1975 +                destination = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
  1.1976 +                if (parent.getIcon() != null) {
  1.1977 +                    ((SubMenu) destination).getItem().setIcon(parent.getIcon());
  1.1978 +                }
  1.1979 +            } else {
  1.1980 +                destination = parent.getSubMenu();
  1.1981 +            }
  1.1982 +        }
  1.1983 +
  1.1984 +        MenuItem item = destination.add(Menu.NONE, info.id, Menu.NONE, info.label);
  1.1985 +
  1.1986 +        item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
  1.1987 +            @Override
  1.1988 +            public boolean onMenuItemClick(MenuItem item) {
  1.1989 +                Log.i(LOGTAG, "Menu item clicked");
  1.1990 +                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Menu:Clicked", Integer.toString(info.id - ADDON_MENU_OFFSET)));
  1.1991 +                return true;
  1.1992 +            }
  1.1993 +        });
  1.1994 +
  1.1995 +        if (info.icon == null) {
  1.1996 +            item.setIcon(R.drawable.ic_menu_addons_filler);
  1.1997 +        } else {
  1.1998 +            final int id = info.id;
  1.1999 +            BitmapUtils.getDrawable(this, info.icon, new BitmapUtils.BitmapLoader() {
  1.2000 +                @Override
  1.2001 +                public void onBitmapFound(Drawable d) {
  1.2002 +                    // TODO: why do we re-find the item?
  1.2003 +                    MenuItem item = destination.findItem(id);
  1.2004 +                    if (item == null) {
  1.2005 +                        return;
  1.2006 +                    }
  1.2007 +                    if (d == null) {
  1.2008 +                        item.setIcon(R.drawable.ic_menu_addons_filler);
  1.2009 +                        return;
  1.2010 +                    }
  1.2011 +                    item.setIcon(d);
  1.2012 +                }
  1.2013 +            });
  1.2014 +        }
  1.2015 +
  1.2016 +        item.setCheckable(info.checkable);
  1.2017 +        item.setChecked(info.checked);
  1.2018 +        item.setEnabled(info.enabled);
  1.2019 +        item.setVisible(info.visible);
  1.2020 +    }
  1.2021 +
  1.2022 +    private void addAddonMenuItem(final MenuItemInfo info) {
  1.2023 +        if (mAddonMenuItemsCache == null) {
  1.2024 +            mAddonMenuItemsCache = new Vector<MenuItemInfo>();
  1.2025 +        }
  1.2026 +
  1.2027 +        // Mark it as added if the menu was ready.
  1.2028 +        info.added = (mMenu != null);
  1.2029 +
  1.2030 +        // Always cache so we can rebuild after a locale switch.
  1.2031 +        mAddonMenuItemsCache.add(info);
  1.2032 +
  1.2033 +        if (mMenu == null) {
  1.2034 +            return;
  1.2035 +        }
  1.2036 +
  1.2037 +        addAddonMenuItemToMenu(mMenu, info);
  1.2038 +    }
  1.2039 +
  1.2040 +    private void removeAddonMenuItem(int id) {
  1.2041 +        // Remove add-on menu item from cache, if available.
  1.2042 +        if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
  1.2043 +            for (MenuItemInfo item : mAddonMenuItemsCache) {
  1.2044 +                 if (item.id == id) {
  1.2045 +                     mAddonMenuItemsCache.remove(item);
  1.2046 +                     break;
  1.2047 +                 }
  1.2048 +            }
  1.2049 +        }
  1.2050 +
  1.2051 +        if (mMenu == null)
  1.2052 +            return;
  1.2053 +
  1.2054 +        MenuItem menuItem = mMenu.findItem(id);
  1.2055 +        if (menuItem != null)
  1.2056 +            mMenu.removeItem(id);
  1.2057 +    }
  1.2058 +
  1.2059 +    private void updateAddonMenuItem(int id, JSONObject options) {
  1.2060 +        // Set attribute for the menu item in cache, if available
  1.2061 +        if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
  1.2062 +            for (MenuItemInfo item : mAddonMenuItemsCache) {
  1.2063 +                if (item.id == id) {
  1.2064 +                    item.label = options.optString("name", item.label);
  1.2065 +                    item.checkable = options.optBoolean("checkable", item.checkable);
  1.2066 +                    item.checked = options.optBoolean("checked", item.checked);
  1.2067 +                    item.enabled = options.optBoolean("enabled", item.enabled);
  1.2068 +                    item.visible = options.optBoolean("visible", item.visible);
  1.2069 +                    item.added = (mMenu != null);
  1.2070 +                    break;
  1.2071 +                }
  1.2072 +            }
  1.2073 +        }
  1.2074 +
  1.2075 +        if (mMenu == null) {
  1.2076 +            return;
  1.2077 +        }
  1.2078 +
  1.2079 +        MenuItem menuItem = mMenu.findItem(id);
  1.2080 +        if (menuItem != null) {
  1.2081 +            menuItem.setTitle(options.optString("name", menuItem.getTitle().toString()));
  1.2082 +            menuItem.setCheckable(options.optBoolean("checkable", menuItem.isCheckable()));
  1.2083 +            menuItem.setChecked(options.optBoolean("checked", menuItem.isChecked()));
  1.2084 +            menuItem.setEnabled(options.optBoolean("enabled", menuItem.isEnabled()));
  1.2085 +            menuItem.setVisible(options.optBoolean("visible", menuItem.isVisible()));
  1.2086 +        }
  1.2087 +    }
  1.2088 +
  1.2089 +    @Override
  1.2090 +    public boolean onCreateOptionsMenu(Menu menu) {
  1.2091 +        // Sets mMenu = menu.
  1.2092 +        super.onCreateOptionsMenu(menu);
  1.2093 +
  1.2094 +        // Inform the menu about the action-items bar.
  1.2095 +        if (menu instanceof GeckoMenu &&
  1.2096 +            HardwareUtils.isTablet()) {
  1.2097 +            ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
  1.2098 +        }
  1.2099 +
  1.2100 +        MenuInflater inflater = getMenuInflater();
  1.2101 +        inflater.inflate(R.menu.browser_app_menu, mMenu);
  1.2102 +
  1.2103 +        // Add add-on menu items, if any exist.
  1.2104 +        if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
  1.2105 +            for (MenuItemInfo item : mAddonMenuItemsCache) {
  1.2106 +                addAddonMenuItemToMenu(mMenu, item);
  1.2107 +            }
  1.2108 +        }
  1.2109 +
  1.2110 +        // Action providers are available only ICS+.
  1.2111 +        if (Build.VERSION.SDK_INT >= 14) {
  1.2112 +            GeckoMenuItem share = (GeckoMenuItem) mMenu.findItem(R.id.share);
  1.2113 +            GeckoActionProvider provider = GeckoActionProvider.getForType(GeckoActionProvider.DEFAULT_MIME_TYPE, this);
  1.2114 +            share.setActionProvider(provider);
  1.2115 +        }
  1.2116 +
  1.2117 +        return true;
  1.2118 +    }
  1.2119 +
  1.2120 +    @Override
  1.2121 +    public void openOptionsMenu() {
  1.2122 +        if (!hasTabsSideBar() && areTabsShown())
  1.2123 +            return;
  1.2124 +
  1.2125 +        // Scroll custom menu to the top
  1.2126 +        if (mMenuPanel != null)
  1.2127 +            mMenuPanel.scrollTo(0, 0);
  1.2128 +
  1.2129 +        if (!mBrowserToolbar.openOptionsMenu())
  1.2130 +            super.openOptionsMenu();
  1.2131 +
  1.2132 +        if (mDynamicToolbar.isEnabled()) {
  1.2133 +            mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  1.2134 +        }
  1.2135 +    }
  1.2136 +
  1.2137 +    @Override
  1.2138 +    public void closeOptionsMenu() {
  1.2139 +        if (!mBrowserToolbar.closeOptionsMenu())
  1.2140 +            super.closeOptionsMenu();
  1.2141 +    }
  1.2142 +
  1.2143 +    @Override
  1.2144 +    public void setFullScreen(final boolean fullscreen) {
  1.2145 +        super.setFullScreen(fullscreen);
  1.2146 +        ThreadUtils.postToUiThread(new Runnable() {
  1.2147 +            @Override
  1.2148 +            public void run() {
  1.2149 +                if (fullscreen) {
  1.2150 +                    mViewFlipper.setVisibility(View.GONE);
  1.2151 +                    if (mDynamicToolbar.isEnabled()) {
  1.2152 +                        mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
  1.2153 +                        mLayerView.getLayerMarginsAnimator().setMaxMargins(0, 0, 0, 0);
  1.2154 +                    } else {
  1.2155 +                        setToolbarMargin(0);
  1.2156 +                    }
  1.2157 +                } else {
  1.2158 +                    mViewFlipper.setVisibility(View.VISIBLE);
  1.2159 +                    if (mDynamicToolbar.isEnabled()) {
  1.2160 +                        mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
  1.2161 +                        mLayerView.getLayerMarginsAnimator().setMaxMargins(0, mToolbarHeight, 0, 0);
  1.2162 +                    }
  1.2163 +                }
  1.2164 +            }
  1.2165 +        });
  1.2166 +    }
  1.2167 +
  1.2168 +    @Override
  1.2169 +    public boolean onPrepareOptionsMenu(Menu aMenu) {
  1.2170 +        if (aMenu == null)
  1.2171 +            return false;
  1.2172 +
  1.2173 +        if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning))
  1.2174 +            aMenu.findItem(R.id.settings).setEnabled(false);
  1.2175 +
  1.2176 +        Tab tab = Tabs.getInstance().getSelectedTab();
  1.2177 +        MenuItem bookmark = aMenu.findItem(R.id.bookmark);
  1.2178 +        MenuItem back = aMenu.findItem(R.id.back);
  1.2179 +        MenuItem forward = aMenu.findItem(R.id.forward);
  1.2180 +        MenuItem share = aMenu.findItem(R.id.share);
  1.2181 +        MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
  1.2182 +        MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
  1.2183 +        MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
  1.2184 +        MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
  1.2185 +        MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
  1.2186 +        MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
  1.2187 +
  1.2188 +        // Only show the "Quit" menu item on pre-ICS or television devices.
  1.2189 +        // In ICS+, it's easy to kill an app through the task switcher.
  1.2190 +        aMenu.findItem(R.id.quit).setVisible(Build.VERSION.SDK_INT < 14 || HardwareUtils.isTelevision());
  1.2191 +
  1.2192 +        if (tab == null || tab.getURL() == null) {
  1.2193 +            bookmark.setEnabled(false);
  1.2194 +            back.setEnabled(false);
  1.2195 +            forward.setEnabled(false);
  1.2196 +            share.setEnabled(false);
  1.2197 +            saveAsPDF.setEnabled(false);
  1.2198 +            findInPage.setEnabled(false);
  1.2199 +
  1.2200 +            // NOTE: Use MenuUtils.safeSetEnabled because some actions might
  1.2201 +            // be on the BrowserToolbar context menu
  1.2202 +            MenuUtils.safeSetEnabled(aMenu, R.id.page, false);
  1.2203 +            MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, false);
  1.2204 +            MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, false);
  1.2205 +            MenuUtils.safeSetEnabled(aMenu, R.id.site_settings, false);
  1.2206 +            MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, false);
  1.2207 +
  1.2208 +            return true;
  1.2209 +        }
  1.2210 +
  1.2211 +        bookmark.setEnabled(!AboutPages.isAboutReader(tab.getURL()));
  1.2212 +        bookmark.setVisible(!GeckoProfile.get(this).inGuestMode());
  1.2213 +        bookmark.setCheckable(true);
  1.2214 +        bookmark.setChecked(tab.isBookmark());
  1.2215 +        bookmark.setIcon(tab.isBookmark() ? R.drawable.ic_menu_bookmark_remove : R.drawable.ic_menu_bookmark_add);
  1.2216 +
  1.2217 +        back.setEnabled(tab.canDoBack());
  1.2218 +        forward.setEnabled(tab.canDoForward());
  1.2219 +        desktopMode.setChecked(tab.getDesktopMode());
  1.2220 +        desktopMode.setIcon(tab.getDesktopMode() ? R.drawable.ic_menu_desktop_mode_on : R.drawable.ic_menu_desktop_mode_off);
  1.2221 +
  1.2222 +        String url = tab.getURL();
  1.2223 +        if (AboutPages.isAboutReader(url)) {
  1.2224 +            String urlFromReader = ReaderModeUtils.getUrlFromAboutReader(url);
  1.2225 +            if (urlFromReader != null) {
  1.2226 +                url = urlFromReader;
  1.2227 +            }
  1.2228 +        }
  1.2229 +
  1.2230 +        // Disable share menuitem for about:, chrome:, file:, and resource: URIs
  1.2231 +        String scheme = Uri.parse(url).getScheme();
  1.2232 +        share.setVisible(!GeckoProfile.get(this).inGuestMode());
  1.2233 +        share.setEnabled(!(scheme.equals("about") || scheme.equals("chrome") ||
  1.2234 +                           scheme.equals("file") || scheme.equals("resource")));
  1.2235 +
  1.2236 +        // NOTE: Use MenuUtils.safeSetEnabled because some actions might
  1.2237 +        // be on the BrowserToolbar context menu
  1.2238 +        MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
  1.2239 +        MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, tab.hasFeeds());
  1.2240 +        MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, tab.hasOpenSearch());
  1.2241 +        MenuUtils.safeSetEnabled(aMenu, R.id.site_settings, !isAboutHome(tab));
  1.2242 +        MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, !isAboutHome(tab));
  1.2243 +
  1.2244 +        // Action providers are available only ICS+.
  1.2245 +        if (Build.VERSION.SDK_INT >= 14) {
  1.2246 +            final GeckoActionProvider provider = ((GeckoMenuItem) share).getGeckoActionProvider();
  1.2247 +            if (provider != null) {
  1.2248 +                Intent shareIntent = provider.getIntent();
  1.2249 +
  1.2250 +                // For efficiency, the provider's intent is only set once
  1.2251 +                if (shareIntent == null) {
  1.2252 +                    shareIntent = new Intent(Intent.ACTION_SEND);
  1.2253 +                    shareIntent.setType("text/plain");
  1.2254 +                    provider.setIntent(shareIntent);
  1.2255 +                }
  1.2256 +
  1.2257 +                // Replace the existing intent's extras
  1.2258 +                shareIntent.putExtra(Intent.EXTRA_TEXT, url);
  1.2259 +                shareIntent.putExtra(Intent.EXTRA_SUBJECT, tab.getDisplayTitle());
  1.2260 +                shareIntent.putExtra(Intent.EXTRA_TITLE, tab.getDisplayTitle());
  1.2261 +
  1.2262 +                // Clear the existing thumbnail extras so we don't share an old thumbnail.
  1.2263 +                shareIntent.removeExtra("share_screenshot_uri");
  1.2264 +
  1.2265 +                // Include the thumbnail of the page being shared.
  1.2266 +                BitmapDrawable drawable = tab.getThumbnail();
  1.2267 +                if (drawable != null) {
  1.2268 +                    Bitmap thumbnail = drawable.getBitmap();
  1.2269 +
  1.2270 +                    // Kobo uses a custom intent extra for sharing thumbnails.
  1.2271 +                    if (Build.MANUFACTURER.equals("Kobo") && thumbnail != null) {
  1.2272 +                        File cacheDir = getExternalCacheDir();
  1.2273 +
  1.2274 +                        if (cacheDir != null) {
  1.2275 +                            File outFile = new File(cacheDir, "thumbnail.png");
  1.2276 +
  1.2277 +                            try {
  1.2278 +                                java.io.FileOutputStream out = new java.io.FileOutputStream(outFile);
  1.2279 +                                thumbnail.compress(Bitmap.CompressFormat.PNG, 90, out);
  1.2280 +                            } catch (FileNotFoundException e) {
  1.2281 +                                Log.e(LOGTAG, "File not found", e);
  1.2282 +                            }
  1.2283 +
  1.2284 +                            shareIntent.putExtra("share_screenshot_uri", Uri.parse(outFile.getPath()));
  1.2285 +                        }
  1.2286 +                    }
  1.2287 +                }
  1.2288 +            }
  1.2289 +        }
  1.2290 +
  1.2291 +        // Disable save as PDF for about:home and xul pages.
  1.2292 +        saveAsPDF.setEnabled(!(isAboutHome(tab) ||
  1.2293 +                               tab.getContentType().equals("application/vnd.mozilla.xul+xml")));
  1.2294 +
  1.2295 +        // Disable find in page for about:home, since it won't work on Java content.
  1.2296 +        findInPage.setEnabled(!isAboutHome(tab));
  1.2297 +
  1.2298 +        charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
  1.2299 +
  1.2300 +        if (mProfile.inGuestMode())
  1.2301 +            exitGuestMode.setVisible(true);
  1.2302 +        else
  1.2303 +            enterGuestMode.setVisible(true);
  1.2304 +
  1.2305 +        return true;
  1.2306 +    }
  1.2307 +
  1.2308 +    @Override
  1.2309 +    public boolean onOptionsItemSelected(MenuItem item) {
  1.2310 +        Tab tab = null;
  1.2311 +        Intent intent = null;
  1.2312 +
  1.2313 +        final int itemId = item.getItemId();
  1.2314 +
  1.2315 +        // Track the menu action. We don't know much about the context, but we can use this to determine
  1.2316 +        // the frequency of use for various actions.
  1.2317 +        Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, getResources().getResourceEntryName(itemId));
  1.2318 +
  1.2319 +        if (itemId == R.id.bookmark) {
  1.2320 +            tab = Tabs.getInstance().getSelectedTab();
  1.2321 +            if (tab != null) {
  1.2322 +                if (item.isChecked()) {
  1.2323 +                    tab.removeBookmark();
  1.2324 +                    Toast.makeText(this, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
  1.2325 +                    item.setIcon(R.drawable.ic_menu_bookmark_add);
  1.2326 +                } else {
  1.2327 +                    tab.addBookmark();
  1.2328 +                    getButtonToast().show(false,
  1.2329 +                        getResources().getString(R.string.bookmark_added),
  1.2330 +                        getResources().getString(R.string.bookmark_options),
  1.2331 +                        null,
  1.2332 +                        new ButtonToast.ToastListener() {
  1.2333 +                            @Override
  1.2334 +                            public void onButtonClicked() {
  1.2335 +                                showBookmarkDialog();
  1.2336 +                            }
  1.2337 +
  1.2338 +                            @Override
  1.2339 +                            public void onToastHidden(ButtonToast.ReasonHidden reason) { }
  1.2340 +                        });
  1.2341 +                    item.setIcon(R.drawable.ic_menu_bookmark_remove);
  1.2342 +                }
  1.2343 +            }
  1.2344 +            return true;
  1.2345 +        }
  1.2346 +
  1.2347 +        if (itemId == R.id.share) {
  1.2348 +            shareCurrentUrl();
  1.2349 +            return true;
  1.2350 +        }
  1.2351 +
  1.2352 +        if (itemId == R.id.reload) {
  1.2353 +            tab = Tabs.getInstance().getSelectedTab();
  1.2354 +            if (tab != null)
  1.2355 +                tab.doReload();
  1.2356 +            return true;
  1.2357 +        }
  1.2358 +
  1.2359 +        if (itemId == R.id.back) {
  1.2360 +            tab = Tabs.getInstance().getSelectedTab();
  1.2361 +            if (tab != null)
  1.2362 +                tab.doBack();
  1.2363 +            return true;
  1.2364 +        }
  1.2365 +
  1.2366 +        if (itemId == R.id.forward) {
  1.2367 +            tab = Tabs.getInstance().getSelectedTab();
  1.2368 +            if (tab != null)
  1.2369 +                tab.doForward();
  1.2370 +            return true;
  1.2371 +        }
  1.2372 +
  1.2373 +        if (itemId == R.id.save_as_pdf) {
  1.2374 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SaveAs:PDF", null));
  1.2375 +            return true;
  1.2376 +        }
  1.2377 +
  1.2378 +        if (itemId == R.id.settings) {
  1.2379 +            intent = new Intent(this, GeckoPreferences.class);
  1.2380 +            startActivity(intent);
  1.2381 +            return true;
  1.2382 +        }
  1.2383 +
  1.2384 +        if (itemId == R.id.addons) {
  1.2385 +            Tabs.getInstance().loadUrlInTab(AboutPages.ADDONS);
  1.2386 +            return true;
  1.2387 +        }
  1.2388 +
  1.2389 +        if (itemId == R.id.apps) {
  1.2390 +            Tabs.getInstance().loadUrlInTab(AboutPages.APPS);
  1.2391 +            return true;
  1.2392 +        }
  1.2393 +
  1.2394 +        if (itemId == R.id.downloads) {
  1.2395 +            Tabs.getInstance().loadUrlInTab(AboutPages.DOWNLOADS);
  1.2396 +            return true;
  1.2397 +        }
  1.2398 +
  1.2399 +        if (itemId == R.id.char_encoding) {
  1.2400 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("CharEncoding:Get", null));
  1.2401 +            return true;
  1.2402 +        }
  1.2403 +
  1.2404 +        if (itemId == R.id.find_in_page) {
  1.2405 +            mFindInPageBar.show();
  1.2406 +            return true;
  1.2407 +        }
  1.2408 +
  1.2409 +        if (itemId == R.id.desktop_mode) {
  1.2410 +            Tab selectedTab = Tabs.getInstance().getSelectedTab();
  1.2411 +            if (selectedTab == null)
  1.2412 +                return true;
  1.2413 +            JSONObject args = new JSONObject();
  1.2414 +            try {
  1.2415 +                args.put("desktopMode", !item.isChecked());
  1.2416 +                args.put("tabId", selectedTab.getId());
  1.2417 +            } catch (JSONException e) {
  1.2418 +                Log.e(LOGTAG, "error building json arguments");
  1.2419 +            }
  1.2420 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("DesktopMode:Change", args.toString()));
  1.2421 +            return true;
  1.2422 +        }
  1.2423 +
  1.2424 +        if (itemId == R.id.new_tab) {
  1.2425 +            addTab();
  1.2426 +            return true;
  1.2427 +        }
  1.2428 +
  1.2429 +        if (itemId == R.id.new_private_tab) {
  1.2430 +            addPrivateTab();
  1.2431 +            return true;
  1.2432 +        }
  1.2433 +
  1.2434 +        if (itemId == R.id.new_guest_session) {
  1.2435 +            showGuestModeDialog(GuestModeDialog.ENTERING);
  1.2436 +            return true;
  1.2437 +        }
  1.2438 +
  1.2439 +        if (itemId == R.id.exit_guest_session) {
  1.2440 +            showGuestModeDialog(GuestModeDialog.LEAVING);
  1.2441 +            return true;
  1.2442 +        }
  1.2443 +
  1.2444 +        // We have a few menu items that can also be in the context menu. If
  1.2445 +        // we have not already handled the item, give the context menu handler
  1.2446 +        // a chance.
  1.2447 +        if (onContextItemSelected(item)) {
  1.2448 +            return true;
  1.2449 +        }
  1.2450 +
  1.2451 +        return super.onOptionsItemSelected(item);
  1.2452 +    }
  1.2453 +
  1.2454 +    private void showGuestModeDialog(final GuestModeDialog type) {
  1.2455 +        final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
  1.2456 +            @Override
  1.2457 +            public void onPromptFinished(String result) {
  1.2458 +                try {
  1.2459 +                    int itemId = new JSONObject(result).getInt("button");
  1.2460 +                    if (itemId == 0) {
  1.2461 +                        String args = "";
  1.2462 +                        if (type == GuestModeDialog.ENTERING) {
  1.2463 +                            args = GUEST_BROWSING_ARG;
  1.2464 +                        } else {
  1.2465 +                            GeckoProfile.leaveGuestSession(BrowserApp.this);
  1.2466 +                        }
  1.2467 +                        doRestart(args);
  1.2468 +                        GeckoAppShell.systemExit();
  1.2469 +                    }
  1.2470 +                } catch(JSONException ex) {
  1.2471 +                    Log.e(LOGTAG, "Exception reading guest mode prompt result", ex);
  1.2472 +                }
  1.2473 +            }
  1.2474 +        });
  1.2475 +
  1.2476 +        Resources res = getResources();
  1.2477 +        ps.setButtons(new String[] {
  1.2478 +            res.getString(R.string.guest_session_dialog_continue),
  1.2479 +            res.getString(R.string.guest_session_dialog_cancel)
  1.2480 +        });
  1.2481 +
  1.2482 +        int titleString = 0;
  1.2483 +        int msgString = 0;
  1.2484 +        if (type == GuestModeDialog.ENTERING) {
  1.2485 +            titleString = R.string.new_guest_session_title;
  1.2486 +            msgString = R.string.new_guest_session_text;
  1.2487 +        } else {
  1.2488 +            titleString = R.string.exit_guest_session_title;
  1.2489 +            msgString = R.string.exit_guest_session_text;
  1.2490 +        }
  1.2491 +
  1.2492 +        ps.show(res.getString(titleString), res.getString(msgString), null, ListView.CHOICE_MODE_NONE);
  1.2493 +    }
  1.2494 +
  1.2495 +    /**
  1.2496 +     * This will detect if the key pressed is back. If so, will show the history.
  1.2497 +     */
  1.2498 +    @Override
  1.2499 +    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
  1.2500 +        if (keyCode == KeyEvent.KEYCODE_BACK) {
  1.2501 +            Tab tab = Tabs.getInstance().getSelectedTab();
  1.2502 +            if (tab != null) {
  1.2503 +                return tab.showAllHistory();
  1.2504 +            }
  1.2505 +        }
  1.2506 +        return super.onKeyLongPress(keyCode, event);
  1.2507 +    }
  1.2508 +
  1.2509 +    /*
  1.2510 +     * If the app has been launched a certain number of times, and we haven't asked for feedback before,
  1.2511 +     * open a new tab with about:feedback when launching the app from the icon shortcut.
  1.2512 +     */
  1.2513 +    @Override
  1.2514 +    protected void onNewIntent(Intent intent) {
  1.2515 +        super.onNewIntent(intent);
  1.2516 +
  1.2517 +        String action = intent.getAction();
  1.2518 +
  1.2519 +        if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 10 && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
  1.2520 +            String uri = intent.getDataString();
  1.2521 +            GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
  1.2522 +        }
  1.2523 +
  1.2524 +        if (!mInitialized) {
  1.2525 +            return;
  1.2526 +        }
  1.2527 +
  1.2528 +        if (Intent.ACTION_VIEW.equals(action)) {
  1.2529 +            // Dismiss editing mode if the user is loading a URL from an external app.
  1.2530 +            mBrowserToolbar.cancelEdit();
  1.2531 +
  1.2532 +            Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
  1.2533 +            return;
  1.2534 +        }
  1.2535 +
  1.2536 +        // Only solicit feedback when the app has been launched from the icon shortcut.
  1.2537 +        if (!Intent.ACTION_MAIN.equals(action)) {
  1.2538 +            return;
  1.2539 +        }
  1.2540 +
  1.2541 +        (new UiAsyncTask<Void, Void, Boolean>(ThreadUtils.getBackgroundHandler()) {
  1.2542 +            @Override
  1.2543 +            public synchronized Boolean doInBackground(Void... params) {
  1.2544 +                // Check to see how many times the app has been launched.
  1.2545 +                SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
  1.2546 +                String keyName = getPackageName() + ".feedback_launch_count";
  1.2547 +                int launchCount = settings.getInt(keyName, 0);
  1.2548 +                if (launchCount >= FEEDBACK_LAUNCH_COUNT)
  1.2549 +                    return false;
  1.2550 +
  1.2551 +                // Increment the launch count and store the new value.
  1.2552 +                launchCount++;
  1.2553 +                settings.edit().putInt(keyName, launchCount).commit();
  1.2554 +
  1.2555 +                // If we've reached our magic number, show the feedback page.
  1.2556 +                return launchCount == FEEDBACK_LAUNCH_COUNT;
  1.2557 +            }
  1.2558 +
  1.2559 +            @Override
  1.2560 +            public void onPostExecute(Boolean shouldShowFeedbackPage) {
  1.2561 +                if (shouldShowFeedbackPage)
  1.2562 +                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feedback:Show", null));
  1.2563 +            }
  1.2564 +        }).execute();
  1.2565 +    }
  1.2566 +
  1.2567 +    @Override
  1.2568 +    protected NotificationClient makeNotificationClient() {
  1.2569 +        // The service is local to Fennec, so we can use it to keep
  1.2570 +        // Fennec alive during downloads.
  1.2571 +        return new ServiceNotificationClient(getApplicationContext());
  1.2572 +    }
  1.2573 +
  1.2574 +    private void resetFeedbackLaunchCount() {
  1.2575 +        ThreadUtils.postToBackgroundThread(new Runnable() {
  1.2576 +            @Override
  1.2577 +            public synchronized void run() {
  1.2578 +                SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
  1.2579 +                settings.edit().putInt(getPackageName() + ".feedback_launch_count", 0).commit();
  1.2580 +            }
  1.2581 +        });
  1.2582 +    }
  1.2583 +
  1.2584 +    private void getLastUrl() {
  1.2585 +        (new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
  1.2586 +            @Override
  1.2587 +            public synchronized String doInBackground(Void... params) {
  1.2588 +                // Get the most recent URL stored in browser history.
  1.2589 +                String url = "";
  1.2590 +                Cursor c = null;
  1.2591 +                try {
  1.2592 +                    c = BrowserDB.getRecentHistory(getContentResolver(), 1);
  1.2593 +                    if (c.moveToFirst()) {
  1.2594 +                        url = c.getString(c.getColumnIndexOrThrow(Combined.URL));
  1.2595 +                    }
  1.2596 +                } finally {
  1.2597 +                    if (c != null)
  1.2598 +                        c.close();
  1.2599 +                }
  1.2600 +                return url;
  1.2601 +            }
  1.2602 +
  1.2603 +            @Override
  1.2604 +            public void onPostExecute(String url) {
  1.2605 +                // Don't bother sending a message if there is no URL.
  1.2606 +                if (url.length() > 0)
  1.2607 +                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feedback:LastUrl", url));
  1.2608 +            }
  1.2609 +        }).execute();
  1.2610 +    }
  1.2611 +
  1.2612 +    // HomePager.OnNewTabsListener
  1.2613 +    @Override
  1.2614 +    public void onNewTabs(String[] urls) {
  1.2615 +        final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB);
  1.2616 +
  1.2617 +        for (String url : urls) {
  1.2618 +            if (!maybeSwitchToTab(url, flags)) {
  1.2619 +                openUrlAndStopEditing(url, true);
  1.2620 +            }
  1.2621 +        }
  1.2622 +    }
  1.2623 +
  1.2624 +    // HomePager.OnUrlOpenListener
  1.2625 +    @Override
  1.2626 +    public void onUrlOpen(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
  1.2627 +        if (flags.contains(OnUrlOpenListener.Flags.OPEN_WITH_INTENT)) {
  1.2628 +            Intent intent = new Intent(Intent.ACTION_VIEW);
  1.2629 +            intent.setData(Uri.parse(url));
  1.2630 +            startActivity(intent);
  1.2631 +        } else if (!maybeSwitchToTab(url, flags)) {
  1.2632 +            openUrlAndStopEditing(url);
  1.2633 +        }
  1.2634 +    }
  1.2635 +
  1.2636 +    // BrowserSearch.OnSearchListener
  1.2637 +    @Override
  1.2638 +    public void onSearch(SearchEngine engine, String text) {
  1.2639 +        recordSearch(engine, "barsuggest");
  1.2640 +        openUrlAndStopEditing(text, engine.name);
  1.2641 +    }
  1.2642 +
  1.2643 +    // BrowserSearch.OnEditSuggestionListener
  1.2644 +    @Override
  1.2645 +    public void onEditSuggestion(String suggestion) {
  1.2646 +        mBrowserToolbar.onEditSuggestion(suggestion);
  1.2647 +    }
  1.2648 +
  1.2649 +    @Override
  1.2650 +    public int getLayout() { return R.layout.gecko_app; }
  1.2651 +
  1.2652 +    @Override
  1.2653 +    protected String getDefaultProfileName() throws NoMozillaDirectoryException {
  1.2654 +        return GeckoProfile.getDefaultProfileName(this);
  1.2655 +    }
  1.2656 +
  1.2657 +    /**
  1.2658 +     * Launch UI that lets the user update Firefox.
  1.2659 +     *
  1.2660 +     * This depends on the current channel: Release and Beta both direct to the
  1.2661 +     * Google Play Store.  If updating is enabled, Aurora, Nightly, and custom
  1.2662 +     * builds open about:, which provides an update interface.
  1.2663 +     *
  1.2664 +     * If updating is not enabled, this simply logs an error.
  1.2665 +     *
  1.2666 +     * @return true if update UI was launched.
  1.2667 +     */
  1.2668 +    protected boolean handleUpdaterLaunch() {
  1.2669 +        if (AppConstants.RELEASE_BUILD) {
  1.2670 +            Intent intent = new Intent(Intent.ACTION_VIEW);
  1.2671 +            intent.setData(Uri.parse("market://details?id=" + getPackageName()));
  1.2672 +            startActivity(intent);
  1.2673 +            return true;
  1.2674 +        }
  1.2675 +
  1.2676 +        if (AppConstants.MOZ_UPDATER) {
  1.2677 +            Tabs.getInstance().loadUrlInTab(AboutPages.UPDATER);
  1.2678 +            return true;
  1.2679 +        }
  1.2680 +
  1.2681 +        Log.w(LOGTAG, "No candidate updater found; ignoring launch request.");
  1.2682 +        return false;
  1.2683 +    }
  1.2684 +
  1.2685 +    /* Implementing ActionModeCompat.Presenter */
  1.2686 +    @Override
  1.2687 +    public void startActionModeCompat(final ActionModeCompat.Callback callback) {
  1.2688 +        // If actionMode is null, we're not currently showing one. Flip to the action mode view
  1.2689 +        if (mActionMode == null) {
  1.2690 +            mViewFlipper.showNext();
  1.2691 +            LayerMarginsAnimator margins = mLayerView.getLayerMarginsAnimator();
  1.2692 +
  1.2693 +            // If the toolbar is dynamic and not currently showing, just slide it in
  1.2694 +            if (mDynamicToolbar.isEnabled() && !margins.areMarginsShown()) {
  1.2695 +                margins.setMaxMargins(0, mViewFlipper.getHeight(), 0, 0);
  1.2696 +                mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  1.2697 +                mShowActionModeEndAnimation = true;
  1.2698 +            } else {
  1.2699 +                // Otherwise, we animate the actionbar itself
  1.2700 +                mActionBar.animateIn();
  1.2701 +            }
  1.2702 +
  1.2703 +            mDynamicToolbar.setPinned(true, PinReason.ACTION_MODE);
  1.2704 +        } else {
  1.2705 +            // Otherwise, we're already showing an action mode. Just finish it and show the new one
  1.2706 +            mActionMode.finish();
  1.2707 +        }
  1.2708 +
  1.2709 +        mActionMode = new ActionModeCompat(BrowserApp.this, callback, mActionBar);
  1.2710 +        if (callback.onCreateActionMode(mActionMode, mActionMode.getMenu())) {
  1.2711 +            mActionMode.invalidate();
  1.2712 +        }
  1.2713 +    }
  1.2714 +
  1.2715 +    /* Implementing ActionModeCompat.Presenter */
  1.2716 +    @Override
  1.2717 +    public void endActionModeCompat() {
  1.2718 +        if (mActionMode == null) {
  1.2719 +            return;
  1.2720 +        }
  1.2721 +
  1.2722 +        mActionMode.finish();
  1.2723 +        mActionMode = null;
  1.2724 +        mDynamicToolbar.setPinned(false, PinReason.ACTION_MODE);
  1.2725 +
  1.2726 +        mViewFlipper.showPrevious();
  1.2727 +
  1.2728 +        // Only slide the urlbar out if it was hidden when the action mode started
  1.2729 +        // Don't animate hiding it so that there's no flash as we switch back to url mode
  1.2730 +        if (mShowActionModeEndAnimation) {
  1.2731 +            mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
  1.2732 +            mShowActionModeEndAnimation = false;
  1.2733 +        }
  1.2734 +    }
  1.2735 +
  1.2736 +    @Override
  1.2737 +    protected HealthRecorder createHealthRecorder(final Context context,
  1.2738 +                                                  final String profilePath,
  1.2739 +                                                  final EventDispatcher dispatcher,
  1.2740 +                                                  final String osLocale,
  1.2741 +                                                  final String appLocale,
  1.2742 +                                                  final SessionInformation previousSession) {
  1.2743 +        return new BrowserHealthRecorder(context,
  1.2744 +                                         GeckoSharedPrefs.forApp(context),
  1.2745 +                                         profilePath,
  1.2746 +                                         dispatcher,
  1.2747 +                                         osLocale,
  1.2748 +                                         appLocale,
  1.2749 +                                         previousSession);
  1.2750 +    }
  1.2751 +}

mercurial