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