• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.browser;
18 
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.app.DownloadManager;
22 import android.app.ProgressDialog;
23 import android.content.ClipboardManager;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.DialogInterface.OnCancelListener;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.content.res.Configuration;
34 import android.content.res.TypedArray;
35 import android.database.ContentObserver;
36 import android.database.Cursor;
37 import android.database.sqlite.SQLiteDatabase;
38 import android.database.sqlite.SQLiteException;
39 import android.graphics.Bitmap;
40 import android.graphics.Canvas;
41 import android.net.Uri;
42 import android.net.http.SslError;
43 import android.os.AsyncTask;
44 import android.os.Bundle;
45 import android.os.Environment;
46 import android.os.Handler;
47 import android.os.Message;
48 import android.os.PowerManager;
49 import android.os.PowerManager.WakeLock;
50 import android.preference.PreferenceActivity;
51 import android.provider.Browser;
52 import android.provider.BrowserContract;
53 import android.provider.BrowserContract.Images;
54 import android.provider.ContactsContract;
55 import android.provider.ContactsContract.Intents.Insert;
56 import android.speech.RecognizerIntent;
57 import android.text.TextUtils;
58 import android.util.Log;
59 import android.util.Patterns;
60 import android.view.ActionMode;
61 import android.view.ContextMenu;
62 import android.view.ContextMenu.ContextMenuInfo;
63 import android.view.Gravity;
64 import android.view.KeyEvent;
65 import android.view.Menu;
66 import android.view.MenuInflater;
67 import android.view.MenuItem;
68 import android.view.MenuItem.OnMenuItemClickListener;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.webkit.CookieManager;
72 import android.webkit.CookieSyncManager;
73 import android.webkit.HttpAuthHandler;
74 import android.webkit.MimeTypeMap;
75 import android.webkit.SslErrorHandler;
76 import android.webkit.ValueCallback;
77 import android.webkit.WebChromeClient;
78 import android.webkit.WebIconDatabase;
79 import android.webkit.WebSettings;
80 import android.webkit.WebView;
81 import android.widget.Toast;
82 
83 import com.android.browser.IntentHandler.UrlData;
84 import com.android.browser.UI.ComboViews;
85 import com.android.browser.provider.BrowserProvider2.Thumbnails;
86 import com.android.browser.provider.SnapshotProvider.Snapshots;
87 
88 import java.io.ByteArrayOutputStream;
89 import java.io.File;
90 import java.io.FileOutputStream;
91 import java.io.IOException;
92 import java.net.URLEncoder;
93 import java.text.DateFormat;
94 import java.text.SimpleDateFormat;
95 import java.util.ArrayList;
96 import java.util.Calendar;
97 import java.util.Date;
98 import java.util.HashMap;
99 import java.util.List;
100 import java.util.Locale;
101 import java.util.Map;
102 
103 /**
104  * Controller for browser
105  */
106 public class Controller
107         implements WebViewController, UiController, ActivityController {
108 
109     private static final String LOGTAG = "Controller";
110     private static final String SEND_APP_ID_EXTRA =
111         "android.speech.extras.SEND_APPLICATION_ID_EXTRA";
112     private static final String INCOGNITO_URI = "browser:incognito";
113 
114 
115     // public message ids
116     public final static int LOAD_URL = 1001;
117     public final static int STOP_LOAD = 1002;
118 
119     // Message Ids
120     private static final int FOCUS_NODE_HREF = 102;
121     private static final int RELEASE_WAKELOCK = 107;
122 
123     static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
124 
125     private static final int OPEN_BOOKMARKS = 201;
126 
127     private static final int EMPTY_MENU = -1;
128 
129     // activity requestCode
130     final static int COMBO_VIEW = 1;
131     final static int PREFERENCES_PAGE = 3;
132     final static int FILE_SELECTED = 4;
133     final static int VOICE_RESULT = 6;
134 
135     private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
136 
137     // As the ids are dynamically created, we can't guarantee that they will
138     // be in sequence, so this static array maps ids to a window number.
139     final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
140     { R.id.window_one_menu_id, R.id.window_two_menu_id,
141       R.id.window_three_menu_id, R.id.window_four_menu_id,
142       R.id.window_five_menu_id, R.id.window_six_menu_id,
143       R.id.window_seven_menu_id, R.id.window_eight_menu_id };
144 
145     // "source" parameter for Google search through search key
146     final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
147     // "source" parameter for Google search through simplily type
148     final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
149 
150     // "no-crash-recovery" parameter in intent to suppress crash recovery
151     final static String NO_CRASH_RECOVERY = "no-crash-recovery";
152 
153     // A bitmap that is re-used in createScreenshot as scratch space
154     private static Bitmap sThumbnailBitmap;
155 
156     private Activity mActivity;
157     private UI mUi;
158     private TabControl mTabControl;
159     private BrowserSettings mSettings;
160     private WebViewFactory mFactory;
161 
162     private WakeLock mWakeLock;
163 
164     private UrlHandler mUrlHandler;
165     private UploadHandler mUploadHandler;
166     private IntentHandler mIntentHandler;
167     private PageDialogsHandler mPageDialogsHandler;
168     private NetworkStateHandler mNetworkHandler;
169 
170     private Message mAutoFillSetupMessage;
171 
172     private boolean mShouldShowErrorConsole;
173 
174     private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
175 
176     // FIXME, temp address onPrepareMenu performance problem.
177     // When we move everything out of view, we should rewrite this.
178     private int mCurrentMenuState = 0;
179     private int mMenuState = R.id.MAIN_MENU;
180     private int mOldMenuState = EMPTY_MENU;
181     private Menu mCachedMenu;
182 
183     private boolean mMenuIsDown;
184 
185     // For select and find, we keep track of the ActionMode so that
186     // finish() can be called as desired.
187     private ActionMode mActionMode;
188 
189     /**
190      * Only meaningful when mOptionsMenuOpen is true.  This variable keeps track
191      * of whether the configuration has changed.  The first onMenuOpened call
192      * after a configuration change is simply a reopening of the same menu
193      * (i.e. mIconView did not change).
194      */
195     private boolean mConfigChanged;
196 
197     /**
198      * Keeps track of whether the options menu is open. This is important in
199      * determining whether to show or hide the title bar overlay
200      */
201     private boolean mOptionsMenuOpen;
202 
203     /**
204      * Whether or not the options menu is in its bigger, popup menu form. When
205      * true, we want the title bar overlay to be gone. When false, we do not.
206      * Only meaningful if mOptionsMenuOpen is true.
207      */
208     private boolean mExtendedMenuOpen;
209 
210     private boolean mActivityPaused = true;
211     private boolean mLoadStopped;
212 
213     private Handler mHandler;
214     // Checks to see when the bookmarks database has changed, and updates the
215     // Tabs' notion of whether they represent bookmarked sites.
216     private ContentObserver mBookmarksObserver;
217     private CrashRecoveryHandler mCrashRecoveryHandler;
218 
219     private boolean mBlockEvents;
220 
221     private String mVoiceResult;
222 
Controller(Activity browser)223     public Controller(Activity browser) {
224         mActivity = browser;
225         mSettings = BrowserSettings.getInstance();
226         mTabControl = new TabControl(this);
227         mSettings.setController(this);
228         mCrashRecoveryHandler = CrashRecoveryHandler.initialize(this);
229         mCrashRecoveryHandler.preloadCrashState();
230         mFactory = new BrowserWebViewFactory(browser);
231 
232         mUrlHandler = new UrlHandler(this);
233         mIntentHandler = new IntentHandler(mActivity, this);
234         mPageDialogsHandler = new PageDialogsHandler(mActivity, this);
235 
236         startHandler();
237         mBookmarksObserver = new ContentObserver(mHandler) {
238             @Override
239             public void onChange(boolean selfChange) {
240                 int size = mTabControl.getTabCount();
241                 for (int i = 0; i < size; i++) {
242                     mTabControl.getTab(i).updateBookmarkedStatus();
243                 }
244             }
245 
246         };
247         browser.getContentResolver().registerContentObserver(
248                 BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver);
249 
250         mNetworkHandler = new NetworkStateHandler(mActivity, this);
251         // Start watching the default geolocation permissions
252         mSystemAllowGeolocationOrigins =
253                 new SystemAllowGeolocationOrigins(mActivity.getApplicationContext());
254         mSystemAllowGeolocationOrigins.start();
255 
256         openIconDatabase();
257     }
258 
259     @Override
start(final Intent intent)260     public void start(final Intent intent) {
261         // mCrashRecoverHandler has any previously saved state.
262         mCrashRecoveryHandler.startRecovery(intent);
263     }
264 
doStart(final Bundle icicle, final Intent intent)265     void doStart(final Bundle icicle, final Intent intent) {
266         // Unless the last browser usage was within 24 hours, destroy any
267         // remaining incognito tabs.
268 
269         Calendar lastActiveDate = icicle != null ?
270                 (Calendar) icicle.getSerializable("lastActiveDate") : null;
271         Calendar today = Calendar.getInstance();
272         Calendar yesterday = Calendar.getInstance();
273         yesterday.add(Calendar.DATE, -1);
274 
275         final boolean restoreIncognitoTabs = !(lastActiveDate == null
276             || lastActiveDate.before(yesterday)
277             || lastActiveDate.after(today));
278 
279         // Find out if we will restore any state and remember the tab.
280         final long currentTabId =
281                 mTabControl.canRestoreState(icicle, restoreIncognitoTabs);
282 
283         if (currentTabId == -1) {
284             // Not able to restore so we go ahead and clear session cookies.  We
285             // must do this before trying to login the user as we don't want to
286             // clear any session cookies set during login.
287             CookieManager.getInstance().removeSessionCookie();
288         }
289 
290         GoogleAccountLogin.startLoginIfNeeded(mActivity,
291                 new Runnable() {
292                     @Override public void run() {
293                         onPreloginFinished(icicle, intent, currentTabId,
294                                 restoreIncognitoTabs);
295                     }
296                 });
297     }
298 
onPreloginFinished(Bundle icicle, Intent intent, long currentTabId, boolean restoreIncognitoTabs)299     private void onPreloginFinished(Bundle icicle, Intent intent, long currentTabId,
300             boolean restoreIncognitoTabs) {
301         if (currentTabId == -1) {
302             BackgroundHandler.execute(new PruneThumbnails(mActivity, null));
303             if (intent == null) {
304                 // This won't happen under common scenarios. The icicle is
305                 // not null, but there aren't any tabs to restore.
306                 openTabToHomePage();
307             } else {
308                 final Bundle extra = intent.getExtras();
309                 // Create an initial tab.
310                 // If the intent is ACTION_VIEW and data is not null, the Browser is
311                 // invoked to view the content by another application. In this case,
312                 // the tab will be close when exit.
313                 UrlData urlData = IntentHandler.getUrlDataFromIntent(intent);
314                 Tab t = null;
315                 if (urlData.isEmpty()) {
316                     t = openTabToHomePage();
317                 } else {
318                     t = openTab(urlData);
319                 }
320                 if (t != null) {
321                     t.setAppId(intent.getStringExtra(Browser.EXTRA_APPLICATION_ID));
322                 }
323                 WebView webView = t.getWebView();
324                 if (extra != null) {
325                     int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
326                     if (scale > 0 && scale <= 1000) {
327                         webView.setInitialScale(scale);
328                     }
329                 }
330             }
331             mUi.updateTabs(mTabControl.getTabs());
332         } else {
333             mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs,
334                     mUi.needsRestoreAllTabs());
335             List<Tab> tabs = mTabControl.getTabs();
336             ArrayList<Long> restoredTabs = new ArrayList<Long>(tabs.size());
337             for (Tab t : tabs) {
338                 restoredTabs.add(t.getId());
339             }
340             BackgroundHandler.execute(new PruneThumbnails(mActivity, restoredTabs));
341             if (tabs.size() == 0) {
342                 openTabToHomePage();
343             }
344             mUi.updateTabs(tabs);
345             // TabControl.restoreState() will create a new tab even if
346             // restoring the state fails.
347             setActiveTab(mTabControl.getCurrentTab());
348             // Intent is non-null when framework thinks the browser should be
349             // launching with a new intent (icicle is null).
350             if (intent != null) {
351                 mIntentHandler.onNewIntent(intent);
352             }
353         }
354         // Read JavaScript flags if it exists.
355         String jsFlags = getSettings().getJsEngineFlags();
356         if (intent != null
357                 && BrowserActivity.ACTION_SHOW_BOOKMARKS.equals(intent.getAction())) {
358             bookmarksOrHistoryPicker(ComboViews.Bookmarks);
359         }
360     }
361 
362     private static class PruneThumbnails implements Runnable {
363         private Context mContext;
364         private List<Long> mIds;
365 
PruneThumbnails(Context context, List<Long> preserveIds)366         PruneThumbnails(Context context, List<Long> preserveIds) {
367             mContext = context.getApplicationContext();
368             mIds = preserveIds;
369         }
370 
371         @Override
run()372         public void run() {
373             ContentResolver cr = mContext.getContentResolver();
374             if (mIds == null || mIds.size() == 0) {
375                 cr.delete(Thumbnails.CONTENT_URI, null, null);
376             } else {
377                 int length = mIds.size();
378                 StringBuilder where = new StringBuilder();
379                 where.append(Thumbnails._ID);
380                 where.append(" not in (");
381                 for (int i = 0; i < length; i++) {
382                     where.append(mIds.get(i));
383                     if (i < (length - 1)) {
384                         where.append(",");
385                     }
386                 }
387                 where.append(")");
388                 cr.delete(Thumbnails.CONTENT_URI, where.toString(), null);
389             }
390         }
391 
392     }
393 
394     @Override
getWebViewFactory()395     public WebViewFactory getWebViewFactory() {
396         return mFactory;
397     }
398 
399     @Override
onSetWebView(Tab tab, WebView view)400     public void onSetWebView(Tab tab, WebView view) {
401         mUi.onSetWebView(tab, view);
402     }
403 
404     @Override
createSubWindow(Tab tab)405     public void createSubWindow(Tab tab) {
406         endActionMode();
407         WebView mainView = tab.getWebView();
408         WebView subView = mFactory.createWebView((mainView == null)
409                 ? false
410                 : mainView.isPrivateBrowsingEnabled());
411         mUi.createSubWindow(tab, subView);
412     }
413 
414     @Override
getContext()415     public Context getContext() {
416         return mActivity;
417     }
418 
419     @Override
getActivity()420     public Activity getActivity() {
421         return mActivity;
422     }
423 
setUi(UI ui)424     void setUi(UI ui) {
425         mUi = ui;
426     }
427 
428     @Override
getSettings()429     public BrowserSettings getSettings() {
430         return mSettings;
431     }
432 
getIntentHandler()433     IntentHandler getIntentHandler() {
434         return mIntentHandler;
435     }
436 
437     @Override
getUi()438     public UI getUi() {
439         return mUi;
440     }
441 
getMaxTabs()442     int getMaxTabs() {
443         return mActivity.getResources().getInteger(R.integer.max_tabs);
444     }
445 
446     @Override
getTabControl()447     public TabControl getTabControl() {
448         return mTabControl;
449     }
450 
451     @Override
getTabs()452     public List<Tab> getTabs() {
453         return mTabControl.getTabs();
454     }
455 
456     // Open the icon database.
openIconDatabase()457     private void openIconDatabase() {
458         // We have to call getInstance on the UI thread
459         final WebIconDatabase instance = WebIconDatabase.getInstance();
460         BackgroundHandler.execute(new Runnable() {
461 
462             @Override
463             public void run() {
464                 instance.open(mActivity.getDir("icons", 0).getPath());
465             }
466         });
467     }
468 
startHandler()469     private void startHandler() {
470         mHandler = new Handler() {
471 
472             @Override
473             public void handleMessage(Message msg) {
474                 switch (msg.what) {
475                     case OPEN_BOOKMARKS:
476                         bookmarksOrHistoryPicker(ComboViews.Bookmarks);
477                         break;
478                     case FOCUS_NODE_HREF:
479                     {
480                         String url = (String) msg.getData().get("url");
481                         String title = (String) msg.getData().get("title");
482                         String src = (String) msg.getData().get("src");
483                         if (url == "") url = src; // use image if no anchor
484                         if (TextUtils.isEmpty(url)) {
485                             break;
486                         }
487                         HashMap focusNodeMap = (HashMap) msg.obj;
488                         WebView view = (WebView) focusNodeMap.get("webview");
489                         // Only apply the action if the top window did not change.
490                         if (getCurrentTopWebView() != view) {
491                             break;
492                         }
493                         switch (msg.arg1) {
494                             case R.id.open_context_menu_id:
495                                 loadUrlFromContext(url);
496                                 break;
497                             case R.id.view_image_context_menu_id:
498                                 loadUrlFromContext(src);
499                                 break;
500                             case R.id.open_newtab_context_menu_id:
501                                 final Tab parent = mTabControl.getCurrentTab();
502                                 openTab(url, parent,
503                                         !mSettings.openInBackground(), true);
504                                 break;
505                             case R.id.copy_link_context_menu_id:
506                                 copy(url);
507                                 break;
508                             case R.id.save_link_context_menu_id:
509                             case R.id.download_context_menu_id:
510                                 DownloadHandler.onDownloadStartNoStream(
511                                         mActivity, url, view.getSettings().getUserAgentString(),
512                                         null, null, null, view.isPrivateBrowsingEnabled());
513                                 break;
514                         }
515                         break;
516                     }
517 
518                     case LOAD_URL:
519                         loadUrlFromContext((String) msg.obj);
520                         break;
521 
522                     case STOP_LOAD:
523                         stopLoading();
524                         break;
525 
526                     case RELEASE_WAKELOCK:
527                         if (mWakeLock != null && mWakeLock.isHeld()) {
528                             mWakeLock.release();
529                             // if we reach here, Browser should be still in the
530                             // background loading after WAKELOCK_TIMEOUT (5-min).
531                             // To avoid burning the battery, stop loading.
532                             mTabControl.stopAllLoading();
533                         }
534                         break;
535 
536                     case UPDATE_BOOKMARK_THUMBNAIL:
537                         Tab tab = (Tab) msg.obj;
538                         if (tab != null) {
539                             updateScreenshot(tab);
540                         }
541                         break;
542                 }
543             }
544         };
545 
546     }
547 
548     @Override
getCurrentTab()549     public Tab getCurrentTab() {
550         return mTabControl.getCurrentTab();
551     }
552 
553     @Override
shareCurrentPage()554     public void shareCurrentPage() {
555         shareCurrentPage(mTabControl.getCurrentTab());
556     }
557 
shareCurrentPage(Tab tab)558     private void shareCurrentPage(Tab tab) {
559         if (tab != null) {
560             sharePage(mActivity, tab.getTitle(),
561                     tab.getUrl(), tab.getFavicon(),
562                     createScreenshot(tab.getWebView(),
563                             getDesiredThumbnailWidth(mActivity),
564                             getDesiredThumbnailHeight(mActivity)));
565         }
566     }
567 
568     /**
569      * Share a page, providing the title, url, favicon, and a screenshot.  Uses
570      * an {@link Intent} to launch the Activity chooser.
571      * @param c Context used to launch a new Activity.
572      * @param title Title of the page.  Stored in the Intent with
573      *          {@link Intent#EXTRA_SUBJECT}
574      * @param url URL of the page.  Stored in the Intent with
575      *          {@link Intent#EXTRA_TEXT}
576      * @param favicon Bitmap of the favicon for the page.  Stored in the Intent
577      *          with {@link Browser#EXTRA_SHARE_FAVICON}
578      * @param screenshot Bitmap of a screenshot of the page.  Stored in the
579      *          Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT}
580      */
sharePage(Context c, String title, String url, Bitmap favicon, Bitmap screenshot)581     static final void sharePage(Context c, String title, String url,
582             Bitmap favicon, Bitmap screenshot) {
583         Intent send = new Intent(Intent.ACTION_SEND);
584         send.setType("text/plain");
585         send.putExtra(Intent.EXTRA_TEXT, url);
586         send.putExtra(Intent.EXTRA_SUBJECT, title);
587         send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
588         send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
589         try {
590             c.startActivity(Intent.createChooser(send, c.getString(
591                     R.string.choosertitle_sharevia)));
592         } catch(android.content.ActivityNotFoundException ex) {
593             // if no app handles it, do nothing
594         }
595     }
596 
copy(CharSequence text)597     private void copy(CharSequence text) {
598         ClipboardManager cm = (ClipboardManager) mActivity
599                 .getSystemService(Context.CLIPBOARD_SERVICE);
600         cm.setText(text);
601     }
602 
603     // lifecycle
604 
605     @Override
onConfgurationChanged(Configuration config)606     public void onConfgurationChanged(Configuration config) {
607         mConfigChanged = true;
608         // update the menu in case of a locale change
609         mActivity.invalidateOptionsMenu();
610         if (mPageDialogsHandler != null) {
611             mPageDialogsHandler.onConfigurationChanged(config);
612         }
613         mUi.onConfigurationChanged(config);
614     }
615 
616     @Override
handleNewIntent(Intent intent)617     public void handleNewIntent(Intent intent) {
618         if (!mUi.isWebShowing()) {
619             mUi.showWeb(false);
620         }
621         mIntentHandler.onNewIntent(intent);
622     }
623 
624     @Override
onPause()625     public void onPause() {
626         if (mUi.isCustomViewShowing()) {
627             hideCustomView();
628         }
629         if (mActivityPaused) {
630             Log.e(LOGTAG, "BrowserActivity is already paused.");
631             return;
632         }
633         mActivityPaused = true;
634         Tab tab = mTabControl.getCurrentTab();
635         if (tab != null) {
636             tab.pause();
637             if (!pauseWebViewTimers(tab)) {
638                 if (mWakeLock == null) {
639                     PowerManager pm = (PowerManager) mActivity
640                             .getSystemService(Context.POWER_SERVICE);
641                     mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
642                 }
643                 mWakeLock.acquire();
644                 mHandler.sendMessageDelayed(mHandler
645                         .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
646             }
647         }
648         mUi.onPause();
649         mNetworkHandler.onPause();
650 
651         WebView.disablePlatformNotifications();
652         NfcHandler.unregister(mActivity);
653         if (sThumbnailBitmap != null) {
654             sThumbnailBitmap.recycle();
655             sThumbnailBitmap = null;
656         }
657     }
658 
659     @Override
onSaveInstanceState(Bundle outState)660     public void onSaveInstanceState(Bundle outState) {
661         // Save all the tabs
662         Bundle saveState = createSaveState();
663 
664         // crash recovery manages all save & restore state
665         mCrashRecoveryHandler.writeState(saveState);
666         mSettings.setLastRunPaused(true);
667     }
668 
669     /**
670      * Save the current state to outState. Does not write the state to
671      * disk.
672      * @return Bundle containing the current state of all tabs.
673      */
createSaveState()674     /* package */ Bundle createSaveState() {
675         Bundle saveState = new Bundle();
676         mTabControl.saveState(saveState);
677         if (!saveState.isEmpty()) {
678             // Save time so that we know how old incognito tabs (if any) are.
679             saveState.putSerializable("lastActiveDate", Calendar.getInstance());
680         }
681         return saveState;
682     }
683 
684     @Override
onResume()685     public void onResume() {
686         if (!mActivityPaused) {
687             Log.e(LOGTAG, "BrowserActivity is already resumed.");
688             return;
689         }
690         mSettings.setLastRunPaused(false);
691         mActivityPaused = false;
692         Tab current = mTabControl.getCurrentTab();
693         if (current != null) {
694             current.resume();
695             resumeWebViewTimers(current);
696         }
697         releaseWakeLock();
698 
699         mUi.onResume();
700         mNetworkHandler.onResume();
701         WebView.enablePlatformNotifications();
702         NfcHandler.register(mActivity, this);
703         if (mVoiceResult != null) {
704             mUi.onVoiceResult(mVoiceResult);
705             mVoiceResult = null;
706         }
707     }
708 
releaseWakeLock()709     private void releaseWakeLock() {
710         if (mWakeLock != null && mWakeLock.isHeld()) {
711             mHandler.removeMessages(RELEASE_WAKELOCK);
712             mWakeLock.release();
713         }
714     }
715 
716     /**
717      * resume all WebView timers using the WebView instance of the given tab
718      * @param tab guaranteed non-null
719      */
resumeWebViewTimers(Tab tab)720     private void resumeWebViewTimers(Tab tab) {
721         boolean inLoad = tab.inPageLoad();
722         if ((!mActivityPaused && !inLoad) || (mActivityPaused && inLoad)) {
723             CookieSyncManager.getInstance().startSync();
724             WebView w = tab.getWebView();
725             WebViewTimersControl.getInstance().onBrowserActivityResume(w);
726         }
727     }
728 
729     /**
730      * Pause all WebView timers using the WebView of the given tab
731      * @param tab
732      * @return true if the timers are paused or tab is null
733      */
pauseWebViewTimers(Tab tab)734     private boolean pauseWebViewTimers(Tab tab) {
735         if (tab == null) {
736             return true;
737         } else if (!tab.inPageLoad()) {
738             CookieSyncManager.getInstance().stopSync();
739             WebViewTimersControl.getInstance().onBrowserActivityPause(getCurrentWebView());
740             return true;
741         }
742         return false;
743     }
744 
745     @Override
onDestroy()746     public void onDestroy() {
747         if (mUploadHandler != null && !mUploadHandler.handled()) {
748             mUploadHandler.onResult(Activity.RESULT_CANCELED, null);
749             mUploadHandler = null;
750         }
751         if (mTabControl == null) return;
752         mUi.onDestroy();
753         // Remove the current tab and sub window
754         Tab t = mTabControl.getCurrentTab();
755         if (t != null) {
756             dismissSubWindow(t);
757             removeTab(t);
758         }
759         mActivity.getContentResolver().unregisterContentObserver(mBookmarksObserver);
760         // Destroy all the tabs
761         mTabControl.destroy();
762         WebIconDatabase.getInstance().close();
763         // Stop watching the default geolocation permissions
764         mSystemAllowGeolocationOrigins.stop();
765         mSystemAllowGeolocationOrigins = null;
766     }
767 
isActivityPaused()768     protected boolean isActivityPaused() {
769         return mActivityPaused;
770     }
771 
772     @Override
onLowMemory()773     public void onLowMemory() {
774         mTabControl.freeMemory();
775     }
776 
777     @Override
shouldShowErrorConsole()778     public boolean shouldShowErrorConsole() {
779         return mShouldShowErrorConsole;
780     }
781 
setShouldShowErrorConsole(boolean show)782     protected void setShouldShowErrorConsole(boolean show) {
783         if (show == mShouldShowErrorConsole) {
784             // Nothing to do.
785             return;
786         }
787         mShouldShowErrorConsole = show;
788         Tab t = mTabControl.getCurrentTab();
789         if (t == null) {
790             // There is no current tab so we cannot toggle the error console
791             return;
792         }
793         mUi.setShouldShowErrorConsole(t, show);
794     }
795 
796     @Override
stopLoading()797     public void stopLoading() {
798         mLoadStopped = true;
799         Tab tab = mTabControl.getCurrentTab();
800         WebView w = getCurrentTopWebView();
801         if (w != null) {
802             w.stopLoading();
803             mUi.onPageStopped(tab);
804         }
805     }
806 
didUserStopLoading()807     boolean didUserStopLoading() {
808         return mLoadStopped;
809     }
810 
811     // WebViewController
812 
813     @Override
onPageStarted(Tab tab, WebView view, Bitmap favicon)814     public void onPageStarted(Tab tab, WebView view, Bitmap favicon) {
815 
816         // We've started to load a new page. If there was a pending message
817         // to save a screenshot then we will now take the new page and save
818         // an incorrect screenshot. Therefore, remove any pending thumbnail
819         // messages from the queue.
820         mHandler.removeMessages(Controller.UPDATE_BOOKMARK_THUMBNAIL,
821                 tab);
822 
823         // reset sync timer to avoid sync starts during loading a page
824         CookieSyncManager.getInstance().resetSync();
825 
826         if (!mNetworkHandler.isNetworkUp()) {
827             view.setNetworkAvailable(false);
828         }
829 
830         // when BrowserActivity just starts, onPageStarted may be called before
831         // onResume as it is triggered from onCreate. Call resumeWebViewTimers
832         // to start the timer. As we won't switch tabs while an activity is in
833         // pause state, we can ensure calling resume and pause in pair.
834         if (mActivityPaused) {
835             resumeWebViewTimers(tab);
836         }
837         mLoadStopped = false;
838         endActionMode();
839 
840         mUi.onTabDataChanged(tab);
841 
842         String url = tab.getUrl();
843         // update the bookmark database for favicon
844         maybeUpdateFavicon(tab, null, url, favicon);
845 
846         Performance.tracePageStart(url);
847 
848         // Performance probe
849         if (false) {
850             Performance.onPageStarted();
851         }
852 
853     }
854 
855     @Override
onPageFinished(Tab tab)856     public void onPageFinished(Tab tab) {
857         mCrashRecoveryHandler.backupState();
858         mUi.onTabDataChanged(tab);
859 
860         // Performance probe
861         if (false) {
862             Performance.onPageFinished(tab.getUrl());
863          }
864 
865         Performance.tracePageFinished();
866     }
867 
868     @Override
onProgressChanged(Tab tab)869     public void onProgressChanged(Tab tab) {
870         int newProgress = tab.getLoadProgress();
871 
872         if (newProgress == 100) {
873             CookieSyncManager.getInstance().sync();
874             // onProgressChanged() may continue to be called after the main
875             // frame has finished loading, as any remaining sub frames continue
876             // to load. We'll only get called once though with newProgress as
877             // 100 when everything is loaded. (onPageFinished is called once
878             // when the main frame completes loading regardless of the state of
879             // any sub frames so calls to onProgressChanges may continue after
880             // onPageFinished has executed)
881             if (tab.inPageLoad()) {
882                 updateInLoadMenuItems(mCachedMenu, tab);
883             } else if (mActivityPaused && pauseWebViewTimers(tab)) {
884                 // pause the WebView timer and release the wake lock if it is
885                 // finished while BrowserActivity is in pause state.
886                 releaseWakeLock();
887             }
888             if (!tab.isPrivateBrowsingEnabled()
889                     && !TextUtils.isEmpty(tab.getUrl())
890                     && !tab.isSnapshot()) {
891                 // Only update the bookmark screenshot if the user did not
892                 // cancel the load early and there is not already
893                 // a pending update for the tab.
894                 if (tab.shouldUpdateThumbnail() &&
895                         (tab.inForeground() && !didUserStopLoading()
896                         || !tab.inForeground())) {
897                     if (!mHandler.hasMessages(UPDATE_BOOKMARK_THUMBNAIL, tab)) {
898                         mHandler.sendMessageDelayed(mHandler.obtainMessage(
899                                 UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab),
900                                 500);
901                     }
902                 }
903             }
904         } else {
905             if (!tab.inPageLoad()) {
906                 // onPageFinished may have already been called but a subframe is
907                 // still loading
908                 // updating the progress and
909                 // update the menu items.
910                 updateInLoadMenuItems(mCachedMenu, tab);
911             }
912         }
913         mUi.onProgressChanged(tab);
914     }
915 
916     @Override
onUpdatedSecurityState(Tab tab)917     public void onUpdatedSecurityState(Tab tab) {
918         mUi.onTabDataChanged(tab);
919     }
920 
921     @Override
onReceivedTitle(Tab tab, final String title)922     public void onReceivedTitle(Tab tab, final String title) {
923         mUi.onTabDataChanged(tab);
924         final String pageUrl = tab.getOriginalUrl();
925         if (TextUtils.isEmpty(pageUrl) || pageUrl.length()
926                 >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
927             return;
928         }
929         // Update the title in the history database if not in private browsing mode
930         if (!tab.isPrivateBrowsingEnabled()) {
931             DataController.getInstance(mActivity).updateHistoryTitle(pageUrl, title);
932         }
933     }
934 
935     @Override
onFavicon(Tab tab, WebView view, Bitmap icon)936     public void onFavicon(Tab tab, WebView view, Bitmap icon) {
937         mUi.onTabDataChanged(tab);
938         maybeUpdateFavicon(tab, view.getOriginalUrl(), view.getUrl(), icon);
939     }
940 
941     @Override
shouldOverrideUrlLoading(Tab tab, WebView view, String url)942     public boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
943         return mUrlHandler.shouldOverrideUrlLoading(tab, view, url);
944     }
945 
946     @Override
shouldOverrideKeyEvent(KeyEvent event)947     public boolean shouldOverrideKeyEvent(KeyEvent event) {
948         if (mMenuIsDown) {
949             // only check shortcut key when MENU is held
950             return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
951                     event);
952         } else {
953             return false;
954         }
955     }
956 
957     @Override
onUnhandledKeyEvent(KeyEvent event)958     public boolean onUnhandledKeyEvent(KeyEvent event) {
959         if (!isActivityPaused()) {
960             if (event.getAction() == KeyEvent.ACTION_DOWN) {
961                 return mActivity.onKeyDown(event.getKeyCode(), event);
962             } else {
963                 return mActivity.onKeyUp(event.getKeyCode(), event);
964             }
965         }
966         return false;
967     }
968 
969     @Override
doUpdateVisitedHistory(Tab tab, boolean isReload)970     public void doUpdateVisitedHistory(Tab tab, boolean isReload) {
971         // Don't save anything in private browsing mode
972         if (tab.isPrivateBrowsingEnabled()) return;
973         String url = tab.getOriginalUrl();
974 
975         if (TextUtils.isEmpty(url)
976                 || url.regionMatches(true, 0, "about:", 0, 6)) {
977             return;
978         }
979         DataController.getInstance(mActivity).updateVisitedHistory(url);
980         mCrashRecoveryHandler.backupState();
981     }
982 
983     @Override
getVisitedHistory(final ValueCallback<String[]> callback)984     public void getVisitedHistory(final ValueCallback<String[]> callback) {
985         AsyncTask<Void, Void, String[]> task =
986                 new AsyncTask<Void, Void, String[]>() {
987             @Override
988             public String[] doInBackground(Void... unused) {
989                 return Browser.getVisitedHistory(mActivity.getContentResolver());
990             }
991             @Override
992             public void onPostExecute(String[] result) {
993                 callback.onReceiveValue(result);
994             }
995         };
996         task.execute();
997     }
998 
999     @Override
onReceivedHttpAuthRequest(Tab tab, WebView view, final HttpAuthHandler handler, final String host, final String realm)1000     public void onReceivedHttpAuthRequest(Tab tab, WebView view,
1001             final HttpAuthHandler handler, final String host,
1002             final String realm) {
1003         String username = null;
1004         String password = null;
1005 
1006         boolean reuseHttpAuthUsernamePassword
1007                 = handler.useHttpAuthUsernamePassword();
1008 
1009         if (reuseHttpAuthUsernamePassword && view != null) {
1010             String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
1011             if (credentials != null && credentials.length == 2) {
1012                 username = credentials[0];
1013                 password = credentials[1];
1014             }
1015         }
1016 
1017         if (username != null && password != null) {
1018             handler.proceed(username, password);
1019         } else {
1020             if (tab.inForeground() && !handler.suppressDialog()) {
1021                 mPageDialogsHandler.showHttpAuthentication(tab, handler, host, realm);
1022             } else {
1023                 handler.cancel();
1024             }
1025         }
1026     }
1027 
1028     @Override
onDownloadStart(Tab tab, String url, String userAgent, String contentDisposition, String mimetype, String referer, long contentLength)1029     public void onDownloadStart(Tab tab, String url, String userAgent,
1030             String contentDisposition, String mimetype, String referer,
1031             long contentLength) {
1032         WebView w = tab.getWebView();
1033         DownloadHandler.onDownloadStart(mActivity, url, userAgent,
1034                 contentDisposition, mimetype, referer, w.isPrivateBrowsingEnabled());
1035         if (w.copyBackForwardList().getSize() == 0) {
1036             // This Tab was opened for the sole purpose of downloading a
1037             // file. Remove it.
1038             if (tab == mTabControl.getCurrentTab()) {
1039                 // In this case, the Tab is still on top.
1040                 goBackOnePageOrQuit();
1041             } else {
1042                 // In this case, it is not.
1043                 closeTab(tab);
1044             }
1045         }
1046     }
1047 
1048     @Override
getDefaultVideoPoster()1049     public Bitmap getDefaultVideoPoster() {
1050         return mUi.getDefaultVideoPoster();
1051     }
1052 
1053     @Override
getVideoLoadingProgressView()1054     public View getVideoLoadingProgressView() {
1055         return mUi.getVideoLoadingProgressView();
1056     }
1057 
1058     @Override
showSslCertificateOnError(WebView view, SslErrorHandler handler, SslError error)1059     public void showSslCertificateOnError(WebView view, SslErrorHandler handler,
1060             SslError error) {
1061         mPageDialogsHandler.showSSLCertificateOnError(view, handler, error);
1062     }
1063 
1064     @Override
showAutoLogin(Tab tab)1065     public void showAutoLogin(Tab tab) {
1066         assert tab.inForeground();
1067         // Update the title bar to show the auto-login request.
1068         mUi.showAutoLogin(tab);
1069     }
1070 
1071     @Override
hideAutoLogin(Tab tab)1072     public void hideAutoLogin(Tab tab) {
1073         assert tab.inForeground();
1074         mUi.hideAutoLogin(tab);
1075     }
1076 
1077     // helper method
1078 
1079     /*
1080      * Update the favorites icon if the private browsing isn't enabled and the
1081      * icon is valid.
1082      */
maybeUpdateFavicon(Tab tab, final String originalUrl, final String url, Bitmap favicon)1083     private void maybeUpdateFavicon(Tab tab, final String originalUrl,
1084             final String url, Bitmap favicon) {
1085         if (favicon == null) {
1086             return;
1087         }
1088         if (!tab.isPrivateBrowsingEnabled()) {
1089             Bookmarks.updateFavicon(mActivity
1090                     .getContentResolver(), originalUrl, url, favicon);
1091         }
1092     }
1093 
1094     @Override
bookmarkedStatusHasChanged(Tab tab)1095     public void bookmarkedStatusHasChanged(Tab tab) {
1096         // TODO: Switch to using onTabDataChanged after b/3262950 is fixed
1097         mUi.bookmarkedStatusHasChanged(tab);
1098     }
1099 
1100     // end WebViewController
1101 
pageUp()1102     protected void pageUp() {
1103         getCurrentTopWebView().pageUp(false);
1104     }
1105 
pageDown()1106     protected void pageDown() {
1107         getCurrentTopWebView().pageDown(false);
1108     }
1109 
1110     // callback from phone title bar
1111     @Override
editUrl()1112     public void editUrl() {
1113         if (mOptionsMenuOpen) mActivity.closeOptionsMenu();
1114         mUi.editUrl(false, true);
1115     }
1116 
1117     @Override
showCustomView(Tab tab, View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback)1118     public void showCustomView(Tab tab, View view, int requestedOrientation,
1119             WebChromeClient.CustomViewCallback callback) {
1120         if (tab.inForeground()) {
1121             if (mUi.isCustomViewShowing()) {
1122                 callback.onCustomViewHidden();
1123                 return;
1124             }
1125             mUi.showCustomView(view, requestedOrientation, callback);
1126             // Save the menu state and set it to empty while the custom
1127             // view is showing.
1128             mOldMenuState = mMenuState;
1129             mMenuState = EMPTY_MENU;
1130             mActivity.invalidateOptionsMenu();
1131         }
1132     }
1133 
1134     @Override
hideCustomView()1135     public void hideCustomView() {
1136         if (mUi.isCustomViewShowing()) {
1137             mUi.onHideCustomView();
1138             // Reset the old menu state.
1139             mMenuState = mOldMenuState;
1140             mOldMenuState = EMPTY_MENU;
1141             mActivity.invalidateOptionsMenu();
1142         }
1143     }
1144 
1145     @Override
onActivityResult(int requestCode, int resultCode, Intent intent)1146     public void onActivityResult(int requestCode, int resultCode,
1147             Intent intent) {
1148         if (getCurrentTopWebView() == null) return;
1149         switch (requestCode) {
1150             case PREFERENCES_PAGE:
1151                 if (resultCode == Activity.RESULT_OK && intent != null) {
1152                     String action = intent.getStringExtra(Intent.EXTRA_TEXT);
1153                     if (PreferenceKeys.PREF_PRIVACY_CLEAR_HISTORY.equals(action)) {
1154                         mTabControl.removeParentChildRelationShips();
1155                     }
1156                 }
1157                 break;
1158             case FILE_SELECTED:
1159                 // Chose a file from the file picker.
1160                 if (null == mUploadHandler) break;
1161                 mUploadHandler.onResult(resultCode, intent);
1162                 break;
1163             case COMBO_VIEW:
1164                 if (intent == null || resultCode != Activity.RESULT_OK) {
1165                     break;
1166                 }
1167                 mUi.showWeb(false);
1168                 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
1169                     Tab t = getCurrentTab();
1170                     Uri uri = intent.getData();
1171                     loadUrl(t, uri.toString());
1172                 } else if (intent.hasExtra(ComboViewActivity.EXTRA_OPEN_ALL)) {
1173                     String[] urls = intent.getStringArrayExtra(
1174                             ComboViewActivity.EXTRA_OPEN_ALL);
1175                     Tab parent = getCurrentTab();
1176                     for (String url : urls) {
1177                         parent = openTab(url, parent,
1178                                 !mSettings.openInBackground(), true);
1179                     }
1180                 } else if (intent.hasExtra(ComboViewActivity.EXTRA_OPEN_SNAPSHOT)) {
1181                     long id = intent.getLongExtra(
1182                             ComboViewActivity.EXTRA_OPEN_SNAPSHOT, -1);
1183                     if (id >= 0) {
1184                         Toast.makeText(mActivity, "Snapshot Tab no longer supported",
1185                             Toast.LENGTH_LONG).show();
1186                     }
1187                 }
1188                 break;
1189             case VOICE_RESULT:
1190                 if (resultCode == Activity.RESULT_OK && intent != null) {
1191                     ArrayList<String> results = intent.getStringArrayListExtra(
1192                             RecognizerIntent.EXTRA_RESULTS);
1193                     if (results.size() >= 1) {
1194                         mVoiceResult = results.get(0);
1195                     }
1196                 }
1197                 break;
1198             default:
1199                 break;
1200         }
1201         getCurrentTopWebView().requestFocus();
1202     }
1203 
1204     /**
1205      * Open the Go page.
1206      * @param startWithHistory If true, open starting on the history tab.
1207      *                         Otherwise, start with the bookmarks tab.
1208      */
1209     @Override
bookmarksOrHistoryPicker(ComboViews startView)1210     public void bookmarksOrHistoryPicker(ComboViews startView) {
1211         if (mTabControl.getCurrentWebView() == null) {
1212             return;
1213         }
1214         // clear action mode
1215         if (isInCustomActionMode()) {
1216             endActionMode();
1217         }
1218         Bundle extras = new Bundle();
1219         // Disable opening in a new window if we have maxed out the windows
1220         extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,
1221                 !mTabControl.canCreateNewTab());
1222         mUi.showComboView(startView, extras);
1223     }
1224 
1225     // combo view callbacks
1226 
1227     // key handling
onBackKey()1228     protected void onBackKey() {
1229         if (!mUi.onBackKey()) {
1230             WebView subwindow = mTabControl.getCurrentSubWindow();
1231             if (subwindow != null) {
1232                 if (subwindow.canGoBack()) {
1233                     subwindow.goBack();
1234                 } else {
1235                     dismissSubWindow(mTabControl.getCurrentTab());
1236                 }
1237             } else {
1238                 goBackOnePageOrQuit();
1239             }
1240         }
1241     }
1242 
onMenuKey()1243     protected boolean onMenuKey() {
1244         return mUi.onMenuKey();
1245     }
1246 
1247     // menu handling and state
1248     // TODO: maybe put into separate handler
1249 
1250     @Override
onCreateOptionsMenu(Menu menu)1251     public boolean onCreateOptionsMenu(Menu menu) {
1252         if (mMenuState == EMPTY_MENU) {
1253             return false;
1254         }
1255         MenuInflater inflater = mActivity.getMenuInflater();
1256         inflater.inflate(R.menu.browser, menu);
1257         return true;
1258     }
1259 
1260     @Override
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)1261     public void onCreateContextMenu(ContextMenu menu, View v,
1262             ContextMenuInfo menuInfo) {
1263         if (v instanceof TitleBar) {
1264             return;
1265         }
1266         if (!(v instanceof WebView)) {
1267             return;
1268         }
1269         final WebView webview = (WebView) v;
1270         WebView.HitTestResult result = webview.getHitTestResult();
1271         if (result == null) {
1272             return;
1273         }
1274 
1275         int type = result.getType();
1276         if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1277             Log.w(LOGTAG,
1278                     "We should not show context menu when nothing is touched");
1279             return;
1280         }
1281         if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1282             // let TextView handles context menu
1283             return;
1284         }
1285 
1286         // Note, http://b/issue?id=1106666 is requesting that
1287         // an inflated menu can be used again. This is not available
1288         // yet, so inflate each time (yuk!)
1289         MenuInflater inflater = mActivity.getMenuInflater();
1290         inflater.inflate(R.menu.browsercontext, menu);
1291 
1292         // Show the correct menu group
1293         final String extra = result.getExtra();
1294         if (extra == null) return;
1295         menu.setGroupVisible(R.id.PHONE_MENU,
1296                 type == WebView.HitTestResult.PHONE_TYPE);
1297         menu.setGroupVisible(R.id.EMAIL_MENU,
1298                 type == WebView.HitTestResult.EMAIL_TYPE);
1299         menu.setGroupVisible(R.id.GEO_MENU,
1300                 type == WebView.HitTestResult.GEO_TYPE);
1301         menu.setGroupVisible(R.id.IMAGE_MENU,
1302                 type == WebView.HitTestResult.IMAGE_TYPE
1303                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1304         menu.setGroupVisible(R.id.ANCHOR_MENU,
1305                 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1306                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1307         // Setup custom handling depending on the type
1308         switch (type) {
1309             case WebView.HitTestResult.PHONE_TYPE:
1310                 menu.setHeaderTitle(Uri.decode(extra));
1311                 menu.findItem(R.id.dial_context_menu_id).setIntent(
1312                         new Intent(Intent.ACTION_VIEW, Uri
1313                                 .parse(WebView.SCHEME_TEL + extra)));
1314                 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1315                 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1316                 addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
1317                 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1318                         addIntent);
1319                 menu.findItem(R.id.copy_phone_context_menu_id)
1320                         .setOnMenuItemClickListener(
1321                         new Copy(extra));
1322                 break;
1323 
1324             case WebView.HitTestResult.EMAIL_TYPE:
1325                 menu.setHeaderTitle(extra);
1326                 menu.findItem(R.id.email_context_menu_id).setIntent(
1327                         new Intent(Intent.ACTION_VIEW, Uri
1328                                 .parse(WebView.SCHEME_MAILTO + extra)));
1329                 menu.findItem(R.id.copy_mail_context_menu_id)
1330                         .setOnMenuItemClickListener(
1331                         new Copy(extra));
1332                 break;
1333 
1334             case WebView.HitTestResult.GEO_TYPE:
1335                 menu.setHeaderTitle(extra);
1336                 menu.findItem(R.id.map_context_menu_id).setIntent(
1337                         new Intent(Intent.ACTION_VIEW, Uri
1338                                 .parse(WebView.SCHEME_GEO
1339                                         + URLEncoder.encode(extra))));
1340                 menu.findItem(R.id.copy_geo_context_menu_id)
1341                         .setOnMenuItemClickListener(
1342                         new Copy(extra));
1343                 break;
1344 
1345             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1346             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1347                 menu.setHeaderTitle(extra);
1348                 // decide whether to show the open link in new tab option
1349                 boolean showNewTab = mTabControl.canCreateNewTab();
1350                 MenuItem newTabItem
1351                         = menu.findItem(R.id.open_newtab_context_menu_id);
1352                 newTabItem.setTitle(getSettings().openInBackground()
1353                         ? R.string.contextmenu_openlink_newwindow_background
1354                         : R.string.contextmenu_openlink_newwindow);
1355                 newTabItem.setVisible(showNewTab);
1356                 if (showNewTab) {
1357                     if (WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE == type) {
1358                         newTabItem.setOnMenuItemClickListener(
1359                                 new MenuItem.OnMenuItemClickListener() {
1360                                     @Override
1361                                     public boolean onMenuItemClick(MenuItem item) {
1362                                         final HashMap<String, WebView> hrefMap =
1363                                                 new HashMap<String, WebView>();
1364                                         hrefMap.put("webview", webview);
1365                                         final Message msg = mHandler.obtainMessage(
1366                                                 FOCUS_NODE_HREF,
1367                                                 R.id.open_newtab_context_menu_id,
1368                                                 0, hrefMap);
1369                                         webview.requestFocusNodeHref(msg);
1370                                         return true;
1371                                     }
1372                                 });
1373                     } else {
1374                         newTabItem.setOnMenuItemClickListener(
1375                                 new MenuItem.OnMenuItemClickListener() {
1376                                     @Override
1377                                     public boolean onMenuItemClick(MenuItem item) {
1378                                         final Tab parent = mTabControl.getCurrentTab();
1379                                         openTab(extra, parent,
1380                                                 !mSettings.openInBackground(),
1381                                                 true);
1382                                         return true;
1383                                     }
1384                                 });
1385                     }
1386                 }
1387                 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1388                     break;
1389                 }
1390                 // otherwise fall through to handle image part
1391             case WebView.HitTestResult.IMAGE_TYPE:
1392                 MenuItem shareItem = menu.findItem(R.id.share_link_context_menu_id);
1393                 shareItem.setVisible(type == WebView.HitTestResult.IMAGE_TYPE);
1394                 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1395                     menu.setHeaderTitle(extra);
1396                     shareItem.setOnMenuItemClickListener(
1397                             new MenuItem.OnMenuItemClickListener() {
1398                                 @Override
1399                                 public boolean onMenuItemClick(MenuItem item) {
1400                                     sharePage(mActivity, null, extra, null,
1401                                     null);
1402                                     return true;
1403                                 }
1404                             }
1405                         );
1406                 }
1407                 menu.findItem(R.id.view_image_context_menu_id)
1408                         .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1409                     @Override
1410                     public boolean onMenuItemClick(MenuItem item) {
1411                         openTab(extra, mTabControl.getCurrentTab(), true, true);
1412                         return false;
1413                     }
1414                 });
1415                 menu.findItem(R.id.download_context_menu_id).setOnMenuItemClickListener(
1416                         new Download(mActivity, extra, webview.isPrivateBrowsingEnabled(),
1417                                 webview.getSettings().getUserAgentString()));
1418                 menu.findItem(R.id.set_wallpaper_context_menu_id).
1419                         setOnMenuItemClickListener(new WallpaperHandler(mActivity,
1420                                 extra));
1421                 break;
1422 
1423             default:
1424                 Log.w(LOGTAG, "We should not get here.");
1425                 break;
1426         }
1427         //update the ui
1428         mUi.onContextMenuCreated(menu);
1429     }
1430 
1431     /**
1432      * As the menu can be open when loading state changes
1433      * we must manually update the state of the stop/reload menu
1434      * item
1435      */
updateInLoadMenuItems(Menu menu, Tab tab)1436     private void updateInLoadMenuItems(Menu menu, Tab tab) {
1437         if (menu == null) {
1438             return;
1439         }
1440         MenuItem dest = menu.findItem(R.id.stop_reload_menu_id);
1441         MenuItem src = ((tab != null) && tab.inPageLoad()) ?
1442                 menu.findItem(R.id.stop_menu_id):
1443                 menu.findItem(R.id.reload_menu_id);
1444         if (src != null) {
1445             dest.setIcon(src.getIcon());
1446             dest.setTitle(src.getTitle());
1447         }
1448     }
1449 
1450     @Override
onPrepareOptionsMenu(Menu menu)1451     public boolean onPrepareOptionsMenu(Menu menu) {
1452         updateInLoadMenuItems(menu, getCurrentTab());
1453         // hold on to the menu reference here; it is used by the page callbacks
1454         // to update the menu based on loading state
1455         mCachedMenu = menu;
1456         // Note: setVisible will decide whether an item is visible; while
1457         // setEnabled() will decide whether an item is enabled, which also means
1458         // whether the matching shortcut key will function.
1459         switch (mMenuState) {
1460             case EMPTY_MENU:
1461                 if (mCurrentMenuState != mMenuState) {
1462                     menu.setGroupVisible(R.id.MAIN_MENU, false);
1463                     menu.setGroupEnabled(R.id.MAIN_MENU, false);
1464                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1465                 }
1466                 break;
1467             default:
1468                 if (mCurrentMenuState != mMenuState) {
1469                     menu.setGroupVisible(R.id.MAIN_MENU, true);
1470                     menu.setGroupEnabled(R.id.MAIN_MENU, true);
1471                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1472                 }
1473                 updateMenuState(getCurrentTab(), menu);
1474                 break;
1475         }
1476         mCurrentMenuState = mMenuState;
1477         return mUi.onPrepareOptionsMenu(menu);
1478     }
1479 
1480     @Override
updateMenuState(Tab tab, Menu menu)1481     public void updateMenuState(Tab tab, Menu menu) {
1482         boolean canGoBack = false;
1483         boolean canGoForward = false;
1484         boolean isHome = false;
1485         boolean isDesktopUa = false;
1486         boolean isLive = false;
1487         if (tab != null) {
1488             canGoBack = tab.canGoBack();
1489             canGoForward = tab.canGoForward();
1490             isHome = mSettings.getHomePage().equals(tab.getUrl());
1491             isDesktopUa = mSettings.hasDesktopUseragent(tab.getWebView());
1492             isLive = !tab.isSnapshot();
1493         }
1494         final MenuItem back = menu.findItem(R.id.back_menu_id);
1495         back.setEnabled(canGoBack);
1496 
1497         final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1498         home.setEnabled(!isHome);
1499 
1500         final MenuItem forward = menu.findItem(R.id.forward_menu_id);
1501         forward.setEnabled(canGoForward);
1502 
1503         final MenuItem source = menu.findItem(isInLoad() ? R.id.stop_menu_id
1504                 : R.id.reload_menu_id);
1505         final MenuItem dest = menu.findItem(R.id.stop_reload_menu_id);
1506         if (source != null && dest != null) {
1507             dest.setTitle(source.getTitle());
1508             dest.setIcon(source.getIcon());
1509         }
1510         menu.setGroupVisible(R.id.NAV_MENU, isLive);
1511 
1512         // decide whether to show the share link option
1513         PackageManager pm = mActivity.getPackageManager();
1514         Intent send = new Intent(Intent.ACTION_SEND);
1515         send.setType("text/plain");
1516         ResolveInfo ri = pm.resolveActivity(send,
1517                 PackageManager.MATCH_DEFAULT_ONLY);
1518         menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1519 
1520         boolean isNavDump = mSettings.enableNavDump();
1521         final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1522         nav.setVisible(isNavDump);
1523         nav.setEnabled(isNavDump);
1524 
1525         boolean showDebugSettings = mSettings.isDebugEnabled();
1526         final MenuItem uaSwitcher = menu.findItem(R.id.ua_desktop_menu_id);
1527         uaSwitcher.setChecked(isDesktopUa);
1528         menu.setGroupVisible(R.id.LIVE_MENU, isLive);
1529         menu.setGroupVisible(R.id.SNAPSHOT_MENU, !isLive);
1530         menu.setGroupVisible(R.id.COMBO_MENU, false);
1531 
1532         mUi.updateMenuState(tab, menu);
1533     }
1534 
1535     @Override
onOptionsItemSelected(MenuItem item)1536     public boolean onOptionsItemSelected(MenuItem item) {
1537         if (null == getCurrentTopWebView()) {
1538             return false;
1539         }
1540         if (mMenuIsDown) {
1541             // The shortcut action consumes the MENU. Even if it is still down,
1542             // it won't trigger the next shortcut action. In the case of the
1543             // shortcut action triggering a new activity, like Bookmarks, we
1544             // won't get onKeyUp for MENU. So it is important to reset it here.
1545             mMenuIsDown = false;
1546         }
1547         if (mUi.onOptionsItemSelected(item)) {
1548             // ui callback handled it
1549             return true;
1550         }
1551         switch (item.getItemId()) {
1552             // -- Main menu
1553             case R.id.new_tab_menu_id:
1554                 openTabToHomePage();
1555                 break;
1556 
1557             case R.id.incognito_menu_id:
1558                 openIncognitoTab();
1559                 break;
1560 
1561             case R.id.close_other_tabs_id:
1562                 closeOtherTabs();
1563                 break;
1564 
1565             case R.id.goto_menu_id:
1566                 editUrl();
1567                 break;
1568 
1569             case R.id.bookmarks_menu_id:
1570                 bookmarksOrHistoryPicker(ComboViews.Bookmarks);
1571                 break;
1572 
1573             case R.id.history_menu_id:
1574                 bookmarksOrHistoryPicker(ComboViews.History);
1575                 break;
1576 
1577             case R.id.snapshots_menu_id:
1578                 bookmarksOrHistoryPicker(ComboViews.Snapshots);
1579                 break;
1580 
1581             case R.id.add_bookmark_menu_id:
1582                 bookmarkCurrentPage();
1583                 break;
1584 
1585             case R.id.stop_reload_menu_id:
1586                 if (isInLoad()) {
1587                     stopLoading();
1588                 } else {
1589                     getCurrentTopWebView().reload();
1590                 }
1591                 break;
1592 
1593             case R.id.back_menu_id:
1594                 getCurrentTab().goBack();
1595                 break;
1596 
1597             case R.id.forward_menu_id:
1598                 getCurrentTab().goForward();
1599                 break;
1600 
1601             case R.id.close_menu_id:
1602                 // Close the subwindow if it exists.
1603                 if (mTabControl.getCurrentSubWindow() != null) {
1604                     dismissSubWindow(mTabControl.getCurrentTab());
1605                     break;
1606                 }
1607                 closeCurrentTab();
1608                 break;
1609 
1610             case R.id.homepage_menu_id:
1611                 Tab current = mTabControl.getCurrentTab();
1612                 loadUrl(current, mSettings.getHomePage());
1613                 break;
1614 
1615             case R.id.preferences_menu_id:
1616                 openPreferences();
1617                 break;
1618 
1619             case R.id.find_menu_id:
1620                 findOnPage();
1621                 break;
1622 
1623             case R.id.page_info_menu_id:
1624                 showPageInfo();
1625                 break;
1626 
1627             case R.id.snapshot_go_live:
1628                 goLive();
1629                 return true;
1630 
1631             case R.id.share_page_menu_id:
1632                 Tab currentTab = mTabControl.getCurrentTab();
1633                 if (null == currentTab) {
1634                     return false;
1635                 }
1636                 shareCurrentPage(currentTab);
1637                 break;
1638 
1639             case R.id.dump_nav_menu_id:
1640                 getCurrentTopWebView().debugDump();
1641                 break;
1642 
1643             case R.id.zoom_in_menu_id:
1644                 getCurrentTopWebView().zoomIn();
1645                 break;
1646 
1647             case R.id.zoom_out_menu_id:
1648                 getCurrentTopWebView().zoomOut();
1649                 break;
1650 
1651             case R.id.view_downloads_menu_id:
1652                 viewDownloads();
1653                 break;
1654 
1655             case R.id.ua_desktop_menu_id:
1656                 toggleUserAgent();
1657                 break;
1658 
1659             case R.id.window_one_menu_id:
1660             case R.id.window_two_menu_id:
1661             case R.id.window_three_menu_id:
1662             case R.id.window_four_menu_id:
1663             case R.id.window_five_menu_id:
1664             case R.id.window_six_menu_id:
1665             case R.id.window_seven_menu_id:
1666             case R.id.window_eight_menu_id:
1667                 {
1668                     int menuid = item.getItemId();
1669                     for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1670                         if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1671                             Tab desiredTab = mTabControl.getTab(id);
1672                             if (desiredTab != null &&
1673                                     desiredTab != mTabControl.getCurrentTab()) {
1674                                 switchToTab(desiredTab);
1675                             }
1676                             break;
1677                         }
1678                     }
1679                 }
1680                 break;
1681 
1682             default:
1683                 return false;
1684         }
1685         return true;
1686     }
1687 
1688     @Override
toggleUserAgent()1689     public void toggleUserAgent() {
1690         WebView web = getCurrentWebView();
1691         mSettings.toggleDesktopUseragent(web);
1692         web.loadUrl(web.getOriginalUrl());
1693     }
1694 
1695     @Override
findOnPage()1696     public void findOnPage() {
1697         getCurrentTopWebView().showFindDialog(null, true);
1698     }
1699 
1700     @Override
openPreferences()1701     public void openPreferences() {
1702         Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
1703         intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE,
1704                 getCurrentTopWebView().getUrl());
1705         mActivity.startActivityForResult(intent, PREFERENCES_PAGE);
1706     }
1707 
1708     @Override
bookmarkCurrentPage()1709     public void bookmarkCurrentPage() {
1710         Intent bookmarkIntent = createBookmarkCurrentPageIntent(false);
1711         if (bookmarkIntent != null) {
1712             mActivity.startActivity(bookmarkIntent);
1713         }
1714     }
1715 
goLive()1716     private void goLive() {
1717         Tab t = getCurrentTab();
1718         t.loadUrl(t.getUrl(), null);
1719     }
1720 
1721     @Override
showPageInfo()1722     public void showPageInfo() {
1723         mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(), false, null);
1724     }
1725 
1726     @Override
onContextItemSelected(MenuItem item)1727     public boolean onContextItemSelected(MenuItem item) {
1728         // Let the History and Bookmark fragments handle menus they created.
1729         if (item.getGroupId() == R.id.CONTEXT_MENU) {
1730             return false;
1731         }
1732 
1733         int id = item.getItemId();
1734         boolean result = true;
1735         switch (id) {
1736             // -- Browser context menu
1737             case R.id.open_context_menu_id:
1738             case R.id.save_link_context_menu_id:
1739             case R.id.copy_link_context_menu_id:
1740                 final WebView webView = getCurrentTopWebView();
1741                 if (null == webView) {
1742                     result = false;
1743                     break;
1744                 }
1745                 final HashMap<String, WebView> hrefMap =
1746                         new HashMap<String, WebView>();
1747                 hrefMap.put("webview", webView);
1748                 final Message msg = mHandler.obtainMessage(
1749                         FOCUS_NODE_HREF, id, 0, hrefMap);
1750                 webView.requestFocusNodeHref(msg);
1751                 break;
1752 
1753             default:
1754                 // For other context menus
1755                 result = onOptionsItemSelected(item);
1756         }
1757         return result;
1758     }
1759 
1760     /**
1761      * support programmatically opening the context menu
1762      */
openContextMenu(View view)1763     public void openContextMenu(View view) {
1764         mActivity.openContextMenu(view);
1765     }
1766 
1767     /**
1768      * programmatically open the options menu
1769      */
openOptionsMenu()1770     public void openOptionsMenu() {
1771         mActivity.openOptionsMenu();
1772     }
1773 
1774     @Override
onMenuOpened(int featureId, Menu menu)1775     public boolean onMenuOpened(int featureId, Menu menu) {
1776         if (mOptionsMenuOpen) {
1777             if (mConfigChanged) {
1778                 // We do not need to make any changes to the state of the
1779                 // title bar, since the only thing that happened was a
1780                 // change in orientation
1781                 mConfigChanged = false;
1782             } else {
1783                 if (!mExtendedMenuOpen) {
1784                     mExtendedMenuOpen = true;
1785                     mUi.onExtendedMenuOpened();
1786                 } else {
1787                     // Switching the menu back to icon view, so show the
1788                     // title bar once again.
1789                     mExtendedMenuOpen = false;
1790                     mUi.onExtendedMenuClosed(isInLoad());
1791                 }
1792             }
1793         } else {
1794             // The options menu is closed, so open it, and show the title
1795             mOptionsMenuOpen = true;
1796             mConfigChanged = false;
1797             mExtendedMenuOpen = false;
1798             mUi.onOptionsMenuOpened();
1799         }
1800         return true;
1801     }
1802 
1803     @Override
onOptionsMenuClosed(Menu menu)1804     public void onOptionsMenuClosed(Menu menu) {
1805         mOptionsMenuOpen = false;
1806         mUi.onOptionsMenuClosed(isInLoad());
1807     }
1808 
1809     @Override
onContextMenuClosed(Menu menu)1810     public void onContextMenuClosed(Menu menu) {
1811         mUi.onContextMenuClosed(menu, isInLoad());
1812     }
1813 
1814     // Helper method for getting the top window.
1815     @Override
getCurrentTopWebView()1816     public WebView getCurrentTopWebView() {
1817         return mTabControl.getCurrentTopWebView();
1818     }
1819 
1820     @Override
getCurrentWebView()1821     public WebView getCurrentWebView() {
1822         return mTabControl.getCurrentWebView();
1823     }
1824 
1825     /*
1826      * This method is called as a result of the user selecting the options
1827      * menu to see the download window. It shows the download window on top of
1828      * the current window.
1829      */
viewDownloads()1830     void viewDownloads() {
1831         Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1832         mActivity.startActivity(intent);
1833     }
1834 
getActionModeHeight()1835     int getActionModeHeight() {
1836         TypedArray actionBarSizeTypedArray = mActivity.obtainStyledAttributes(
1837                     new int[] { android.R.attr.actionBarSize });
1838         int size = (int) actionBarSizeTypedArray.getDimension(0, 0f);
1839         actionBarSizeTypedArray.recycle();
1840         return size;
1841     }
1842 
1843     // action mode
1844 
1845     @Override
onActionModeStarted(ActionMode mode)1846     public void onActionModeStarted(ActionMode mode) {
1847         mUi.onActionModeStarted(mode);
1848         mActionMode = mode;
1849     }
1850 
1851     /*
1852      * True if a custom ActionMode (i.e. find or select) is in use.
1853      */
1854     @Override
isInCustomActionMode()1855     public boolean isInCustomActionMode() {
1856         return mActionMode != null;
1857     }
1858 
1859     /*
1860      * End the current ActionMode.
1861      */
1862     @Override
endActionMode()1863     public void endActionMode() {
1864         if (mActionMode != null) {
1865             mActionMode.finish();
1866         }
1867     }
1868 
1869     /*
1870      * Called by find and select when they are finished.  Replace title bars
1871      * as necessary.
1872      */
1873     @Override
onActionModeFinished(ActionMode mode)1874     public void onActionModeFinished(ActionMode mode) {
1875         if (!isInCustomActionMode()) return;
1876         mUi.onActionModeFinished(isInLoad());
1877         mActionMode = null;
1878     }
1879 
isInLoad()1880     boolean isInLoad() {
1881         final Tab tab = getCurrentTab();
1882         return (tab != null) && tab.inPageLoad();
1883     }
1884 
1885     // bookmark handling
1886 
1887     /**
1888      * add the current page as a bookmark to the given folder id
1889      * @param folderId use -1 for the default folder
1890      * @param editExisting If true, check to see whether the site is already
1891      *          bookmarked, and if it is, edit that bookmark.  If false, and
1892      *          the site is already bookmarked, do not attempt to edit the
1893      *          existing bookmark.
1894      */
1895     @Override
createBookmarkCurrentPageIntent(boolean editExisting)1896     public Intent createBookmarkCurrentPageIntent(boolean editExisting) {
1897         WebView w = getCurrentTopWebView();
1898         if (w == null) {
1899             return null;
1900         }
1901         Intent i = new Intent(mActivity,
1902                 AddBookmarkPage.class);
1903         i.putExtra(BrowserContract.Bookmarks.URL, w.getUrl());
1904         i.putExtra(BrowserContract.Bookmarks.TITLE, w.getTitle());
1905         String touchIconUrl = w.getTouchIconUrl();
1906         if (touchIconUrl != null) {
1907             i.putExtra(AddBookmarkPage.TOUCH_ICON_URL, touchIconUrl);
1908             WebSettings settings = w.getSettings();
1909             if (settings != null) {
1910                 i.putExtra(AddBookmarkPage.USER_AGENT,
1911                         settings.getUserAgentString());
1912             }
1913         }
1914         i.putExtra(BrowserContract.Bookmarks.THUMBNAIL,
1915                 createScreenshot(w, getDesiredThumbnailWidth(mActivity),
1916                 getDesiredThumbnailHeight(mActivity)));
1917         i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon());
1918         if (editExisting) {
1919             i.putExtra(AddBookmarkPage.CHECK_FOR_DUPE, true);
1920         }
1921         // Put the dialog at the upper right of the screen, covering the
1922         // star on the title bar.
1923         i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP);
1924         return i;
1925     }
1926 
1927     // file chooser
1928     @Override
openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)1929     public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
1930         mUploadHandler = new UploadHandler(this);
1931         mUploadHandler.openFileChooser(uploadMsg, acceptType, capture);
1932     }
1933 
1934     // thumbnails
1935 
1936     /**
1937      * Return the desired width for thumbnail screenshots, which are stored in
1938      * the database, and used on the bookmarks screen.
1939      * @param context Context for finding out the density of the screen.
1940      * @return desired width for thumbnail screenshot.
1941      */
getDesiredThumbnailWidth(Context context)1942     static int getDesiredThumbnailWidth(Context context) {
1943         return context.getResources().getDimensionPixelOffset(
1944                 R.dimen.bookmarkThumbnailWidth);
1945     }
1946 
1947     /**
1948      * Return the desired height for thumbnail screenshots, which are stored in
1949      * the database, and used on the bookmarks screen.
1950      * @param context Context for finding out the density of the screen.
1951      * @return desired height for thumbnail screenshot.
1952      */
getDesiredThumbnailHeight(Context context)1953     static int getDesiredThumbnailHeight(Context context) {
1954         return context.getResources().getDimensionPixelOffset(
1955                 R.dimen.bookmarkThumbnailHeight);
1956     }
1957 
createScreenshot(WebView view, int width, int height)1958     static Bitmap createScreenshot(WebView view, int width, int height) {
1959         if (view == null || view.getContentHeight() == 0
1960                 || view.getContentWidth() == 0) {
1961             return null;
1962         }
1963         // We render to a bitmap 2x the desired size so that we can then
1964         // re-scale it with filtering since canvas.scale doesn't filter
1965         // This helps reduce aliasing at the cost of being slightly blurry
1966         final int filter_scale = 2;
1967         int scaledWidth = width * filter_scale;
1968         int scaledHeight = height * filter_scale;
1969         if (sThumbnailBitmap == null || sThumbnailBitmap.getWidth() != scaledWidth
1970                 || sThumbnailBitmap.getHeight() != scaledHeight) {
1971             if (sThumbnailBitmap != null) {
1972                 sThumbnailBitmap.recycle();
1973                 sThumbnailBitmap = null;
1974             }
1975             sThumbnailBitmap =
1976                     Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.RGB_565);
1977         }
1978         Canvas canvas = new Canvas(sThumbnailBitmap);
1979         int contentWidth = view.getContentWidth();
1980         float overviewScale = scaledWidth / (view.getScale() * contentWidth);
1981         if (view instanceof BrowserWebView) {
1982             int dy = -((BrowserWebView)view).getTitleHeight();
1983             canvas.translate(0, dy * overviewScale);
1984         }
1985 
1986         canvas.scale(overviewScale, overviewScale);
1987 
1988         if (view instanceof BrowserWebView) {
1989             ((BrowserWebView)view).drawContent(canvas);
1990         } else {
1991             view.draw(canvas);
1992         }
1993         Bitmap ret = Bitmap.createScaledBitmap(sThumbnailBitmap,
1994                 width, height, true);
1995         canvas.setBitmap(null);
1996         return ret;
1997     }
1998 
updateScreenshot(Tab tab)1999     private void updateScreenshot(Tab tab) {
2000         // If this is a bookmarked site, add a screenshot to the database.
2001         // FIXME: Would like to make sure there is actually something to
2002         // draw, but the API for that (WebViewCore.pictureReady()) is not
2003         // currently accessible here.
2004 
2005         WebView view = tab.getWebView();
2006         if (view == null) {
2007             // Tab was destroyed
2008             return;
2009         }
2010         final String url = tab.getUrl();
2011         final String originalUrl = view.getOriginalUrl();
2012         if (TextUtils.isEmpty(url)) {
2013             return;
2014         }
2015 
2016         // Only update thumbnails for web urls (http(s)://), not for
2017         // about:, javascript:, data:, etc...
2018         // Unless it is a bookmarked site, then always update
2019         if (!Patterns.WEB_URL.matcher(url).matches() && !tab.isBookmarkedSite()) {
2020             return;
2021         }
2022 
2023         final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity),
2024                 getDesiredThumbnailHeight(mActivity));
2025         if (bm == null) {
2026             return;
2027         }
2028 
2029         final ContentResolver cr = mActivity.getContentResolver();
2030         new AsyncTask<Void, Void, Void>() {
2031             @Override
2032             protected Void doInBackground(Void... unused) {
2033                 Cursor cursor = null;
2034                 try {
2035                     // TODO: Clean this up
2036                     cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
2037                     if (cursor != null && cursor.moveToFirst()) {
2038                         final ByteArrayOutputStream os =
2039                                 new ByteArrayOutputStream();
2040                         bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2041 
2042                         ContentValues values = new ContentValues();
2043                         values.put(Images.THUMBNAIL, os.toByteArray());
2044 
2045                         do {
2046                             values.put(Images.URL, cursor.getString(0));
2047                             cr.update(Images.CONTENT_URI, values, null, null);
2048                         } while (cursor.moveToNext());
2049                     }
2050                 } catch (IllegalStateException e) {
2051                     // Ignore
2052                 } catch (SQLiteException s) {
2053                     // Added for possible error when user tries to remove the same bookmark
2054                     // that is being updated with a screen shot
2055                     Log.w(LOGTAG, "Error when running updateScreenshot ", s);
2056                 } finally {
2057                     if (cursor != null) cursor.close();
2058                 }
2059                 return null;
2060             }
2061         }.execute();
2062     }
2063 
2064     private class Copy implements OnMenuItemClickListener {
2065         private CharSequence mText;
2066 
2067         @Override
onMenuItemClick(MenuItem item)2068         public boolean onMenuItemClick(MenuItem item) {
2069             copy(mText);
2070             return true;
2071         }
2072 
Copy(CharSequence toCopy)2073         public Copy(CharSequence toCopy) {
2074             mText = toCopy;
2075         }
2076     }
2077 
2078     private static class Download implements OnMenuItemClickListener {
2079         private Activity mActivity;
2080         private String mText;
2081         private boolean mPrivateBrowsing;
2082         private String mUserAgent;
2083         private static final String FALLBACK_EXTENSION = "dat";
2084         private static final String IMAGE_BASE_FORMAT = "yyyy-MM-dd-HH-mm-ss-";
2085 
2086         @Override
onMenuItemClick(MenuItem item)2087         public boolean onMenuItemClick(MenuItem item) {
2088             if (DataUri.isDataUri(mText)) {
2089                 saveDataUri();
2090             } else {
2091                 DownloadHandler.onDownloadStartNoStream(mActivity, mText, mUserAgent,
2092                         null, null, null, mPrivateBrowsing);
2093             }
2094             return true;
2095         }
2096 
Download(Activity activity, String toDownload, boolean privateBrowsing, String userAgent)2097         public Download(Activity activity, String toDownload, boolean privateBrowsing,
2098                 String userAgent) {
2099             mActivity = activity;
2100             mText = toDownload;
2101             mPrivateBrowsing = privateBrowsing;
2102             mUserAgent = userAgent;
2103         }
2104 
2105         /**
2106          * Treats mText as a data URI and writes its contents to a file
2107          * based on the current time.
2108          */
saveDataUri()2109         private void saveDataUri() {
2110             FileOutputStream outputStream = null;
2111             try {
2112                 DataUri uri = new DataUri(mText);
2113                 File target = getTarget(uri);
2114                 outputStream = new FileOutputStream(target);
2115                 outputStream.write(uri.getData());
2116                 final DownloadManager manager =
2117                         (DownloadManager) mActivity.getSystemService(Context.DOWNLOAD_SERVICE);
2118                  manager.addCompletedDownload(target.getName(),
2119                         mActivity.getTitle().toString(), false,
2120                         uri.getMimeType(), target.getAbsolutePath(),
2121                         uri.getData().length, true);
2122             } catch (IOException e) {
2123                 Log.e(LOGTAG, "Could not save data URL");
2124             } finally {
2125                 if (outputStream != null) {
2126                     try {
2127                         outputStream.close();
2128                     } catch (IOException e) {
2129                         // ignore close errors
2130                     }
2131                 }
2132             }
2133         }
2134 
2135         /**
2136          * Creates a File based on the current time stamp and uses
2137          * the mime type of the DataUri to get the extension.
2138          */
getTarget(DataUri uri)2139         private File getTarget(DataUri uri) throws IOException {
2140             File dir = mActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
2141             DateFormat format = new SimpleDateFormat(IMAGE_BASE_FORMAT, Locale.US);
2142             String nameBase = format.format(new Date());
2143             String mimeType = uri.getMimeType();
2144             MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
2145             String extension = mimeTypeMap.getExtensionFromMimeType(mimeType);
2146             if (extension == null) {
2147                 Log.w(LOGTAG, "Unknown mime type in data URI" + mimeType);
2148                 extension = FALLBACK_EXTENSION;
2149             }
2150             extension = "." + extension; // createTempFile needs the '.'
2151             File targetFile = File.createTempFile(nameBase, extension, dir);
2152             return targetFile;
2153         }
2154     }
2155 
2156     /********************** TODO: UI stuff *****************************/
2157 
2158     // these methods have been copied, they still need to be cleaned up
2159 
2160     /****************** tabs ***************************************************/
2161 
2162     // basic tab interactions:
2163 
2164     // it is assumed that tabcontrol already knows about the tab
addTab(Tab tab)2165     protected void addTab(Tab tab) {
2166         mUi.addTab(tab);
2167     }
2168 
removeTab(Tab tab)2169     protected void removeTab(Tab tab) {
2170         mUi.removeTab(tab);
2171         mTabControl.removeTab(tab);
2172         mCrashRecoveryHandler.backupState();
2173     }
2174 
2175     @Override
setActiveTab(Tab tab)2176     public void setActiveTab(Tab tab) {
2177         // monkey protection against delayed start
2178         if (tab != null) {
2179             mTabControl.setCurrentTab(tab);
2180             // the tab is guaranteed to have a webview after setCurrentTab
2181             mUi.setActiveTab(tab);
2182         }
2183     }
2184 
closeEmptyTab()2185     protected void closeEmptyTab() {
2186         Tab current = mTabControl.getCurrentTab();
2187         if (current != null
2188                 && current.getWebView().copyBackForwardList().getSize() == 0) {
2189             closeCurrentTab();
2190         }
2191     }
2192 
reuseTab(Tab appTab, UrlData urlData)2193     protected void reuseTab(Tab appTab, UrlData urlData) {
2194         // Dismiss the subwindow if applicable.
2195         dismissSubWindow(appTab);
2196         // Since we might kill the WebView, remove it from the
2197         // content view first.
2198         mUi.detachTab(appTab);
2199         // Recreate the main WebView after destroying the old one.
2200         mTabControl.recreateWebView(appTab);
2201         // TODO: analyze why the remove and add are necessary
2202         mUi.attachTab(appTab);
2203         if (mTabControl.getCurrentTab() != appTab) {
2204             switchToTab(appTab);
2205             loadUrlDataIn(appTab, urlData);
2206         } else {
2207             // If the tab was the current tab, we have to attach
2208             // it to the view system again.
2209             setActiveTab(appTab);
2210             loadUrlDataIn(appTab, urlData);
2211         }
2212     }
2213 
2214     // Remove the sub window if it exists. Also called by TabControl when the
2215     // user clicks the 'X' to dismiss a sub window.
2216     @Override
dismissSubWindow(Tab tab)2217     public void dismissSubWindow(Tab tab) {
2218         removeSubWindow(tab);
2219         // dismiss the subwindow. This will destroy the WebView.
2220         tab.dismissSubWindow();
2221         WebView wv = getCurrentTopWebView();
2222         if (wv != null) {
2223             wv.requestFocus();
2224         }
2225     }
2226 
2227     @Override
removeSubWindow(Tab t)2228     public void removeSubWindow(Tab t) {
2229         if (t.getSubWebView() != null) {
2230             mUi.removeSubWindow(t.getSubViewContainer());
2231         }
2232     }
2233 
2234     @Override
attachSubWindow(Tab tab)2235     public void attachSubWindow(Tab tab) {
2236         if (tab.getSubWebView() != null) {
2237             mUi.attachSubWindow(tab.getSubViewContainer());
2238             getCurrentTopWebView().requestFocus();
2239         }
2240     }
2241 
showPreloadedTab(final UrlData urlData)2242     private Tab showPreloadedTab(final UrlData urlData) {
2243         if (!urlData.isPreloaded()) {
2244             return null;
2245         }
2246         final PreloadedTabControl tabControl = urlData.getPreloadedTab();
2247         final String sbQuery = urlData.getSearchBoxQueryToSubmit();
2248         if (sbQuery != null) {
2249             if (!tabControl.searchBoxSubmit(sbQuery, urlData.mUrl, urlData.mHeaders)) {
2250                 // Could not submit query. Fallback to regular tab creation
2251                 tabControl.destroy();
2252                 return null;
2253             }
2254         }
2255         // check tab count and make room for new tab
2256         if (!mTabControl.canCreateNewTab()) {
2257             Tab leastUsed = mTabControl.getLeastUsedTab(getCurrentTab());
2258             if (leastUsed != null) {
2259                 closeTab(leastUsed);
2260             }
2261         }
2262         Tab t = tabControl.getTab();
2263         t.refreshIdAfterPreload();
2264         mTabControl.addPreloadedTab(t);
2265         addTab(t);
2266         setActiveTab(t);
2267         return t;
2268     }
2269 
2270     // open a non inconito tab with the given url data
2271     // and set as active tab
openTab(UrlData urlData)2272     public Tab openTab(UrlData urlData) {
2273         Tab tab = showPreloadedTab(urlData);
2274         if (tab == null) {
2275             tab = createNewTab(false, true, true);
2276             if ((tab != null) && !urlData.isEmpty()) {
2277                 loadUrlDataIn(tab, urlData);
2278             }
2279         }
2280         return tab;
2281     }
2282 
2283     @Override
openTabToHomePage()2284     public Tab openTabToHomePage() {
2285         return openTab(mSettings.getHomePage(), false, true, false);
2286     }
2287 
2288     @Override
openIncognitoTab()2289     public Tab openIncognitoTab() {
2290         return openTab(INCOGNITO_URI, true, true, false);
2291     }
2292 
2293     @Override
openTab(String url, boolean incognito, boolean setActive, boolean useCurrent)2294     public Tab openTab(String url, boolean incognito, boolean setActive,
2295             boolean useCurrent) {
2296         return openTab(url, incognito, setActive, useCurrent, null);
2297     }
2298 
2299     @Override
openTab(String url, Tab parent, boolean setActive, boolean useCurrent)2300     public Tab openTab(String url, Tab parent, boolean setActive,
2301             boolean useCurrent) {
2302         return openTab(url, (parent != null) && parent.isPrivateBrowsingEnabled(),
2303                 setActive, useCurrent, parent);
2304     }
2305 
openTab(String url, boolean incognito, boolean setActive, boolean useCurrent, Tab parent)2306     public Tab openTab(String url, boolean incognito, boolean setActive,
2307             boolean useCurrent, Tab parent) {
2308         Tab tab = createNewTab(incognito, setActive, useCurrent);
2309         if (tab != null) {
2310             if (parent != null && parent != tab) {
2311                 parent.addChildTab(tab);
2312             }
2313             if (url != null) {
2314                 loadUrl(tab, url);
2315             }
2316         }
2317         return tab;
2318     }
2319 
2320     // this method will attempt to create a new tab
2321     // incognito: private browsing tab
2322     // setActive: ste tab as current tab
2323     // useCurrent: if no new tab can be created, return current tab
createNewTab(boolean incognito, boolean setActive, boolean useCurrent)2324     private Tab createNewTab(boolean incognito, boolean setActive,
2325             boolean useCurrent) {
2326         Tab tab = null;
2327         if (mTabControl.canCreateNewTab()) {
2328             tab = mTabControl.createNewTab(incognito);
2329             addTab(tab);
2330             if (setActive) {
2331                 setActiveTab(tab);
2332             }
2333         } else {
2334             if (useCurrent) {
2335                 tab = mTabControl.getCurrentTab();
2336                 reuseTab(tab, null);
2337             } else {
2338                 mUi.showMaxTabsWarning();
2339             }
2340         }
2341         return tab;
2342     }
2343 
2344     /**
2345      * @param tab the tab to switch to
2346      * @return boolean True if we successfully switched to a different tab.  If
2347      *                 the indexth tab is null, or if that tab is the same as
2348      *                 the current one, return false.
2349      */
2350     @Override
switchToTab(Tab tab)2351     public boolean switchToTab(Tab tab) {
2352         Tab currentTab = mTabControl.getCurrentTab();
2353         if (tab == null || tab == currentTab) {
2354             return false;
2355         }
2356         setActiveTab(tab);
2357         return true;
2358     }
2359 
2360     @Override
closeCurrentTab()2361     public void closeCurrentTab() {
2362         closeCurrentTab(false);
2363     }
2364 
closeCurrentTab(boolean andQuit)2365     protected void closeCurrentTab(boolean andQuit) {
2366         if (mTabControl.getTabCount() == 1) {
2367             mCrashRecoveryHandler.clearState();
2368             mTabControl.removeTab(getCurrentTab());
2369             mActivity.finish();
2370             return;
2371         }
2372         final Tab current = mTabControl.getCurrentTab();
2373         final int pos = mTabControl.getCurrentPosition();
2374         Tab newTab = current.getParent();
2375         if (newTab == null) {
2376             newTab = mTabControl.getTab(pos + 1);
2377             if (newTab == null) {
2378                 newTab = mTabControl.getTab(pos - 1);
2379             }
2380         }
2381         if (andQuit) {
2382             mTabControl.setCurrentTab(newTab);
2383             closeTab(current);
2384         } else if (switchToTab(newTab)) {
2385             // Close window
2386             closeTab(current);
2387         }
2388     }
2389 
2390     /**
2391      * Close the tab, remove its associated title bar, and adjust mTabControl's
2392      * current tab to a valid value.
2393      */
2394     @Override
closeTab(Tab tab)2395     public void closeTab(Tab tab) {
2396         if (tab == mTabControl.getCurrentTab()) {
2397             closeCurrentTab();
2398         } else {
2399             removeTab(tab);
2400         }
2401     }
2402 
2403     /**
2404      * Close all tabs except the current one
2405      */
2406     @Override
closeOtherTabs()2407     public void closeOtherTabs() {
2408         int inactiveTabs = mTabControl.getTabCount() - 1;
2409         for (int i = inactiveTabs; i >= 0; i--) {
2410             Tab tab = mTabControl.getTab(i);
2411             if (tab != mTabControl.getCurrentTab()) {
2412                 removeTab(tab);
2413             }
2414         }
2415     }
2416 
2417     // Called when loading from context menu or LOAD_URL message
loadUrlFromContext(String url)2418     protected void loadUrlFromContext(String url) {
2419         Tab tab = getCurrentTab();
2420         WebView view = tab != null ? tab.getWebView() : null;
2421         // In case the user enters nothing.
2422         if (url != null && url.length() != 0 && tab != null && view != null) {
2423             url = UrlUtils.smartUrlFilter(url);
2424             if (!((BrowserWebView) view).getWebViewClient().
2425                     shouldOverrideUrlLoading(view, url)) {
2426                 loadUrl(tab, url);
2427             }
2428         }
2429     }
2430 
2431     /**
2432      * Load the URL into the given WebView and update the title bar
2433      * to reflect the new load.  Call this instead of WebView.loadUrl
2434      * directly.
2435      * @param view The WebView used to load url.
2436      * @param url The URL to load.
2437      */
2438     @Override
loadUrl(Tab tab, String url)2439     public void loadUrl(Tab tab, String url) {
2440         loadUrl(tab, url, null);
2441     }
2442 
loadUrl(Tab tab, String url, Map<String, String> headers)2443     protected void loadUrl(Tab tab, String url, Map<String, String> headers) {
2444         if (tab != null) {
2445             dismissSubWindow(tab);
2446             tab.loadUrl(url, headers);
2447             mUi.onProgressChanged(tab);
2448         }
2449     }
2450 
2451     /**
2452      * Load UrlData into a Tab and update the title bar to reflect the new
2453      * load.  Call this instead of UrlData.loadIn directly.
2454      * @param t The Tab used to load.
2455      * @param data The UrlData being loaded.
2456      */
loadUrlDataIn(Tab t, UrlData data)2457     protected void loadUrlDataIn(Tab t, UrlData data) {
2458         if (data != null) {
2459             if (data.isPreloaded()) {
2460                 // this isn't called for preloaded tabs
2461             } else {
2462                 if (t != null && data.mDisableUrlOverride) {
2463                     t.disableUrlOverridingForLoad();
2464                 }
2465                 loadUrl(t, data.mUrl, data.mHeaders);
2466             }
2467         }
2468     }
2469 
2470     @Override
onUserCanceledSsl(Tab tab)2471     public void onUserCanceledSsl(Tab tab) {
2472         // TODO: Figure out the "right" behavior
2473         if (tab.canGoBack()) {
2474             tab.goBack();
2475         } else {
2476             tab.loadUrl(mSettings.getHomePage(), null);
2477         }
2478     }
2479 
goBackOnePageOrQuit()2480     void goBackOnePageOrQuit() {
2481         Tab current = mTabControl.getCurrentTab();
2482         if (current == null) {
2483             /*
2484              * Instead of finishing the activity, simply push this to the back
2485              * of the stack and let ActivityManager to choose the foreground
2486              * activity. As BrowserActivity is singleTask, it will be always the
2487              * root of the task. So we can use either true or false for
2488              * moveTaskToBack().
2489              */
2490             mActivity.moveTaskToBack(true);
2491             return;
2492         }
2493         if (current.canGoBack()) {
2494             current.goBack();
2495         } else {
2496             // Check to see if we are closing a window that was created by
2497             // another window. If so, we switch back to that window.
2498             Tab parent = current.getParent();
2499             if (parent != null) {
2500                 switchToTab(parent);
2501                 // Now we close the other tab
2502                 closeTab(current);
2503             } else {
2504                 if ((current.getAppId() != null) || current.closeOnBack()) {
2505                     closeCurrentTab(true);
2506                 }
2507                 /*
2508                  * Instead of finishing the activity, simply push this to the back
2509                  * of the stack and let ActivityManager to choose the foreground
2510                  * activity. As BrowserActivity is singleTask, it will be always the
2511                  * root of the task. So we can use either true or false for
2512                  * moveTaskToBack().
2513                  */
2514                 mActivity.moveTaskToBack(true);
2515             }
2516         }
2517     }
2518 
2519     /**
2520      * helper method for key handler
2521      * returns the current tab if it can't advance
2522      */
getNextTab()2523     private Tab getNextTab() {
2524         int pos = mTabControl.getCurrentPosition() + 1;
2525         if (pos >= mTabControl.getTabCount()) {
2526             pos = 0;
2527         }
2528         return mTabControl.getTab(pos);
2529     }
2530 
2531     /**
2532      * helper method for key handler
2533      * returns the current tab if it can't advance
2534      */
getPrevTab()2535     private Tab getPrevTab() {
2536         int pos  = mTabControl.getCurrentPosition() - 1;
2537         if ( pos < 0) {
2538             pos = mTabControl.getTabCount() - 1;
2539         }
2540         return  mTabControl.getTab(pos);
2541     }
2542 
isMenuOrCtrlKey(int keyCode)2543     boolean isMenuOrCtrlKey(int keyCode) {
2544         return (KeyEvent.KEYCODE_MENU == keyCode)
2545                 || (KeyEvent.KEYCODE_CTRL_LEFT == keyCode)
2546                 || (KeyEvent.KEYCODE_CTRL_RIGHT == keyCode);
2547     }
2548 
2549     /**
2550      * handle key events in browser
2551      *
2552      * @param keyCode
2553      * @param event
2554      * @return true if handled, false to pass to super
2555      */
2556     @Override
onKeyDown(int keyCode, KeyEvent event)2557     public boolean onKeyDown(int keyCode, KeyEvent event) {
2558         boolean noModifiers = event.hasNoModifiers();
2559         // Even if MENU is already held down, we need to call to super to open
2560         // the IME on long press.
2561         if (!noModifiers && isMenuOrCtrlKey(keyCode)) {
2562             mMenuIsDown = true;
2563             return false;
2564         }
2565 
2566         WebView webView = getCurrentTopWebView();
2567         Tab tab = getCurrentTab();
2568         if (webView == null || tab == null) return false;
2569 
2570         boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
2571         boolean shift = event.hasModifiers(KeyEvent.META_SHIFT_ON);
2572 
2573         switch(keyCode) {
2574             case KeyEvent.KEYCODE_TAB:
2575                 if (event.isCtrlPressed()) {
2576                     if (event.isShiftPressed()) {
2577                         // prev tab
2578                         switchToTab(getPrevTab());
2579                     } else {
2580                         // next tab
2581                         switchToTab(getNextTab());
2582                     }
2583                     return true;
2584                 }
2585                 break;
2586             case KeyEvent.KEYCODE_SPACE:
2587                 // WebView/WebTextView handle the keys in the KeyDown. As
2588                 // the Activity's shortcut keys are only handled when WebView
2589                 // doesn't, have to do it in onKeyDown instead of onKeyUp.
2590                 if (shift) {
2591                     pageUp();
2592                 } else if (noModifiers) {
2593                     pageDown();
2594                 }
2595                 return true;
2596             case KeyEvent.KEYCODE_BACK:
2597                 if (!noModifiers) break;
2598                 event.startTracking();
2599                 return true;
2600             case KeyEvent.KEYCODE_FORWARD:
2601                 if (!noModifiers) break;
2602                 tab.goForward();
2603                 return true;
2604             case KeyEvent.KEYCODE_DPAD_LEFT:
2605                 if (ctrl) {
2606                     tab.goBack();
2607                     return true;
2608                 }
2609                 break;
2610             case KeyEvent.KEYCODE_DPAD_RIGHT:
2611                 if (ctrl) {
2612                     tab.goForward();
2613                     return true;
2614                 }
2615                 break;
2616 //          case KeyEvent.KEYCODE_B:    // menu
2617 //          case KeyEvent.KEYCODE_D:    // menu
2618 //          case KeyEvent.KEYCODE_E:    // in Chrome: puts '?' in URL bar
2619 //          case KeyEvent.KEYCODE_F:    // menu
2620 //          case KeyEvent.KEYCODE_G:    // in Chrome: finds next match
2621 //          case KeyEvent.KEYCODE_H:    // menu
2622 //          case KeyEvent.KEYCODE_I:    // unused
2623 //          case KeyEvent.KEYCODE_J:    // menu
2624 //          case KeyEvent.KEYCODE_K:    // in Chrome: puts '?' in URL bar
2625 //          case KeyEvent.KEYCODE_L:    // menu
2626 //          case KeyEvent.KEYCODE_M:    // unused
2627 //          case KeyEvent.KEYCODE_N:    // in Chrome: new window
2628 //          case KeyEvent.KEYCODE_O:    // in Chrome: open file
2629 //          case KeyEvent.KEYCODE_P:    // in Chrome: print page
2630 //          case KeyEvent.KEYCODE_Q:    // unused
2631 //          case KeyEvent.KEYCODE_R:
2632 //          case KeyEvent.KEYCODE_S:    // in Chrome: saves page
2633             case KeyEvent.KEYCODE_T:
2634                 // we can't use the ctrl/shift flags, they check for
2635                 // exclusive use of a modifier
2636                 if (event.isCtrlPressed()) {
2637                     if (event.isShiftPressed()) {
2638                         openIncognitoTab();
2639                     } else {
2640                         openTabToHomePage();
2641                     }
2642                     return true;
2643                 }
2644                 break;
2645 //          case KeyEvent.KEYCODE_U:    // in Chrome: opens source of page
2646 //          case KeyEvent.KEYCODE_V:    // text view intercepts to paste
2647 //          case KeyEvent.KEYCODE_W:    // menu
2648 //          case KeyEvent.KEYCODE_X:    // text view intercepts to cut
2649 //          case KeyEvent.KEYCODE_Y:    // unused
2650 //          case KeyEvent.KEYCODE_Z:    // unused
2651         }
2652         // it is a regular key and webview is not null
2653          return mUi.dispatchKey(keyCode, event);
2654     }
2655 
2656     @Override
onKeyLongPress(int keyCode, KeyEvent event)2657     public boolean onKeyLongPress(int keyCode, KeyEvent event) {
2658         switch(keyCode) {
2659         case KeyEvent.KEYCODE_BACK:
2660             if (mUi.isWebShowing()) {
2661                 bookmarksOrHistoryPicker(ComboViews.History);
2662                 return true;
2663             }
2664             break;
2665         }
2666         return false;
2667     }
2668 
2669     @Override
onKeyUp(int keyCode, KeyEvent event)2670     public boolean onKeyUp(int keyCode, KeyEvent event) {
2671         if (isMenuOrCtrlKey(keyCode)) {
2672             mMenuIsDown = false;
2673             if (KeyEvent.KEYCODE_MENU == keyCode
2674                     && event.isTracking() && !event.isCanceled()) {
2675                 return onMenuKey();
2676             }
2677         }
2678         if (!event.hasNoModifiers()) return false;
2679         switch(keyCode) {
2680             case KeyEvent.KEYCODE_BACK:
2681                 if (event.isTracking() && !event.isCanceled()) {
2682                     onBackKey();
2683                     return true;
2684                 }
2685                 break;
2686         }
2687         return false;
2688     }
2689 
isMenuDown()2690     public boolean isMenuDown() {
2691         return mMenuIsDown;
2692     }
2693 
2694     @Override
onSearchRequested()2695     public boolean onSearchRequested() {
2696         mUi.editUrl(false, true);
2697         return true;
2698     }
2699 
2700     @Override
shouldCaptureThumbnails()2701     public boolean shouldCaptureThumbnails() {
2702         return mUi.shouldCaptureThumbnails();
2703     }
2704 
2705     @Override
supportsVoice()2706     public boolean supportsVoice() {
2707         PackageManager pm = mActivity.getPackageManager();
2708         List activities = pm.queryIntentActivities(new Intent(
2709                 RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
2710         return activities.size() != 0;
2711     }
2712 
2713     @Override
startVoiceRecognizer()2714     public void startVoiceRecognizer() {
2715         Intent voice = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
2716         voice.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
2717                 RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
2718         voice.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
2719         mActivity.startActivityForResult(voice, VOICE_RESULT);
2720     }
2721 
2722     @Override
setBlockEvents(boolean block)2723     public void setBlockEvents(boolean block) {
2724         mBlockEvents = block;
2725     }
2726 
2727     @Override
dispatchKeyEvent(KeyEvent event)2728     public boolean dispatchKeyEvent(KeyEvent event) {
2729         return mBlockEvents;
2730     }
2731 
2732     @Override
dispatchKeyShortcutEvent(KeyEvent event)2733     public boolean dispatchKeyShortcutEvent(KeyEvent event) {
2734         return mBlockEvents;
2735     }
2736 
2737     @Override
dispatchTouchEvent(MotionEvent ev)2738     public boolean dispatchTouchEvent(MotionEvent ev) {
2739         return mBlockEvents;
2740     }
2741 
2742     @Override
dispatchTrackballEvent(MotionEvent ev)2743     public boolean dispatchTrackballEvent(MotionEvent ev) {
2744         return mBlockEvents;
2745     }
2746 
2747     @Override
dispatchGenericMotionEvent(MotionEvent ev)2748     public boolean dispatchGenericMotionEvent(MotionEvent ev) {
2749         return mBlockEvents;
2750     }
2751 
2752 }
2753