OSDN Git Service

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