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