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