OSDN Git Service

Force a load when getting a new intent from voice search.
[android-x86/packages-apps-Browser.git] / src / com / android / browser / BrowserActivity.java
1 /*
2  * Copyright (C) 2006 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.AlertDialog;
21 import android.app.ProgressDialog;
22 import android.app.SearchManager;
23 import android.content.ActivityNotFoundException;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.ContentProvider;
27 import android.content.ContentProviderClient;
28 import android.content.ContentResolver;
29 import android.content.ContentUris;
30 import android.content.ContentValues;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.PackageInfo;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Configuration;
39 import android.content.res.Resources;
40 import android.database.Cursor;
41 import android.database.DatabaseUtils;
42 import android.graphics.Bitmap;
43 import android.graphics.BitmapFactory;
44 import android.graphics.Canvas;
45 import android.graphics.Picture;
46 import android.graphics.PixelFormat;
47 import android.graphics.Rect;
48 import android.graphics.drawable.Drawable;
49 import android.net.ConnectivityManager;
50 import android.net.NetworkInfo;
51 import android.net.Uri;
52 import android.net.WebAddress;
53 import android.net.http.SslCertificate;
54 import android.net.http.SslError;
55 import android.os.AsyncTask;
56 import android.os.Bundle;
57 import android.os.Debug;
58 import android.os.Environment;
59 import android.os.Handler;
60 import android.os.Message;
61 import android.os.PowerManager;
62 import android.os.Process;
63 import android.os.ServiceManager;
64 import android.os.SystemClock;
65 import android.provider.Browser;
66 import android.provider.ContactsContract;
67 import android.provider.ContactsContract.Intents.Insert;
68 import android.provider.Downloads;
69 import android.provider.MediaStore;
70 import android.speech.RecognizerResultsIntent;
71 import android.text.IClipboard;
72 import android.text.TextUtils;
73 import android.text.format.DateFormat;
74 import android.util.AttributeSet;
75 import android.util.Log;
76 import android.util.Patterns;
77 import android.view.ContextMenu;
78 import android.view.Gravity;
79 import android.view.KeyEvent;
80 import android.view.LayoutInflater;
81 import android.view.Menu;
82 import android.view.MenuInflater;
83 import android.view.MenuItem;
84 import android.view.View;
85 import android.view.ViewGroup;
86 import android.view.Window;
87 import android.view.WindowManager;
88 import android.view.ContextMenu.ContextMenuInfo;
89 import android.view.MenuItem.OnMenuItemClickListener;
90 import android.webkit.CookieManager;
91 import android.webkit.CookieSyncManager;
92 import android.webkit.DownloadListener;
93 import android.webkit.HttpAuthHandler;
94 import android.webkit.PluginManager;
95 import android.webkit.SslErrorHandler;
96 import android.webkit.URLUtil;
97 import android.webkit.ValueCallback;
98 import android.webkit.WebChromeClient;
99 import android.webkit.WebHistoryItem;
100 import android.webkit.WebIconDatabase;
101 import android.webkit.WebView;
102 import android.widget.EditText;
103 import android.widget.FrameLayout;
104 import android.widget.LinearLayout;
105 import android.widget.TextView;
106 import android.widget.Toast;
107 import android.accounts.Account;
108 import android.accounts.AccountManager;
109 import android.accounts.AccountManagerFuture;
110 import android.accounts.AuthenticatorException;
111 import android.accounts.OperationCanceledException;
112 import android.accounts.AccountManagerCallback;
113
114 import com.android.common.Search;
115 import com.android.common.speech.LoggingEvents;
116
117 import java.io.ByteArrayOutputStream;
118 import java.io.File;
119 import java.io.IOException;
120 import java.io.InputStream;
121 import java.net.MalformedURLException;
122 import java.net.URI;
123 import java.net.URISyntaxException;
124 import java.net.URL;
125 import java.net.URLEncoder;
126 import java.text.ParseException;
127 import java.util.Date;
128 import java.util.HashMap;
129 import java.util.HashSet;
130 import java.util.Iterator;
131 import java.util.List;
132 import java.util.Map;
133 import java.util.Set;
134 import java.util.regex.Matcher;
135 import java.util.regex.Pattern;
136
137 public class BrowserActivity extends Activity
138     implements View.OnCreateContextMenuListener, DownloadListener,
139         AccountManagerCallback<Account[]> {
140
141     /* Define some aliases to make these debugging flags easier to refer to.
142      * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
143      */
144     private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
145     private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
146     private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
147
148     // These are single-character shortcuts for searching popular sources.
149     private static final int SHORTCUT_INVALID = 0;
150     private static final int SHORTCUT_GOOGLE_SEARCH = 1;
151     private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
152     private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
153     private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
154
155     private Account[] mAccountsGoogle;
156     private Account[] mAccountsPreferHosted;
157
158     // XXX: These constants should be exposed through some public api. Hardcode
159     // the values for now until some solution for gsf can be worked out.
160     // http://b/issue?id=2425179
161     private static final String ACCOUNT_TYPE = "com.google";
162     private static final String FEATURE_LEGACY_GOOGLE = "legacy_google";
163     private static final String FEATURE_LEGACY_HOSTED_OR_GOOGLE =
164             "legacy_hosted_or_google";
165
166     private void startReadOfGoogleAccounts() {
167         mAccountsGoogle = null;
168         mAccountsPreferHosted = null;
169
170         AccountManager.get(this).getAccountsByTypeAndFeatures(
171                 ACCOUNT_TYPE, new String[]{ FEATURE_LEGACY_HOSTED_OR_GOOGLE },
172                 this, null);
173     }
174
175     /** This implements AccountManagerCallback<Account[]> */
176     public void run(AccountManagerFuture<Account[]> accountManagerFuture) {
177         try {
178             if (mAccountsGoogle == null) {
179                 mAccountsGoogle = accountManagerFuture.getResult();
180
181                 AccountManager.get(this).getAccountsByTypeAndFeatures(
182                         ACCOUNT_TYPE, new String[]{ FEATURE_LEGACY_GOOGLE },
183                         this, null);
184             } else {
185                 mAccountsPreferHosted = accountManagerFuture.getResult();
186                 setupHomePage();
187             }
188         } catch (OperationCanceledException e) {
189             setupHomePage();
190         } catch (IOException e) {
191             setupHomePage();
192         } catch (AuthenticatorException e) {
193             setupHomePage();
194         }
195     }
196
197     private void setupHomePage() {
198         // get the default home page
199         String homepage = mSettings.getHomePage();
200
201         if (mAccountsPreferHosted != null && mAccountsGoogle != null) {
202             // three cases:
203             //
204             //   hostedUser == googleUser
205             //      The device has only a google account
206             //
207             //   hostedUser != googleUser
208             //      The device has a hosted account and a google account
209             //
210             //   hostedUser != null, googleUser == null
211             //      The device has only a hosted account (so far)
212             String hostedUser = mAccountsPreferHosted.length == 0 
213                     ? null
214                     : mAccountsPreferHosted[0].name;
215             String googleUser = mAccountsGoogle.length == 0 ? null : mAccountsGoogle[0].name;
216
217             // developers might have no accounts at all
218             if (hostedUser == null) return;
219
220             if (googleUser == null || !hostedUser.equals(googleUser)) {
221                 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
222                 homepage = homepage.replace("?", "/a/" + domain + "?");
223             }
224         }
225
226         mSettings.setHomePage(BrowserActivity.this, homepage);
227         resumeAfterCredentials();
228     }
229
230     private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
231         @Override
232         public Void doInBackground(File... files) {
233             if (files != null) {
234                 for (File f : files) {
235                     if (!f.delete()) {
236                       Log.e(LOGTAG, f.getPath() + " was not deleted");
237                     }
238                 }
239             }
240             return null;
241         }
242     }
243
244     /**
245      * This layout holds everything you see below the status bar, including the
246      * error console, the custom view container, and the webviews.
247      */
248     private FrameLayout mBrowserFrameLayout;
249
250     @Override
251     public void onCreate(Bundle icicle) {
252         if (LOGV_ENABLED) {
253             Log.v(LOGTAG, this + " onStart");
254         }
255         super.onCreate(icicle);
256         // test the browser in OpenGL
257         // requestWindowFeature(Window.FEATURE_OPENGL);
258
259         // enable this to test the browser in 32bit
260         if (false) {
261             getWindow().setFormat(PixelFormat.RGBX_8888);
262             BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888);
263         }
264
265         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
266
267         mResolver = getContentResolver();
268
269         // If this was a web search request, pass it on to the default web
270         // search provider and finish this activity.
271         if (handleWebSearchIntent(getIntent())) {
272             finish();
273             return;
274         }
275
276         mSecLockIcon = Resources.getSystem().getDrawable(
277                 android.R.drawable.ic_secure);
278         mMixLockIcon = Resources.getSystem().getDrawable(
279                 android.R.drawable.ic_partial_secure);
280
281         FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
282                 .findViewById(com.android.internal.R.id.content);
283         mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this)
284                 .inflate(R.layout.custom_screen, null);
285         mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
286                 R.id.main_content);
287         mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
288                 .findViewById(R.id.error_console);
289         mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
290                 .findViewById(R.id.fullscreen_custom_content);
291         frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
292         mTitleBar = new TitleBar(this);
293         mFakeTitleBar = new TitleBar(this);
294
295         // Create the tab control and our initial tab
296         mTabControl = new TabControl(this);
297
298         // Open the icon database and retain all the bookmark urls for favicons
299         retainIconsOnStartup();
300
301         // Keep a settings instance handy.
302         mSettings = BrowserSettings.getInstance();
303         mSettings.setTabControl(mTabControl);
304
305         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
306         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
307
308         // Find out if the network is currently up.
309         ConnectivityManager cm = (ConnectivityManager) getSystemService(
310                 Context.CONNECTIVITY_SERVICE);
311         NetworkInfo info = cm.getActiveNetworkInfo();
312         if (info != null) {
313             mIsNetworkUp = info.isAvailable();
314         }
315
316         /* enables registration for changes in network status from
317            http stack */
318         mNetworkStateChangedFilter = new IntentFilter();
319         mNetworkStateChangedFilter.addAction(
320                 ConnectivityManager.CONNECTIVITY_ACTION);
321         mNetworkStateIntentReceiver = new BroadcastReceiver() {
322                 @Override
323                 public void onReceive(Context context, Intent intent) {
324                     if (intent.getAction().equals(
325                             ConnectivityManager.CONNECTIVITY_ACTION)) {
326
327                         NetworkInfo info = intent.getParcelableExtra(
328                                 ConnectivityManager.EXTRA_NETWORK_INFO);
329                         String typeName = info.getTypeName();
330                         String subtypeName = info.getSubtypeName();
331                         sendNetworkType(typeName.toLowerCase(),
332                                 (subtypeName != null ? subtypeName.toLowerCase() : ""));
333
334                         onNetworkToggle(info.isAvailable());
335                     }
336                 }
337             };
338
339         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
340         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
341         filter.addDataScheme("package");
342         mPackageInstallationReceiver = new BroadcastReceiver() {
343             @Override
344             public void onReceive(Context context, Intent intent) {
345                 final String action = intent.getAction();
346                 final String packageName = intent.getData()
347                         .getSchemeSpecificPart();
348                 final boolean replacing = intent.getBooleanExtra(
349                         Intent.EXTRA_REPLACING, false);
350                 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
351                     // if it is replacing, refreshPlugins() when adding
352                     return;
353                 }
354
355                 if (sGoogleApps.contains(packageName)) {
356                     BrowserActivity.this.packageChanged(packageName,
357                             Intent.ACTION_PACKAGE_ADDED.equals(action));
358                 }
359
360                 PackageManager pm = BrowserActivity.this.getPackageManager();
361                 PackageInfo pkgInfo = null;
362                 try {
363                     pkgInfo = pm.getPackageInfo(packageName,
364                             PackageManager.GET_PERMISSIONS);
365                 } catch (PackageManager.NameNotFoundException e) {
366                     return;
367                 }
368                 if (pkgInfo != null) {
369                     String permissions[] = pkgInfo.requestedPermissions;
370                     if (permissions == null) {
371                         return;
372                     }
373                     boolean permissionOk = false;
374                     for (String permit : permissions) {
375                         if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
376                             permissionOk = true;
377                             break;
378                         }
379                     }
380                     if (permissionOk) {
381                         PluginManager.getInstance(BrowserActivity.this)
382                                 .refreshPlugins(
383                                         Intent.ACTION_PACKAGE_ADDED
384                                                 .equals(action));
385                     }
386                 }
387             }
388         };
389         registerReceiver(mPackageInstallationReceiver, filter);
390
391         if (!mTabControl.restoreState(icicle)) {
392             // clear up the thumbnail directory if we can't restore the state as
393             // none of the files in the directory are referenced any more.
394             new ClearThumbnails().execute(
395                     mTabControl.getThumbnailDir().listFiles());
396             // there is no quit on Android. But if we can't restore the state,
397             // we can treat it as a new Browser, remove the old session cookies.
398             CookieManager.getInstance().removeSessionCookie();
399             final Intent intent = getIntent();
400             final Bundle extra = intent.getExtras();
401             // Create an initial tab.
402             // If the intent is ACTION_VIEW and data is not null, the Browser is
403             // invoked to view the content by another application. In this case,
404             // the tab will be close when exit.
405             UrlData urlData = getUrlDataFromIntent(intent);
406
407             String action = intent.getAction();
408             final Tab t = mTabControl.createNewTab(
409                     (Intent.ACTION_VIEW.equals(action) &&
410                     intent.getData() != null)
411                     || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
412                     .equals(action),
413                     intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
414             mTabControl.setCurrentTab(t);
415             attachTabToContentView(t);
416             WebView webView = t.getWebView();
417             if (extra != null) {
418                 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
419                 if (scale > 0 && scale <= 1000) {
420                     webView.setInitialScale(scale);
421                 }
422             }
423             // If we are not restoring from an icicle, then there is a high
424             // likely hood this is the first run. So, check to see if the
425             // homepage needs to be configured and copy any plugins from our
426             // asset directory to the data partition.
427             if ((extra == null || !extra.getBoolean("testing"))
428                     && !mSettings.isLoginInitialized()) {
429                 startReadOfGoogleAccounts();
430             }
431
432             if (urlData.isEmpty()) {
433                 if (mSettings.isLoginInitialized()) {
434                     loadUrl(webView, mSettings.getHomePage());
435                 } else {
436                     waitForCredentials();
437                 }
438             } else {
439                 loadUrlDataIn(t, urlData);
440             }
441         } else {
442             // TabControl.restoreState() will create a new tab even if
443             // restoring the state fails.
444             attachTabToContentView(mTabControl.getCurrentTab());
445         }
446
447         // Read JavaScript flags if it exists.
448         String jsFlags = mSettings.getJsFlags();
449         if (jsFlags.trim().length() != 0) {
450             mTabControl.getCurrentWebView().setJsFlags(jsFlags);
451         }
452         // Work out which packages are installed on the system.
453         getInstalledPackages();
454     }
455
456     /**
457      * Feed the previously stored results strings to the BrowserProvider so that
458      * the SearchDialog will show them instead of the standard searches.
459      * @param result String to show on the editable line of the SearchDialog.
460      */
461     /* package */ void showVoiceSearchResults(String result) {
462         ContentProviderClient client = mResolver.acquireContentProviderClient(
463                 Browser.BOOKMARKS_URI);
464         ContentProvider prov = client.getLocalContentProvider();
465         BrowserProvider bp = (BrowserProvider) prov;
466         bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
467         client.release();
468
469         Bundle bundle = createGoogleSearchSourceBundle(
470                 GOOGLE_SEARCH_SOURCE_SEARCHKEY);
471         bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true);
472         startSearch(result, false, bundle, false);
473     }
474
475     @Override
476     protected void onNewIntent(Intent intent) {
477         Tab current = mTabControl.getCurrentTab();
478         // When a tab is closed on exit, the current tab index is set to -1.
479         // Reset before proceed as Browser requires the current tab to be set.
480         if (current == null) {
481             // Try to reset the tab in case the index was incorrect.
482             current = mTabControl.getTab(0);
483             if (current == null) {
484                 // No tabs at all so just ignore this intent.
485                 return;
486             }
487             mTabControl.setCurrentTab(current);
488             attachTabToContentView(current);
489             resetTitleAndIcon(current.getWebView());
490         }
491         final String action = intent.getAction();
492         final int flags = intent.getFlags();
493         if (Intent.ACTION_MAIN.equals(action) ||
494                 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
495             // just resume the browser
496             return;
497         }
498         boolean activateVoiceSearch = RecognizerResultsIntent
499                 .ACTION_VOICE_SEARCH_RESULTS.equals(action);
500         if (Intent.ACTION_VIEW.equals(action)
501                 || Intent.ACTION_SEARCH.equals(action)
502                 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
503                 || Intent.ACTION_WEB_SEARCH.equals(action)
504                 || activateVoiceSearch) {
505             if (current.isInVoiceSearchMode()) {
506                 String title = current.getVoiceDisplayTitle();
507                 if (title != null && title.equals(intent.getStringExtra(
508                         SearchManager.QUERY))) {
509                     // The user submitted the same search as the last voice
510                     // search, so do nothing.
511                     return;
512                 }
513                 if (Intent.ACTION_SEARCH.equals(action)
514                         && current.voiceSearchSourceIsGoogle()) {
515                     Intent logIntent = new Intent(
516                             LoggingEvents.ACTION_LOG_EVENT);
517                     logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
518                             LoggingEvents.VoiceSearch.QUERY_UPDATED);
519                     logIntent.putExtra(
520                             LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE,
521                             intent.getDataString());
522                     sendBroadcast(logIntent);
523                     // Note, onPageStarted will revert the voice title bar
524                     // When http://b/issue?id=2379215 is fixed, we should update
525                     // the title bar here.
526                 }
527             }
528             // If this was a search request (e.g. search query directly typed into the address bar),
529             // pass it on to the default web search provider.
530             if (handleWebSearchIntent(intent)) {
531                 return;
532             }
533
534             UrlData urlData = getUrlDataFromIntent(intent);
535             if (urlData.isEmpty()) {
536                 urlData = new UrlData(mSettings.getHomePage());
537             }
538
539             final String appId = intent
540                     .getStringExtra(Browser.EXTRA_APPLICATION_ID);
541             if ((Intent.ACTION_VIEW.equals(action) || activateVoiceSearch)
542                     && !getPackageName().equals(appId)
543                     && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
544                 Tab appTab = mTabControl.getTabFromId(appId);
545                 if (appTab != null) {
546                     Log.i(LOGTAG, "Reusing tab for " + appId);
547                     // Dismiss the subwindow if applicable.
548                     dismissSubWindow(appTab);
549                     // Since we might kill the WebView, remove it from the
550                     // content view first.
551                     removeTabFromContentView(appTab);
552                     // Recreate the main WebView after destroying the old one.
553                     // If the WebView has the same original url and is on that
554                     // page, it can be reused.
555                     boolean needsLoad =
556                             mTabControl.recreateWebView(appTab, urlData.mUrl)
557                             // If there is a voice intent in the UrlData, then
558                             // recreateWebView may return false, but we still
559                             // need to force a load.
560                             || urlData.mVoiceIntent != null;
561
562                     if (current != appTab) {
563                         switchToTab(mTabControl.getTabIndex(appTab));
564                         if (needsLoad) {
565                             loadUrlDataIn(appTab, urlData);
566                         }
567                     } else {
568                         // If the tab was the current tab, we have to attach
569                         // it to the view system again.
570                         attachTabToContentView(appTab);
571                         if (needsLoad) {
572                             loadUrlDataIn(appTab, urlData);
573                         }
574                     }
575                     return;
576                 } else {
577                     // No matching application tab, try to find a regular tab
578                     // with a matching url.
579                     appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
580                     if (appTab != null) {
581                         if (current != appTab) {
582                             switchToTab(mTabControl.getTabIndex(appTab));
583                         }
584                         // Otherwise, we are already viewing the correct tab.
585                     } else {
586                         // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
587                         // will be opened in a new tab unless we have reached
588                         // MAX_TABS. Then the url will be opened in the current
589                         // tab. If a new tab is created, it will have "true" for
590                         // exit on close.
591                         openTabAndShow(urlData, true, appId);
592                     }
593                 }
594             } else {
595                 if (!urlData.isEmpty()
596                         && urlData.mUrl.startsWith("about:debug")) {
597                     if ("about:debug.dom".equals(urlData.mUrl)) {
598                         current.getWebView().dumpDomTree(false);
599                     } else if ("about:debug.dom.file".equals(urlData.mUrl)) {
600                         current.getWebView().dumpDomTree(true);
601                     } else if ("about:debug.render".equals(urlData.mUrl)) {
602                         current.getWebView().dumpRenderTree(false);
603                     } else if ("about:debug.render.file".equals(urlData.mUrl)) {
604                         current.getWebView().dumpRenderTree(true);
605                     } else if ("about:debug.display".equals(urlData.mUrl)) {
606                         current.getWebView().dumpDisplayTree();
607                     } else if (urlData.mUrl.startsWith("about:debug.drag")) {
608                         int index = urlData.mUrl.codePointAt(16) - '0';
609                         if (index <= 0 || index > 9) {
610                             current.getWebView().setDragTracker(null);
611                         } else {
612                             current.getWebView().setDragTracker(new MeshTracker(index));
613                         }
614                     } else {
615                         mSettings.toggleDebugSettings();
616                     }
617                     return;
618                 }
619                 // Get rid of the subwindow if it exists
620                 dismissSubWindow(current);
621                 loadUrlDataIn(current, urlData);
622             }
623         }
624     }
625
626     private int parseUrlShortcut(String url) {
627         if (url == null) return SHORTCUT_INVALID;
628
629         // FIXME: quick search, need to be customized by setting
630         if (url.length() > 2 && url.charAt(1) == ' ') {
631             switch (url.charAt(0)) {
632             case 'g': return SHORTCUT_GOOGLE_SEARCH;
633             case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
634             case 'd': return SHORTCUT_DICTIONARY_SEARCH;
635             case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
636             }
637         }
638         return SHORTCUT_INVALID;
639     }
640
641     /**
642      * Launches the default web search activity with the query parameters if the given intent's data
643      * are identified as plain search terms and not URLs/shortcuts.
644      * @return true if the intent was handled and web search activity was launched, false if not.
645      */
646     private boolean handleWebSearchIntent(Intent intent) {
647         if (intent == null) return false;
648
649         String url = null;
650         final String action = intent.getAction();
651         if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals(
652                 action)) {
653             return false;
654         }
655         if (Intent.ACTION_VIEW.equals(action)) {
656             Uri data = intent.getData();
657             if (data != null) url = data.toString();
658         } else if (Intent.ACTION_SEARCH.equals(action)
659                 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
660                 || Intent.ACTION_WEB_SEARCH.equals(action)) {
661             url = intent.getStringExtra(SearchManager.QUERY);
662         }
663         return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA),
664                 intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
665     }
666
667     /**
668      * Launches the default web search activity with the query parameters if the given url string
669      * was identified as plain search terms and not URL/shortcut.
670      * @return true if the request was handled and web search activity was launched, false if not.
671      */
672     private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) {
673         if (inUrl == null) return false;
674
675         // In general, we shouldn't modify URL from Intent.
676         // But currently, we get the user-typed URL from search box as well.
677         String url = fixUrl(inUrl).trim();
678
679         // URLs and site specific search shortcuts are handled by the regular flow of control, so
680         // return early.
681         if (Patterns.WEB_URL.matcher(url).matches()
682                 || ACCEPTED_URI_SCHEMA.matcher(url).matches()
683                 || parseUrlShortcut(url) != SHORTCUT_INVALID) {
684             return false;
685         }
686
687         Browser.updateVisitedHistory(mResolver, url, false);
688         Browser.addSearchUrl(mResolver, url);
689
690         Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
691         intent.addCategory(Intent.CATEGORY_DEFAULT);
692         intent.putExtra(SearchManager.QUERY, url);
693         if (appData != null) {
694             intent.putExtra(SearchManager.APP_DATA, appData);
695         }
696         if (extraData != null) {
697             intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
698         }
699         intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
700         startActivity(intent);
701
702         return true;
703     }
704
705     private UrlData getUrlDataFromIntent(Intent intent) {
706         String url = "";
707         Map<String, String> headers = null;
708         if (intent != null) {
709             final String action = intent.getAction();
710             if (Intent.ACTION_VIEW.equals(action)) {
711                 url = smartUrlFilter(intent.getData());
712                 if (url != null && url.startsWith("content:")) {
713                     /* Append mimetype so webview knows how to display */
714                     String mimeType = intent.resolveType(getContentResolver());
715                     if (mimeType != null) {
716                         url += "?" + mimeType;
717                     }
718                 }
719                 if (url != null && url.startsWith("http")) {
720                     final Bundle pairs = intent
721                             .getBundleExtra(Browser.EXTRA_HEADERS);
722                     if (pairs != null && !pairs.isEmpty()) {
723                         Iterator<String> iter = pairs.keySet().iterator();
724                         headers = new HashMap<String, String>();
725                         while (iter.hasNext()) {
726                             String key = iter.next();
727                             headers.put(key, pairs.getString(key));
728                         }
729                     }
730                 }
731             } else if (Intent.ACTION_SEARCH.equals(action)
732                     || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
733                     || Intent.ACTION_WEB_SEARCH.equals(action)) {
734                 url = intent.getStringExtra(SearchManager.QUERY);
735                 if (url != null) {
736                     mLastEnteredUrl = url;
737                     // In general, we shouldn't modify URL from Intent.
738                     // But currently, we get the user-typed URL from search box as well.
739                     url = fixUrl(url);
740                     url = smartUrlFilter(url);
741                     Browser.updateVisitedHistory(mResolver, url, false);
742                     String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
743                     if (url.contains(searchSource)) {
744                         String source = null;
745                         final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
746                         if (appData != null) {
747                             source = appData.getString(Search.SOURCE);
748                         }
749                         if (TextUtils.isEmpty(source)) {
750                             source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
751                         }
752                         url = url.replace(searchSource, "&source=android-"+source+"&");
753                     }
754                 }
755             }
756         }
757         return new UrlData(url, headers, intent);
758     }
759     /* package */ void showVoiceTitleBar(String title) {
760         mTitleBar.setInVoiceMode(true);
761         mFakeTitleBar.setInVoiceMode(true);
762
763         mTitleBar.setDisplayTitle(title);
764         mFakeTitleBar.setDisplayTitle(title);
765     }
766     /* package */ void revertVoiceTitleBar() {
767         mTitleBar.setInVoiceMode(false);
768         mFakeTitleBar.setInVoiceMode(false);
769
770         mTitleBar.setDisplayTitle(mUrl);
771         mFakeTitleBar.setDisplayTitle(mUrl);
772     }
773     /* package */ static String fixUrl(String inUrl) {
774         // FIXME: Converting the url to lower case
775         // duplicates functionality in smartUrlFilter().
776         // However, changing all current callers of fixUrl to
777         // call smartUrlFilter in addition may have unwanted
778         // consequences, and is deferred for now.
779         int colon = inUrl.indexOf(':');
780         boolean allLower = true;
781         for (int index = 0; index < colon; index++) {
782             char ch = inUrl.charAt(index);
783             if (!Character.isLetter(ch)) {
784                 break;
785             }
786             allLower &= Character.isLowerCase(ch);
787             if (index == colon - 1 && !allLower) {
788                 inUrl = inUrl.substring(0, colon).toLowerCase()
789                         + inUrl.substring(colon);
790             }
791         }
792         if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
793             return inUrl;
794         if (inUrl.startsWith("http:") ||
795                 inUrl.startsWith("https:")) {
796             if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
797                 inUrl = inUrl.replaceFirst("/", "//");
798             } else inUrl = inUrl.replaceFirst(":", "://");
799         }
800         return inUrl;
801     }
802
803     @Override
804     protected void onResume() {
805         super.onResume();
806         if (LOGV_ENABLED) {
807             Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
808         }
809
810         if (!mActivityInPause) {
811             Log.e(LOGTAG, "BrowserActivity is already resumed.");
812             return;
813         }
814
815         mTabControl.resumeCurrentTab();
816         mActivityInPause = false;
817         resumeWebViewTimers();
818
819         if (mWakeLock.isHeld()) {
820             mHandler.removeMessages(RELEASE_WAKELOCK);
821             mWakeLock.release();
822         }
823
824         if (mCredsDlg != null) {
825             if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
826              // In case credential request never comes back
827                 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
828             }
829         }
830
831         registerReceiver(mNetworkStateIntentReceiver,
832                          mNetworkStateChangedFilter);
833         WebView.enablePlatformNotifications();
834     }
835
836     /**
837      * Since the actual title bar is embedded in the WebView, and removing it
838      * would change its appearance, use a different TitleBar to show overlayed
839      * at the top of the screen, when the menu is open or the page is loading.
840      */
841     private TitleBar mFakeTitleBar;
842
843     /**
844      * Holder for the fake title bar.  It will have a foreground shadow, as well
845      * as a white background, so the fake title bar looks like the real one.
846      */
847     private ViewGroup mFakeTitleBarHolder;
848
849     /**
850      * Layout parameters for the fake title bar within mFakeTitleBarHolder
851      */
852     private FrameLayout.LayoutParams mFakeTitleBarParams
853             = new FrameLayout.LayoutParams(
854             ViewGroup.LayoutParams.MATCH_PARENT,
855             ViewGroup.LayoutParams.WRAP_CONTENT);
856     /**
857      * Keeps track of whether the options menu is open.  This is important in
858      * determining whether to show or hide the title bar overlay.
859      */
860     private boolean mOptionsMenuOpen;
861
862     /**
863      * Only meaningful when mOptionsMenuOpen is true.  This variable keeps track
864      * of whether the configuration has changed.  The first onMenuOpened call
865      * after a configuration change is simply a reopening of the same menu
866      * (i.e. mIconView did not change).
867      */
868     private boolean mConfigChanged;
869
870     /**
871      * Whether or not the options menu is in its smaller, icon menu form.  When
872      * true, we want the title bar overlay to be up.  When false, we do not.
873      * Only meaningful if mOptionsMenuOpen is true.
874      */
875     private boolean mIconView;
876
877     @Override
878     public boolean onMenuOpened(int featureId, Menu menu) {
879         if (Window.FEATURE_OPTIONS_PANEL == featureId) {
880             if (mOptionsMenuOpen) {
881                 if (mConfigChanged) {
882                     // We do not need to make any changes to the state of the
883                     // title bar, since the only thing that happened was a
884                     // change in orientation
885                     mConfigChanged = false;
886                 } else {
887                     if (mIconView) {
888                         // Switching the menu to expanded view, so hide the
889                         // title bar.
890                         hideFakeTitleBar();
891                         mIconView = false;
892                     } else {
893                         // Switching the menu back to icon view, so show the
894                         // title bar once again.
895                         showFakeTitleBar();
896                         mIconView = true;
897                     }
898                 }
899             } else {
900                 // The options menu is closed, so open it, and show the title
901                 showFakeTitleBar();
902                 mOptionsMenuOpen = true;
903                 mConfigChanged = false;
904                 mIconView = true;
905             }
906         }
907         return true;
908     }
909
910     /**
911      * Special class used exclusively for the shadow drawn underneath the fake
912      * title bar.  The shadow does not need to be drawn if the WebView
913      * underneath is scrolled to the top, because it will draw directly on top
914      * of the embedded shadow.
915      */
916     private static class Shadow extends View {
917         private WebView mWebView;
918
919         public Shadow(Context context, AttributeSet attrs) {
920             super(context, attrs);
921         }
922
923         public void setWebView(WebView view) {
924             mWebView = view;
925         }
926
927         @Override
928         public void draw(Canvas canvas) {
929             // In general onDraw is the method to override, but we care about
930             // whether or not the background gets drawn, which happens in draw()
931             if (mWebView == null || mWebView.getScrollY() > getHeight()) {
932                 super.draw(canvas);
933             }
934             // Need to invalidate so that if the scroll position changes, we
935             // still draw as appropriate.
936             invalidate();
937         }
938     }
939
940     private void showFakeTitleBar() {
941         final View decor = getWindow().peekDecorView();
942         if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null
943                 && !mActivityInPause && decor != null
944                 && decor.getWindowToken() != null) {
945             Rect visRect = new Rect();
946             if (!mBrowserFrameLayout.getGlobalVisibleRect(visRect)) {
947                 if (LOGD_ENABLED) {
948                     Log.d(LOGTAG, "showFakeTitleBar visRect failed");
949                 }
950                 return;
951             }
952
953             WindowManager manager
954                     = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
955
956             // Add the title bar to the window manager so it can receive touches
957             // while the menu is up
958             WindowManager.LayoutParams params
959                     = new WindowManager.LayoutParams(
960                     ViewGroup.LayoutParams.MATCH_PARENT,
961                     ViewGroup.LayoutParams.WRAP_CONTENT,
962                     WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
963                     WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
964                     PixelFormat.TRANSLUCENT);
965             params.gravity = Gravity.TOP;
966             WebView mainView = mTabControl.getCurrentWebView();
967             boolean atTop = mainView != null && mainView.getScrollY() == 0;
968             params.windowAnimations = atTop ? 0 : R.style.TitleBar;
969             // XXX : Without providing an offset, the fake title bar will be
970             // placed underneath the status bar.  Use the global visible rect
971             // of mBrowserFrameLayout to determine the bottom of the status bar
972             params.y = visRect.top;
973             // Add a holder for the title bar.  It also holds a shadow to show
974             // below the title bar.
975             if (mFakeTitleBarHolder == null) {
976                 mFakeTitleBarHolder = (ViewGroup) LayoutInflater.from(this)
977                     .inflate(R.layout.title_bar_bg, null);
978             }
979             Shadow shadow = (Shadow) mFakeTitleBarHolder.findViewById(
980                     R.id.shadow);
981             shadow.setWebView(mainView);
982             mFakeTitleBarHolder.addView(mFakeTitleBar, 0, mFakeTitleBarParams);
983             manager.addView(mFakeTitleBarHolder, params);
984         }
985     }
986
987     @Override
988     public void onOptionsMenuClosed(Menu menu) {
989         mOptionsMenuOpen = false;
990         if (!mInLoad) {
991             hideFakeTitleBar();
992         } else if (!mIconView) {
993             // The page is currently loading, and we are in expanded mode, so
994             // we were not showing the menu.  Show it once again.  It will be
995             // removed when the page finishes.
996             showFakeTitleBar();
997         }
998     }
999
1000     private void hideFakeTitleBar() {
1001         if (mFakeTitleBar.getParent() == null) return;
1002         WindowManager.LayoutParams params = (WindowManager.LayoutParams)
1003                 mFakeTitleBarHolder.getLayoutParams();
1004         WebView mainView = mTabControl.getCurrentWebView();
1005         // Although we decided whether or not to animate based on the current
1006         // scroll position, the scroll position may have changed since the
1007         // fake title bar was displayed.  Make sure it has the appropriate
1008         // animation/lack thereof before removing.
1009         params.windowAnimations = mainView != null && mainView.getScrollY() == 0
1010                 ? 0 : R.style.TitleBar;
1011         WindowManager manager
1012                     = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
1013         manager.updateViewLayout(mFakeTitleBarHolder, params);
1014         mFakeTitleBarHolder.removeView(mFakeTitleBar);
1015         manager.removeView(mFakeTitleBarHolder);
1016     }
1017
1018     /**
1019      * Special method for the fake title bar to call when displaying its context
1020      * menu, since it is in its own Window, and its parent does not show a
1021      * context menu.
1022      */
1023     /* package */ void showTitleBarContextMenu() {
1024         if (null == mTitleBar.getParent()) {
1025             return;
1026         }
1027         openContextMenu(mTitleBar);
1028     }
1029
1030     @Override
1031     public void onContextMenuClosed(Menu menu) {
1032         super.onContextMenuClosed(menu);
1033         if (mInLoad) {
1034             showFakeTitleBar();
1035         }
1036     }
1037
1038     /**
1039      *  onSaveInstanceState(Bundle map)
1040      *  onSaveInstanceState is called right before onStop(). The map contains
1041      *  the saved state.
1042      */
1043     @Override
1044     protected void onSaveInstanceState(Bundle outState) {
1045         if (LOGV_ENABLED) {
1046             Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
1047         }
1048         // the default implementation requires each view to have an id. As the
1049         // browser handles the state itself and it doesn't use id for the views,
1050         // don't call the default implementation. Otherwise it will trigger the
1051         // warning like this, "couldn't save which view has focus because the
1052         // focused view XXX has no id".
1053
1054         // Save all the tabs
1055         mTabControl.saveState(outState);
1056     }
1057
1058     @Override
1059     protected void onPause() {
1060         super.onPause();
1061
1062         if (mActivityInPause) {
1063             Log.e(LOGTAG, "BrowserActivity is already paused.");
1064             return;
1065         }
1066
1067         mTabControl.pauseCurrentTab();
1068         mActivityInPause = true;
1069         if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
1070             mWakeLock.acquire();
1071             mHandler.sendMessageDelayed(mHandler
1072                     .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
1073         }
1074
1075         // Clear the credentials toast if it is up
1076         if (mCredsDlg != null && mCredsDlg.isShowing()) {
1077             mCredsDlg.dismiss();
1078         }
1079         mCredsDlg = null;
1080
1081         // FIXME: This removes the active tabs page and resets the menu to
1082         // MAIN_MENU.  A better solution might be to do this work in onNewIntent
1083         // but then we would need to save it in onSaveInstanceState and restore
1084         // it in onCreate/onRestoreInstanceState
1085         if (mActiveTabsPage != null) {
1086             removeActiveTabPage(true);
1087         }
1088
1089         cancelStopToast();
1090
1091         // unregister network state listener
1092         unregisterReceiver(mNetworkStateIntentReceiver);
1093         WebView.disablePlatformNotifications();
1094     }
1095
1096     @Override
1097     protected void onDestroy() {
1098         if (LOGV_ENABLED) {
1099             Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
1100         }
1101         super.onDestroy();
1102
1103         if (mUploadMessage != null) {
1104             mUploadMessage.onReceiveValue(null);
1105             mUploadMessage = null;
1106         }
1107
1108         if (mTabControl == null) return;
1109
1110         // Remove the fake title bar if it is there
1111         hideFakeTitleBar();
1112
1113         // Remove the current tab and sub window
1114         Tab t = mTabControl.getCurrentTab();
1115         if (t != null) {
1116             dismissSubWindow(t);
1117             removeTabFromContentView(t);
1118         }
1119         // Destroy all the tabs
1120         mTabControl.destroy();
1121         WebIconDatabase.getInstance().close();
1122
1123         unregisterReceiver(mPackageInstallationReceiver);
1124     }
1125
1126     @Override
1127     public void onConfigurationChanged(Configuration newConfig) {
1128         mConfigChanged = true;
1129         super.onConfigurationChanged(newConfig);
1130
1131         if (mPageInfoDialog != null) {
1132             mPageInfoDialog.dismiss();
1133             showPageInfo(
1134                 mPageInfoView,
1135                 mPageInfoFromShowSSLCertificateOnError);
1136         }
1137         if (mSSLCertificateDialog != null) {
1138             mSSLCertificateDialog.dismiss();
1139             showSSLCertificate(
1140                 mSSLCertificateView);
1141         }
1142         if (mSSLCertificateOnErrorDialog != null) {
1143             mSSLCertificateOnErrorDialog.dismiss();
1144             showSSLCertificateOnError(
1145                 mSSLCertificateOnErrorView,
1146                 mSSLCertificateOnErrorHandler,
1147                 mSSLCertificateOnErrorError);
1148         }
1149         if (mHttpAuthenticationDialog != null) {
1150             String title = ((TextView) mHttpAuthenticationDialog
1151                     .findViewById(com.android.internal.R.id.alertTitle)).getText()
1152                     .toString();
1153             String name = ((TextView) mHttpAuthenticationDialog
1154                     .findViewById(R.id.username_edit)).getText().toString();
1155             String password = ((TextView) mHttpAuthenticationDialog
1156                     .findViewById(R.id.password_edit)).getText().toString();
1157             int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1158                     .getId();
1159             mHttpAuthenticationDialog.dismiss();
1160             showHttpAuthentication(mHttpAuthHandler, null, null, title,
1161                     name, password, focusId);
1162         }
1163     }
1164
1165     @Override
1166     public void onLowMemory() {
1167         super.onLowMemory();
1168         mTabControl.freeMemory();
1169     }
1170
1171     private boolean resumeWebViewTimers() {
1172         Tab tab = mTabControl.getCurrentTab();
1173         boolean inLoad = tab.inLoad();
1174         if ((!mActivityInPause && !inLoad) || (mActivityInPause && inLoad)) {
1175             CookieSyncManager.getInstance().startSync();
1176             WebView w = tab.getWebView();
1177             if (w != null) {
1178                 w.resumeTimers();
1179             }
1180             return true;
1181         } else {
1182             return false;
1183         }
1184     }
1185
1186     private boolean pauseWebViewTimers() {
1187         Tab tab = mTabControl.getCurrentTab();
1188         boolean inLoad = tab.inLoad();
1189         if (mActivityInPause && !inLoad) {
1190             CookieSyncManager.getInstance().stopSync();
1191             WebView w = mTabControl.getCurrentWebView();
1192             if (w != null) {
1193                 w.pauseTimers();
1194             }
1195             return true;
1196         } else {
1197             return false;
1198         }
1199     }
1200
1201     // FIXME: Do we want to call this when loading google for the first time?
1202     /*
1203      * This function is called when we are launching for the first time. We
1204      * are waiting for the login credentials before loading Google home
1205      * pages. This way the user will be logged in straight away.
1206      */
1207     private void waitForCredentials() {
1208         // Show a toast
1209         mCredsDlg = new ProgressDialog(this);
1210         mCredsDlg.setIndeterminate(true);
1211         mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1212         // If the user cancels the operation, then cancel the Google
1213         // Credentials request.
1214         mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1215         mCredsDlg.show();
1216
1217         // We set a timeout for the retrieval of credentials in onResume()
1218         // as that is when we have freed up some CPU time to get
1219         // the login credentials.
1220     }
1221
1222     /*
1223      * If we have received the credentials or we have timed out and we are
1224      * showing the credentials dialog, then it is time to move on.
1225      */
1226     private void resumeAfterCredentials() {
1227         if (mCredsDlg == null) {
1228             return;
1229         }
1230
1231         // Clear the toast
1232         if (mCredsDlg.isShowing()) {
1233             mCredsDlg.dismiss();
1234         }
1235         mCredsDlg = null;
1236
1237         // Clear any pending timeout
1238         mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1239
1240         // Load the page
1241         WebView w = mTabControl.getCurrentWebView();
1242         if (w != null) {
1243             loadUrl(w, mSettings.getHomePage());
1244         }
1245
1246         // Update the settings, need to do this last as it can take a moment
1247         // to persist the settings. In the mean time we could be loading
1248         // content.
1249         mSettings.setLoginInitialized(this);
1250     }
1251
1252     // Open the icon database and retain all the icons for visited sites.
1253     private void retainIconsOnStartup() {
1254         final WebIconDatabase db = WebIconDatabase.getInstance();
1255         db.open(getDir("icons", 0).getPath());
1256         Cursor c = null;
1257         try {
1258             c = Browser.getAllBookmarks(mResolver);
1259             if (c.moveToFirst()) {
1260                 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1261                 do {
1262                     String url = c.getString(urlIndex);
1263                     db.retainIconForPageUrl(url);
1264                 } while (c.moveToNext());
1265             }
1266         } catch (IllegalStateException e) {
1267             Log.e(LOGTAG, "retainIconsOnStartup", e);
1268         } finally {
1269             if (c!= null) c.close();
1270         }
1271     }
1272
1273     // Helper method for getting the top window.
1274     WebView getTopWindow() {
1275         return mTabControl.getCurrentTopWebView();
1276     }
1277
1278     TabControl getTabControl() {
1279         return mTabControl;
1280     }
1281
1282     @Override
1283     public boolean onCreateOptionsMenu(Menu menu) {
1284         super.onCreateOptionsMenu(menu);
1285
1286         MenuInflater inflater = getMenuInflater();
1287         inflater.inflate(R.menu.browser, menu);
1288         mMenu = menu;
1289         updateInLoadMenuItems();
1290         return true;
1291     }
1292
1293     /**
1294      * As the menu can be open when loading state changes
1295      * we must manually update the state of the stop/reload menu
1296      * item
1297      */
1298     private void updateInLoadMenuItems() {
1299         if (mMenu == null) {
1300             return;
1301         }
1302         MenuItem src = mInLoad ?
1303                 mMenu.findItem(R.id.stop_menu_id):
1304                     mMenu.findItem(R.id.reload_menu_id);
1305         MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1306         dest.setIcon(src.getIcon());
1307         dest.setTitle(src.getTitle());
1308     }
1309
1310     @Override
1311     public boolean onContextItemSelected(MenuItem item) {
1312         // chording is not an issue with context menus, but we use the same
1313         // options selector, so set mCanChord to true so we can access them.
1314         mCanChord = true;
1315         int id = item.getItemId();
1316         boolean result = true;
1317         switch (id) {
1318             // For the context menu from the title bar
1319             case R.id.title_bar_copy_page_url:
1320                 Tab currentTab = mTabControl.getCurrentTab();
1321                 if (null == currentTab) {
1322                     result = false;
1323                     break;
1324                 }
1325                 WebView mainView = currentTab.getWebView();
1326                 if (null == mainView) {
1327                     result = false;
1328                     break;
1329                 }
1330                 copy(mainView.getUrl());
1331                 break;
1332             // -- Browser context menu
1333             case R.id.open_context_menu_id:
1334             case R.id.open_newtab_context_menu_id:
1335             case R.id.bookmark_context_menu_id:
1336             case R.id.save_link_context_menu_id:
1337             case R.id.share_link_context_menu_id:
1338             case R.id.copy_link_context_menu_id:
1339                 final WebView webView = getTopWindow();
1340                 if (null == webView) {
1341                     result = false;
1342                     break;
1343                 }
1344                 final HashMap hrefMap = new HashMap();
1345                 hrefMap.put("webview", webView);
1346                 final Message msg = mHandler.obtainMessage(
1347                         FOCUS_NODE_HREF, id, 0, hrefMap);
1348                 webView.requestFocusNodeHref(msg);
1349                 break;
1350
1351             default:
1352                 // For other context menus
1353                 result = onOptionsItemSelected(item);
1354         }
1355         mCanChord = false;
1356         return result;
1357     }
1358
1359     private Bundle createGoogleSearchSourceBundle(String source) {
1360         Bundle bundle = new Bundle();
1361         bundle.putString(Search.SOURCE, source);
1362         return bundle;
1363     }
1364
1365     /* package */ void editUrl() {
1366         if (mOptionsMenuOpen) closeOptionsMenu();
1367         String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
1368         startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
1369                 null, false);
1370     }
1371
1372     /**
1373      * Overriding this to insert a local information bundle
1374      */
1375     @Override
1376     public void startSearch(String initialQuery, boolean selectInitialQuery,
1377             Bundle appSearchData, boolean globalSearch) {
1378         if (appSearchData == null) {
1379             appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1380         }
1381         super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1382     }
1383
1384     /**
1385      * Switch tabs.  Called by the TitleBarSet when sliding the title bar
1386      * results in changing tabs.
1387      * @param index Index of the tab to change to, as defined by
1388      *              mTabControl.getTabIndex(Tab t).
1389      * @return boolean True if we successfully switched to a different tab.  If
1390      *                 the indexth tab is null, or if that tab is the same as
1391      *                 the current one, return false.
1392      */
1393     /* package */ boolean switchToTab(int index) {
1394         Tab tab = mTabControl.getTab(index);
1395         Tab currentTab = mTabControl.getCurrentTab();
1396         if (tab == null || tab == currentTab) {
1397             return false;
1398         }
1399         if (currentTab != null) {
1400             // currentTab may be null if it was just removed.  In that case,
1401             // we do not need to remove it
1402             removeTabFromContentView(currentTab);
1403         }
1404         mTabControl.setCurrentTab(tab);
1405         attachTabToContentView(tab);
1406         resetTitleIconAndProgress();
1407         updateLockIconToLatest();
1408         return true;
1409     }
1410
1411     /* package */ Tab openTabToHomePage() {
1412         return openTabAndShow(mSettings.getHomePage(), false, null);
1413     }
1414
1415     /* package */ void closeCurrentWindow() {
1416         final Tab current = mTabControl.getCurrentTab();
1417         if (mTabControl.getTabCount() == 1) {
1418             // This is the last tab.  Open a new one, with the home
1419             // page and close the current one.
1420             openTabToHomePage();
1421             closeTab(current);
1422             return;
1423         }
1424         final Tab parent = current.getParentTab();
1425         int indexToShow = -1;
1426         if (parent != null) {
1427             indexToShow = mTabControl.getTabIndex(parent);
1428         } else {
1429             final int currentIndex = mTabControl.getCurrentIndex();
1430             // Try to move to the tab to the right
1431             indexToShow = currentIndex + 1;
1432             if (indexToShow > mTabControl.getTabCount() - 1) {
1433                 // Try to move to the tab to the left
1434                 indexToShow = currentIndex - 1;
1435             }
1436         }
1437         if (switchToTab(indexToShow)) {
1438             // Close window
1439             closeTab(current);
1440         }
1441     }
1442
1443     private ActiveTabsPage mActiveTabsPage;
1444
1445     /**
1446      * Remove the active tabs page.
1447      * @param needToAttach If true, the active tabs page did not attach a tab
1448      *                     to the content view, so we need to do that here.
1449      */
1450     /* package */ void removeActiveTabPage(boolean needToAttach) {
1451         mContentView.removeView(mActiveTabsPage);
1452         mActiveTabsPage = null;
1453         mMenuState = R.id.MAIN_MENU;
1454         if (needToAttach) {
1455             attachTabToContentView(mTabControl.getCurrentTab());
1456         }
1457         getTopWindow().requestFocus();
1458     }
1459
1460     @Override
1461     public boolean onOptionsItemSelected(MenuItem item) {
1462         if (!mCanChord) {
1463             // The user has already fired a shortcut with this hold down of the
1464             // menu key.
1465             return false;
1466         }
1467         if (null == getTopWindow()) {
1468             return false;
1469         }
1470         if (mMenuIsDown) {
1471             // The shortcut action consumes the MENU. Even if it is still down,
1472             // it won't trigger the next shortcut action. In the case of the
1473             // shortcut action triggering a new activity, like Bookmarks, we
1474             // won't get onKeyUp for MENU. So it is important to reset it here.
1475             mMenuIsDown = false;
1476         }
1477         switch (item.getItemId()) {
1478             // -- Main menu
1479             case R.id.new_tab_menu_id:
1480                 openTabToHomePage();
1481                 break;
1482
1483             case R.id.goto_menu_id:
1484                 editUrl();
1485                 break;
1486
1487             case R.id.bookmarks_menu_id:
1488                 bookmarksOrHistoryPicker(false);
1489                 break;
1490
1491             case R.id.active_tabs_menu_id:
1492                 mActiveTabsPage = new ActiveTabsPage(this, mTabControl);
1493                 removeTabFromContentView(mTabControl.getCurrentTab());
1494                 hideFakeTitleBar();
1495                 mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
1496                 mActiveTabsPage.requestFocus();
1497                 mMenuState = EMPTY_MENU;
1498                 break;
1499
1500             case R.id.add_bookmark_menu_id:
1501                 Intent i = new Intent(BrowserActivity.this,
1502                         AddBookmarkPage.class);
1503                 WebView w = getTopWindow();
1504                 i.putExtra("url", w.getUrl());
1505                 i.putExtra("title", w.getTitle());
1506                 i.putExtra("touch_icon_url", w.getTouchIconUrl());
1507                 i.putExtra("thumbnail", createScreenshot(w));
1508                 startActivity(i);
1509                 break;
1510
1511             case R.id.stop_reload_menu_id:
1512                 if (mInLoad) {
1513                     stopLoading();
1514                 } else {
1515                     getTopWindow().reload();
1516                 }
1517                 break;
1518
1519             case R.id.back_menu_id:
1520                 getTopWindow().goBack();
1521                 break;
1522
1523             case R.id.forward_menu_id:
1524                 getTopWindow().goForward();
1525                 break;
1526
1527             case R.id.close_menu_id:
1528                 // Close the subwindow if it exists.
1529                 if (mTabControl.getCurrentSubWindow() != null) {
1530                     dismissSubWindow(mTabControl.getCurrentTab());
1531                     break;
1532                 }
1533                 closeCurrentWindow();
1534                 break;
1535
1536             case R.id.homepage_menu_id:
1537                 Tab current = mTabControl.getCurrentTab();
1538                 if (current != null) {
1539                     dismissSubWindow(current);
1540                     loadUrl(current.getWebView(), mSettings.getHomePage());
1541                 }
1542                 break;
1543
1544             case R.id.preferences_menu_id:
1545                 Intent intent = new Intent(this,
1546                         BrowserPreferencesPage.class);
1547                 intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE,
1548                         getTopWindow().getUrl());
1549                 startActivityForResult(intent, PREFERENCES_PAGE);
1550                 break;
1551
1552             case R.id.find_menu_id:
1553                 if (null == mFindDialog) {
1554                     mFindDialog = new FindDialog(this);
1555                 }
1556                 mFindDialog.setWebView(getTopWindow());
1557                 mFindDialog.show();
1558                 getTopWindow().setFindIsUp(true);
1559                 mMenuState = EMPTY_MENU;
1560                 break;
1561
1562             case R.id.select_text_id:
1563                 getTopWindow().emulateShiftHeld();
1564                 break;
1565             case R.id.page_info_menu_id:
1566                 showPageInfo(mTabControl.getCurrentTab(), false);
1567                 break;
1568
1569             case R.id.classic_history_menu_id:
1570                 bookmarksOrHistoryPicker(true);
1571                 break;
1572
1573             case R.id.title_bar_share_page_url:
1574             case R.id.share_page_menu_id:
1575                 Tab currentTab = mTabControl.getCurrentTab();
1576                 if (null == currentTab) {
1577                     mCanChord = false;
1578                     return false;
1579                 }
1580                 currentTab.populatePickerData();
1581                 sharePage(this, currentTab.getTitle(),
1582                         currentTab.getUrl(), currentTab.getFavicon(),
1583                         createScreenshot(currentTab.getWebView()));
1584                 break;
1585
1586             case R.id.dump_nav_menu_id:
1587                 getTopWindow().debugDump();
1588                 break;
1589
1590             case R.id.dump_counters_menu_id:
1591                 getTopWindow().dumpV8Counters();
1592                 break;
1593
1594             case R.id.zoom_in_menu_id:
1595                 getTopWindow().zoomIn();
1596                 break;
1597
1598             case R.id.zoom_out_menu_id:
1599                 getTopWindow().zoomOut();
1600                 break;
1601
1602             case R.id.view_downloads_menu_id:
1603                 viewDownloads(null);
1604                 break;
1605
1606             case R.id.window_one_menu_id:
1607             case R.id.window_two_menu_id:
1608             case R.id.window_three_menu_id:
1609             case R.id.window_four_menu_id:
1610             case R.id.window_five_menu_id:
1611             case R.id.window_six_menu_id:
1612             case R.id.window_seven_menu_id:
1613             case R.id.window_eight_menu_id:
1614                 {
1615                     int menuid = item.getItemId();
1616                     for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1617                         if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1618                             Tab desiredTab = mTabControl.getTab(id);
1619                             if (desiredTab != null &&
1620                                     desiredTab != mTabControl.getCurrentTab()) {
1621                                 switchToTab(id);
1622                             }
1623                             break;
1624                         }
1625                     }
1626                 }
1627                 break;
1628
1629             default:
1630                 if (!super.onOptionsItemSelected(item)) {
1631                     return false;
1632                 }
1633                 // Otherwise fall through.
1634         }
1635         mCanChord = false;
1636         return true;
1637     }
1638
1639     public void closeFind() {
1640         mMenuState = R.id.MAIN_MENU;
1641     }
1642
1643     @Override
1644     public boolean onPrepareOptionsMenu(Menu menu) {
1645         // This happens when the user begins to hold down the menu key, so
1646         // allow them to chord to get a shortcut.
1647         mCanChord = true;
1648         // Note: setVisible will decide whether an item is visible; while
1649         // setEnabled() will decide whether an item is enabled, which also means
1650         // whether the matching shortcut key will function.
1651         super.onPrepareOptionsMenu(menu);
1652         switch (mMenuState) {
1653             case EMPTY_MENU:
1654                 if (mCurrentMenuState != mMenuState) {
1655                     menu.setGroupVisible(R.id.MAIN_MENU, false);
1656                     menu.setGroupEnabled(R.id.MAIN_MENU, false);
1657                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1658                 }
1659                 break;
1660             default:
1661                 if (mCurrentMenuState != mMenuState) {
1662                     menu.setGroupVisible(R.id.MAIN_MENU, true);
1663                     menu.setGroupEnabled(R.id.MAIN_MENU, true);
1664                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1665                 }
1666                 final WebView w = getTopWindow();
1667                 boolean canGoBack = false;
1668                 boolean canGoForward = false;
1669                 boolean isHome = false;
1670                 if (w != null) {
1671                     canGoBack = w.canGoBack();
1672                     canGoForward = w.canGoForward();
1673                     isHome = mSettings.getHomePage().equals(w.getUrl());
1674                 }
1675                 final MenuItem back = menu.findItem(R.id.back_menu_id);
1676                 back.setEnabled(canGoBack);
1677
1678                 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1679                 home.setEnabled(!isHome);
1680
1681                 menu.findItem(R.id.forward_menu_id)
1682                         .setEnabled(canGoForward);
1683
1684                 menu.findItem(R.id.new_tab_menu_id).setEnabled(
1685                         mTabControl.canCreateNewTab());
1686
1687                 // decide whether to show the share link option
1688                 PackageManager pm = getPackageManager();
1689                 Intent send = new Intent(Intent.ACTION_SEND);
1690                 send.setType("text/plain");
1691                 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1692                 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1693
1694                 boolean isNavDump = mSettings.isNavDump();
1695                 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1696                 nav.setVisible(isNavDump);
1697                 nav.setEnabled(isNavDump);
1698
1699                 boolean showDebugSettings = mSettings.showDebugSettings();
1700                 final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id);
1701                 counter.setVisible(showDebugSettings);
1702                 counter.setEnabled(showDebugSettings);
1703
1704                 break;
1705         }
1706         mCurrentMenuState = mMenuState;
1707         return true;
1708     }
1709
1710     @Override
1711     public void onCreateContextMenu(ContextMenu menu, View v,
1712             ContextMenuInfo menuInfo) {
1713         if (v instanceof TitleBar) {
1714             return;
1715         }
1716         WebView webview = (WebView) v;
1717         WebView.HitTestResult result = webview.getHitTestResult();
1718         if (result == null) {
1719             return;
1720         }
1721
1722         int type = result.getType();
1723         if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1724             Log.w(LOGTAG,
1725                     "We should not show context menu when nothing is touched");
1726             return;
1727         }
1728         if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1729             // let TextView handles context menu
1730             return;
1731         }
1732
1733         // Note, http://b/issue?id=1106666 is requesting that
1734         // an inflated menu can be used again. This is not available
1735         // yet, so inflate each time (yuk!)
1736         MenuInflater inflater = getMenuInflater();
1737         inflater.inflate(R.menu.browsercontext, menu);
1738
1739         // Show the correct menu group
1740         String extra = result.getExtra();
1741         menu.setGroupVisible(R.id.PHONE_MENU,
1742                 type == WebView.HitTestResult.PHONE_TYPE);
1743         menu.setGroupVisible(R.id.EMAIL_MENU,
1744                 type == WebView.HitTestResult.EMAIL_TYPE);
1745         menu.setGroupVisible(R.id.GEO_MENU,
1746                 type == WebView.HitTestResult.GEO_TYPE);
1747         menu.setGroupVisible(R.id.IMAGE_MENU,
1748                 type == WebView.HitTestResult.IMAGE_TYPE
1749                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1750         menu.setGroupVisible(R.id.ANCHOR_MENU,
1751                 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1752                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1753
1754         // Setup custom handling depending on the type
1755         switch (type) {
1756             case WebView.HitTestResult.PHONE_TYPE:
1757                 menu.setHeaderTitle(Uri.decode(extra));
1758                 menu.findItem(R.id.dial_context_menu_id).setIntent(
1759                         new Intent(Intent.ACTION_VIEW, Uri
1760                                 .parse(WebView.SCHEME_TEL + extra)));
1761                 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1762                 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1763                 addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
1764                 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1765                         addIntent);
1766                 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1767                         new Copy(extra));
1768                 break;
1769
1770             case WebView.HitTestResult.EMAIL_TYPE:
1771                 menu.setHeaderTitle(extra);
1772                 menu.findItem(R.id.email_context_menu_id).setIntent(
1773                         new Intent(Intent.ACTION_VIEW, Uri
1774                                 .parse(WebView.SCHEME_MAILTO + extra)));
1775                 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1776                         new Copy(extra));
1777                 break;
1778
1779             case WebView.HitTestResult.GEO_TYPE:
1780                 menu.setHeaderTitle(extra);
1781                 menu.findItem(R.id.map_context_menu_id).setIntent(
1782                         new Intent(Intent.ACTION_VIEW, Uri
1783                                 .parse(WebView.SCHEME_GEO
1784                                         + URLEncoder.encode(extra))));
1785                 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1786                         new Copy(extra));
1787                 break;
1788
1789             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1790             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1791                 TextView titleView = (TextView) LayoutInflater.from(this)
1792                         .inflate(android.R.layout.browser_link_context_header,
1793                         null);
1794                 titleView.setText(extra);
1795                 menu.setHeaderView(titleView);
1796                 // decide whether to show the open link in new tab option
1797                 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1798                         mTabControl.canCreateNewTab());
1799                 menu.findItem(R.id.bookmark_context_menu_id).setVisible(
1800                         Bookmarks.urlHasAcceptableScheme(extra));
1801                 PackageManager pm = getPackageManager();
1802                 Intent send = new Intent(Intent.ACTION_SEND);
1803                 send.setType("text/plain");
1804                 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1805                 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1806                 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1807                     break;
1808                 }
1809                 // otherwise fall through to handle image part
1810             case WebView.HitTestResult.IMAGE_TYPE:
1811                 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1812                     menu.setHeaderTitle(extra);
1813                 }
1814                 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1815                         new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1816                 menu.findItem(R.id.download_context_menu_id).
1817                         setOnMenuItemClickListener(new Download(extra));
1818                 menu.findItem(R.id.set_wallpaper_context_menu_id).
1819                         setOnMenuItemClickListener(new SetAsWallpaper(extra));
1820                 break;
1821
1822             default:
1823                 Log.w(LOGTAG, "We should not get here.");
1824                 break;
1825         }
1826         hideFakeTitleBar();
1827     }
1828
1829     // Attach the given tab to the content view.
1830     // this should only be called for the current tab.
1831     private void attachTabToContentView(Tab t) {
1832         // Attach the container that contains the main WebView and any other UI
1833         // associated with the tab.
1834         t.attachTabToContentView(mContentView);
1835
1836         if (mShouldShowErrorConsole) {
1837             ErrorConsoleView errorConsole = t.getErrorConsole(true);
1838             if (errorConsole.numberOfErrors() == 0) {
1839                 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1840             } else {
1841                 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1842             }
1843
1844             mErrorConsoleContainer.addView(errorConsole,
1845                     new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1846                                                   ViewGroup.LayoutParams.WRAP_CONTENT));
1847         }
1848
1849         WebView view = t.getWebView();
1850         view.setEmbeddedTitleBar(mTitleBar);
1851         if (t.isInVoiceSearchMode()) {
1852             showVoiceTitleBar(t.getVoiceDisplayTitle());
1853         } else {
1854             revertVoiceTitleBar();
1855         }
1856         // Request focus on the top window.
1857         t.getTopWindow().requestFocus();
1858     }
1859
1860     // Attach a sub window to the main WebView of the given tab.
1861     void attachSubWindow(Tab t) {
1862         t.attachSubWindow(mContentView);
1863         getTopWindow().requestFocus();
1864     }
1865
1866     // Remove the given tab from the content view.
1867     private void removeTabFromContentView(Tab t) {
1868         // Remove the container that contains the main WebView.
1869         t.removeTabFromContentView(mContentView);
1870
1871         ErrorConsoleView errorConsole = t.getErrorConsole(false);
1872         if (errorConsole != null) {
1873             mErrorConsoleContainer.removeView(errorConsole);
1874         }
1875
1876         WebView view = t.getWebView();
1877         if (view != null) {
1878             view.setEmbeddedTitleBar(null);
1879         }
1880     }
1881
1882     // Remove the sub window if it exists. Also called by TabControl when the
1883     // user clicks the 'X' to dismiss a sub window.
1884     /* package */ void dismissSubWindow(Tab t) {
1885         t.removeSubWindow(mContentView);
1886         // dismiss the subwindow. This will destroy the WebView.
1887         t.dismissSubWindow();
1888         getTopWindow().requestFocus();
1889     }
1890
1891     // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
1892     // that accepts url as string.
1893     private Tab openTabAndShow(String url, boolean closeOnExit, String appId) {
1894         return openTabAndShow(new UrlData(url), closeOnExit, appId);
1895     }
1896
1897     // This method does a ton of stuff. It will attempt to create a new tab
1898     // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
1899     // url isn't null, it will load the given url.
1900     /* package */Tab openTabAndShow(UrlData urlData, boolean closeOnExit,
1901             String appId) {
1902         final Tab currentTab = mTabControl.getCurrentTab();
1903         if (mTabControl.canCreateNewTab()) {
1904             final Tab tab = mTabControl.createNewTab(closeOnExit, appId,
1905                     urlData.mUrl);
1906             WebView webview = tab.getWebView();
1907             // If the last tab was removed from the active tabs page, currentTab
1908             // will be null.
1909             if (currentTab != null) {
1910                 removeTabFromContentView(currentTab);
1911             }
1912             // We must set the new tab as the current tab to reflect the old
1913             // animation behavior.
1914             mTabControl.setCurrentTab(tab);
1915             attachTabToContentView(tab);
1916             if (!urlData.isEmpty()) {
1917                 loadUrlDataIn(tab, urlData);
1918             }
1919             return tab;
1920         } else {
1921             // Get rid of the subwindow if it exists
1922             dismissSubWindow(currentTab);
1923             if (!urlData.isEmpty()) {
1924                 // Load the given url.
1925                 loadUrlDataIn(currentTab, urlData);
1926             }
1927             return currentTab;
1928         }
1929     }
1930
1931     private Tab openTab(String url) {
1932         if (mSettings.openInBackground()) {
1933             Tab t = mTabControl.createNewTab();
1934             if (t != null) {
1935                 WebView view = t.getWebView();
1936                 loadUrl(view, url);
1937             }
1938             return t;
1939         } else {
1940             return openTabAndShow(url, false, null);
1941         }
1942     }
1943
1944     private class Copy implements OnMenuItemClickListener {
1945         private CharSequence mText;
1946
1947         public boolean onMenuItemClick(MenuItem item) {
1948             copy(mText);
1949             return true;
1950         }
1951
1952         public Copy(CharSequence toCopy) {
1953             mText = toCopy;
1954         }
1955     }
1956
1957     private class Download implements OnMenuItemClickListener {
1958         private String mText;
1959
1960         public boolean onMenuItemClick(MenuItem item) {
1961             onDownloadStartNoStream(mText, null, null, null, -1);
1962             return true;
1963         }
1964
1965         public Download(String toDownload) {
1966             mText = toDownload;
1967         }
1968     }
1969
1970     private class SetAsWallpaper extends Thread implements
1971             OnMenuItemClickListener, DialogInterface.OnCancelListener {
1972         private URL mUrl;
1973         private ProgressDialog mWallpaperProgress;
1974         private boolean mCanceled = false;
1975
1976         public SetAsWallpaper(String url) {
1977             try {
1978                 mUrl = new URL(url);
1979             } catch (MalformedURLException e) {
1980                 mUrl = null;
1981             }
1982         }
1983
1984         public void onCancel(DialogInterface dialog) {
1985             mCanceled = true;
1986         }
1987
1988         public boolean onMenuItemClick(MenuItem item) {
1989             if (mUrl != null) {
1990                 // The user may have tried to set a image with a large file size as their
1991                 // background so it may take a few moments to perform the operation. Display
1992                 // a progress spinner while it is working.
1993                 mWallpaperProgress = new ProgressDialog(BrowserActivity.this);
1994                 mWallpaperProgress.setIndeterminate(true);
1995                 mWallpaperProgress.setMessage(getText(R.string.progress_dialog_setting_wallpaper));
1996                 mWallpaperProgress.setCancelable(true);
1997                 mWallpaperProgress.setOnCancelListener(this);
1998                 mWallpaperProgress.show();
1999                 start();
2000             }
2001             return true;
2002         }
2003
2004         public void run() {
2005             Drawable oldWallpaper = BrowserActivity.this.getWallpaper();
2006             try {
2007                 // TODO: This will cause the resource to be downloaded again, when we
2008                 // should in most cases be able to grab it from the cache. To fix this
2009                 // we should query WebCore to see if we can access a cached version and
2010                 // instead open an input stream on that. This pattern could also be used
2011                 // in the download manager where the same problem exists.
2012                 InputStream inputstream = mUrl.openStream();
2013                 if (inputstream != null) {
2014                     setWallpaper(inputstream);
2015                 }
2016             } catch (IOException e) {
2017                 Log.e(LOGTAG, "Unable to set new wallpaper");
2018                 // Act as though the user canceled the operation so we try to
2019                 // restore the old wallpaper.
2020                 mCanceled = true;
2021             }
2022
2023             if (mCanceled) {
2024                 // Restore the old wallpaper if the user cancelled whilst we were setting
2025                 // the new wallpaper.
2026                 int width = oldWallpaper.getIntrinsicWidth();
2027                 int height = oldWallpaper.getIntrinsicHeight();
2028                 Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
2029                 Canvas canvas = new Canvas(bm);
2030                 oldWallpaper.setBounds(0, 0, width, height);
2031                 oldWallpaper.draw(canvas);
2032                 try {
2033                     setWallpaper(bm);
2034                 } catch (IOException e) {
2035                     Log.e(LOGTAG, "Unable to restore old wallpaper.");
2036                 }
2037                 mCanceled = false;
2038             }
2039
2040             if (mWallpaperProgress.isShowing()) {
2041                 mWallpaperProgress.dismiss();
2042             }
2043         }
2044     }
2045
2046     private void copy(CharSequence text) {
2047         try {
2048             IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
2049             if (clip != null) {
2050                 clip.setClipboardText(text);
2051             }
2052         } catch (android.os.RemoteException e) {
2053             Log.e(LOGTAG, "Copy failed", e);
2054         }
2055     }
2056
2057     /**
2058      * Resets the browser title-view to whatever it must be
2059      * (for example, if we had a loading error)
2060      * When we have a new page, we call resetTitle, when we
2061      * have to reset the titlebar to whatever it used to be
2062      * (for example, if the user chose to stop loading), we
2063      * call resetTitleAndRevertLockIcon.
2064      */
2065     /* package */ void resetTitleAndRevertLockIcon() {
2066         mTabControl.getCurrentTab().revertLockIcon();
2067         updateLockIconToLatest();
2068         resetTitleIconAndProgress();
2069     }
2070
2071     /**
2072      * Reset the title, favicon, and progress.
2073      */
2074     private void resetTitleIconAndProgress() {
2075         WebView current = mTabControl.getCurrentWebView();
2076         if (current == null) {
2077             return;
2078         }
2079         resetTitleAndIcon(current);
2080         int progress = current.getProgress();
2081         current.getWebChromeClient().onProgressChanged(current, progress);
2082     }
2083
2084     // Reset the title and the icon based on the given item.
2085     private void resetTitleAndIcon(WebView view) {
2086         WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
2087         if (item != null) {
2088             setUrlTitle(item.getUrl(), item.getTitle());
2089             setFavicon(item.getFavicon());
2090         } else {
2091             setUrlTitle(null, null);
2092             setFavicon(null);
2093         }
2094     }
2095
2096     /**
2097      * Sets a title composed of the URL and the title string.
2098      * @param url The URL of the site being loaded.
2099      * @param title The title of the site being loaded.
2100      */
2101     void setUrlTitle(String url, String title) {
2102         mUrl = url;
2103         mTitle = title;
2104
2105         // If we are in voice search mode, the title has already been set.
2106         if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return;
2107         mTitleBar.setDisplayTitle(url);
2108         mFakeTitleBar.setDisplayTitle(url);
2109     }
2110
2111     /**
2112      * @param url The URL to build a title version of the URL from.
2113      * @return The title version of the URL or null if fails.
2114      * The title version of the URL can be either the URL hostname,
2115      * or the hostname with an "https://" prefix (for secure URLs),
2116      * or an empty string if, for example, the URL in question is a
2117      * file:// URL with no hostname.
2118      */
2119     /* package */ static String buildTitleUrl(String url) {
2120         String titleUrl = null;
2121
2122         if (url != null) {
2123             try {
2124                 // parse the url string
2125                 URL urlObj = new URL(url);
2126                 if (urlObj != null) {
2127                     titleUrl = "";
2128
2129                     String protocol = urlObj.getProtocol();
2130                     String host = urlObj.getHost();
2131
2132                     if (host != null && 0 < host.length()) {
2133                         titleUrl = host;
2134                         if (protocol != null) {
2135                             // if a secure site, add an "https://" prefix!
2136                             if (protocol.equalsIgnoreCase("https")) {
2137                                 titleUrl = protocol + "://" + host;
2138                             }
2139                         }
2140                     }
2141                 }
2142             } catch (MalformedURLException e) {}
2143         }
2144
2145         return titleUrl;
2146     }
2147
2148     // Set the favicon in the title bar.
2149     void setFavicon(Bitmap icon) {
2150         mTitleBar.setFavicon(icon);
2151         mFakeTitleBar.setFavicon(icon);
2152     }
2153
2154     /**
2155      * Close the tab, remove its associated title bar, and adjust mTabControl's
2156      * current tab to a valid value.
2157      */
2158     /* package */ void closeTab(Tab t) {
2159         int currentIndex = mTabControl.getCurrentIndex();
2160         int removeIndex = mTabControl.getTabIndex(t);
2161         mTabControl.removeTab(t);
2162         if (currentIndex >= removeIndex && currentIndex != 0) {
2163             currentIndex--;
2164         }
2165         mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
2166         resetTitleIconAndProgress();
2167     }
2168
2169     /* package */ void goBackOnePageOrQuit() {
2170         Tab current = mTabControl.getCurrentTab();
2171         if (current == null) {
2172             /*
2173              * Instead of finishing the activity, simply push this to the back
2174              * of the stack and let ActivityManager to choose the foreground
2175              * activity. As BrowserActivity is singleTask, it will be always the
2176              * root of the task. So we can use either true or false for
2177              * moveTaskToBack().
2178              */
2179             moveTaskToBack(true);
2180             return;
2181         }
2182         WebView w = current.getWebView();
2183         if (w.canGoBack()) {
2184             w.goBack();
2185         } else {
2186             // Check to see if we are closing a window that was created by
2187             // another window. If so, we switch back to that window.
2188             Tab parent = current.getParentTab();
2189             if (parent != null) {
2190                 switchToTab(mTabControl.getTabIndex(parent));
2191                 // Now we close the other tab
2192                 closeTab(current);
2193             } else {
2194                 if (current.closeOnExit()) {
2195                     // force the tab's inLoad() to be false as we are going to
2196                     // either finish the activity or remove the tab. This will
2197                     // ensure pauseWebViewTimers() taking action.
2198                     mTabControl.getCurrentTab().clearInLoad();
2199                     if (mTabControl.getTabCount() == 1) {
2200                         finish();
2201                         return;
2202                     }
2203                     // call pauseWebViewTimers() now, we won't be able to call
2204                     // it in onPause() as the WebView won't be valid.
2205                     // Temporarily change mActivityInPause to be true as
2206                     // pauseWebViewTimers() will do nothing if mActivityInPause
2207                     // is false.
2208                     boolean savedState = mActivityInPause;
2209                     if (savedState) {
2210                         Log.e(LOGTAG, "BrowserActivity is already paused "
2211                                 + "while handing goBackOnePageOrQuit.");
2212                     }
2213                     mActivityInPause = true;
2214                     pauseWebViewTimers();
2215                     mActivityInPause = savedState;
2216                     removeTabFromContentView(current);
2217                     mTabControl.removeTab(current);
2218                 }
2219                 /*
2220                  * Instead of finishing the activity, simply push this to the back
2221                  * of the stack and let ActivityManager to choose the foreground
2222                  * activity. As BrowserActivity is singleTask, it will be always the
2223                  * root of the task. So we can use either true or false for
2224                  * moveTaskToBack().
2225                  */
2226                 moveTaskToBack(true);
2227             }
2228         }
2229     }
2230
2231     boolean isMenuDown() {
2232         return mMenuIsDown;
2233     }
2234
2235     @Override
2236     public boolean onKeyDown(int keyCode, KeyEvent event) {
2237         // Even if MENU is already held down, we need to call to super to open
2238         // the IME on long press.
2239         if (KeyEvent.KEYCODE_MENU == keyCode) {
2240             mMenuIsDown = true;
2241             return super.onKeyDown(keyCode, event);
2242         }
2243         // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2244         // still down, we don't want to trigger the search. Pretend to consume
2245         // the key and do nothing.
2246         if (mMenuIsDown) return true;
2247
2248         switch(keyCode) {
2249             case KeyEvent.KEYCODE_SPACE:
2250                 // WebView/WebTextView handle the keys in the KeyDown. As
2251                 // the Activity's shortcut keys are only handled when WebView
2252                 // doesn't, have to do it in onKeyDown instead of onKeyUp.
2253                 if (event.isShiftPressed()) {
2254                     getTopWindow().pageUp(false);
2255                 } else {
2256                     getTopWindow().pageDown(false);
2257                 }
2258                 return true;
2259             case KeyEvent.KEYCODE_BACK:
2260                 if (event.getRepeatCount() == 0) {
2261                     event.startTracking();
2262                     return true;
2263                 } else if (mCustomView == null && mActiveTabsPage == null
2264                         && event.isLongPress()) {
2265                     bookmarksOrHistoryPicker(true);
2266                     return true;
2267                 }
2268                 break;
2269         }
2270         return super.onKeyDown(keyCode, event);
2271     }
2272
2273     @Override
2274     public boolean onKeyUp(int keyCode, KeyEvent event) {
2275         switch(keyCode) {
2276             case KeyEvent.KEYCODE_MENU:
2277                 mMenuIsDown = false;
2278                 break;
2279             case KeyEvent.KEYCODE_BACK:
2280                 if (event.isTracking() && !event.isCanceled()) {
2281                     if (mCustomView != null) {
2282                         // if a custom view is showing, hide it
2283                         mTabControl.getCurrentWebView().getWebChromeClient()
2284                                 .onHideCustomView();
2285                     } else if (mActiveTabsPage != null) {
2286                         // if tab page is showing, hide it
2287                         removeActiveTabPage(true);
2288                     } else {
2289                         WebView subwindow = mTabControl.getCurrentSubWindow();
2290                         if (subwindow != null) {
2291                             if (subwindow.canGoBack()) {
2292                                 subwindow.goBack();
2293                             } else {
2294                                 dismissSubWindow(mTabControl.getCurrentTab());
2295                             }
2296                         } else {
2297                             goBackOnePageOrQuit();
2298                         }
2299                     }
2300                     return true;
2301                 }
2302                 break;
2303         }
2304         return super.onKeyUp(keyCode, event);
2305     }
2306
2307     /* package */ void stopLoading() {
2308         mDidStopLoad = true;
2309         resetTitleAndRevertLockIcon();
2310         WebView w = getTopWindow();
2311         w.stopLoading();
2312         // FIXME: before refactor, it is using mWebViewClient. So I keep the
2313         // same logic here. But for subwindow case, should we call into the main
2314         // WebView's onPageFinished as we never call its onPageStarted and if
2315         // the page finishes itself, we don't call onPageFinished.
2316         mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w,
2317                 w.getUrl());
2318
2319         cancelStopToast();
2320         mStopToast = Toast
2321                 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2322         mStopToast.show();
2323     }
2324
2325     boolean didUserStopLoading() {
2326         return mDidStopLoad;
2327     }
2328
2329     private void cancelStopToast() {
2330         if (mStopToast != null) {
2331             mStopToast.cancel();
2332             mStopToast = null;
2333         }
2334     }
2335
2336     // called by a UI or non-UI thread to post the message
2337     public void postMessage(int what, int arg1, int arg2, Object obj,
2338             long delayMillis) {
2339         mHandler.sendMessageDelayed(mHandler.obtainMessage(what, arg1, arg2,
2340                 obj), delayMillis);
2341     }
2342
2343     // called by a UI or non-UI thread to remove the message
2344     void removeMessages(int what, Object object) {
2345         mHandler.removeMessages(what, object);
2346     }
2347
2348     // public message ids
2349     public final static int LOAD_URL                = 1001;
2350     public final static int STOP_LOAD               = 1002;
2351
2352     // Message Ids
2353     private static final int FOCUS_NODE_HREF         = 102;
2354     private static final int CANCEL_CREDS_REQUEST    = 103;
2355     private static final int RELEASE_WAKELOCK        = 107;
2356
2357     static final int UPDATE_BOOKMARK_THUMBNAIL       = 108;
2358
2359     // Private handler for handling javascript and saving passwords
2360     private Handler mHandler = new Handler() {
2361
2362         public void handleMessage(Message msg) {
2363             switch (msg.what) {
2364                 case FOCUS_NODE_HREF:
2365                 {
2366                     String url = (String) msg.getData().get("url");
2367                     String title = (String) msg.getData().get("title");
2368                     if (url == null || url.length() == 0) {
2369                         break;
2370                     }
2371                     HashMap focusNodeMap = (HashMap) msg.obj;
2372                     WebView view = (WebView) focusNodeMap.get("webview");
2373                     // Only apply the action if the top window did not change.
2374                     if (getTopWindow() != view) {
2375                         break;
2376                     }
2377                     switch (msg.arg1) {
2378                         case R.id.open_context_menu_id:
2379                         case R.id.view_image_context_menu_id:
2380                             loadUrlFromContext(getTopWindow(), url);
2381                             break;
2382                         case R.id.open_newtab_context_menu_id:
2383                             final Tab parent = mTabControl.getCurrentTab();
2384                             final Tab newTab = openTab(url);
2385                             if (newTab != parent) {
2386                                 parent.addChildTab(newTab);
2387                             }
2388                             break;
2389                         case R.id.bookmark_context_menu_id:
2390                             Intent intent = new Intent(BrowserActivity.this,
2391                                     AddBookmarkPage.class);
2392                             intent.putExtra("url", url);
2393                             intent.putExtra("title", title);
2394                             startActivity(intent);
2395                             break;
2396                         case R.id.share_link_context_menu_id:
2397                             // See if this site has been visited before
2398                             StringBuilder sb = new StringBuilder(
2399                                     Browser.BookmarkColumns.URL + " = ");
2400                             DatabaseUtils.appendEscapedSQLString(sb, url);
2401                             Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
2402                                     Browser.HISTORY_PROJECTION,
2403                                     sb.toString(),
2404                                     null,
2405                                     null);
2406                             if (c.moveToFirst()) {
2407                                 // The site has been visited before, so grab the
2408                                 // info from the database.
2409                                 Bitmap favicon = null;
2410                                 Bitmap thumbnail = null;
2411                                 String linkTitle = c.getString(Browser.
2412                                         HISTORY_PROJECTION_TITLE_INDEX);
2413                                 byte[] data = c.getBlob(Browser.
2414                                         HISTORY_PROJECTION_FAVICON_INDEX);
2415                                 if (data != null) {
2416                                     favicon = BitmapFactory.decodeByteArray(
2417                                             data, 0, data.length);
2418                                 }
2419                                 data = c.getBlob(Browser.
2420                                         HISTORY_PROJECTION_THUMBNAIL_INDEX);
2421                                 if (data != null) {
2422                                     thumbnail = BitmapFactory.decodeByteArray(
2423                                             data, 0, data.length);
2424                                 }
2425                                 sharePage(BrowserActivity.this,
2426                                         linkTitle, url, favicon, thumbnail);
2427                             } else {
2428                                 Browser.sendString(BrowserActivity.this, url,
2429                                         getString(
2430                                         R.string.choosertitle_sharevia));
2431                             }
2432                             break;
2433                         case R.id.copy_link_context_menu_id:
2434                             copy(url);
2435                             break;
2436                         case R.id.save_link_context_menu_id:
2437                         case R.id.download_context_menu_id:
2438                             onDownloadStartNoStream(url, null, null, null, -1);
2439                             break;
2440                     }
2441                     break;
2442                 }
2443
2444                 case LOAD_URL:
2445                     loadUrlFromContext(getTopWindow(), (String) msg.obj);
2446                     break;
2447
2448                 case STOP_LOAD:
2449                     stopLoading();
2450                     break;
2451
2452                 case CANCEL_CREDS_REQUEST:
2453                     resumeAfterCredentials();
2454                     break;
2455
2456                 case RELEASE_WAKELOCK:
2457                     if (mWakeLock.isHeld()) {
2458                         mWakeLock.release();
2459                         // if we reach here, Browser should be still in the
2460                         // background loading after WAKELOCK_TIMEOUT (5-min).
2461                         // To avoid burning the battery, stop loading.
2462                         mTabControl.stopAllLoading();
2463                     }
2464                     break;
2465
2466                 case UPDATE_BOOKMARK_THUMBNAIL:
2467                     WebView view = (WebView) msg.obj;
2468                     if (view != null) {
2469                         updateScreenshot(view);
2470                     }
2471                     break;
2472             }
2473         }
2474     };
2475
2476     /**
2477      * Share a page, providing the title, url, favicon, and a screenshot.  Uses
2478      * an {@link Intent} to launch the Activity chooser.
2479      * @param c Context used to launch a new Activity.
2480      * @param title Title of the page.  Stored in the Intent with
2481      *          {@link Intent#EXTRA_SUBJECT}
2482      * @param url URL of the page.  Stored in the Intent with
2483      *          {@link Intent#EXTRA_TEXT}
2484      * @param favicon Bitmap of the favicon for the page.  Stored in the Intent
2485      *          with {@link Browser#EXTRA_SHARE_FAVICON}
2486      * @param screenshot Bitmap of a screenshot of the page.  Stored in the
2487      *          Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT}
2488      */
2489     public static final void sharePage(Context c, String title, String url,
2490             Bitmap favicon, Bitmap screenshot) {
2491         Intent send = new Intent(Intent.ACTION_SEND);
2492         send.setType("text/plain");
2493         send.putExtra(Intent.EXTRA_TEXT, url);
2494         send.putExtra(Intent.EXTRA_SUBJECT, title);
2495         send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
2496         send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
2497         try {
2498             c.startActivity(Intent.createChooser(send, c.getString(
2499                     R.string.choosertitle_sharevia)));
2500         } catch(android.content.ActivityNotFoundException ex) {
2501             // if no app handles it, do nothing
2502         }
2503     }
2504
2505     private void updateScreenshot(WebView view) {
2506         // If this is a bookmarked site, add a screenshot to the database.
2507         // FIXME: When should we update?  Every time?
2508         // FIXME: Would like to make sure there is actually something to
2509         // draw, but the API for that (WebViewCore.pictureReady()) is not
2510         // currently accessible here.
2511
2512         ContentResolver cr = getContentResolver();
2513         final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
2514                 cr, view.getOriginalUrl(), view.getUrl(), true);
2515         if (c != null) {
2516             boolean succeed = c.moveToFirst();
2517             ContentValues values = null;
2518             while (succeed) {
2519                 if (values == null) {
2520                     final ByteArrayOutputStream os
2521                             = new ByteArrayOutputStream();
2522                     Bitmap bm = createScreenshot(view);
2523                     if (bm == null) {
2524                         c.close();
2525                         return;
2526                     }
2527                     bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2528                     values = new ContentValues();
2529                     values.put(Browser.BookmarkColumns.THUMBNAIL,
2530                             os.toByteArray());
2531                 }
2532                 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2533                         c.getInt(0)), values, null, null);
2534                 succeed = c.moveToNext();
2535             }
2536             c.close();
2537         }
2538     }
2539
2540     /**
2541      * Values for the size of the thumbnail created when taking a screenshot.
2542      * Lazily initialized.  Instead of using these directly, use
2543      * getDesiredThumbnailWidth() or getDesiredThumbnailHeight().
2544      */
2545     private static int THUMBNAIL_WIDTH = 0;
2546     private static int THUMBNAIL_HEIGHT = 0;
2547
2548     /**
2549      * Return the desired width for thumbnail screenshots, which are stored in
2550      * the database, and used on the bookmarks screen.
2551      * @param context Context for finding out the density of the screen.
2552      * @return int desired width for thumbnail screenshot.
2553      */
2554     /* package */ static int getDesiredThumbnailWidth(Context context) {
2555         if (THUMBNAIL_WIDTH == 0) {
2556             float density = context.getResources().getDisplayMetrics().density;
2557             THUMBNAIL_WIDTH = (int) (90 * density);
2558             THUMBNAIL_HEIGHT = (int) (80 * density);
2559         }
2560         return THUMBNAIL_WIDTH;
2561     }
2562
2563     /**
2564      * Return the desired height for thumbnail screenshots, which are stored in
2565      * the database, and used on the bookmarks screen.
2566      * @param context Context for finding out the density of the screen.
2567      * @return int desired height for thumbnail screenshot.
2568      */
2569     /* package */ static int getDesiredThumbnailHeight(Context context) {
2570         // To ensure that they are both initialized.
2571         getDesiredThumbnailWidth(context);
2572         return THUMBNAIL_HEIGHT;
2573     }
2574
2575     private Bitmap createScreenshot(WebView view) {
2576         Picture thumbnail = view.capturePicture();
2577         if (thumbnail == null) {
2578             return null;
2579         }
2580         Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this),
2581                 getDesiredThumbnailHeight(this), Bitmap.Config.ARGB_4444);
2582         Canvas canvas = new Canvas(bm);
2583         // May need to tweak these values to determine what is the
2584         // best scale factor
2585         int thumbnailWidth = thumbnail.getWidth();
2586         int thumbnailHeight = thumbnail.getHeight();
2587         float scaleFactorX = 1.0f;
2588         float scaleFactorY = 1.0f;
2589         if (thumbnailWidth > 0) {
2590             scaleFactorX = (float) getDesiredThumbnailWidth(this) /
2591                     (float)thumbnailWidth;
2592         } else {
2593             return null;
2594         }
2595
2596         if (view.getWidth() > view.getHeight() &&
2597                 thumbnailHeight < view.getHeight() && thumbnailHeight > 0) {
2598             // If the device is in landscape and the page is shorter
2599             // than the height of the view, stretch the thumbnail to fill the
2600             // space.
2601             scaleFactorY = (float) getDesiredThumbnailHeight(this) /
2602                     (float)thumbnailHeight;
2603         } else {
2604             // In the portrait case, this looks nice.
2605             scaleFactorY = scaleFactorX;
2606         }
2607
2608         canvas.scale(scaleFactorX, scaleFactorY);
2609
2610         thumbnail.draw(canvas);
2611         return bm;
2612     }
2613
2614     // -------------------------------------------------------------------------
2615     // Helper function for WebViewClient.
2616     //-------------------------------------------------------------------------
2617
2618     // Use in overrideUrlLoading
2619     /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2620     /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2621     /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2622     /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2623
2624     // Keep this initial progress in sync with initialProgressValue (* 100)
2625     // in ProgressTracker.cpp
2626     private final static int INITIAL_PROGRESS = 10;
2627
2628     void onPageStarted(WebView view, String url, Bitmap favicon) {
2629         // when BrowserActivity just starts, onPageStarted may be called before
2630         // onResume as it is triggered from onCreate. Call resumeWebViewTimers
2631         // to start the timer. As we won't switch tabs while an activity is in
2632         // pause state, we can ensure calling resume and pause in pair.
2633         if (mActivityInPause) resumeWebViewTimers();
2634
2635         resetLockIcon(url);
2636         setUrlTitle(url, null);
2637         setFavicon(favicon);
2638         // Show some progress so that the user knows the page is beginning to
2639         // load
2640         onProgressChanged(view, INITIAL_PROGRESS);
2641         mDidStopLoad = false;
2642         if (!mIsNetworkUp) createAndShowNetworkDialog();
2643
2644         if (mSettings.isTracing()) {
2645             String host;
2646             try {
2647                 WebAddress uri = new WebAddress(url);
2648                 host = uri.mHost;
2649             } catch (android.net.ParseException ex) {
2650                 host = "browser";
2651             }
2652             host = host.replace('.', '_');
2653             host += ".trace";
2654             mInTrace = true;
2655             Debug.startMethodTracing(host, 20 * 1024 * 1024);
2656         }
2657
2658         // Performance probe
2659         if (false) {
2660             mStart = SystemClock.uptimeMillis();
2661             mProcessStart = Process.getElapsedCpuTime();
2662             long[] sysCpu = new long[7];
2663             if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2664                     sysCpu, null)) {
2665                 mUserStart = sysCpu[0] + sysCpu[1];
2666                 mSystemStart = sysCpu[2];
2667                 mIdleStart = sysCpu[3];
2668                 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2669             }
2670             mUiStart = SystemClock.currentThreadTimeMillis();
2671         }
2672     }
2673
2674     void onPageFinished(WebView view, String url) {
2675         // Reset the title and icon in case we stopped a provisional load.
2676         resetTitleAndIcon(view);
2677         // Update the lock icon image only once we are done loading
2678         updateLockIconToLatest();
2679         // pause the WebView timer and release the wake lock if it is finished
2680         // while BrowserActivity is in pause state.
2681         if (mActivityInPause && pauseWebViewTimers()) {
2682             if (mWakeLock.isHeld()) {
2683                 mHandler.removeMessages(RELEASE_WAKELOCK);
2684                 mWakeLock.release();
2685             }
2686         }
2687
2688         // Performance probe
2689         if (false) {
2690             long[] sysCpu = new long[7];
2691             if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2692                     sysCpu, null)) {
2693                 String uiInfo = "UI thread used "
2694                         + (SystemClock.currentThreadTimeMillis() - mUiStart)
2695                         + " ms";
2696                 if (LOGD_ENABLED) {
2697                     Log.d(LOGTAG, uiInfo);
2698                 }
2699                 //The string that gets written to the log
2700                 String performanceString = "It took total "
2701                         + (SystemClock.uptimeMillis() - mStart)
2702                         + " ms clock time to load the page."
2703                         + "\nbrowser process used "
2704                         + (Process.getElapsedCpuTime() - mProcessStart)
2705                         + " ms, user processes used "
2706                         + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2707                         + " ms, kernel used "
2708                         + (sysCpu[2] - mSystemStart) * 10
2709                         + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2710                         + " ms and irq took "
2711                         + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2712                         * 10 + " ms, " + uiInfo;
2713                 if (LOGD_ENABLED) {
2714                     Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2715                 }
2716                 if (url != null) {
2717                     // strip the url to maintain consistency
2718                     String newUrl = new String(url);
2719                     if (newUrl.startsWith("http://www.")) {
2720                         newUrl = newUrl.substring(11);
2721                     } else if (newUrl.startsWith("http://")) {
2722                         newUrl = newUrl.substring(7);
2723                     } else if (newUrl.startsWith("https://www.")) {
2724                         newUrl = newUrl.substring(12);
2725                     } else if (newUrl.startsWith("https://")) {
2726                         newUrl = newUrl.substring(8);
2727                     }
2728                     if (LOGD_ENABLED) {
2729                         Log.d(LOGTAG, newUrl + " loaded");
2730                     }
2731                 }
2732             }
2733          }
2734
2735         if (mInTrace) {
2736             mInTrace = false;
2737             Debug.stopMethodTracing();
2738         }
2739     }
2740
2741     boolean shouldOverrideUrlLoading(WebView view, String url) {
2742         if (url.startsWith(SCHEME_WTAI)) {
2743             // wtai://wp/mc;number
2744             // number=string(phone-number)
2745             if (url.startsWith(SCHEME_WTAI_MC)) {
2746                 Intent intent = new Intent(Intent.ACTION_VIEW,
2747                         Uri.parse(WebView.SCHEME_TEL +
2748                         url.substring(SCHEME_WTAI_MC.length())));
2749                 startActivity(intent);
2750                 return true;
2751             }
2752             // wtai://wp/sd;dtmf
2753             // dtmf=string(dialstring)
2754             if (url.startsWith(SCHEME_WTAI_SD)) {
2755                 // TODO: only send when there is active voice connection
2756                 return false;
2757             }
2758             // wtai://wp/ap;number;name
2759             // number=string(phone-number)
2760             // name=string
2761             if (url.startsWith(SCHEME_WTAI_AP)) {
2762                 // TODO
2763                 return false;
2764             }
2765         }
2766
2767         // The "about:" schemes are internal to the browser; don't want these to
2768         // be dispatched to other apps.
2769         if (url.startsWith("about:")) {
2770             return false;
2771         }
2772
2773         Intent intent;
2774         // perform generic parsing of the URI to turn it into an Intent.
2775         try {
2776             intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2777         } catch (URISyntaxException ex) {
2778             Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
2779             return false;
2780         }
2781
2782         // check whether the intent can be resolved. If not, we will see
2783         // whether we can download it from the Market.
2784         if (getPackageManager().resolveActivity(intent, 0) == null) {
2785             String packagename = intent.getPackage();
2786             if (packagename != null) {
2787                 intent = new Intent(Intent.ACTION_VIEW, Uri
2788                         .parse("market://search?q=pname:" + packagename));
2789                 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2790                 startActivity(intent);
2791                 return true;
2792             } else {
2793                 return false;
2794             }
2795         }
2796
2797         // sanitize the Intent, ensuring web pages can not bypass browser
2798         // security (only access to BROWSABLE activities).
2799         intent.addCategory(Intent.CATEGORY_BROWSABLE);
2800         intent.setComponent(null);
2801         try {
2802             if (startActivityIfNeeded(intent, -1)) {
2803                 return true;
2804             }
2805         } catch (ActivityNotFoundException ex) {
2806             // ignore the error. If no application can handle the URL,
2807             // eg about:blank, assume the browser can handle it.
2808         }
2809
2810         if (mMenuIsDown) {
2811             openTab(url);
2812             closeOptionsMenu();
2813             return true;
2814         }
2815         return false;
2816     }
2817
2818     // -------------------------------------------------------------------------
2819     // Helper function for WebChromeClient
2820     // -------------------------------------------------------------------------
2821
2822     void onProgressChanged(WebView view, int newProgress) {
2823         mTitleBar.setProgress(newProgress);
2824         mFakeTitleBar.setProgress(newProgress);
2825
2826         if (newProgress == 100) {
2827             // onProgressChanged() may continue to be called after the main
2828             // frame has finished loading, as any remaining sub frames continue
2829             // to load. We'll only get called once though with newProgress as
2830             // 100 when everything is loaded. (onPageFinished is called once
2831             // when the main frame completes loading regardless of the state of
2832             // any sub frames so calls to onProgressChanges may continue after
2833             // onPageFinished has executed)
2834             if (mInLoad) {
2835                 mInLoad = false;
2836                 updateInLoadMenuItems();
2837                 // If the options menu is open, leave the title bar
2838                 if (!mOptionsMenuOpen || !mIconView) {
2839                     hideFakeTitleBar();
2840                 }
2841             }
2842         } else {
2843             if (!mInLoad) {
2844                 // onPageFinished may have already been called but a subframe is
2845                 // still loading and updating the progress. Reset mInLoad and
2846                 // update the menu items.
2847                 mInLoad = true;
2848                 updateInLoadMenuItems();
2849             }
2850             // When the page first begins to load, the Activity may still be
2851             // paused, in which case showFakeTitleBar will do nothing.  Call
2852             // again as the page continues to load so that it will be shown.
2853             // (Calling it will the fake title bar is already showing will also
2854             // do nothing.
2855             if (!mOptionsMenuOpen || mIconView) {
2856                 // This page has begun to load, so show the title bar
2857                 showFakeTitleBar();
2858             }
2859         }
2860     }
2861
2862     void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
2863         // if a view already exists then immediately terminate the new one
2864         if (mCustomView != null) {
2865             callback.onCustomViewHidden();
2866             return;
2867         }
2868
2869         // Add the custom view to its container.
2870         mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
2871         mCustomView = view;
2872         mCustomViewCallback = callback;
2873         // Save the menu state and set it to empty while the custom
2874         // view is showing.
2875         mOldMenuState = mMenuState;
2876         mMenuState = EMPTY_MENU;
2877         // Hide the content view.
2878         mContentView.setVisibility(View.GONE);
2879         // Finally show the custom view container.
2880         setStatusBarVisibility(false);
2881         mCustomViewContainer.setVisibility(View.VISIBLE);
2882         mCustomViewContainer.bringToFront();
2883     }
2884
2885     void onHideCustomView() {
2886         if (mCustomView == null)
2887             return;
2888
2889         // Hide the custom view.
2890         mCustomView.setVisibility(View.GONE);
2891         // Remove the custom view from its container.
2892         mCustomViewContainer.removeView(mCustomView);
2893         mCustomView = null;
2894         // Reset the old menu state.
2895         mMenuState = mOldMenuState;
2896         mOldMenuState = EMPTY_MENU;
2897         mCustomViewContainer.setVisibility(View.GONE);
2898         mCustomViewCallback.onCustomViewHidden();
2899         // Show the content view.
2900         setStatusBarVisibility(true);
2901         mContentView.setVisibility(View.VISIBLE);
2902     }
2903
2904     Bitmap getDefaultVideoPoster() {
2905         if (mDefaultVideoPoster == null) {
2906             mDefaultVideoPoster = BitmapFactory.decodeResource(
2907                     getResources(), R.drawable.default_video_poster);
2908         }
2909         return mDefaultVideoPoster;
2910     }
2911
2912     View getVideoLoadingProgressView() {
2913         if (mVideoProgressView == null) {
2914             LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this);
2915             mVideoProgressView = inflater.inflate(
2916                     R.layout.video_loading_progress, null);
2917         }
2918         return mVideoProgressView;
2919     }
2920
2921     /*
2922      * The Object used to inform the WebView of the file to upload.
2923      */
2924     private ValueCallback<Uri> mUploadMessage;
2925
2926     void openFileChooser(ValueCallback<Uri> uploadMsg) {
2927         if (mUploadMessage != null) return;
2928         mUploadMessage = uploadMsg;
2929         Intent i = new Intent(Intent.ACTION_GET_CONTENT);
2930         i.addCategory(Intent.CATEGORY_OPENABLE);
2931         i.setType("*/*");
2932         BrowserActivity.this.startActivityForResult(Intent.createChooser(i,
2933                 getString(R.string.choose_upload)), FILE_SELECTED);
2934     }
2935
2936     // -------------------------------------------------------------------------
2937     // Implement functions for DownloadListener
2938     // -------------------------------------------------------------------------
2939
2940     /**
2941      * Notify the host application a download should be done, or that
2942      * the data should be streamed if a streaming viewer is available.
2943      * @param url The full url to the content that should be downloaded
2944      * @param contentDisposition Content-disposition http header, if
2945      *                           present.
2946      * @param mimetype The mimetype of the content reported by the server
2947      * @param contentLength The file size reported by the server
2948      */
2949     public void onDownloadStart(String url, String userAgent,
2950             String contentDisposition, String mimetype, long contentLength) {
2951         // if we're dealing wih A/V content that's not explicitly marked
2952         //     for download, check if it's streamable.
2953         if (contentDisposition == null
2954                 || !contentDisposition.regionMatches(
2955                         true, 0, "attachment", 0, 10)) {
2956             // query the package manager to see if there's a registered handler
2957             //     that matches.
2958             Intent intent = new Intent(Intent.ACTION_VIEW);
2959             intent.setDataAndType(Uri.parse(url), mimetype);
2960             ResolveInfo info = getPackageManager().resolveActivity(intent,
2961                     PackageManager.MATCH_DEFAULT_ONLY);
2962             if (info != null) {
2963                 ComponentName myName = getComponentName();
2964                 // If we resolved to ourselves, we don't want to attempt to
2965                 // load the url only to try and download it again.
2966                 if (!myName.getPackageName().equals(
2967                         info.activityInfo.packageName)
2968                         || !myName.getClassName().equals(
2969                                 info.activityInfo.name)) {
2970                     // someone (other than us) knows how to handle this mime
2971                     // type with this scheme, don't download.
2972                     try {
2973                         startActivity(intent);
2974                         return;
2975                     } catch (ActivityNotFoundException ex) {
2976                         if (LOGD_ENABLED) {
2977                             Log.d(LOGTAG, "activity not found for " + mimetype
2978                                     + " over " + Uri.parse(url).getScheme(),
2979                                     ex);
2980                         }
2981                         // Best behavior is to fall back to a download in this
2982                         // case
2983                     }
2984                 }
2985             }
2986         }
2987         onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
2988     }
2989
2990     /**
2991      * Notify the host application a download should be done, even if there
2992      * is a streaming viewer available for thise type.
2993      * @param url The full url to the content that should be downloaded
2994      * @param contentDisposition Content-disposition http header, if
2995      *                           present.
2996      * @param mimetype The mimetype of the content reported by the server
2997      * @param contentLength The file size reported by the server
2998      */
2999     /*package */ void onDownloadStartNoStream(String url, String userAgent,
3000             String contentDisposition, String mimetype, long contentLength) {
3001
3002         String filename = URLUtil.guessFileName(url,
3003                 contentDisposition, mimetype);
3004
3005         // Check to see if we have an SDCard
3006         String status = Environment.getExternalStorageState();
3007         if (!status.equals(Environment.MEDIA_MOUNTED)) {
3008             int title;
3009             String msg;
3010
3011             // Check to see if the SDCard is busy, same as the music app
3012             if (status.equals(Environment.MEDIA_SHARED)) {
3013                 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3014                 title = R.string.download_sdcard_busy_dlg_title;
3015             } else {
3016                 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3017                 title = R.string.download_no_sdcard_dlg_title;
3018             }
3019
3020             new AlertDialog.Builder(this)
3021                 .setTitle(title)
3022                 .setIcon(android.R.drawable.ic_dialog_alert)
3023                 .setMessage(msg)
3024                 .setPositiveButton(R.string.ok, null)
3025                 .show();
3026             return;
3027         }
3028
3029         // java.net.URI is a lot stricter than KURL so we have to undo
3030         // KURL's percent-encoding and redo the encoding using java.net.URI.
3031         URI uri = null;
3032         try {
3033             // Undo the percent-encoding that KURL may have done.
3034             String newUrl = new String(URLUtil.decode(url.getBytes()));
3035             // Parse the url into pieces
3036             WebAddress w = new WebAddress(newUrl);
3037             String frag = null;
3038             String query = null;
3039             String path = w.mPath;
3040             // Break the path into path, query, and fragment
3041             if (path.length() > 0) {
3042                 // Strip the fragment
3043                 int idx = path.lastIndexOf('#');
3044                 if (idx != -1) {
3045                     frag = path.substring(idx + 1);
3046                     path = path.substring(0, idx);
3047                 }
3048                 idx = path.lastIndexOf('?');
3049                 if (idx != -1) {
3050                     query = path.substring(idx + 1);
3051                     path = path.substring(0, idx);
3052                 }
3053             }
3054             uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3055                     query, frag);
3056         } catch (Exception e) {
3057             Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3058             return;
3059         }
3060
3061         // XXX: Have to use the old url since the cookies were stored using the
3062         // old percent-encoded url.
3063         String cookies = CookieManager.getInstance().getCookie(url);
3064
3065         ContentValues values = new ContentValues();
3066         values.put(Downloads.Impl.COLUMN_URI, uri.toString());
3067         values.put(Downloads.Impl.COLUMN_COOKIE_DATA, cookies);
3068         values.put(Downloads.Impl.COLUMN_USER_AGENT, userAgent);
3069         values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
3070                 getPackageName());
3071         values.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
3072                 BrowserDownloadPage.class.getCanonicalName());
3073         values.put(Downloads.Impl.COLUMN_VISIBILITY,
3074                 Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3075         values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimetype);
3076         values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, filename);
3077         values.put(Downloads.Impl.COLUMN_DESCRIPTION, uri.getHost());
3078         if (contentLength > 0) {
3079             values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength);
3080         }
3081         if (mimetype == null) {
3082             // We must have long pressed on a link or image to download it. We
3083             // are not sure of the mimetype in this case, so do a head request
3084             new FetchUrlMimeType(this).execute(values);
3085         } else {
3086             final Uri contentUri =
3087                     getContentResolver().insert(Downloads.Impl.CONTENT_URI, values);
3088         }
3089
3090     }
3091
3092     // -------------------------------------------------------------------------
3093
3094     /**
3095      * Resets the lock icon. This method is called when we start a new load and
3096      * know the url to be loaded.
3097      */
3098     private void resetLockIcon(String url) {
3099         // Save the lock-icon state (we revert to it if the load gets cancelled)
3100         mTabControl.getCurrentTab().resetLockIcon(url);
3101         updateLockIconImage(LOCK_ICON_UNSECURE);
3102     }
3103
3104     /**
3105      * Update the lock icon to correspond to our latest state.
3106      */
3107     private void updateLockIconToLatest() {
3108         updateLockIconImage(mTabControl.getCurrentTab().getLockIconType());
3109     }
3110
3111     /**
3112      * Updates the lock-icon image in the title-bar.
3113      */
3114     private void updateLockIconImage(int lockIconType) {
3115         Drawable d = null;
3116         if (lockIconType == LOCK_ICON_SECURE) {
3117             d = mSecLockIcon;
3118         } else if (lockIconType == LOCK_ICON_MIXED) {
3119             d = mMixLockIcon;
3120         }
3121         mTitleBar.setLock(d);
3122         mFakeTitleBar.setLock(d);
3123     }
3124
3125     /**
3126      * Displays a page-info dialog.
3127      * @param tab The tab to show info about
3128      * @param fromShowSSLCertificateOnError The flag that indicates whether
3129      * this dialog was opened from the SSL-certificate-on-error dialog or
3130      * not. This is important, since we need to know whether to return to
3131      * the parent dialog or simply dismiss.
3132      */
3133     private void showPageInfo(final Tab tab,
3134                               final boolean fromShowSSLCertificateOnError) {
3135         final LayoutInflater factory = LayoutInflater
3136                 .from(this);
3137
3138         final View pageInfoView = factory.inflate(R.layout.page_info, null);
3139
3140         final WebView view = tab.getWebView();
3141
3142         String url = null;
3143         String title = null;
3144
3145         if (view == null) {
3146             url = tab.getUrl();
3147             title = tab.getTitle();
3148         } else if (view == mTabControl.getCurrentWebView()) {
3149              // Use the cached title and url if this is the current WebView
3150             url = mUrl;
3151             title = mTitle;
3152         } else {
3153             url = view.getUrl();
3154             title = view.getTitle();
3155         }
3156
3157         if (url == null) {
3158             url = "";
3159         }
3160         if (title == null) {
3161             title = "";
3162         }
3163
3164         ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3165         ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3166
3167         mPageInfoView = tab;
3168         mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError;
3169
3170         AlertDialog.Builder alertDialogBuilder =
3171             new AlertDialog.Builder(this)
3172             .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3173             .setView(pageInfoView)
3174             .setPositiveButton(
3175                 R.string.ok,
3176                 new DialogInterface.OnClickListener() {
3177                     public void onClick(DialogInterface dialog,
3178                                         int whichButton) {
3179                         mPageInfoDialog = null;
3180                         mPageInfoView = null;
3181
3182                         // if we came here from the SSL error dialog
3183                         if (fromShowSSLCertificateOnError) {
3184                             // go back to the SSL error dialog
3185                             showSSLCertificateOnError(
3186                                 mSSLCertificateOnErrorView,
3187                                 mSSLCertificateOnErrorHandler,
3188                                 mSSLCertificateOnErrorError);
3189                         }
3190                     }
3191                 })
3192             .setOnCancelListener(
3193                 new DialogInterface.OnCancelListener() {
3194                     public void onCancel(DialogInterface dialog) {
3195                         mPageInfoDialog = null;
3196                         mPageInfoView = null;
3197
3198                         // if we came here from the SSL error dialog
3199                         if (fromShowSSLCertificateOnError) {
3200                             // go back to the SSL error dialog
3201                             showSSLCertificateOnError(
3202                                 mSSLCertificateOnErrorView,
3203                                 mSSLCertificateOnErrorHandler,
3204                                 mSSLCertificateOnErrorError);
3205                         }
3206                     }
3207                 });
3208
3209         // if we have a main top-level page SSL certificate set or a certificate
3210         // error
3211         if (fromShowSSLCertificateOnError ||
3212                 (view != null && view.getCertificate() != null)) {
3213             // add a 'View Certificate' button
3214             alertDialogBuilder.setNeutralButton(
3215                 R.string.view_certificate,
3216                 new DialogInterface.OnClickListener() {
3217                     public void onClick(DialogInterface dialog,
3218                                         int whichButton) {
3219                         mPageInfoDialog = null;
3220                         mPageInfoView = null;
3221
3222                         // if we came here from the SSL error dialog
3223                         if (fromShowSSLCertificateOnError) {
3224                             // go back to the SSL error dialog
3225                             showSSLCertificateOnError(
3226                                 mSSLCertificateOnErrorView,
3227                                 mSSLCertificateOnErrorHandler,
3228                                 mSSLCertificateOnErrorError);
3229                         } else {
3230                             // otherwise, display the top-most certificate from
3231                             // the chain
3232                             if (view.getCertificate() != null) {
3233                                 showSSLCertificate(tab);
3234                             }
3235                         }
3236                     }
3237                 });
3238         }
3239
3240         mPageInfoDialog = alertDialogBuilder.show();
3241     }
3242
3243        /**
3244      * Displays the main top-level page SSL certificate dialog
3245      * (accessible from the Page-Info dialog).
3246      * @param tab The tab to show certificate for.
3247      */
3248     private void showSSLCertificate(final Tab tab) {
3249         final View certificateView =
3250                 inflateCertificateView(tab.getWebView().getCertificate());
3251         if (certificateView == null) {
3252             return;
3253         }
3254
3255         LayoutInflater factory = LayoutInflater.from(this);
3256
3257         final LinearLayout placeholder =
3258                 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3259
3260         LinearLayout ll = (LinearLayout) factory.inflate(
3261             R.layout.ssl_success, placeholder);
3262         ((TextView)ll.findViewById(R.id.success))
3263             .setText(R.string.ssl_certificate_is_valid);
3264
3265         mSSLCertificateView = tab;
3266         mSSLCertificateDialog =
3267             new AlertDialog.Builder(this)
3268                 .setTitle(R.string.ssl_certificate).setIcon(
3269                     R.drawable.ic_dialog_browser_certificate_secure)
3270                 .setView(certificateView)
3271                 .setPositiveButton(R.string.ok,
3272                         new DialogInterface.OnClickListener() {
3273                             public void onClick(DialogInterface dialog,
3274                                     int whichButton) {
3275                                 mSSLCertificateDialog = null;
3276                                 mSSLCertificateView = null;
3277
3278                                 showPageInfo(tab, false);
3279                             }
3280                         })
3281                 .setOnCancelListener(
3282                         new DialogInterface.OnCancelListener() {
3283                             public void onCancel(DialogInterface dialog) {
3284                                 mSSLCertificateDialog = null;
3285                                 mSSLCertificateView = null;
3286
3287                                 showPageInfo(tab, false);
3288                             }
3289                         })
3290                 .show();
3291     }
3292
3293     /**
3294      * Displays the SSL error certificate dialog.
3295      * @param view The target web-view.
3296      * @param handler The SSL error handler responsible for cancelling the
3297      * connection that resulted in an SSL error or proceeding per user request.
3298      * @param error The SSL error object.
3299      */
3300     void showSSLCertificateOnError(
3301         final WebView view, final SslErrorHandler handler, final SslError error) {
3302
3303         final View certificateView =
3304             inflateCertificateView(error.getCertificate());
3305         if (certificateView == null) {
3306             return;
3307         }
3308
3309         LayoutInflater factory = LayoutInflater.from(this);
3310
3311         final LinearLayout placeholder =
3312                 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3313
3314         if (error.hasError(SslError.SSL_UNTRUSTED)) {
3315             LinearLayout ll = (LinearLayout)factory
3316                 .inflate(R.layout.ssl_warning, placeholder);
3317             ((TextView)ll.findViewById(R.id.warning))
3318                 .setText(R.string.ssl_untrusted);
3319         }
3320
3321         if (error.hasError(SslError.SSL_IDMISMATCH)) {
3322             LinearLayout ll = (LinearLayout)factory
3323                 .inflate(R.layout.ssl_warning, placeholder);
3324             ((TextView)ll.findViewById(R.id.warning))
3325                 .setText(R.string.ssl_mismatch);
3326         }
3327
3328         if (error.hasError(SslError.SSL_EXPIRED)) {
3329             LinearLayout ll = (LinearLayout)factory
3330                 .inflate(R.layout.ssl_warning, placeholder);
3331             ((TextView)ll.findViewById(R.id.warning))
3332                 .setText(R.string.ssl_expired);
3333         }
3334
3335         if (error.hasError(SslError.SSL_NOTYETVALID)) {
3336             LinearLayout ll = (LinearLayout)factory
3337                 .inflate(R.layout.ssl_warning, placeholder);
3338             ((TextView)ll.findViewById(R.id.warning))
3339                 .setText(R.string.ssl_not_yet_valid);
3340         }
3341
3342         mSSLCertificateOnErrorHandler = handler;
3343         mSSLCertificateOnErrorView = view;
3344         mSSLCertificateOnErrorError = error;
3345         mSSLCertificateOnErrorDialog =
3346             new AlertDialog.Builder(this)
3347                 .setTitle(R.string.ssl_certificate).setIcon(
3348                     R.drawable.ic_dialog_browser_certificate_partially_secure)
3349                 .setView(certificateView)
3350                 .setPositiveButton(R.string.ok,
3351                         new DialogInterface.OnClickListener() {
3352                             public void onClick(DialogInterface dialog,
3353                                     int whichButton) {
3354                                 mSSLCertificateOnErrorDialog = null;
3355                                 mSSLCertificateOnErrorView = null;
3356                                 mSSLCertificateOnErrorHandler = null;
3357                                 mSSLCertificateOnErrorError = null;
3358
3359                                 view.getWebViewClient().onReceivedSslError(
3360                                                 view, handler, error);
3361                             }
3362                         })
3363                  .setNeutralButton(R.string.page_info_view,
3364                         new DialogInterface.OnClickListener() {
3365                             public void onClick(DialogInterface dialog,
3366                                     int whichButton) {
3367                                 mSSLCertificateOnErrorDialog = null;
3368
3369                                 // do not clear the dialog state: we will
3370                                 // need to show the dialog again once the
3371                                 // user is done exploring the page-info details
3372
3373                                 showPageInfo(mTabControl.getTabFromView(view),
3374                                         true);
3375                             }
3376                         })
3377                 .setOnCancelListener(
3378                         new DialogInterface.OnCancelListener() {
3379                             public void onCancel(DialogInterface dialog) {
3380                                 mSSLCertificateOnErrorDialog = null;
3381                                 mSSLCertificateOnErrorView = null;
3382                                 mSSLCertificateOnErrorHandler = null;
3383                                 mSSLCertificateOnErrorError = null;
3384
3385                                 view.getWebViewClient().onReceivedSslError(
3386                                                 view, handler, error);
3387                             }
3388                         })
3389                 .show();
3390     }
3391
3392     /**
3393      * Inflates the SSL certificate view (helper method).
3394      * @param certificate The SSL certificate.
3395      * @return The resultant certificate view with issued-to, issued-by,
3396      * issued-on, expires-on, and possibly other fields set.
3397      * If the input certificate is null, returns null.
3398      */
3399     private View inflateCertificateView(SslCertificate certificate) {
3400         if (certificate == null) {
3401             return null;
3402         }
3403
3404         LayoutInflater factory = LayoutInflater.from(this);
3405
3406         View certificateView = factory.inflate(
3407             R.layout.ssl_certificate, null);
3408
3409         // issued to:
3410         SslCertificate.DName issuedTo = certificate.getIssuedTo();
3411         if (issuedTo != null) {
3412             ((TextView) certificateView.findViewById(R.id.to_common))
3413                 .setText(issuedTo.getCName());
3414             ((TextView) certificateView.findViewById(R.id.to_org))
3415                 .setText(issuedTo.getOName());
3416             ((TextView) certificateView.findViewById(R.id.to_org_unit))
3417                 .setText(issuedTo.getUName());
3418         }
3419
3420         // issued by:
3421         SslCertificate.DName issuedBy = certificate.getIssuedBy();
3422         if (issuedBy != null) {
3423             ((TextView) certificateView.findViewById(R.id.by_common))
3424                 .setText(issuedBy.getCName());
3425             ((TextView) certificateView.findViewById(R.id.by_org))
3426                 .setText(issuedBy.getOName());
3427             ((TextView) certificateView.findViewById(R.id.by_org_unit))
3428                 .setText(issuedBy.getUName());
3429         }
3430
3431         // issued on:
3432         String issuedOn = formatCertificateDate(
3433             certificate.getValidNotBeforeDate());
3434         ((TextView) certificateView.findViewById(R.id.issued_on))
3435             .setText(issuedOn);
3436
3437         // expires on:
3438         String expiresOn = formatCertificateDate(
3439             certificate.getValidNotAfterDate());
3440         ((TextView) certificateView.findViewById(R.id.expires_on))
3441             .setText(expiresOn);
3442
3443         return certificateView;
3444     }
3445
3446     /**
3447      * Formats the certificate date to a properly localized date string.
3448      * @return Properly localized version of the certificate date string and
3449      * the "" if it fails to localize.
3450      */
3451     private String formatCertificateDate(Date certificateDate) {
3452       if (certificateDate == null) {
3453           return "";
3454       }
3455       String formattedDate = DateFormat.getDateFormat(this).format(certificateDate);
3456       if (formattedDate == null) {
3457           return "";
3458       }
3459       return formattedDate;
3460     }
3461
3462     /**
3463      * Displays an http-authentication dialog.
3464      */
3465     void showHttpAuthentication(final HttpAuthHandler handler,
3466             final String host, final String realm, final String title,
3467             final String name, final String password, int focusId) {
3468         LayoutInflater factory = LayoutInflater.from(this);
3469         final View v = factory
3470                 .inflate(R.layout.http_authentication, null);
3471         if (name != null) {
3472             ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3473         }
3474         if (password != null) {
3475             ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3476         }
3477
3478         String titleText = title;
3479         if (titleText == null) {
3480             titleText = getText(R.string.sign_in_to).toString().replace(
3481                     "%s1", host).replace("%s2", realm);
3482         }
3483
3484         mHttpAuthHandler = handler;
3485         AlertDialog dialog = new AlertDialog.Builder(this)
3486                 .setTitle(titleText)
3487                 .setIcon(android.R.drawable.ic_dialog_alert)
3488                 .setView(v)
3489                 .setPositiveButton(R.string.action,
3490                         new DialogInterface.OnClickListener() {
3491                              public void onClick(DialogInterface dialog,
3492                                      int whichButton) {
3493                                 String nm = ((EditText) v
3494                                         .findViewById(R.id.username_edit))
3495                                         .getText().toString();
3496                                 String pw = ((EditText) v
3497                                         .findViewById(R.id.password_edit))
3498                                         .getText().toString();
3499                                 BrowserActivity.this.setHttpAuthUsernamePassword
3500                                         (host, realm, nm, pw);
3501                                 handler.proceed(nm, pw);
3502                                 mHttpAuthenticationDialog = null;
3503                                 mHttpAuthHandler = null;
3504                             }})
3505                 .setNegativeButton(R.string.cancel,
3506                         new DialogInterface.OnClickListener() {
3507                             public void onClick(DialogInterface dialog,
3508                                     int whichButton) {
3509                                 handler.cancel();
3510                                 BrowserActivity.this.resetTitleAndRevertLockIcon();
3511                                 mHttpAuthenticationDialog = null;
3512                                 mHttpAuthHandler = null;
3513                             }})
3514                 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3515                         public void onCancel(DialogInterface dialog) {
3516                             handler.cancel();
3517                             BrowserActivity.this.resetTitleAndRevertLockIcon();
3518                             mHttpAuthenticationDialog = null;
3519                             mHttpAuthHandler = null;
3520                         }})
3521                 .create();
3522         // Make the IME appear when the dialog is displayed if applicable.
3523         dialog.getWindow().setSoftInputMode(
3524                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3525         dialog.show();
3526         if (focusId != 0) {
3527             dialog.findViewById(focusId).requestFocus();
3528         } else {
3529             v.findViewById(R.id.username_edit).requestFocus();
3530         }
3531         mHttpAuthenticationDialog = dialog;
3532     }
3533
3534     public int getProgress() {
3535         WebView w = mTabControl.getCurrentWebView();
3536         if (w != null) {
3537             return w.getProgress();
3538         } else {
3539             return 100;
3540         }
3541     }
3542
3543     /**
3544      * Set HTTP authentication password.
3545      *
3546      * @param host The host for the password
3547      * @param realm The realm for the password
3548      * @param username The username for the password. If it is null, it means
3549      *            password can't be saved.
3550      * @param password The password
3551      */
3552     public void setHttpAuthUsernamePassword(String host, String realm,
3553                                             String username,
3554                                             String password) {
3555         WebView w = mTabControl.getCurrentWebView();
3556         if (w != null) {
3557             w.setHttpAuthUsernamePassword(host, realm, username, password);
3558         }
3559     }
3560
3561     /**
3562      * connectivity manager says net has come or gone... inform the user
3563      * @param up true if net has come up, false if net has gone down
3564      */
3565     public void onNetworkToggle(boolean up) {
3566         if (up == mIsNetworkUp) {
3567             return;
3568         } else if (up) {
3569             mIsNetworkUp = true;
3570             if (mAlertDialog != null) {
3571                 mAlertDialog.cancel();
3572                 mAlertDialog = null;
3573             }
3574         } else {
3575             mIsNetworkUp = false;
3576             if (mInLoad) {
3577                 createAndShowNetworkDialog();
3578            }
3579         }
3580         WebView w = mTabControl.getCurrentWebView();
3581         if (w != null) {
3582             w.setNetworkAvailable(up);
3583         }
3584     }
3585
3586     boolean isNetworkUp() {
3587         return mIsNetworkUp;
3588     }
3589
3590     // This method shows the network dialog alerting the user that the net is
3591     // down. It will only show the dialog if mAlertDialog is null.
3592     private void createAndShowNetworkDialog() {
3593         if (mAlertDialog == null) {
3594             mAlertDialog = new AlertDialog.Builder(this)
3595                     .setTitle(R.string.loadSuspendedTitle)
3596                     .setMessage(R.string.loadSuspended)
3597                     .setPositiveButton(R.string.ok, null)
3598                     .show();
3599         }
3600     }
3601
3602     @Override
3603     protected void onActivityResult(int requestCode, int resultCode,
3604                                     Intent intent) {
3605         if (getTopWindow() == null) return;
3606
3607         switch (requestCode) {
3608             case COMBO_PAGE:
3609                 if (resultCode == RESULT_OK && intent != null) {
3610                     String data = intent.getAction();
3611                     Bundle extras = intent.getExtras();
3612                     if (extras != null && extras.getBoolean("new_window", false)) {
3613                         openTab(data);
3614                     } else {
3615                         final Tab currentTab =
3616                                 mTabControl.getCurrentTab();
3617                         dismissSubWindow(currentTab);
3618                         if (data != null && data.length() != 0) {
3619                             loadUrl(getTopWindow(), data);
3620                         }
3621                     }
3622                 }
3623                 // Deliberately fall through to PREFERENCES_PAGE, since the
3624                 // same extra may be attached to the COMBO_PAGE
3625             case PREFERENCES_PAGE:
3626                 if (resultCode == RESULT_OK && intent != null) {
3627                     String action = intent.getStringExtra(Intent.EXTRA_TEXT);
3628                     if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) {
3629                         mTabControl.removeParentChildRelationShips();
3630                     }
3631                 }
3632                 break;
3633             // Choose a file from the file picker.
3634             case FILE_SELECTED:
3635                 if (null == mUploadMessage) break;
3636                 Uri result = intent == null || resultCode != RESULT_OK ? null
3637                         : intent.getData();
3638                 mUploadMessage.onReceiveValue(result);
3639                 mUploadMessage = null;
3640                 break;
3641             default:
3642                 break;
3643         }
3644         getTopWindow().requestFocus();
3645     }
3646
3647     /*
3648      * This method is called as a result of the user selecting the options
3649      * menu to see the download window. It shows the download window on top of
3650      * the current window.
3651      */
3652     private void viewDownloads(Uri downloadRecord) {
3653         Intent intent = new Intent(this,
3654                 BrowserDownloadPage.class);
3655         intent.setData(downloadRecord);
3656         startActivityForResult(intent, BrowserActivity.DOWNLOAD_PAGE);
3657
3658     }
3659
3660     /**
3661      * Open the Go page.
3662      * @param startWithHistory If true, open starting on the history tab.
3663      *                         Otherwise, start with the bookmarks tab.
3664      */
3665     /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
3666         WebView current = mTabControl.getCurrentWebView();
3667         if (current == null) {
3668             return;
3669         }
3670         Intent intent = new Intent(this,
3671                 CombinedBookmarkHistoryActivity.class);
3672         String title = current.getTitle();
3673         String url = current.getUrl();
3674         Bitmap thumbnail = createScreenshot(current);
3675
3676         // Just in case the user opens bookmarks before a page finishes loading
3677         // so the current history item, and therefore the page, is null.
3678         if (null == url) {
3679             url = mLastEnteredUrl;
3680             // This can happen.
3681             if (null == url) {
3682                 url = mSettings.getHomePage();
3683             }
3684         }
3685         // In case the web page has not yet received its associated title.
3686         if (title == null) {
3687             title = url;
3688         }
3689         intent.putExtra("title", title);
3690         intent.putExtra("url", url);
3691         intent.putExtra("thumbnail", thumbnail);
3692         // Disable opening in a new window if we have maxed out the windows
3693         intent.putExtra("disable_new_window", !mTabControl.canCreateNewTab());
3694         intent.putExtra("touch_icon_url", current.getTouchIconUrl());
3695         if (startWithHistory) {
3696             intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3697                     CombinedBookmarkHistoryActivity.HISTORY_TAB);
3698         }
3699         startActivityForResult(intent, COMBO_PAGE);
3700     }
3701
3702     // Called when loading from context menu or LOAD_URL message
3703     private void loadUrlFromContext(WebView view, String url) {
3704         // In case the user enters nothing.
3705         if (url != null && url.length() != 0 && view != null) {
3706             url = smartUrlFilter(url);
3707             if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) {
3708                 loadUrl(view, url);
3709             }
3710         }
3711     }
3712
3713     /**
3714      * Load the URL into the given WebView and update the title bar
3715      * to reflect the new load.  Call this instead of WebView.loadUrl
3716      * directly.
3717      * @param view The WebView used to load url.
3718      * @param url The URL to load.
3719      */
3720     private void loadUrl(WebView view, String url) {
3721         updateTitleBarForNewLoad(view, url);
3722         view.loadUrl(url);
3723     }
3724
3725     /**
3726      * Load UrlData into a Tab and update the title bar to reflect the new
3727      * load.  Call this instead of UrlData.loadIn directly.
3728      * @param t The Tab used to load.
3729      * @param data The UrlData being loaded.
3730      */
3731     private void loadUrlDataIn(Tab t, UrlData data) {
3732         updateTitleBarForNewLoad(t.getWebView(), data.mUrl);
3733         data.loadIn(t);
3734     }
3735
3736     /**
3737      * If the WebView is the top window, update the title bar to reflect
3738      * loading the new URL.  i.e. set its text, clear the favicon (which
3739      * will be set once the page begins loading), and set the progress to
3740      * INITIAL_PROGRESS to show that the page has begun to load. Called
3741      * by loadUrl and loadUrlDataIn.
3742      * @param view The WebView that is starting a load.
3743      * @param url The URL that is being loaded.
3744      */
3745     private void updateTitleBarForNewLoad(WebView view, String url) {
3746         if (view == getTopWindow()) {
3747             setUrlTitle(url, null);
3748             setFavicon(null);
3749             onProgressChanged(view, INITIAL_PROGRESS);
3750         }
3751     }
3752
3753     private String smartUrlFilter(Uri inUri) {
3754         if (inUri != null) {
3755             return smartUrlFilter(inUri.toString());
3756         }
3757         return null;
3758     }
3759
3760     protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
3761             "(?i)" + // switch on case insensitive matching
3762             "(" +    // begin group for schema
3763             "(?:http|https|file):\\/\\/" +
3764             "|(?:inline|data|about|content|javascript):" +
3765             ")" +
3766             "(.*)" );
3767
3768     /**
3769      * Attempts to determine whether user input is a URL or search
3770      * terms.  Anything with a space is passed to search.
3771      *
3772      * Converts to lowercase any mistakenly uppercased schema (i.e.,
3773      * "Http://" converts to "http://"
3774      *
3775      * @return Original or modified URL
3776      *
3777      */
3778     String smartUrlFilter(String url) {
3779
3780         String inUrl = url.trim();
3781         boolean hasSpace = inUrl.indexOf(' ') != -1;
3782
3783         Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
3784         if (matcher.matches()) {
3785             // force scheme to lowercase
3786             String scheme = matcher.group(1);
3787             String lcScheme = scheme.toLowerCase();
3788             if (!lcScheme.equals(scheme)) {
3789                 inUrl = lcScheme + matcher.group(2);
3790             }
3791             if (hasSpace) {
3792                 inUrl = inUrl.replace(" ", "%20");
3793             }
3794             return inUrl;
3795         }
3796         if (hasSpace) {
3797             // FIXME: Is this the correct place to add to searches?
3798             // what if someone else calls this function?
3799             int shortcut = parseUrlShortcut(inUrl);
3800             if (shortcut != SHORTCUT_INVALID) {
3801                 Browser.addSearchUrl(mResolver, inUrl);
3802                 String query = inUrl.substring(2);
3803                 switch (shortcut) {
3804                 case SHORTCUT_GOOGLE_SEARCH:
3805                     return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
3806                 case SHORTCUT_WIKIPEDIA_SEARCH:
3807                     return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
3808                 case SHORTCUT_DICTIONARY_SEARCH:
3809                     return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
3810                 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
3811                     // FIXME: we need location in this case
3812                     return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
3813                 }
3814             }
3815         } else {
3816             if (Patterns.WEB_URL.matcher(inUrl).matches()) {
3817                 return URLUtil.guessUrl(inUrl);
3818             }
3819         }
3820
3821         Browser.addSearchUrl(mResolver, inUrl);
3822         return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
3823     }
3824
3825     /* package */ void setShouldShowErrorConsole(boolean flag) {
3826         if (flag == mShouldShowErrorConsole) {
3827             // Nothing to do.
3828             return;
3829         }
3830
3831         mShouldShowErrorConsole = flag;
3832
3833         ErrorConsoleView errorConsole = mTabControl.getCurrentTab()
3834                 .getErrorConsole(true);
3835
3836         if (flag) {
3837             // Setting the show state of the console will cause it's the layout to be inflated.
3838             if (errorConsole.numberOfErrors() > 0) {
3839                 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3840             } else {
3841                 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
3842             }
3843
3844             // Now we can add it to the main view.
3845             mErrorConsoleContainer.addView(errorConsole,
3846                     new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
3847                                                   ViewGroup.LayoutParams.WRAP_CONTENT));
3848         } else {
3849             mErrorConsoleContainer.removeView(errorConsole);
3850         }
3851
3852     }
3853
3854     boolean shouldShowErrorConsole() {
3855         return mShouldShowErrorConsole;
3856     }
3857
3858     private void setStatusBarVisibility(boolean visible) {
3859         int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
3860         getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
3861     }
3862
3863
3864     private void sendNetworkType(String type, String subtype) {
3865         WebView w = mTabControl.getCurrentWebView();
3866         if (w != null) {
3867             w.setNetworkType(type, subtype);
3868         }
3869     }
3870
3871     private void packageChanged(String packageName, boolean wasAdded) {
3872         WebView w = mTabControl.getCurrentWebView();
3873         if (w == null) {
3874             return;
3875         }
3876
3877         if (wasAdded) {
3878             w.addPackageName(packageName);
3879         } else {
3880             w.removePackageName(packageName);
3881         }
3882     }
3883
3884     private void addPackageNames(Set<String> packageNames) {
3885         WebView w = mTabControl.getCurrentWebView();
3886         if (w == null) {
3887             return;
3888         }
3889
3890         w.addPackageNames(packageNames);
3891     }
3892
3893     private void getInstalledPackages() {
3894         AsyncTask<Void, Void, Set<String> > task =
3895             new AsyncTask<Void, Void, Set<String> >() {
3896             protected Set<String> doInBackground(Void... unused) {
3897                 Set<String> installedPackages = new HashSet<String>();
3898                 PackageManager pm = BrowserActivity.this.getPackageManager();
3899                 if (pm != null) {
3900                     List<PackageInfo> packages = pm.getInstalledPackages(0);
3901                     for (PackageInfo p : packages) {
3902                         if (BrowserActivity.this.sGoogleApps.contains(p.packageName)) {
3903                             installedPackages.add(p.packageName);
3904                         }
3905                     }
3906                 }
3907
3908                 return installedPackages;
3909             }
3910
3911             // Executes on the UI thread
3912             protected void onPostExecute(Set<String> installedPackages) {
3913                 addPackageNames(installedPackages);
3914             }
3915         };
3916         task.execute();
3917     }
3918
3919     final static int LOCK_ICON_UNSECURE = 0;
3920     final static int LOCK_ICON_SECURE   = 1;
3921     final static int LOCK_ICON_MIXED    = 2;
3922
3923     private BrowserSettings mSettings;
3924     private TabControl      mTabControl;
3925     private ContentResolver mResolver;
3926     private FrameLayout     mContentView;
3927     private View            mCustomView;
3928     private FrameLayout     mCustomViewContainer;
3929     private WebChromeClient.CustomViewCallback mCustomViewCallback;
3930
3931     // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
3932     // view, we should rewrite this.
3933     private int mCurrentMenuState = 0;
3934     private int mMenuState = R.id.MAIN_MENU;
3935     private int mOldMenuState = EMPTY_MENU;
3936     private static final int EMPTY_MENU = -1;
3937     private Menu mMenu;
3938
3939     private FindDialog mFindDialog;
3940     // Used to prevent chording to result in firing two shortcuts immediately
3941     // one after another.  Fixes bug 1211714.
3942     boolean mCanChord;
3943
3944     private boolean mInLoad;
3945     private boolean mIsNetworkUp;
3946     private boolean mDidStopLoad;
3947
3948     private boolean mActivityInPause = true;
3949
3950     private boolean mMenuIsDown;
3951
3952     private static boolean mInTrace;
3953
3954     // Performance probe
3955     private static final int[] SYSTEM_CPU_FORMAT = new int[] {
3956             Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
3957             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
3958             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
3959             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
3960             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
3961             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
3962             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
3963             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG  // 7: softirq time
3964     };
3965
3966     private long mStart;
3967     private long mProcessStart;
3968     private long mUserStart;
3969     private long mSystemStart;
3970     private long mIdleStart;
3971     private long mIrqStart;
3972
3973     private long mUiStart;
3974
3975     private Drawable    mMixLockIcon;
3976     private Drawable    mSecLockIcon;
3977
3978     /* hold a ref so we can auto-cancel if necessary */
3979     private AlertDialog mAlertDialog;
3980
3981     // Wait for credentials before loading google.com
3982     private ProgressDialog mCredsDlg;
3983
3984     // The up-to-date URL and title (these can be different from those stored
3985     // in WebView, since it takes some time for the information in WebView to
3986     // get updated)
3987     private String mUrl;
3988     private String mTitle;
3989
3990     // As PageInfo has different style for landscape / portrait, we have
3991     // to re-open it when configuration changed
3992     private AlertDialog mPageInfoDialog;
3993     private Tab mPageInfoView;
3994     // If the Page-Info dialog is launched from the SSL-certificate-on-error
3995     // dialog, we should not just dismiss it, but should get back to the
3996     // SSL-certificate-on-error dialog. This flag is used to store this state
3997     private boolean mPageInfoFromShowSSLCertificateOnError;
3998
3999     // as SSLCertificateOnError has different style for landscape / portrait,
4000     // we have to re-open it when configuration changed
4001     private AlertDialog mSSLCertificateOnErrorDialog;
4002     private WebView mSSLCertificateOnErrorView;
4003     private SslErrorHandler mSSLCertificateOnErrorHandler;
4004     private SslError mSSLCertificateOnErrorError;
4005
4006     // as SSLCertificate has different style for landscape / portrait, we
4007     // have to re-open it when configuration changed
4008     private AlertDialog mSSLCertificateDialog;
4009     private Tab mSSLCertificateView;
4010
4011     // as HttpAuthentication has different style for landscape / portrait, we
4012     // have to re-open it when configuration changed
4013     private AlertDialog mHttpAuthenticationDialog;
4014     private HttpAuthHandler mHttpAuthHandler;
4015
4016     /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4017                                             new FrameLayout.LayoutParams(
4018                                             ViewGroup.LayoutParams.MATCH_PARENT,
4019                                             ViewGroup.LayoutParams.MATCH_PARENT);
4020     /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4021                                             new FrameLayout.LayoutParams(
4022                                             ViewGroup.LayoutParams.MATCH_PARENT,
4023                                             ViewGroup.LayoutParams.MATCH_PARENT,
4024                                             Gravity.CENTER);
4025     // Google search
4026     final static String QuickSearch_G = "http://www.google.com/m?q=%s";
4027     // Wikipedia search
4028     final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4029     // Dictionary search
4030     final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4031     // Google Mobile Local search
4032     final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4033
4034     final static String QUERY_PLACE_HOLDER = "%s";
4035
4036     // "source" parameter for Google search through search key
4037     final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4038     // "source" parameter for Google search through goto menu
4039     final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4040     // "source" parameter for Google search through simplily type
4041     final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4042     // "source" parameter for Google search suggested by the browser
4043     final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4044     // "source" parameter for Google search from unknown source
4045     final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4046
4047     private final static String LOGTAG = "browser";
4048
4049     private String mLastEnteredUrl;
4050
4051     private PowerManager.WakeLock mWakeLock;
4052     private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4053
4054     private Toast mStopToast;
4055
4056     private TitleBar mTitleBar;
4057
4058     private LinearLayout mErrorConsoleContainer = null;
4059     private boolean mShouldShowErrorConsole = false;
4060
4061     // As the ids are dynamically created, we can't guarantee that they will
4062     // be in sequence, so this static array maps ids to a window number.
4063     final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4064     { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4065       R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4066       R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4067
4068     // monitor platform changes
4069     private IntentFilter mNetworkStateChangedFilter;
4070     private BroadcastReceiver mNetworkStateIntentReceiver;
4071
4072     private BroadcastReceiver mPackageInstallationReceiver;
4073
4074     // activity requestCode
4075     final static int COMBO_PAGE                 = 1;
4076     final static int DOWNLOAD_PAGE              = 2;
4077     final static int PREFERENCES_PAGE           = 3;
4078     final static int FILE_SELECTED              = 4;
4079
4080     // the default <video> poster
4081     private Bitmap mDefaultVideoPoster;
4082     // the video progress view
4083     private View mVideoProgressView;
4084
4085     // The Google packages we monitor for the navigator.isApplicationInstalled()
4086     // API. Add as needed.
4087     private static Set<String> sGoogleApps;
4088     static {
4089         sGoogleApps = new HashSet<String>();
4090         sGoogleApps.add("com.google.android.youtube");
4091     }
4092
4093     /**
4094      * A UrlData class to abstract how the content will be set to WebView.
4095      * This base class uses loadUrl to show the content.
4096      */
4097     private static class UrlData {
4098         final String mUrl;
4099         final Map<String, String> mHeaders;
4100         final Intent mVoiceIntent;
4101
4102         UrlData(String url) {
4103             this.mUrl = url;
4104             this.mHeaders = null;
4105             this.mVoiceIntent = null;
4106         }
4107
4108         UrlData(String url, Map<String, String> headers, Intent intent) {
4109             this.mUrl = url;
4110             this.mHeaders = headers;
4111             if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
4112                     .equals(intent.getAction())) {
4113                 this.mVoiceIntent = intent;
4114             } else {
4115                 this.mVoiceIntent = null;
4116             }
4117         }
4118
4119         boolean isEmpty() {
4120             return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
4121         }
4122
4123         /**
4124          * Load this UrlData into the given Tab.  Use loadUrlDataIn to update
4125          * the title bar as well.
4126          */
4127         public void loadIn(Tab t) {
4128             if (mVoiceIntent != null) {
4129                 t.activateVoiceSearchMode(mVoiceIntent);
4130             } else {
4131                 t.getWebView().loadUrl(mUrl, mHeaders);
4132             }
4133         }
4134     };
4135
4136     /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
4137 }