mobile/android/base/BrowserApp.java

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

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

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

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

mercurial