mobile/android/base/BrowserApp.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import java.io.File;
     9 import java.io.FileNotFoundException;
    10 import java.net.URLEncoder;
    11 import java.util.EnumSet;
    12 import java.util.Vector;
    14 import org.json.JSONArray;
    15 import org.json.JSONException;
    16 import org.json.JSONObject;
    18 import org.mozilla.gecko.DynamicToolbar.PinReason;
    19 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
    20 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
    21 import org.mozilla.gecko.Telemetry;
    22 import org.mozilla.gecko.TelemetryContract;
    23 import org.mozilla.gecko.animation.PropertyAnimator;
    24 import org.mozilla.gecko.animation.ViewHelper;
    25 import org.mozilla.gecko.db.BrowserContract.Combined;
    26 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
    27 import org.mozilla.gecko.db.BrowserDB;
    28 import org.mozilla.gecko.favicons.Favicons;
    29 import org.mozilla.gecko.favicons.LoadFaviconTask;
    30 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
    31 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
    32 import org.mozilla.gecko.fxa.FirefoxAccounts;
    33 import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
    34 import org.mozilla.gecko.gfx.BitmapUtils;
    35 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
    36 import org.mozilla.gecko.gfx.LayerMarginsAnimator;
    37 import org.mozilla.gecko.gfx.LayerView;
    38 import org.mozilla.gecko.health.BrowserHealthRecorder;
    39 import org.mozilla.gecko.health.BrowserHealthReporter;
    40 import org.mozilla.gecko.health.HealthRecorder;
    41 import org.mozilla.gecko.health.SessionInformation;
    42 import org.mozilla.gecko.home.BrowserSearch;
    43 import org.mozilla.gecko.home.HomeBanner;
    44 import org.mozilla.gecko.home.HomePanelsManager;
    45 import org.mozilla.gecko.home.HomePager;
    46 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
    47 import org.mozilla.gecko.home.SearchEngine;
    48 import org.mozilla.gecko.menu.GeckoMenu;
    49 import org.mozilla.gecko.menu.GeckoMenuItem;
    50 import org.mozilla.gecko.preferences.GeckoPreferences;
    51 import org.mozilla.gecko.prompts.Prompt;
    52 import org.mozilla.gecko.prompts.PromptListItem;
    53 import org.mozilla.gecko.sync.setup.SyncAccounts;
    54 import org.mozilla.gecko.toolbar.AutocompleteHandler;
    55 import org.mozilla.gecko.toolbar.BrowserToolbar;
    56 import org.mozilla.gecko.toolbar.ToolbarProgressView;
    57 import org.mozilla.gecko.util.Clipboard;
    58 import org.mozilla.gecko.util.GamepadUtils;
    59 import org.mozilla.gecko.util.HardwareUtils;
    60 import org.mozilla.gecko.util.MenuUtils;
    61 import org.mozilla.gecko.util.StringUtils;
    62 import org.mozilla.gecko.util.ThreadUtils;
    63 import org.mozilla.gecko.util.UiAsyncTask;
    64 import org.mozilla.gecko.widget.ButtonToast;
    65 import org.mozilla.gecko.widget.GeckoActionProvider;
    67 import android.app.Activity;
    68 import android.app.AlertDialog;
    69 import android.content.ContentValues;
    70 import android.content.Context;
    71 import android.content.DialogInterface;
    72 import android.content.Intent;
    73 import android.content.SharedPreferences;
    74 import android.content.res.Configuration;
    75 import android.content.res.Resources;
    76 import android.database.Cursor;
    77 import android.graphics.Bitmap;
    78 import android.graphics.Rect;
    79 import android.graphics.drawable.BitmapDrawable;
    80 import android.graphics.drawable.Drawable;
    81 import android.net.Uri;
    82 import android.nfc.NdefMessage;
    83 import android.nfc.NdefRecord;
    84 import android.nfc.NfcAdapter;
    85 import android.nfc.NfcEvent;
    86 import android.os.Build;
    87 import android.os.Bundle;
    88 import android.support.v4.app.FragmentManager;
    89 import android.text.TextUtils;
    90 import android.util.Log;
    91 import android.view.InputDevice;
    92 import android.view.KeyEvent;
    93 import android.view.Menu;
    94 import android.view.MenuInflater;
    95 import android.view.MenuItem;
    96 import android.view.MotionEvent;
    97 import android.view.SubMenu;
    98 import android.view.View;
    99 import android.view.ViewGroup;
   100 import android.view.ViewStub;
   101 import android.view.ViewTreeObserver;
   102 import android.view.Window;
   103 import android.view.animation.Interpolator;
   104 import android.widget.RelativeLayout;
   105 import android.widget.ListView;
   106 import android.widget.Toast;
   107 import android.widget.ViewFlipper;
   109 abstract public class BrowserApp extends GeckoApp
   110                                  implements TabsPanel.TabsLayoutChangeListener,
   111                                             PropertyAnimator.PropertyAnimationListener,
   112                                             View.OnKeyListener,
   113                                             LayerView.OnMetricsChangedListener,
   114                                             BrowserSearch.OnSearchListener,
   115                                             BrowserSearch.OnEditSuggestionListener,
   116                                             HomePager.OnNewTabsListener,
   117                                             OnUrlOpenListener,
   118                                             ActionModeCompat.Presenter {
   119     private static final String LOGTAG = "GeckoBrowserApp";
   121     private static final int TABS_ANIMATION_DURATION = 450;
   123     private static final int READER_ADD_SUCCESS = 0;
   124     private static final int READER_ADD_FAILED = 1;
   125     private static final int READER_ADD_DUPLICATE = 2;
   127     private static final String ADD_SHORTCUT_TOAST = "add_shortcut_toast";
   128     public static final String GUEST_BROWSING_ARG = "--guest";
   130     private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding";
   132     private static final String BROWSER_SEARCH_TAG = "browser_search";
   133     private BrowserSearch mBrowserSearch;
   134     private View mBrowserSearchContainer;
   136     public ViewFlipper mViewFlipper;
   137     public ActionModeCompatView mActionBar;
   138     private BrowserToolbar mBrowserToolbar;
   139     private ToolbarProgressView mProgressView;
   140     private HomePager mHomePager;
   141     private TabsPanel mTabsPanel;
   142     private ViewGroup mHomePagerContainer;
   143     protected Telemetry.Timer mAboutHomeStartupTimer = null;
   144     private ActionModeCompat mActionMode;
   145     private boolean mShowActionModeEndAnimation = false;
   147     private static final int GECKO_TOOLS_MENU = -1;
   148     private static final int ADDON_MENU_OFFSET = 1000;
   149     private static class MenuItemInfo {
   150         public int id;
   151         public String label;
   152         public String icon;
   153         public boolean checkable = false;
   154         public boolean checked = false;
   155         public boolean enabled = true;
   156         public boolean visible = true;
   157         public int parent;
   158         public boolean added = false;    // So we can re-add after a locale change.
   159     }
   161     // The types of guest mdoe dialogs we show
   162     private static enum GuestModeDialog {
   163         ENTERING,
   164         LEAVING
   165     }
   167     private Vector<MenuItemInfo> mAddonMenuItemsCache;
   168     private PropertyAnimator mMainLayoutAnimator;
   170     private static final Interpolator sTabsInterpolator = new Interpolator() {
   171         @Override
   172         public float getInterpolation(float t) {
   173             t -= 1.0f;
   174             return t * t * t * t * t + 1.0f;
   175         }
   176     };
   178     private FindInPageBar mFindInPageBar;
   179     private MediaCastingBar mMediaCastingBar;
   181     // We'll ask for feedback after the user launches the app this many times.
   182     private static final int FEEDBACK_LAUNCH_COUNT = 15;
   184     // Stored value of the toolbar height, so we know when it's changed.
   185     private int mToolbarHeight = 0;
   187     // Stored value of whether the last metrics change allowed for toolbar
   188     // scrolling.
   189     private boolean mDynamicToolbarCanScroll = false;
   191     private SharedPreferencesHelper mSharedPreferencesHelper;
   193     private OrderedBroadcastHelper mOrderedBroadcastHelper;
   195     private BrowserHealthReporter mBrowserHealthReporter;
   197     // The tab to be selected on editing mode exit.
   198     private Integer mTargetTabForEditingMode = null;
   200     // The animator used to toggle HomePager visibility has a race where if the HomePager is shown
   201     // (starting the animation), the HomePager is hidden, and the HomePager animation completes,
   202     // both the web content and the HomePager will be hidden. This flag is used to prevent the
   203     // race by determining if the web content should be hidden at the animation's end.
   204     private boolean mHideWebContentOnAnimationEnd = false;
   206     private DynamicToolbar mDynamicToolbar = new DynamicToolbar();
   208     @Override
   209     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
   210         if (tab == null) {
   211             // Only RESTORED is allowed a null tab: it's the only event that
   212             // isn't tied to a specific tab.
   213             if (msg != Tabs.TabEvents.RESTORED) {
   214                 throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
   215             }
   216             return;
   217         }
   219         Log.d(LOGTAG, "BrowserApp.onTabChanged: " + tab.getId() + ": " + msg);
   220         switch(msg) {
   221             case LOCATION_CHANGE:
   222                 if (Tabs.getInstance().isSelectedTab(tab)) {
   223                     maybeCancelFaviconLoad(tab);
   224                 }
   225                 // fall through
   226             case SELECTED:
   227                 if (Tabs.getInstance().isSelectedTab(tab)) {
   228                     updateHomePagerForTab(tab);
   230                     final TabsPanel.Panel panel = tab.isPrivate()
   231                                                 ? TabsPanel.Panel.PRIVATE_TABS
   232                                                 : TabsPanel.Panel.NORMAL_TABS;
   234                     if (areTabsShown() && mTabsPanel.getCurrentPanel() != panel) {
   235                         showTabs(panel);
   236                     }
   237                 }
   238                 break;
   239             case START:
   240                 if (Tabs.getInstance().isSelectedTab(tab)) {
   241                     invalidateOptionsMenu();
   243                     if (mDynamicToolbar.isEnabled()) {
   244                         mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   245                     }
   246                 }
   247                 break;
   248             case LOAD_ERROR:
   249             case STOP:
   250             case MENU_UPDATED:
   251                 if (Tabs.getInstance().isSelectedTab(tab)) {
   252                     invalidateOptionsMenu();
   253                 }
   254                 break;
   255             case PAGE_SHOW:
   256                 loadFavicon(tab);
   257                 break;
   258             case LINK_FAVICON:
   259                 // If tab is not loading and the favicon is updated, we
   260                 // want to load the image straight away. If tab is still
   261                 // loading, we only load the favicon once the page's content
   262                 // is fully loaded.
   263                 if (tab.getState() != Tab.STATE_LOADING) {
   264                     loadFavicon(tab);
   265                 }
   266                 break;
   267         }
   268         super.onTabChanged(tab, msg, data);
   269     }
   271     @Override
   272     public boolean onKey(View v, int keyCode, KeyEvent event) {
   273         // Global onKey handler. This is called if the focused UI doesn't
   274         // handle the key event, and before Gecko swallows the events.
   275         if (event.getAction() != KeyEvent.ACTION_DOWN) {
   276             return false;
   277         }
   279         // Gamepad support only exists in API-level >= 9
   280         if (Build.VERSION.SDK_INT >= 9 &&
   281             (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
   282             switch (keyCode) {
   283                 case KeyEvent.KEYCODE_BUTTON_Y:
   284                     // Toggle/focus the address bar on gamepad-y button.
   285                     if (mViewFlipper.getVisibility() == View.VISIBLE) {
   286                         if (mDynamicToolbar.isEnabled() && !isHomePagerVisible()) {
   287                             mDynamicToolbar.setVisible(false, VisibilityTransition.ANIMATE);
   288                             if (mLayerView != null) {
   289                                 mLayerView.requestFocus();
   290                             }
   291                         } else {
   292                             // Just focus the address bar when about:home is visible
   293                             // or when the dynamic toolbar isn't enabled.
   294                             mBrowserToolbar.requestFocusFromTouch();
   295                         }
   296                     } else {
   297                         mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   298                         mBrowserToolbar.requestFocusFromTouch();
   299                     }
   300                     return true;
   301                 case KeyEvent.KEYCODE_BUTTON_L1:
   302                     // Go back on L1
   303                     Tabs.getInstance().getSelectedTab().doBack();
   304                     return true;
   305                 case KeyEvent.KEYCODE_BUTTON_R1:
   306                     // Go forward on R1
   307                     Tabs.getInstance().getSelectedTab().doForward();
   308                     return true;
   309             }
   310         }
   312         // Check if this was a shortcut. Meta keys exists only on 11+.
   313         final Tab tab = Tabs.getInstance().getSelectedTab();
   314         if (Build.VERSION.SDK_INT >= 11 && tab != null && event.isCtrlPressed()) {
   315             switch (keyCode) {
   316                 case KeyEvent.KEYCODE_LEFT_BRACKET:
   317                     tab.doBack();
   318                     return true;
   320                 case KeyEvent.KEYCODE_RIGHT_BRACKET:
   321                     tab.doForward();
   322                     return true;
   324                 case KeyEvent.KEYCODE_R:
   325                     tab.doReload();
   326                     return true;
   328                 case KeyEvent.KEYCODE_PERIOD:
   329                     tab.doStop();
   330                     return true;
   332                 case KeyEvent.KEYCODE_T:
   333                     addTab();
   334                     return true;
   336                 case KeyEvent.KEYCODE_W:
   337                     Tabs.getInstance().closeTab(tab);
   338                     return true;
   340                 case KeyEvent.KEYCODE_F:
   341                     mFindInPageBar.show();
   342                 return true;
   343             }
   344         }
   346         return false;
   347     }
   349     @Override
   350     public boolean onKeyDown(int keyCode, KeyEvent event) {
   351         if (!mBrowserToolbar.isEditing() && onKey(null, keyCode, event)) {
   352             return true;
   353         }
   354         return super.onKeyDown(keyCode, event);
   355     }
   357     void handleReaderListStatusRequest(final String url) {
   358         ThreadUtils.postToBackgroundThread(new Runnable() {
   359             @Override
   360             public void run() {
   361                 final int inReadingList = BrowserDB.isReadingListItem(getContentResolver(), url) ? 1 : 0;
   363                 final JSONObject json = new JSONObject();
   364                 try {
   365                     json.put("url", url);
   366                     json.put("inReadingList", inReadingList);
   367                 } catch (JSONException e) {
   368                     Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
   369                     return;
   370                 }
   372                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
   373             }
   374         });
   375     }
   377     private void handleReaderAdded(int result, final ContentValues values) {
   378         if (result != READER_ADD_SUCCESS) {
   379             if (result == READER_ADD_FAILED) {
   380                 showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
   381             } else if (result == READER_ADD_DUPLICATE) {
   382                 showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
   383             }
   385             return;
   386         }
   388         ThreadUtils.postToBackgroundThread(new Runnable() {
   389             @Override
   390             public void run() {
   391                 BrowserDB.addReadingListItem(getContentResolver(), values);
   392                 showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
   393             }
   394         });
   395     }
   397     private ContentValues messageToReadingListContentValues(JSONObject message) {
   398         final ContentValues values = new ContentValues();
   399         values.put(ReadingListItems.URL, message.optString("url"));
   400         values.put(ReadingListItems.TITLE, message.optString("title"));
   401         values.put(ReadingListItems.LENGTH, message.optInt("length"));
   402         values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
   403         return values;
   404     }
   406     void handleReaderRemoved(final String url) {
   407         ThreadUtils.postToBackgroundThread(new Runnable() {
   408             @Override
   409             public void run() {
   410                 BrowserDB.removeReadingListItemWithURL(getContentResolver(), url);
   411                 showToast(R.string.reading_list_removed, Toast.LENGTH_SHORT);
   413                 final int count = BrowserDB.getReadingListCount(getContentResolver());
   414                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListCountUpdated", Integer.toString(count)));
   415             }
   416         });
   417     }
   419     @Override
   420     public void onCreate(Bundle savedInstanceState) {
   421         mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
   423         final Intent intent = getIntent();
   425         String args = intent.getStringExtra("args");
   426         if (args != null && args.contains(GUEST_BROWSING_ARG)) {
   427             mProfile = GeckoProfile.createGuestProfile(this);
   428         } else {
   429             GeckoProfile.maybeCleanupGuestProfile(this);
   430         }
   432         // This has to be prepared prior to calling GeckoApp.onCreate, because
   433         // widget code and BrowserToolbar need it, and they're created by the
   434         // layout, which GeckoApp takes care of.
   435         ((GeckoApplication) getApplication()).prepareLightweightTheme();
   436         super.onCreate(savedInstanceState);
   438         mViewFlipper = (ViewFlipper) findViewById(R.id.browser_actionbar);
   439         mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
   441         mBrowserToolbar = (BrowserToolbar) findViewById(R.id.browser_toolbar);
   442         mProgressView = (ToolbarProgressView) findViewById(R.id.progress);
   443         mBrowserToolbar.setProgressBar(mProgressView);
   444         if (Intent.ACTION_VIEW.equals(intent.getAction())) {
   445             // Show the target URL immediately in the toolbar.
   446             mBrowserToolbar.setTitle(intent.getDataString());
   448             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
   449         }
   451         ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideTabsTouchListener());
   452         ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
   453             @Override
   454             public boolean onInterceptMotionEvent(View view, MotionEvent event) {
   455                 // If we get a gamepad panning MotionEvent while the focus is not on the layerview,
   456                 // put the focus on the layerview and carry on
   457                 if (mLayerView != null && !mLayerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
   458                     if (mHomePager == null) {
   459                         return false;
   460                     }
   462                     if (isHomePagerVisible()) {
   463                         mLayerView.requestFocus();
   464                     } else {
   465                         mHomePager.requestFocus();
   466                     }
   467                 }
   468                 return false;
   469             }
   470         });
   472         mHomePagerContainer = (ViewGroup) findViewById(R.id.home_pager_container);
   474         mBrowserSearchContainer = findViewById(R.id.search_container);
   475         mBrowserSearch = (BrowserSearch) getSupportFragmentManager().findFragmentByTag(BROWSER_SEARCH_TAG);
   476         if (mBrowserSearch == null) {
   477             mBrowserSearch = BrowserSearch.newInstance();
   478             mBrowserSearch.setUserVisibleHint(false);
   479         }
   481         setBrowserToolbarListeners();
   483         mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
   484         mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
   486         registerEventListener("CharEncoding:Data");
   487         registerEventListener("CharEncoding:State");
   488         registerEventListener("Feedback:LastUrl");
   489         registerEventListener("Feedback:OpenPlayStore");
   490         registerEventListener("Feedback:MaybeLater");
   491         registerEventListener("Telemetry:Gather");
   492         registerEventListener("Settings:Show");
   493         registerEventListener("Updater:Launch");
   494         registerEventListener("Menu:Add");
   495         registerEventListener("Menu:Remove");
   496         registerEventListener("Menu:Update");
   497         registerEventListener("Accounts:Create");
   498         registerEventListener("Accounts:Exist");
   499         registerEventListener("Prompt:ShowTop");
   501         Distribution.init(this);
   502         JavaAddonManager.getInstance().init(getApplicationContext());
   503         mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
   504         mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
   505         mBrowserHealthReporter = new BrowserHealthReporter();
   507         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
   508             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
   509             if (nfc != null) {
   510                 nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
   511                     @Override
   512                     public NdefMessage createNdefMessage(NfcEvent event) {
   513                         Tab tab = Tabs.getInstance().getSelectedTab();
   514                         if (tab == null || tab.isPrivate()) {
   515                             return null;
   516                         }
   517                         return new NdefMessage(new NdefRecord[] { NdefRecord.createUri(tab.getURL()) });
   518                     }
   519                 }, this);
   520             }
   521         }
   523         if (savedInstanceState != null) {
   524             mDynamicToolbar.onRestoreInstanceState(savedInstanceState);
   525             mHomePagerContainer.setPadding(0, savedInstanceState.getInt(STATE_ABOUT_HOME_TOP_PADDING), 0, 0);
   526         }
   528         mDynamicToolbar.setEnabledChangedListener(new DynamicToolbar.OnEnabledChangedListener() {
   529             @Override
   530             public void onEnabledChanged(boolean enabled) {
   531                 setDynamicToolbarEnabled(enabled);
   532             }
   533         });
   535         // Set the maximum bits-per-pixel the favicon system cares about.
   536         IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
   537     }
   539     @Override
   540     public void onBackPressed() {
   541         if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
   542             super.onBackPressed();
   543             return;
   544         }
   546         if (mBrowserToolbar.onBackPressed()) {
   547             return;
   548         }
   550         if (mActionMode != null) {
   551             endActionModeCompat();
   552             return;
   553         }
   555         super.onBackPressed();
   556     }
   558     @Override
   559     public void onResume() {
   560         super.onResume();
   561         unregisterEventListener("Prompt:ShowTop");
   562     }
   564     @Override
   565     public void onPause() {
   566         super.onPause();
   567         // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
   568         registerEventListener("Prompt:ShowTop");
   569     }
   571     private void setBrowserToolbarListeners() {
   572         mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() {
   573             public void onActivate() {
   574                 enterEditingMode();
   575             }
   576         });
   578         mBrowserToolbar.setOnCommitListener(new BrowserToolbar.OnCommitListener() {
   579             public void onCommit() {
   580                 commitEditingMode();
   581             }
   582         });
   584         mBrowserToolbar.setOnDismissListener(new BrowserToolbar.OnDismissListener() {
   585             public void onDismiss() {
   586                 mBrowserToolbar.cancelEdit();
   587             }
   588         });
   590         mBrowserToolbar.setOnFilterListener(new BrowserToolbar.OnFilterListener() {
   591             public void onFilter(String searchText, AutocompleteHandler handler) {
   592                 filterEditingMode(searchText, handler);
   593             }
   594         });
   596         mBrowserToolbar.setOnFocusChangeListener(new View.OnFocusChangeListener() {
   597             @Override
   598             public void onFocusChange(View v, boolean hasFocus) {
   599                 if (isHomePagerVisible()) {
   600                     mHomePager.onToolbarFocusChange(hasFocus);
   601                 }
   602             }
   603         });
   605         mBrowserToolbar.setOnStartEditingListener(new BrowserToolbar.OnStartEditingListener() {
   606             public void onStartEditing() {
   607                 // Temporarily disable doorhanger notifications.
   608                 mDoorHangerPopup.disable();
   609             }
   610         });
   612         mBrowserToolbar.setOnStopEditingListener(new BrowserToolbar.OnStopEditingListener() {
   613             public void onStopEditing() {
   614                 selectTargetTabForEditingMode();
   616                 // Since the underlying LayerView is set visible in hideHomePager, we would
   617                 // ordinarily want to call it first. However, hideBrowserSearch changes the
   618                 // visibility of the HomePager and hideHomePager will take no action if the
   619                 // HomePager is hidden, so we want to call hideBrowserSearch to restore the
   620                 // HomePager visibility first.
   621                 hideBrowserSearch();
   622                 hideHomePager();
   624                 // Re-enable doorhanger notifications. They may trigger on the selected tab above.
   625                 mDoorHangerPopup.enable();
   626             }
   627         });
   629         // Intercept key events for gamepad shortcuts
   630         mBrowserToolbar.setOnKeyListener(this);
   631     }
   633     private void showBookmarkDialog() {
   634         final Tab tab = Tabs.getInstance().getSelectedTab();
   635         final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
   636             @Override
   637             public void onPromptFinished(String result) {
   638                 int itemId = -1;
   639                 try {
   640                   itemId = new JSONObject(result).getInt("button");
   641                 } catch(JSONException ex) {
   642                     Log.e(LOGTAG, "Exception reading bookmark prompt result", ex);
   643                 }
   645                 if (tab == null)
   646                     return;
   648                 if (itemId == 0) {
   649                     new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
   650                 } else if (itemId == 1) {
   651                     String url = tab.getURL();
   652                     String title = tab.getDisplayTitle();
   653                     Bitmap favicon = tab.getFavicon();
   654                     if (url != null && title != null) {
   655                         GeckoAppShell.createShortcut(title, url, url, favicon, "");
   656                     }
   657                 }
   658             }
   659         });
   661         final PromptListItem[] items = new PromptListItem[2];
   662         Resources res = getResources();
   663         items[0] = new PromptListItem(res.getString(R.string.contextmenu_edit_bookmark));
   664         items[1] = new PromptListItem(res.getString(R.string.contextmenu_add_to_launcher));
   666         ps.show("", "", items, ListView.CHOICE_MODE_NONE);
   667     }
   669     private void setDynamicToolbarEnabled(boolean enabled) {
   670         ThreadUtils.assertOnUiThread();
   672         if (enabled) {
   673             if (mLayerView != null) {
   674                 mLayerView.setOnMetricsChangedListener(this);
   675             }
   676             setToolbarMargin(0);
   677             mHomePagerContainer.setPadding(0, mViewFlipper.getHeight(), 0, 0);
   678         } else {
   679             // Immediately show the toolbar when disabling the dynamic
   680             // toolbar.
   681             if (mLayerView != null) {
   682                 mLayerView.setOnMetricsChangedListener(null);
   683             }
   684             mHomePagerContainer.setPadding(0, 0, 0, 0);
   685             if (mViewFlipper != null) {
   686                 ViewHelper.setTranslationY(mViewFlipper, 0);
   687             }
   688         }
   690         refreshToolbarHeight();
   691     }
   693     private static boolean isAboutHome(final Tab tab) {
   694         return AboutPages.isAboutHome(tab.getURL());
   695     }
   697     @Override
   698     public boolean onSearchRequested() {
   699         enterEditingMode();
   700         return true;
   701     }
   703     @Override
   704     public boolean onContextItemSelected(MenuItem item) {
   705         final int itemId = item.getItemId();
   706         if (itemId == R.id.pasteandgo) {
   707             String text = Clipboard.getText();
   708             if (!TextUtils.isEmpty(text)) {
   709                 Tabs.getInstance().loadUrl(text);
   710                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
   711                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "pasteandgo");
   712             }
   713             return true;
   714         }
   716         if (itemId == R.id.site_settings) {
   717             // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
   718             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Permissions:Get", null));
   719             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
   720                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "site_settings");
   721             }
   722             return true;
   723         }
   725         if (itemId == R.id.paste) {
   726             String text = Clipboard.getText();
   727             if (!TextUtils.isEmpty(text)) {
   728                 enterEditingMode(text);
   729                 Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "paste");
   730             }
   731             return true;
   732         }
   734         if (itemId == R.id.share) {
   735             shareCurrentUrl();
   736             return true;
   737         }
   739         if (itemId == R.id.subscribe) {
   740             // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
   741             Tab tab = Tabs.getInstance().getSelectedTab();
   742             if (tab != null && tab.hasFeeds()) {
   743                 JSONObject args = new JSONObject();
   744                 try {
   745                     args.put("tabId", tab.getId());
   746                 } catch (JSONException e) {
   747                     Log.e(LOGTAG, "error building json arguments");
   748                 }
   749                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feeds:Subscribe", args.toString()));
   750                 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
   751                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "subscribe");
   752                 }
   753             }
   754             return true;
   755         }
   757         if (itemId == R.id.add_search_engine) {
   758             // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
   759             Tab tab = Tabs.getInstance().getSelectedTab();
   760             if (tab != null && tab.hasOpenSearch()) {
   761                 JSONObject args = new JSONObject();
   762                 try {
   763                     args.put("tabId", tab.getId());
   764                 } catch (JSONException e) {
   765                     Log.e(LOGTAG, "error building json arguments");
   766                     return true;
   767                 }
   768                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Add", args.toString()));
   770                 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
   771                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "add_search_engine");
   772                 }
   773             }
   774             return true;
   775         }
   777         if (itemId == R.id.copyurl) {
   778             Tab tab = Tabs.getInstance().getSelectedTab();
   779             if (tab != null) {
   780                 String url = tab.getURL();
   781                 if (url != null) {
   782                     Clipboard.setText(url);
   783                     Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "copyurl");
   784                 }
   785             }
   786             return true;
   787         }
   789         if (itemId == R.id.add_to_launcher) {
   790             Tab tab = Tabs.getInstance().getSelectedTab();
   791             if (tab == null) {
   792                 return true;
   793             }
   795             final String url = tab.getURL();
   796             final String title = tab.getDisplayTitle();
   797             if (url == null || title == null) {
   798                 return true;
   799             }
   801             final OnFaviconLoadedListener listener = new GeckoAppShell.CreateShortcutFaviconLoadedListener(url, title);
   802             Favicons.getSizedFavicon(url,
   803                     tab.getFaviconURL(),
   804                     Integer.MAX_VALUE,
   805                     LoadFaviconTask.FLAG_PERSIST,
   806                     listener);
   807             return true;
   808         }
   810         return false;
   811     }
   813     @Override
   814     public void setAccessibilityEnabled(boolean enabled) {
   815         mDynamicToolbar.setAccessibilityEnabled(enabled);
   816     }
   818     @Override
   819     public void onDestroy() {
   820         mDynamicToolbar.destroy();
   822         if (mBrowserToolbar != null)
   823             mBrowserToolbar.onDestroy();
   825         if (mFindInPageBar != null) {
   826             mFindInPageBar.onDestroy();
   827             mFindInPageBar = null;
   828         }
   830         if (mMediaCastingBar != null) {
   831             mMediaCastingBar.onDestroy();
   832             mMediaCastingBar = null;
   833         }
   835         if (mSharedPreferencesHelper != null) {
   836             mSharedPreferencesHelper.uninit();
   837             mSharedPreferencesHelper = null;
   838         }
   840         if (mOrderedBroadcastHelper != null) {
   841             mOrderedBroadcastHelper.uninit();
   842             mOrderedBroadcastHelper = null;
   843         }
   845         if (mBrowserHealthReporter != null) {
   846             mBrowserHealthReporter.uninit();
   847             mBrowserHealthReporter = null;
   848         }
   850         unregisterEventListener("CharEncoding:Data");
   851         unregisterEventListener("CharEncoding:State");
   852         unregisterEventListener("Feedback:LastUrl");
   853         unregisterEventListener("Feedback:OpenPlayStore");
   854         unregisterEventListener("Feedback:MaybeLater");
   855         unregisterEventListener("Telemetry:Gather");
   856         unregisterEventListener("Settings:Show");
   857         unregisterEventListener("Updater:Launch");
   858         unregisterEventListener("Menu:Add");
   859         unregisterEventListener("Menu:Remove");
   860         unregisterEventListener("Menu:Update");
   861         unregisterEventListener("Accounts:Create");
   862         unregisterEventListener("Accounts:Exist");
   864         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
   865             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
   866             if (nfc != null) {
   867                 // null this out even though the docs say it's not needed,
   868                 // because the source code looks like it will only do this
   869                 // automatically on API 14+
   870                 nfc.setNdefPushMessageCallback(null, this);
   871             }
   872         }
   874         super.onDestroy();
   875     }
   877     @Override
   878     protected void initializeChrome() {
   879         super.initializeChrome();
   881         mDoorHangerPopup.setAnchor(mBrowserToolbar.getDoorHangerAnchor());
   883         mDynamicToolbar.setLayerView(mLayerView);
   884         setDynamicToolbarEnabled(mDynamicToolbar.isEnabled());
   886         // Intercept key events for gamepad shortcuts
   887         mLayerView.setOnKeyListener(this);
   889         // Initialize the actionbar menu items on startup for both large and small tablets
   890         if (HardwareUtils.isTablet()) {
   891             onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
   892             invalidateOptionsMenu();
   893         }
   894     }
   896     private void shareCurrentUrl() {
   897         Tab tab = Tabs.getInstance().getSelectedTab();
   898         if (tab == null) {
   899             return;
   900         }
   902         String url = tab.getURL();
   903         if (url == null) {
   904             return;
   905         }
   907         if (AboutPages.isAboutReader(url)) {
   908             url = ReaderModeUtils.getUrlFromAboutReader(url);
   909         }
   911         GeckoAppShell.openUriExternal(url, "text/plain", "", "",
   912                                       Intent.ACTION_SEND, tab.getDisplayTitle());
   914         // Context: Sharing via chrome list (no explicit session is active)
   915         Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
   916     }
   918     @Override
   919     protected void loadStartupTab(String url) {
   920         // We aren't showing about:home, so cancel the telemetry timer
   921         if (url != null || mShouldRestore) {
   922             mAboutHomeStartupTimer.cancel();
   923         }
   925         super.loadStartupTab(url);
   926     }
   928     private void setToolbarMargin(int margin) {
   929         ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
   930         mGeckoLayout.requestLayout();
   931     }
   933     @Override
   934     public void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
   935         if (isHomePagerVisible() || mViewFlipper == null) {
   936             return;
   937         }
   939         // If the page has shrunk so that the toolbar no longer scrolls, make
   940         // sure the toolbar is visible.
   941         if (aMetrics.getPageHeight() <= aMetrics.getHeight()) {
   942             if (mDynamicToolbarCanScroll) {
   943                 mDynamicToolbarCanScroll = false;
   944                 if (mViewFlipper.getVisibility() != View.VISIBLE) {
   945                     ThreadUtils.postToUiThread(new Runnable() {
   946                         public void run() {
   947                             mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   948                         }
   949                     });
   950                 }
   951             }
   952         } else {
   953             mDynamicToolbarCanScroll = true;
   954         }
   956         final View toolbarLayout = mViewFlipper;
   957         final int marginTop = Math.round(aMetrics.marginTop);
   958         ThreadUtils.postToUiThread(new Runnable() {
   959             public void run() {
   960                 final float translationY = marginTop - toolbarLayout.getHeight();
   961                 ViewHelper.setTranslationY(toolbarLayout, translationY);
   962                 ViewHelper.setTranslationY(mProgressView, translationY);
   964                 if (mDoorHangerPopup.isShowing()) {
   965                     mDoorHangerPopup.updatePopup();
   966                 }
   967             }
   968         });
   970         if (mFormAssistPopup != null)
   971             mFormAssistPopup.onMetricsChanged(aMetrics);
   972     }
   974     @Override
   975     public void onPanZoomStopped() {
   976         if (!mDynamicToolbar.isEnabled() || isHomePagerVisible()) {
   977             return;
   978         }
   980         // Make sure the toolbar is fully hidden or fully shown when the user
   981         // lifts their finger. If the page is shorter than the viewport, the
   982         // toolbar is always shown.
   983         ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
   984         if (metrics.getPageHeight() < metrics.getHeight()
   985               || metrics.marginTop >= mToolbarHeight / 2) {
   986             mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
   987         } else {
   988             mDynamicToolbar.setVisible(false, VisibilityTransition.ANIMATE);
   989         }
   990     }
   992     public void refreshToolbarHeight() {
   993         ThreadUtils.assertOnUiThread();
   995         int height = 0;
   996         if (mViewFlipper != null) {
   997             height = mViewFlipper.getHeight();
   998         }
  1000         if (!mDynamicToolbar.isEnabled() || isHomePagerVisible()) {
  1001             // Use aVisibleHeight here so that when the dynamic toolbar is
  1002             // enabled, the padding will animate with the toolbar becoming
  1003             // visible.
  1004             if (mDynamicToolbar.isEnabled()) {
  1005                 // When the dynamic toolbar is enabled, set the padding on the
  1006                 // about:home widget directly - this is to avoid resizing the
  1007                 // LayerView, which can cause visible artifacts.
  1008                 mHomePagerContainer.setPadding(0, height, 0, 0);
  1009             } else {
  1010                 setToolbarMargin(height);
  1011                 height = 0;
  1013         } else {
  1014             setToolbarMargin(0);
  1017         if (mLayerView != null && height != mToolbarHeight) {
  1018             mToolbarHeight = height;
  1019             mLayerView.getLayerMarginsAnimator().setMaxMargins(0, height, 0, 0);
  1020             mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
  1024     @Override
  1025     void toggleChrome(final boolean aShow) {
  1026         ThreadUtils.postToUiThread(new Runnable() {
  1027             @Override
  1028             public void run() {
  1029                 if (aShow) {
  1030                     mViewFlipper.setVisibility(View.VISIBLE);
  1031                 } else {
  1032                     mViewFlipper.setVisibility(View.GONE);
  1033                     if (hasTabsSideBar()) {
  1034                         hideTabs();
  1038         });
  1040         super.toggleChrome(aShow);
  1043     @Override
  1044     void focusChrome() {
  1045         ThreadUtils.postToUiThread(new Runnable() {
  1046             @Override
  1047             public void run() {
  1048                 mViewFlipper.setVisibility(View.VISIBLE);
  1049                 mViewFlipper.requestFocusFromTouch();
  1051         });
  1054     @Override
  1055     public void refreshChrome() {
  1056         invalidateOptionsMenu();
  1058         if (mTabsPanel != null) {
  1059             updateSideBarState();
  1060             mTabsPanel.refresh();
  1063         mBrowserToolbar.refresh();
  1066     @Override
  1067     public boolean hasTabsSideBar() {
  1068         return (mTabsPanel != null && mTabsPanel.isSideBar());
  1071     private void updateSideBarState() {
  1072         if (mMainLayoutAnimator != null)
  1073             mMainLayoutAnimator.stop();
  1075         boolean isSideBar = (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
  1076         final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width);
  1078         ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams();
  1079         lp.width = (isSideBar ? sidebarWidth : ViewGroup.LayoutParams.FILL_PARENT);
  1080         mTabsPanel.requestLayout();
  1082         final boolean sidebarIsShown = (isSideBar && mTabsPanel.isShown());
  1083         final int mainLayoutScrollX = (sidebarIsShown ? -sidebarWidth : 0);
  1084         mMainLayout.scrollTo(mainLayoutScrollX, 0);
  1086         mTabsPanel.setIsSideBar(isSideBar);
  1089     @Override
  1090     public void handleMessage(String event, JSONObject message) {
  1091         try {
  1092             if (event.equals("Menu:Add")) {
  1093                 MenuItemInfo info = new MenuItemInfo();
  1094                 info.label = message.getString("name");
  1095                 info.id = message.getInt("id") + ADDON_MENU_OFFSET;
  1096                 info.icon = message.optString("icon", null);
  1097                 info.checked = message.optBoolean("checked", false);
  1098                 info.enabled = message.optBoolean("enabled", true);
  1099                 info.visible = message.optBoolean("visible", true);
  1100                 info.checkable = message.optBoolean("checkable", false);
  1101                 int parent = message.optInt("parent", 0);
  1102                 info.parent = parent <= 0 ? parent : parent + ADDON_MENU_OFFSET;
  1103                 final MenuItemInfo menuItemInfo = info;
  1104                 ThreadUtils.postToUiThread(new Runnable() {
  1105                     @Override
  1106                     public void run() {
  1107                         addAddonMenuItem(menuItemInfo);
  1109                 });
  1110             } else if (event.equals("Menu:Remove")) {
  1111                 final int id = message.getInt("id") + ADDON_MENU_OFFSET;
  1112                 ThreadUtils.postToUiThread(new Runnable() {
  1113                     @Override
  1114                     public void run() {
  1115                         removeAddonMenuItem(id);
  1117                 });
  1118             } else if (event.equals("Menu:Update")) {
  1119                 final int id = message.getInt("id") + ADDON_MENU_OFFSET;
  1120                 final JSONObject options = message.getJSONObject("options");
  1121                 ThreadUtils.postToUiThread(new Runnable() {
  1122                     @Override
  1123                     public void run() {
  1124                         updateAddonMenuItem(id, options);
  1126                 });
  1127             } else if (event.equals("CharEncoding:Data")) {
  1128                 final JSONArray charsets = message.getJSONArray("charsets");
  1129                 int selected = message.getInt("selected");
  1131                 final int len = charsets.length();
  1132                 final String[] titleArray = new String[len];
  1133                 for (int i = 0; i < len; i++) {
  1134                     JSONObject charset = charsets.getJSONObject(i);
  1135                     titleArray[i] = charset.getString("title");
  1138                 final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
  1139                 dialogBuilder.setSingleChoiceItems(titleArray, selected, new AlertDialog.OnClickListener() {
  1140                     @Override
  1141                     public void onClick(DialogInterface dialog, int which) {
  1142                         try {
  1143                             JSONObject charset = charsets.getJSONObject(which);
  1144                             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("CharEncoding:Set", charset.getString("code")));
  1145                             dialog.dismiss();
  1146                         } catch (JSONException e) {
  1147                             Log.e(LOGTAG, "error parsing json", e);
  1150                 });
  1151                 dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
  1152                     @Override
  1153                     public void onClick(DialogInterface dialog, int which) {
  1154                         dialog.dismiss();
  1156                 });
  1157                 ThreadUtils.postToUiThread(new Runnable() {
  1158                     @Override
  1159                     public void run() {
  1160                         dialogBuilder.show();
  1162                 });
  1163             } else if (event.equals("CharEncoding:State")) {
  1164                 final boolean visible = message.getString("visible").equals("true");
  1165                 GeckoPreferences.setCharEncodingState(visible);
  1166                 final Menu menu = mMenu;
  1167                 ThreadUtils.postToUiThread(new Runnable() {
  1168                     @Override
  1169                     public void run() {
  1170                         if (menu != null)
  1171                             menu.findItem(R.id.char_encoding).setVisible(visible);
  1173                 });
  1174             } else if (event.equals("Feedback:OpenPlayStore")) {
  1175                 Intent intent = new Intent(Intent.ACTION_VIEW);
  1176                 intent.setData(Uri.parse("market://details?id=" + getPackageName()));
  1177                 startActivity(intent);
  1178             } else if (event.equals("Feedback:MaybeLater")) {
  1179                 resetFeedbackLaunchCount();
  1180             } else if (event.equals("Feedback:LastUrl")) {
  1181                 getLastUrl();
  1182             } else if (event.equals("Gecko:DelayedStartup")) {
  1183                 ThreadUtils.postToUiThread(new Runnable() {
  1184                     @Override
  1185                     public void run() {
  1186                         // Force tabs panel inflation once the initial
  1187                         // pageload is finished.
  1188                         ensureTabsPanelExists();
  1190                 });
  1192                 super.handleMessage(event, message);
  1193             } else if (event.equals("Gecko:Ready")) {
  1194                 // Handle this message in GeckoApp, but also enable the Settings
  1195                 // menuitem, which is specific to BrowserApp.
  1196                 super.handleMessage(event, message);
  1197                 final Menu menu = mMenu;
  1198                 ThreadUtils.postToUiThread(new Runnable() {
  1199                     @Override
  1200                     public void run() {
  1201                         if (menu != null)
  1202                             menu.findItem(R.id.settings).setEnabled(true);
  1204                 });
  1206                 // Display notification for Mozilla data reporting, if data should be collected.
  1207                 if (AppConstants.MOZ_DATA_REPORTING) {
  1208                     DataReportingNotification.checkAndNotifyPolicy(GeckoAppShell.getContext());
  1211             } else if (event.equals("Telemetry:Gather")) {
  1212                 Telemetry.HistogramAdd("PLACES_PAGES_COUNT", BrowserDB.getCount(getContentResolver(), "history"));
  1213                 Telemetry.HistogramAdd("PLACES_BOOKMARKS_COUNT", BrowserDB.getCount(getContentResolver(), "bookmarks"));
  1214                 Telemetry.HistogramAdd("FENNEC_FAVICONS_COUNT", BrowserDB.getCount(getContentResolver(), "favicons"));
  1215                 Telemetry.HistogramAdd("FENNEC_THUMBNAILS_COUNT", BrowserDB.getCount(getContentResolver(), "thumbnails"));
  1216             } else if (event.equals("Reader:ListStatusRequest")) {
  1217                 handleReaderListStatusRequest(message.getString("url"));
  1218             } else if (event.equals("Reader:Added")) {
  1219                 final int result = message.getInt("result");
  1220                 handleReaderAdded(result, messageToReadingListContentValues(message));
  1221             } else if (event.equals("Reader:Removed")) {
  1222                 final String url = message.getString("url");
  1223                 handleReaderRemoved(url);
  1224             } else if (event.equals("Reader:Share")) {
  1225                 final String title = message.getString("title");
  1226                 final String url = message.getString("url");
  1227                 GeckoAppShell.openUriExternal(url, "text/plain", "", "",
  1228                                               Intent.ACTION_SEND, title);
  1229             } else if (event.equals("Settings:Show")) {
  1230                 // null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
  1231                 String resource = null;
  1232                 if (!message.isNull(GeckoPreferences.INTENT_EXTRA_RESOURCES)) {
  1233                     resource = message.getString(GeckoPreferences.INTENT_EXTRA_RESOURCES);
  1235                 Intent settingsIntent = new Intent(this, GeckoPreferences.class);
  1236                 GeckoPreferences.setResourceToOpen(settingsIntent, resource);
  1237                 startActivity(settingsIntent);
  1238             } else if (event.equals("Updater:Launch")) {
  1239                 handleUpdaterLaunch();
  1240             } else if (event.equals("Prompt:ShowTop")) {
  1241                 // Bring this activity to front so the prompt is visible..
  1242                 Intent bringToFrontIntent = new Intent();
  1243                 bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
  1244                 bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
  1245                 startActivity(bringToFrontIntent);
  1246             } else if (event.equals("Accounts:Create")) {
  1247                 // Do exactly the same thing as if you tapped 'Sync'
  1248                 // in Settings.
  1249                 final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
  1250                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  1251                 getContext().startActivity(intent);
  1252             } else if (event.equals("Accounts:Exist")) {
  1253                 final String kind = message.getString("kind");
  1254                 final JSONObject response = new JSONObject();
  1256                 if ("any".equals(kind)) {
  1257                     response.put("exists", SyncAccounts.syncAccountsExist(getContext()) ||
  1258                                            FirefoxAccounts.firefoxAccountsExist(getContext()));
  1259                     EventDispatcher.sendResponse(message, response);
  1260                 } else if ("fxa".equals(kind)) {
  1261                     response.put("exists", FirefoxAccounts.firefoxAccountsExist(getContext()));
  1262                     EventDispatcher.sendResponse(message, response);
  1263                 } else if ("sync11".equals(kind)) {
  1264                     response.put("exists", SyncAccounts.syncAccountsExist(getContext()));
  1265                     EventDispatcher.sendResponse(message, response);
  1266                 } else {
  1267                     response.put("error", "Unknown kind");
  1268                     EventDispatcher.sendError(message, response);
  1270             } else {
  1271                 super.handleMessage(event, message);
  1273         } catch (Exception e) {
  1274             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
  1278     @Override
  1279     public void addTab() {
  1280         // Always load about:home when opening a new tab.
  1281         Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
  1284     @Override
  1285     public void addPrivateTab() {
  1286         Tabs.getInstance().loadUrl(AboutPages.PRIVATEBROWSING, Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_PRIVATE);
  1289     @Override
  1290     public void showNormalTabs() {
  1291         showTabs(TabsPanel.Panel.NORMAL_TABS);
  1294     @Override
  1295     public void showPrivateTabs() {
  1296         showTabs(TabsPanel.Panel.PRIVATE_TABS);
  1298     /**
  1299     * Ensure the TabsPanel view is properly inflated and returns
  1300     * true when the view has been inflated, false otherwise.
  1301     */
  1302     private boolean ensureTabsPanelExists() {
  1303         if (mTabsPanel != null) {
  1304             return false;
  1307         ViewStub tabsPanelStub = (ViewStub) findViewById(R.id.tabs_panel);
  1308         mTabsPanel = (TabsPanel) tabsPanelStub.inflate();
  1310         mTabsPanel.setTabsLayoutChangeListener(this);
  1311         updateSideBarState();
  1313         return true;
  1316     private void showTabs(final TabsPanel.Panel panel) {
  1317         if (Tabs.getInstance().getDisplayCount() == 0)
  1318             return;
  1320         if (ensureTabsPanelExists()) {
  1321             // If we've just inflated the tabs panel, only show it once the current
  1322             // layout pass is done to avoid displayed temporary UI states during
  1323             // relayout.
  1324             ViewTreeObserver vto = mTabsPanel.getViewTreeObserver();
  1325             if (vto.isAlive()) {
  1326                 vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  1327                     @Override
  1328                     public void onGlobalLayout() {
  1329                         mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
  1330                         mTabsPanel.show(panel);
  1332                 });
  1334         } else {
  1335             mTabsPanel.show(panel);
  1339     @Override
  1340     public void hideTabs() {
  1341         mTabsPanel.hide();
  1344     @Override
  1345     public boolean autoHideTabs() {
  1346         if (areTabsShown()) {
  1347             hideTabs();
  1348             return true;
  1350         return false;
  1353     @Override
  1354     public boolean areTabsShown() {
  1355         return (mTabsPanel != null && mTabsPanel.isShown());
  1358     @Override
  1359     public void onTabsLayoutChange(int width, int height) {
  1360         int animationLength = TABS_ANIMATION_DURATION;
  1362         if (mMainLayoutAnimator != null) {
  1363             animationLength = Math.max(1, animationLength - (int)mMainLayoutAnimator.getRemainingTime());
  1364             mMainLayoutAnimator.stop(false);
  1367         if (areTabsShown()) {
  1368             mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  1371         mMainLayoutAnimator = new PropertyAnimator(animationLength, sTabsInterpolator);
  1372         mMainLayoutAnimator.addPropertyAnimationListener(this);
  1374         if (hasTabsSideBar()) {
  1375             mMainLayoutAnimator.attach(mMainLayout,
  1376                                        PropertyAnimator.Property.SCROLL_X,
  1377                                        -width);
  1378         } else {
  1379             mMainLayoutAnimator.attach(mMainLayout,
  1380                                        PropertyAnimator.Property.SCROLL_Y,
  1381                                        -height);
  1384         mTabsPanel.prepareTabsAnimation(mMainLayoutAnimator);
  1385         mBrowserToolbar.prepareTabsAnimation(mMainLayoutAnimator, areTabsShown());
  1387         // If the tabs layout is animating onto the screen, pin the dynamic
  1388         // toolbar.
  1389         if (mDynamicToolbar.isEnabled()) {
  1390             if (width > 0 && height > 0) {
  1391                 mDynamicToolbar.setPinned(true, PinReason.RELAYOUT);
  1392                 mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  1393             } else {
  1394                 mDynamicToolbar.setPinned(false, PinReason.RELAYOUT);
  1398         mMainLayoutAnimator.start();
  1401     @Override
  1402     public void onPropertyAnimationStart() {
  1405     @Override
  1406     public void onPropertyAnimationEnd() {
  1407         if (!areTabsShown()) {
  1408             mTabsPanel.setVisibility(View.INVISIBLE);
  1409             mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
  1412         mTabsPanel.finishTabsAnimation();
  1414         mMainLayoutAnimator = null;
  1417     @Override
  1418     public void onSaveInstanceState(Bundle outState) {
  1419         super.onSaveInstanceState(outState);
  1420         mDynamicToolbar.onSaveInstanceState(outState);
  1421         outState.putInt(STATE_ABOUT_HOME_TOP_PADDING, mHomePagerContainer.getPaddingTop());
  1424     /**
  1425      * Attempts to switch to an open tab with the given URL.
  1427      * @return true if we successfully switched to a tab, false otherwise.
  1428      */
  1429     private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
  1430         if (!flags.contains(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)) {
  1431             return false;
  1434         final Tabs tabs = Tabs.getInstance();
  1435         final Tab tab = tabs.getFirstTabForUrl(url, tabs.getSelectedTab().isPrivate());
  1436         if (tab == null) {
  1437             return false;
  1440         // Set the target tab to null so it does not get selected (on editing
  1441         // mode exit) in lieu of the tab we are about to select.
  1442         mTargetTabForEditingMode = null;
  1443         tabs.selectTab(tab.getId());
  1445         mBrowserToolbar.cancelEdit();
  1447         return true;
  1450     private void openUrlAndStopEditing(String url) {
  1451         openUrlAndStopEditing(url, null, false);
  1454     private void openUrlAndStopEditing(String url, boolean newTab) {
  1455         openUrlAndStopEditing(url, null, newTab);
  1458     private void openUrlAndStopEditing(String url, String searchEngine) {
  1459         openUrlAndStopEditing(url, searchEngine, false);
  1462     private void openUrlAndStopEditing(String url, String searchEngine, boolean newTab) {
  1463         int flags = Tabs.LOADURL_NONE;
  1464         if (newTab) {
  1465             flags |= Tabs.LOADURL_NEW_TAB;
  1468         Tabs.getInstance().loadUrl(url, searchEngine, -1, flags);
  1470         mBrowserToolbar.cancelEdit();
  1473     private boolean isHomePagerVisible() {
  1474         return (mHomePager != null && mHomePager.isVisible()
  1475             && mHomePagerContainer != null && mHomePagerContainer.getVisibility() == View.VISIBLE);
  1478     /* Favicon stuff. */
  1479     private static OnFaviconLoadedListener sFaviconLoadedListener = new OnFaviconLoadedListener() {
  1480         @Override
  1481         public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
  1482             // If we failed to load a favicon, we use the default favicon instead.
  1483             Tabs.getInstance()
  1484                 .updateFaviconForURL(pageUrl,
  1485                                      (favicon == null) ? Favicons.defaultFavicon : favicon);
  1487     };
  1489     private void loadFavicon(final Tab tab) {
  1490         maybeCancelFaviconLoad(tab);
  1492         final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
  1494         int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
  1495         int id = Favicons.getSizedFavicon(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags, sFaviconLoadedListener);
  1497         tab.setFaviconLoadId(id);
  1500     private void maybeCancelFaviconLoad(Tab tab) {
  1501         int faviconLoadId = tab.getFaviconLoadId();
  1503         if (Favicons.NOT_LOADING == faviconLoadId) {
  1504             return;
  1507         // Cancel load task and reset favicon load state if it wasn't already
  1508         // in NOT_LOADING state.
  1509         Favicons.cancelFaviconLoad(faviconLoadId);
  1510         tab.setFaviconLoadId(Favicons.NOT_LOADING);
  1513     /**
  1514      * Enters editing mode with the current tab's URL. There might be no
  1515      * tabs loaded by the time the user enters editing mode e.g. just after
  1516      * the app starts. In this case, we simply fallback to an empty URL.
  1517      */
  1518     private void enterEditingMode() {
  1519         String url = "";
  1521         final Tab tab = Tabs.getInstance().getSelectedTab();
  1522         if (tab != null) {
  1523             final String userSearch = tab.getUserSearch();
  1525             // Check to see if there's a user-entered search term,
  1526             // which we save whenever the user performs a search.
  1527             url = (TextUtils.isEmpty(userSearch) ? tab.getURL() : userSearch);
  1530         enterEditingMode(url);
  1533     /**
  1534      * Enters editing mode with the specified URL. This method will
  1535      * always open the HISTORY page on about:home.
  1536      */
  1537     private void enterEditingMode(String url) {
  1538         if (url == null) {
  1539             throw new IllegalArgumentException("Cannot handle null URLs in enterEditingMode");
  1542         if (mBrowserToolbar.isEditing() || mBrowserToolbar.isAnimating()) {
  1543             return;
  1546         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
  1547         mTargetTabForEditingMode = (selectedTab != null ? selectedTab.getId() : null);
  1549         final PropertyAnimator animator = new PropertyAnimator(250);
  1550         animator.setUseHardwareLayer(false);
  1552         mBrowserToolbar.startEditing(url, animator);
  1554         final String panelId = selectedTab.getMostRecentHomePanel();
  1555         showHomePagerWithAnimator(panelId, animator);
  1557         animator.start();
  1558         Telemetry.startUISession(TelemetryContract.Session.AWESOMESCREEN);
  1561     private void commitEditingMode() {
  1562         if (!mBrowserToolbar.isEditing()) {
  1563             return;
  1566         Telemetry.stopUISession(TelemetryContract.Session.AWESOMESCREEN,
  1567                                 TelemetryContract.Reason.COMMIT);
  1569         final String url = mBrowserToolbar.commitEdit();
  1571         // HACK: We don't know the url that will be loaded when hideHomePager is initially called
  1572         // in BrowserToolbar's onStopEditing listener so on the awesomescreen, hideHomePager will
  1573         // use the url "about:home" and return without taking any action. hideBrowserSearch is
  1574         // then called, but since hideHomePager changes both HomePager and LayerView visibility
  1575         // and exited without taking an action, no Views are displayed and graphical corruption is
  1576         // visible instead.
  1577         //
  1578         // Here we call hideHomePager for the second time with the URL to be loaded so that
  1579         // hideHomePager is called with the correct state for the upcoming page load.
  1580         //
  1581         // Expected to be fixed by bug 915825.
  1582         hideHomePager(url);
  1584         // Don't do anything if the user entered an empty URL.
  1585         if (TextUtils.isEmpty(url)) {
  1586             return;
  1589         // If the URL doesn't look like a search query, just load it.
  1590         if (!StringUtils.isSearchQuery(url, true)) {
  1591             Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED);
  1593             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
  1594             return;
  1597         // Otherwise, check for a bookmark keyword.
  1598         ThreadUtils.postToBackgroundThread(new Runnable() {
  1599             @Override
  1600             public void run() {
  1601                 final String keyword;
  1602                 final String keywordSearch;
  1604                 final int index = url.indexOf(" ");
  1605                 if (index == -1) {
  1606                     keyword = url;
  1607                     keywordSearch = "";
  1608                 } else {
  1609                     keyword = url.substring(0, index);
  1610                     keywordSearch = url.substring(index + 1);
  1613                 final String keywordUrl = BrowserDB.getUrlForKeyword(getContentResolver(), keyword);
  1615                 // If there isn't a bookmark keyword, load the url. This may result in a query
  1616                 // using the default search engine.
  1617                 if (TextUtils.isEmpty(keywordUrl)) {
  1618                     Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED);
  1619                     Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
  1620                     return;
  1623                 recordSearch(null, "barkeyword");
  1625                 // Otherwise, construct a search query from the bookmark keyword.
  1626                 final String searchUrl = keywordUrl.replace("%s", URLEncoder.encode(keywordSearch));
  1627                 Tabs.getInstance().loadUrl(searchUrl, Tabs.LOADURL_USER_ENTERED);
  1628                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, "", "keyword");
  1630         });
  1633     /**
  1634      * Record in Health Report that a search has occurred.
  1636      * @param engine
  1637      *        a search engine instance. Can be null.
  1638      * @param where
  1639      *        where the search was initialized; one of the values in
  1640      *        {@link BrowserHealthRecorder#SEARCH_LOCATIONS}.
  1641      */
  1642     private static void recordSearch(SearchEngine engine, String where) {
  1643         Log.i(LOGTAG, "Recording search: " +
  1644                       ((engine == null) ? "null" : engine.name) +
  1645                       ", " + where);
  1646         try {
  1647             String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
  1648             JSONObject message = new JSONObject();
  1649             message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
  1650             message.put("location", where);
  1651             message.put("identifier", identifier);
  1652             GeckoAppShell.getEventDispatcher().dispatchEvent(message, null);
  1653         } catch (Exception e) {
  1654             Log.w(LOGTAG, "Error recording search.", e);
  1658     void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
  1659         if (TextUtils.isEmpty(searchTerm)) {
  1660             hideBrowserSearch();
  1661         } else {
  1662             showBrowserSearch();
  1663             mBrowserSearch.filter(searchTerm, handler);
  1667     /**
  1668      * Selects the target tab for editing mode. This is expected to be the tab selected on editing
  1669      * mode entry, unless it is subsequently overridden.
  1671      * A background tab may be selected while editing mode is active (e.g. popups), causing the
  1672      * new url to load in the newly selected tab. Call this method on editing mode exit to
  1673      * mitigate this.
  1674      */
  1675     private void selectTargetTabForEditingMode() {
  1676         if (mTargetTabForEditingMode != null) {
  1677             Tabs.getInstance().selectTab(mTargetTabForEditingMode);
  1680         mTargetTabForEditingMode = null;
  1683     /**
  1684      * Shows or hides the home pager for the given tab.
  1685      */
  1686     private void updateHomePagerForTab(Tab tab) {
  1687         // Don't change the visibility of the home pager if we're in editing mode.
  1688         if (mBrowserToolbar.isEditing()) {
  1689             return;
  1692         if (isAboutHome(tab)) {
  1693             String panelId = AboutPages.getPanelIdFromAboutHomeUrl(tab.getURL());
  1694             if (panelId == null) {
  1695                 // No panel was specified in the URL. Try loading the most recent
  1696                 // home panel for this tab.
  1697                 panelId = tab.getMostRecentHomePanel();
  1699             showHomePager(panelId);
  1701             if (mDynamicToolbar.isEnabled()) {
  1702                 mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  1704         } else {
  1705             hideHomePager();
  1709     @Override
  1710     public void onLocaleReady(final String locale) {
  1711         super.onLocaleReady(locale);
  1713         HomePanelsManager.getInstance().onLocaleReady(locale);
  1715         if (mMenu != null) {
  1716             mMenu.clear();
  1717             onCreateOptionsMenu(mMenu);
  1721     private void showHomePager(String panelId) {
  1722         showHomePagerWithAnimator(panelId, null);
  1725     private void showHomePagerWithAnimator(String panelId, PropertyAnimator animator) {
  1726         if (isHomePagerVisible()) {
  1727             // Home pager already visible, make sure it shows the correct panel.
  1728             mHomePager.showPanel(panelId);
  1729             return;
  1732         // Refresh toolbar height to possibly restore the toolbar padding
  1733         refreshToolbarHeight();
  1735         // Show the toolbar before hiding about:home so the
  1736         // onMetricsChanged callback still works.
  1737         if (mDynamicToolbar.isEnabled()) {
  1738             mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
  1741         if (mHomePager == null) {
  1742             final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
  1743             mHomePager = (HomePager) homePagerStub.inflate();
  1745             mHomePager.setOnPanelChangeListener(new HomePager.OnPanelChangeListener() {
  1746                 @Override
  1747                 public void onPanelSelected(String panelId) {
  1748                     final Tab currentTab = Tabs.getInstance().getSelectedTab();
  1749                     if (currentTab != null) {
  1750                         currentTab.setMostRecentHomePanel(panelId);
  1753             });
  1755             // Don't show the banner in guest mode.
  1756             if (!getProfile().inGuestMode()) {
  1757                 final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub);
  1758                 final HomeBanner homeBanner = (HomeBanner) homeBannerStub.inflate();
  1759                 mHomePager.setBanner(homeBanner);
  1761                 // Remove the banner from the view hierarchy if it is dismissed.
  1762                 homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
  1763                     @Override
  1764                     public void onDismiss() {
  1765                         mHomePager.setBanner(null);
  1766                         mHomePagerContainer.removeView(homeBanner);
  1768                 });
  1772         mHomePagerContainer.setVisibility(View.VISIBLE);
  1773         mHomePager.load(getSupportLoaderManager(),
  1774                         getSupportFragmentManager(),
  1775                         panelId, animator);
  1777         // Hide the web content so it cannot be focused by screen readers.
  1778         hideWebContentOnPropertyAnimationEnd(animator);
  1781     private void hideWebContentOnPropertyAnimationEnd(final PropertyAnimator animator) {
  1782         if (animator == null) {
  1783             hideWebContent();
  1784             return;
  1787         animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
  1788             @Override
  1789             public void onPropertyAnimationStart() {
  1790                 mHideWebContentOnAnimationEnd = true;
  1793             @Override
  1794             public void onPropertyAnimationEnd() {
  1795                 if (mHideWebContentOnAnimationEnd) {
  1796                     hideWebContent();
  1799         });
  1802     private void hideWebContent() {
  1803         // The view is set to INVISIBLE, rather than GONE, to avoid
  1804         // the additional requestLayout() call.
  1805         mLayerView.setVisibility(View.INVISIBLE);
  1808     /**
  1809      * Hides the HomePager, using the url of the currently selected tab as the url to be
  1810      * loaded.
  1811      */
  1812     private void hideHomePager() {
  1813         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
  1814         final String url = (selectedTab != null) ? selectedTab.getURL() : null;
  1816         hideHomePager(url);
  1819     /**
  1820      * Hides the HomePager. The given url should be the url of the page to be loaded, or null
  1821      * if a new page is not being loaded.
  1822      */
  1823     private void hideHomePager(final String url) {
  1824         if (!isHomePagerVisible() || AboutPages.isAboutHome(url)) {
  1825             return;
  1828         // Prevent race in hiding web content - see declaration for more info.
  1829         mHideWebContentOnAnimationEnd = false;
  1831         // Display the previously hidden web content (which prevented screen reader access).
  1832         mLayerView.setVisibility(View.VISIBLE);
  1833         mHomePagerContainer.setVisibility(View.GONE);
  1835         if (mHomePager != null) {
  1836             mHomePager.unload();
  1839         mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
  1841         // Refresh toolbar height to possibly restore the toolbar padding
  1842         refreshToolbarHeight();
  1845     private void showBrowserSearch() {
  1846         if (mBrowserSearch.getUserVisibleHint()) {
  1847             return;
  1850         mBrowserSearchContainer.setVisibility(View.VISIBLE);
  1852         // Prevent overdraw by hiding the underlying HomePager View.
  1853         mHomePager.setVisibility(View.INVISIBLE);
  1855         final FragmentManager fm = getSupportFragmentManager();
  1857         // In certain situations, showBrowserSearch() can be called immediately after hideBrowserSearch()
  1858         // (see bug 925012). Because of an Android bug (http://code.google.com/p/android/issues/detail?id=61179),
  1859         // calling FragmentTransaction#add immediately after FragmentTransaction#remove won't add the fragment's
  1860         // view to the layout. Calling FragmentManager#executePendingTransactions before re-adding the fragment
  1861         // prevents this issue.
  1862         fm.executePendingTransactions();
  1864         fm.beginTransaction().add(R.id.search_container, mBrowserSearch, BROWSER_SEARCH_TAG).commitAllowingStateLoss();
  1865         mBrowserSearch.setUserVisibleHint(true);
  1868     private void hideBrowserSearch() {
  1869         if (!mBrowserSearch.getUserVisibleHint()) {
  1870             return;
  1873         // To prevent overdraw, the HomePager is hidden when BrowserSearch is displayed:
  1874         // reverse that.
  1875         mHomePager.setVisibility(View.VISIBLE);
  1877         mBrowserSearchContainer.setVisibility(View.INVISIBLE);
  1879         getSupportFragmentManager().beginTransaction()
  1880                 .remove(mBrowserSearch).commitAllowingStateLoss();
  1881         mBrowserSearch.setUserVisibleHint(false);
  1884     private class HideTabsTouchListener implements TouchEventInterceptor {
  1885         private boolean mIsHidingTabs = false;
  1887         @Override
  1888         public boolean onInterceptTouchEvent(View view, MotionEvent event) {
  1889             // We need to account for scroll state for the touched view otherwise
  1890             // tapping on an "empty" part of the view will still be considered a
  1891             // valid touch event.
  1892             if (view.getScrollX() != 0 || view.getScrollY() != 0) {
  1893                 Rect rect = new Rect();
  1894                 view.getHitRect(rect);
  1895                 rect.offset(-view.getScrollX(), -view.getScrollY());
  1897                 int[] viewCoords = new int[2];
  1898                 view.getLocationOnScreen(viewCoords);
  1900                 int x = (int) event.getRawX() - viewCoords[0];
  1901                 int y = (int) event.getRawY() - viewCoords[1];
  1903                 if (!rect.contains(x, y))
  1904                     return false;
  1907             // If the tab tray is showing, hide the tab tray and don't send the event to content.
  1908             if (event.getActionMasked() == MotionEvent.ACTION_DOWN && autoHideTabs()) {
  1909                 mIsHidingTabs = true;
  1910                 return true;
  1912             return false;
  1915         @Override
  1916         public boolean onTouch(View view, MotionEvent event) {
  1917             if (mIsHidingTabs) {
  1918                 // Keep consuming events until the gesture finishes.
  1919                 int action = event.getActionMasked();
  1920                 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
  1921                     mIsHidingTabs = false;
  1923                 return true;
  1925             return false;
  1929     private static Menu findParentMenu(Menu menu, MenuItem item) {
  1930         final int itemId = item.getItemId();
  1932         final int count = (menu != null) ? menu.size() : 0;
  1933         for (int i = 0; i < count; i++) {
  1934             MenuItem menuItem = menu.getItem(i);
  1935             if (menuItem.getItemId() == itemId) {
  1936                 return menu;
  1938             if (menuItem.hasSubMenu()) {
  1939                 Menu parent = findParentMenu(menuItem.getSubMenu(), item);
  1940                 if (parent != null) {
  1941                     return parent;
  1946         return null;
  1949     /**
  1950      * Add the provided item to the provided menu, which should be
  1951      * the root (mMenu).
  1952      */
  1953     private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
  1954         info.added = true;
  1956         final Menu destination;
  1957         if (info.parent == 0) {
  1958             destination = menu;
  1959         } else if (info.parent == GECKO_TOOLS_MENU) {
  1960             MenuItem tools = menu.findItem(R.id.tools);
  1961             destination = tools != null ? tools.getSubMenu() : menu;
  1962         } else {
  1963             MenuItem parent = menu.findItem(info.parent);
  1964             if (parent == null) {
  1965                 return;
  1968             Menu parentMenu = findParentMenu(menu, parent);
  1970             if (!parent.hasSubMenu()) {
  1971                 parentMenu.removeItem(parent.getItemId());
  1972                 destination = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
  1973                 if (parent.getIcon() != null) {
  1974                     ((SubMenu) destination).getItem().setIcon(parent.getIcon());
  1976             } else {
  1977                 destination = parent.getSubMenu();
  1981         MenuItem item = destination.add(Menu.NONE, info.id, Menu.NONE, info.label);
  1983         item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
  1984             @Override
  1985             public boolean onMenuItemClick(MenuItem item) {
  1986                 Log.i(LOGTAG, "Menu item clicked");
  1987                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Menu:Clicked", Integer.toString(info.id - ADDON_MENU_OFFSET)));
  1988                 return true;
  1990         });
  1992         if (info.icon == null) {
  1993             item.setIcon(R.drawable.ic_menu_addons_filler);
  1994         } else {
  1995             final int id = info.id;
  1996             BitmapUtils.getDrawable(this, info.icon, new BitmapUtils.BitmapLoader() {
  1997                 @Override
  1998                 public void onBitmapFound(Drawable d) {
  1999                     // TODO: why do we re-find the item?
  2000                     MenuItem item = destination.findItem(id);
  2001                     if (item == null) {
  2002                         return;
  2004                     if (d == null) {
  2005                         item.setIcon(R.drawable.ic_menu_addons_filler);
  2006                         return;
  2008                     item.setIcon(d);
  2010             });
  2013         item.setCheckable(info.checkable);
  2014         item.setChecked(info.checked);
  2015         item.setEnabled(info.enabled);
  2016         item.setVisible(info.visible);
  2019     private void addAddonMenuItem(final MenuItemInfo info) {
  2020         if (mAddonMenuItemsCache == null) {
  2021             mAddonMenuItemsCache = new Vector<MenuItemInfo>();
  2024         // Mark it as added if the menu was ready.
  2025         info.added = (mMenu != null);
  2027         // Always cache so we can rebuild after a locale switch.
  2028         mAddonMenuItemsCache.add(info);
  2030         if (mMenu == null) {
  2031             return;
  2034         addAddonMenuItemToMenu(mMenu, info);
  2037     private void removeAddonMenuItem(int id) {
  2038         // Remove add-on menu item from cache, if available.
  2039         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
  2040             for (MenuItemInfo item : mAddonMenuItemsCache) {
  2041                  if (item.id == id) {
  2042                      mAddonMenuItemsCache.remove(item);
  2043                      break;
  2048         if (mMenu == null)
  2049             return;
  2051         MenuItem menuItem = mMenu.findItem(id);
  2052         if (menuItem != null)
  2053             mMenu.removeItem(id);
  2056     private void updateAddonMenuItem(int id, JSONObject options) {
  2057         // Set attribute for the menu item in cache, if available
  2058         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
  2059             for (MenuItemInfo item : mAddonMenuItemsCache) {
  2060                 if (item.id == id) {
  2061                     item.label = options.optString("name", item.label);
  2062                     item.checkable = options.optBoolean("checkable", item.checkable);
  2063                     item.checked = options.optBoolean("checked", item.checked);
  2064                     item.enabled = options.optBoolean("enabled", item.enabled);
  2065                     item.visible = options.optBoolean("visible", item.visible);
  2066                     item.added = (mMenu != null);
  2067                     break;
  2072         if (mMenu == null) {
  2073             return;
  2076         MenuItem menuItem = mMenu.findItem(id);
  2077         if (menuItem != null) {
  2078             menuItem.setTitle(options.optString("name", menuItem.getTitle().toString()));
  2079             menuItem.setCheckable(options.optBoolean("checkable", menuItem.isCheckable()));
  2080             menuItem.setChecked(options.optBoolean("checked", menuItem.isChecked()));
  2081             menuItem.setEnabled(options.optBoolean("enabled", menuItem.isEnabled()));
  2082             menuItem.setVisible(options.optBoolean("visible", menuItem.isVisible()));
  2086     @Override
  2087     public boolean onCreateOptionsMenu(Menu menu) {
  2088         // Sets mMenu = menu.
  2089         super.onCreateOptionsMenu(menu);
  2091         // Inform the menu about the action-items bar.
  2092         if (menu instanceof GeckoMenu &&
  2093             HardwareUtils.isTablet()) {
  2094             ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
  2097         MenuInflater inflater = getMenuInflater();
  2098         inflater.inflate(R.menu.browser_app_menu, mMenu);
  2100         // Add add-on menu items, if any exist.
  2101         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
  2102             for (MenuItemInfo item : mAddonMenuItemsCache) {
  2103                 addAddonMenuItemToMenu(mMenu, item);
  2107         // Action providers are available only ICS+.
  2108         if (Build.VERSION.SDK_INT >= 14) {
  2109             GeckoMenuItem share = (GeckoMenuItem) mMenu.findItem(R.id.share);
  2110             GeckoActionProvider provider = GeckoActionProvider.getForType(GeckoActionProvider.DEFAULT_MIME_TYPE, this);
  2111             share.setActionProvider(provider);
  2114         return true;
  2117     @Override
  2118     public void openOptionsMenu() {
  2119         if (!hasTabsSideBar() && areTabsShown())
  2120             return;
  2122         // Scroll custom menu to the top
  2123         if (mMenuPanel != null)
  2124             mMenuPanel.scrollTo(0, 0);
  2126         if (!mBrowserToolbar.openOptionsMenu())
  2127             super.openOptionsMenu();
  2129         if (mDynamicToolbar.isEnabled()) {
  2130             mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  2134     @Override
  2135     public void closeOptionsMenu() {
  2136         if (!mBrowserToolbar.closeOptionsMenu())
  2137             super.closeOptionsMenu();
  2140     @Override
  2141     public void setFullScreen(final boolean fullscreen) {
  2142         super.setFullScreen(fullscreen);
  2143         ThreadUtils.postToUiThread(new Runnable() {
  2144             @Override
  2145             public void run() {
  2146                 if (fullscreen) {
  2147                     mViewFlipper.setVisibility(View.GONE);
  2148                     if (mDynamicToolbar.isEnabled()) {
  2149                         mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
  2150                         mLayerView.getLayerMarginsAnimator().setMaxMargins(0, 0, 0, 0);
  2151                     } else {
  2152                         setToolbarMargin(0);
  2154                 } else {
  2155                     mViewFlipper.setVisibility(View.VISIBLE);
  2156                     if (mDynamicToolbar.isEnabled()) {
  2157                         mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
  2158                         mLayerView.getLayerMarginsAnimator().setMaxMargins(0, mToolbarHeight, 0, 0);
  2162         });
  2165     @Override
  2166     public boolean onPrepareOptionsMenu(Menu aMenu) {
  2167         if (aMenu == null)
  2168             return false;
  2170         if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning))
  2171             aMenu.findItem(R.id.settings).setEnabled(false);
  2173         Tab tab = Tabs.getInstance().getSelectedTab();
  2174         MenuItem bookmark = aMenu.findItem(R.id.bookmark);
  2175         MenuItem back = aMenu.findItem(R.id.back);
  2176         MenuItem forward = aMenu.findItem(R.id.forward);
  2177         MenuItem share = aMenu.findItem(R.id.share);
  2178         MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
  2179         MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
  2180         MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
  2181         MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
  2182         MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
  2183         MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
  2185         // Only show the "Quit" menu item on pre-ICS or television devices.
  2186         // In ICS+, it's easy to kill an app through the task switcher.
  2187         aMenu.findItem(R.id.quit).setVisible(Build.VERSION.SDK_INT < 14 || HardwareUtils.isTelevision());
  2189         if (tab == null || tab.getURL() == null) {
  2190             bookmark.setEnabled(false);
  2191             back.setEnabled(false);
  2192             forward.setEnabled(false);
  2193             share.setEnabled(false);
  2194             saveAsPDF.setEnabled(false);
  2195             findInPage.setEnabled(false);
  2197             // NOTE: Use MenuUtils.safeSetEnabled because some actions might
  2198             // be on the BrowserToolbar context menu
  2199             MenuUtils.safeSetEnabled(aMenu, R.id.page, false);
  2200             MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, false);
  2201             MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, false);
  2202             MenuUtils.safeSetEnabled(aMenu, R.id.site_settings, false);
  2203             MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, false);
  2205             return true;
  2208         bookmark.setEnabled(!AboutPages.isAboutReader(tab.getURL()));
  2209         bookmark.setVisible(!GeckoProfile.get(this).inGuestMode());
  2210         bookmark.setCheckable(true);
  2211         bookmark.setChecked(tab.isBookmark());
  2212         bookmark.setIcon(tab.isBookmark() ? R.drawable.ic_menu_bookmark_remove : R.drawable.ic_menu_bookmark_add);
  2214         back.setEnabled(tab.canDoBack());
  2215         forward.setEnabled(tab.canDoForward());
  2216         desktopMode.setChecked(tab.getDesktopMode());
  2217         desktopMode.setIcon(tab.getDesktopMode() ? R.drawable.ic_menu_desktop_mode_on : R.drawable.ic_menu_desktop_mode_off);
  2219         String url = tab.getURL();
  2220         if (AboutPages.isAboutReader(url)) {
  2221             String urlFromReader = ReaderModeUtils.getUrlFromAboutReader(url);
  2222             if (urlFromReader != null) {
  2223                 url = urlFromReader;
  2227         // Disable share menuitem for about:, chrome:, file:, and resource: URIs
  2228         String scheme = Uri.parse(url).getScheme();
  2229         share.setVisible(!GeckoProfile.get(this).inGuestMode());
  2230         share.setEnabled(!(scheme.equals("about") || scheme.equals("chrome") ||
  2231                            scheme.equals("file") || scheme.equals("resource")));
  2233         // NOTE: Use MenuUtils.safeSetEnabled because some actions might
  2234         // be on the BrowserToolbar context menu
  2235         MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
  2236         MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, tab.hasFeeds());
  2237         MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, tab.hasOpenSearch());
  2238         MenuUtils.safeSetEnabled(aMenu, R.id.site_settings, !isAboutHome(tab));
  2239         MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, !isAboutHome(tab));
  2241         // Action providers are available only ICS+.
  2242         if (Build.VERSION.SDK_INT >= 14) {
  2243             final GeckoActionProvider provider = ((GeckoMenuItem) share).getGeckoActionProvider();
  2244             if (provider != null) {
  2245                 Intent shareIntent = provider.getIntent();
  2247                 // For efficiency, the provider's intent is only set once
  2248                 if (shareIntent == null) {
  2249                     shareIntent = new Intent(Intent.ACTION_SEND);
  2250                     shareIntent.setType("text/plain");
  2251                     provider.setIntent(shareIntent);
  2254                 // Replace the existing intent's extras
  2255                 shareIntent.putExtra(Intent.EXTRA_TEXT, url);
  2256                 shareIntent.putExtra(Intent.EXTRA_SUBJECT, tab.getDisplayTitle());
  2257                 shareIntent.putExtra(Intent.EXTRA_TITLE, tab.getDisplayTitle());
  2259                 // Clear the existing thumbnail extras so we don't share an old thumbnail.
  2260                 shareIntent.removeExtra("share_screenshot_uri");
  2262                 // Include the thumbnail of the page being shared.
  2263                 BitmapDrawable drawable = tab.getThumbnail();
  2264                 if (drawable != null) {
  2265                     Bitmap thumbnail = drawable.getBitmap();
  2267                     // Kobo uses a custom intent extra for sharing thumbnails.
  2268                     if (Build.MANUFACTURER.equals("Kobo") && thumbnail != null) {
  2269                         File cacheDir = getExternalCacheDir();
  2271                         if (cacheDir != null) {
  2272                             File outFile = new File(cacheDir, "thumbnail.png");
  2274                             try {
  2275                                 java.io.FileOutputStream out = new java.io.FileOutputStream(outFile);
  2276                                 thumbnail.compress(Bitmap.CompressFormat.PNG, 90, out);
  2277                             } catch (FileNotFoundException e) {
  2278                                 Log.e(LOGTAG, "File not found", e);
  2281                             shareIntent.putExtra("share_screenshot_uri", Uri.parse(outFile.getPath()));
  2288         // Disable save as PDF for about:home and xul pages.
  2289         saveAsPDF.setEnabled(!(isAboutHome(tab) ||
  2290                                tab.getContentType().equals("application/vnd.mozilla.xul+xml")));
  2292         // Disable find in page for about:home, since it won't work on Java content.
  2293         findInPage.setEnabled(!isAboutHome(tab));
  2295         charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
  2297         if (mProfile.inGuestMode())
  2298             exitGuestMode.setVisible(true);
  2299         else
  2300             enterGuestMode.setVisible(true);
  2302         return true;
  2305     @Override
  2306     public boolean onOptionsItemSelected(MenuItem item) {
  2307         Tab tab = null;
  2308         Intent intent = null;
  2310         final int itemId = item.getItemId();
  2312         // Track the menu action. We don't know much about the context, but we can use this to determine
  2313         // the frequency of use for various actions.
  2314         Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, getResources().getResourceEntryName(itemId));
  2316         if (itemId == R.id.bookmark) {
  2317             tab = Tabs.getInstance().getSelectedTab();
  2318             if (tab != null) {
  2319                 if (item.isChecked()) {
  2320                     tab.removeBookmark();
  2321                     Toast.makeText(this, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
  2322                     item.setIcon(R.drawable.ic_menu_bookmark_add);
  2323                 } else {
  2324                     tab.addBookmark();
  2325                     getButtonToast().show(false,
  2326                         getResources().getString(R.string.bookmark_added),
  2327                         getResources().getString(R.string.bookmark_options),
  2328                         null,
  2329                         new ButtonToast.ToastListener() {
  2330                             @Override
  2331                             public void onButtonClicked() {
  2332                                 showBookmarkDialog();
  2335                             @Override
  2336                             public void onToastHidden(ButtonToast.ReasonHidden reason) { }
  2337                         });
  2338                     item.setIcon(R.drawable.ic_menu_bookmark_remove);
  2341             return true;
  2344         if (itemId == R.id.share) {
  2345             shareCurrentUrl();
  2346             return true;
  2349         if (itemId == R.id.reload) {
  2350             tab = Tabs.getInstance().getSelectedTab();
  2351             if (tab != null)
  2352                 tab.doReload();
  2353             return true;
  2356         if (itemId == R.id.back) {
  2357             tab = Tabs.getInstance().getSelectedTab();
  2358             if (tab != null)
  2359                 tab.doBack();
  2360             return true;
  2363         if (itemId == R.id.forward) {
  2364             tab = Tabs.getInstance().getSelectedTab();
  2365             if (tab != null)
  2366                 tab.doForward();
  2367             return true;
  2370         if (itemId == R.id.save_as_pdf) {
  2371             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SaveAs:PDF", null));
  2372             return true;
  2375         if (itemId == R.id.settings) {
  2376             intent = new Intent(this, GeckoPreferences.class);
  2377             startActivity(intent);
  2378             return true;
  2381         if (itemId == R.id.addons) {
  2382             Tabs.getInstance().loadUrlInTab(AboutPages.ADDONS);
  2383             return true;
  2386         if (itemId == R.id.apps) {
  2387             Tabs.getInstance().loadUrlInTab(AboutPages.APPS);
  2388             return true;
  2391         if (itemId == R.id.downloads) {
  2392             Tabs.getInstance().loadUrlInTab(AboutPages.DOWNLOADS);
  2393             return true;
  2396         if (itemId == R.id.char_encoding) {
  2397             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("CharEncoding:Get", null));
  2398             return true;
  2401         if (itemId == R.id.find_in_page) {
  2402             mFindInPageBar.show();
  2403             return true;
  2406         if (itemId == R.id.desktop_mode) {
  2407             Tab selectedTab = Tabs.getInstance().getSelectedTab();
  2408             if (selectedTab == null)
  2409                 return true;
  2410             JSONObject args = new JSONObject();
  2411             try {
  2412                 args.put("desktopMode", !item.isChecked());
  2413                 args.put("tabId", selectedTab.getId());
  2414             } catch (JSONException e) {
  2415                 Log.e(LOGTAG, "error building json arguments");
  2417             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("DesktopMode:Change", args.toString()));
  2418             return true;
  2421         if (itemId == R.id.new_tab) {
  2422             addTab();
  2423             return true;
  2426         if (itemId == R.id.new_private_tab) {
  2427             addPrivateTab();
  2428             return true;
  2431         if (itemId == R.id.new_guest_session) {
  2432             showGuestModeDialog(GuestModeDialog.ENTERING);
  2433             return true;
  2436         if (itemId == R.id.exit_guest_session) {
  2437             showGuestModeDialog(GuestModeDialog.LEAVING);
  2438             return true;
  2441         // We have a few menu items that can also be in the context menu. If
  2442         // we have not already handled the item, give the context menu handler
  2443         // a chance.
  2444         if (onContextItemSelected(item)) {
  2445             return true;
  2448         return super.onOptionsItemSelected(item);
  2451     private void showGuestModeDialog(final GuestModeDialog type) {
  2452         final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
  2453             @Override
  2454             public void onPromptFinished(String result) {
  2455                 try {
  2456                     int itemId = new JSONObject(result).getInt("button");
  2457                     if (itemId == 0) {
  2458                         String args = "";
  2459                         if (type == GuestModeDialog.ENTERING) {
  2460                             args = GUEST_BROWSING_ARG;
  2461                         } else {
  2462                             GeckoProfile.leaveGuestSession(BrowserApp.this);
  2464                         doRestart(args);
  2465                         GeckoAppShell.systemExit();
  2467                 } catch(JSONException ex) {
  2468                     Log.e(LOGTAG, "Exception reading guest mode prompt result", ex);
  2471         });
  2473         Resources res = getResources();
  2474         ps.setButtons(new String[] {
  2475             res.getString(R.string.guest_session_dialog_continue),
  2476             res.getString(R.string.guest_session_dialog_cancel)
  2477         });
  2479         int titleString = 0;
  2480         int msgString = 0;
  2481         if (type == GuestModeDialog.ENTERING) {
  2482             titleString = R.string.new_guest_session_title;
  2483             msgString = R.string.new_guest_session_text;
  2484         } else {
  2485             titleString = R.string.exit_guest_session_title;
  2486             msgString = R.string.exit_guest_session_text;
  2489         ps.show(res.getString(titleString), res.getString(msgString), null, ListView.CHOICE_MODE_NONE);
  2492     /**
  2493      * This will detect if the key pressed is back. If so, will show the history.
  2494      */
  2495     @Override
  2496     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
  2497         if (keyCode == KeyEvent.KEYCODE_BACK) {
  2498             Tab tab = Tabs.getInstance().getSelectedTab();
  2499             if (tab != null) {
  2500                 return tab.showAllHistory();
  2503         return super.onKeyLongPress(keyCode, event);
  2506     /*
  2507      * If the app has been launched a certain number of times, and we haven't asked for feedback before,
  2508      * open a new tab with about:feedback when launching the app from the icon shortcut.
  2509      */
  2510     @Override
  2511     protected void onNewIntent(Intent intent) {
  2512         super.onNewIntent(intent);
  2514         String action = intent.getAction();
  2516         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 10 && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
  2517             String uri = intent.getDataString();
  2518             GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
  2521         if (!mInitialized) {
  2522             return;
  2525         if (Intent.ACTION_VIEW.equals(action)) {
  2526             // Dismiss editing mode if the user is loading a URL from an external app.
  2527             mBrowserToolbar.cancelEdit();
  2529             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT);
  2530             return;
  2533         // Only solicit feedback when the app has been launched from the icon shortcut.
  2534         if (!Intent.ACTION_MAIN.equals(action)) {
  2535             return;
  2538         (new UiAsyncTask<Void, Void, Boolean>(ThreadUtils.getBackgroundHandler()) {
  2539             @Override
  2540             public synchronized Boolean doInBackground(Void... params) {
  2541                 // Check to see how many times the app has been launched.
  2542                 SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
  2543                 String keyName = getPackageName() + ".feedback_launch_count";
  2544                 int launchCount = settings.getInt(keyName, 0);
  2545                 if (launchCount >= FEEDBACK_LAUNCH_COUNT)
  2546                     return false;
  2548                 // Increment the launch count and store the new value.
  2549                 launchCount++;
  2550                 settings.edit().putInt(keyName, launchCount).commit();
  2552                 // If we've reached our magic number, show the feedback page.
  2553                 return launchCount == FEEDBACK_LAUNCH_COUNT;
  2556             @Override
  2557             public void onPostExecute(Boolean shouldShowFeedbackPage) {
  2558                 if (shouldShowFeedbackPage)
  2559                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feedback:Show", null));
  2561         }).execute();
  2564     @Override
  2565     protected NotificationClient makeNotificationClient() {
  2566         // The service is local to Fennec, so we can use it to keep
  2567         // Fennec alive during downloads.
  2568         return new ServiceNotificationClient(getApplicationContext());
  2571     private void resetFeedbackLaunchCount() {
  2572         ThreadUtils.postToBackgroundThread(new Runnable() {
  2573             @Override
  2574             public synchronized void run() {
  2575                 SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
  2576                 settings.edit().putInt(getPackageName() + ".feedback_launch_count", 0).commit();
  2578         });
  2581     private void getLastUrl() {
  2582         (new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
  2583             @Override
  2584             public synchronized String doInBackground(Void... params) {
  2585                 // Get the most recent URL stored in browser history.
  2586                 String url = "";
  2587                 Cursor c = null;
  2588                 try {
  2589                     c = BrowserDB.getRecentHistory(getContentResolver(), 1);
  2590                     if (c.moveToFirst()) {
  2591                         url = c.getString(c.getColumnIndexOrThrow(Combined.URL));
  2593                 } finally {
  2594                     if (c != null)
  2595                         c.close();
  2597                 return url;
  2600             @Override
  2601             public void onPostExecute(String url) {
  2602                 // Don't bother sending a message if there is no URL.
  2603                 if (url.length() > 0)
  2604                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Feedback:LastUrl", url));
  2606         }).execute();
  2609     // HomePager.OnNewTabsListener
  2610     @Override
  2611     public void onNewTabs(String[] urls) {
  2612         final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB);
  2614         for (String url : urls) {
  2615             if (!maybeSwitchToTab(url, flags)) {
  2616                 openUrlAndStopEditing(url, true);
  2621     // HomePager.OnUrlOpenListener
  2622     @Override
  2623     public void onUrlOpen(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
  2624         if (flags.contains(OnUrlOpenListener.Flags.OPEN_WITH_INTENT)) {
  2625             Intent intent = new Intent(Intent.ACTION_VIEW);
  2626             intent.setData(Uri.parse(url));
  2627             startActivity(intent);
  2628         } else if (!maybeSwitchToTab(url, flags)) {
  2629             openUrlAndStopEditing(url);
  2633     // BrowserSearch.OnSearchListener
  2634     @Override
  2635     public void onSearch(SearchEngine engine, String text) {
  2636         recordSearch(engine, "barsuggest");
  2637         openUrlAndStopEditing(text, engine.name);
  2640     // BrowserSearch.OnEditSuggestionListener
  2641     @Override
  2642     public void onEditSuggestion(String suggestion) {
  2643         mBrowserToolbar.onEditSuggestion(suggestion);
  2646     @Override
  2647     public int getLayout() { return R.layout.gecko_app; }
  2649     @Override
  2650     protected String getDefaultProfileName() throws NoMozillaDirectoryException {
  2651         return GeckoProfile.getDefaultProfileName(this);
  2654     /**
  2655      * Launch UI that lets the user update Firefox.
  2657      * This depends on the current channel: Release and Beta both direct to the
  2658      * Google Play Store.  If updating is enabled, Aurora, Nightly, and custom
  2659      * builds open about:, which provides an update interface.
  2661      * If updating is not enabled, this simply logs an error.
  2663      * @return true if update UI was launched.
  2664      */
  2665     protected boolean handleUpdaterLaunch() {
  2666         if (AppConstants.RELEASE_BUILD) {
  2667             Intent intent = new Intent(Intent.ACTION_VIEW);
  2668             intent.setData(Uri.parse("market://details?id=" + getPackageName()));
  2669             startActivity(intent);
  2670             return true;
  2673         if (AppConstants.MOZ_UPDATER) {
  2674             Tabs.getInstance().loadUrlInTab(AboutPages.UPDATER);
  2675             return true;
  2678         Log.w(LOGTAG, "No candidate updater found; ignoring launch request.");
  2679         return false;
  2682     /* Implementing ActionModeCompat.Presenter */
  2683     @Override
  2684     public void startActionModeCompat(final ActionModeCompat.Callback callback) {
  2685         // If actionMode is null, we're not currently showing one. Flip to the action mode view
  2686         if (mActionMode == null) {
  2687             mViewFlipper.showNext();
  2688             LayerMarginsAnimator margins = mLayerView.getLayerMarginsAnimator();
  2690             // If the toolbar is dynamic and not currently showing, just slide it in
  2691             if (mDynamicToolbar.isEnabled() && !margins.areMarginsShown()) {
  2692                 margins.setMaxMargins(0, mViewFlipper.getHeight(), 0, 0);
  2693                 mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
  2694                 mShowActionModeEndAnimation = true;
  2695             } else {
  2696                 // Otherwise, we animate the actionbar itself
  2697                 mActionBar.animateIn();
  2700             mDynamicToolbar.setPinned(true, PinReason.ACTION_MODE);
  2701         } else {
  2702             // Otherwise, we're already showing an action mode. Just finish it and show the new one
  2703             mActionMode.finish();
  2706         mActionMode = new ActionModeCompat(BrowserApp.this, callback, mActionBar);
  2707         if (callback.onCreateActionMode(mActionMode, mActionMode.getMenu())) {
  2708             mActionMode.invalidate();
  2712     /* Implementing ActionModeCompat.Presenter */
  2713     @Override
  2714     public void endActionModeCompat() {
  2715         if (mActionMode == null) {
  2716             return;
  2719         mActionMode.finish();
  2720         mActionMode = null;
  2721         mDynamicToolbar.setPinned(false, PinReason.ACTION_MODE);
  2723         mViewFlipper.showPrevious();
  2725         // Only slide the urlbar out if it was hidden when the action mode started
  2726         // Don't animate hiding it so that there's no flash as we switch back to url mode
  2727         if (mShowActionModeEndAnimation) {
  2728             mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
  2729             mShowActionModeEndAnimation = false;
  2733     @Override
  2734     protected HealthRecorder createHealthRecorder(final Context context,
  2735                                                   final String profilePath,
  2736                                                   final EventDispatcher dispatcher,
  2737                                                   final String osLocale,
  2738                                                   final String appLocale,
  2739                                                   final SessionInformation previousSession) {
  2740         return new BrowserHealthRecorder(context,
  2741                                          GeckoSharedPrefs.forApp(context),
  2742                                          profilePath,
  2743                                          dispatcher,
  2744                                          osLocale,
  2745                                          appLocale,
  2746                                          previousSession);

mercurial