Improve browser startup time

A number of optimizations are made to improve the time to interact with the UI quicker.
Steps are made to parallelize the web engine initialization while others removed
impediments to the UI bring up.

Changes:
- Engine Initialization is moved to an async task.
- A new NoShow BrowserLauncher activity is added to chain-load BrowserActivity at startup
   obviating the blank white screen shown.
- Native libraries are loaded asynchronously in a background thread.
- ResourceExtractor is started much earlier than before.
- BrowserSettings is synced to native only after engine initialization.
- Other parts of UI are made aware of engine initialization state to throttle actions.

Change-Id: Icd4959769fa9813170baf7023c46b696b30dfed1
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fb5e875..e4d6dfd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -77,6 +77,19 @@
                     android:readPermission="com.android.swe.browser.permission.READ_HOMEPAGE"
                     android:writePermission="com.android.swe.browser.permission.WRITE_HOMEPAGE" />
         </provider>
+
+        <activity android:name="BrowserLauncher"
+                  android:theme="@android:style/Theme.NoDisplay" >
+            <!-- We are the main entry point of the browser. -->
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.APP_BROWSER" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="BrowserActivity"
                   android:label="@string/application_name_swe"
                   android:launchMode="singleTask"
@@ -84,6 +97,7 @@
                   android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
                   android:theme="@style/BrowserTheme"
                   android:windowSoftInputMode="adjustResize" >
+
             <intent-filter>
                 <action android:name="android.speech.action.VOICE_SEARCH_RESULTS" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -130,14 +144,6 @@
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
-            <!-- We are also the main entry point of the browser. -->
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <category android:name="android.intent.category.APP_BROWSER" />
-            </intent-filter>
             <!-- The maps app is a much better experience, so it's not
                  worth having this at all... especially for a demo!
             <intent-filter android:label="Map In Browser">
diff --git a/res/layout/title_bar.xml b/res/layout/title_bar.xml
index 485892a..7771c59 100644
--- a/res/layout/title_bar.xml
+++ b/res/layout/title_bar.xml
@@ -18,6 +18,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/titlebar"
     android:orientation="vertical"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
     <include
diff --git a/src/com/android/browser/Browser.java b/src/com/android/browser/Browser.java
index 7bb5ebe..4c1d9f2 100644
--- a/src/com/android/browser/Browser.java
+++ b/src/com/android/browser/Browser.java
@@ -17,11 +17,13 @@
 package com.android.browser;
 
 import android.Manifest;
+import android.app.Activity;
 import android.app.Application;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Process;
+import android.os.Bundle;
 import android.util.Log;
+import android.os.Process;
 
 import org.codeaurora.swe.Engine;
 
@@ -39,8 +41,63 @@
     public void onCreate() {
         super.onCreate();
 
-        if (LOGV_ENABLED)
+        if (LOGV_ENABLED) {
             Log.v(LOGTAG, "Browser.onCreate: this=" + this);
+        }
+
+        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
+            @Override
+            public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG, "Browser.onActivityCreated: activity=" + activity);
+                }
+                if (!(activity instanceof BrowserActivity) && !(activity instanceof BrowserLauncher) ) {
+                    EngineInitializer.getInstance().initializeSync((Context) Browser.this);
+                }
+            }
+
+            @Override
+            public void onActivityDestroyed(Activity activity) {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG, "Browser.onActivityDestroyed: activity=" + activity);
+                }
+            }
+
+            @Override
+            public void onActivityPaused(Activity activity) {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG, "Browser.onActivityPaused: activity=" + activity);
+                }
+            }
+
+            @Override
+            public void onActivityResumed(Activity activity) {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG, "Browser.onActivityResumed: activity=" + activity);
+                }
+            }
+
+            @Override
+            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG, "Browser.onActivitySaveInstanceState: activity=" + activity);
+                }
+            }
+
+            @Override
+            public void onActivityStarted(Activity activity) {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG, "Browser.onActivityStarted: activity=" + activity);
+                }
+            }
+
+            @Override
+            public void onActivityStopped(Activity activity) {
+                if (LOGV_ENABLED) {
+                    Log.v(LOGTAG, "Browser.onActivityStopped: activity=" + activity);
+                }
+            }
+        });
 
         // Chromium specific initialization.
         Engine.initializeApplicationParameters();
@@ -48,15 +105,10 @@
         final boolean isSandboxContext = checkPermission(Manifest.permission.INTERNET,
                 Process.myPid(), Process.myUid()) != PackageManager.PERMISSION_GRANTED;
 
-        if (isSandboxContext) {
-            // SWE: Avoid initializing the engine for sandboxed processes.
-        } else {
-            // Initialize the SWE engine.
-            Engine.initialize((Context) this);
+        // SWE: Avoid initializing the engine for sandboxed processes.
+        if (!isSandboxContext) {
             BrowserSettings.initialize((Context) this);
             Preloader.initialize((Context) this);
-            //Enable remote debugging by default
-            Engine.setWebContentsDebuggingEnabled(true);
         }
 
     }
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index d140d5a..351cb96 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -17,7 +17,6 @@
 package com.android.browser;
 
 import android.app.Activity;
-import android.app.AlertDialog;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.Intent;
@@ -34,6 +33,8 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -42,11 +43,9 @@
 import com.android.browser.search.SearchEngine;
 import com.android.browser.stub.NullController;
 
-import org.codeaurora.swe.WebSettings;
 import org.codeaurora.swe.WebView;
-import org.chromium.content.browser.TracingControllerAndroid;
 
-public class BrowserActivity extends Activity {
+public class BrowserActivity extends Activity implements ViewTreeObserver.OnPreDrawListener {
 
     public static final String ACTION_SHOW_BOOKMARKS = "show_bookmarks";
     public static final String ACTION_SHOW_BROWSER = "show_browser";
@@ -59,8 +58,6 @@
     private final static boolean LOGV_ENABLED = Browser.LOGV_ENABLED;
 
     private ActivityController mController = NullController.INSTANCE;
-    private TracingControllerAndroid mTracingController;
-
 
     private Handler mHandler = new Handler();
 
@@ -78,12 +75,8 @@
         }
     };
 
-    private TracingControllerAndroid getTracingController() {
-        if (mTracingController == null) {
-            mTracingController = new TracingControllerAndroid(this);
-        }
-        return mTracingController;
-    }
+    private Bundle mSavedInstanceState;
+    private EngineInitializer mEngineInitializer;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -93,8 +86,6 @@
         }
         super.onCreate(icicle);
 
-        Thread.setDefaultUncaughtExceptionHandler(new CrashLogExceptionHandler(this));
-
         if (shouldIgnoreIntents()) {
             finish();
             return;
@@ -110,12 +101,37 @@
             return;
         }
         */
+
+        mEngineInitializer = EngineInitializer.getInstance();
+        mEngineInitializer.onActivityCreate(BrowserActivity.this);
+
+        Thread.setDefaultUncaughtExceptionHandler(new CrashLogExceptionHandler(this));
+
+        mSavedInstanceState = icicle;
+        // Create the initial UI views
         mController = createController();
 
-        Intent intent = (icicle == null) ? getIntent() : null;
-        mController.start(intent);
+        // Workaround for the black screen flicker on SurfaceView creation
+        ViewGroup topLayout = (ViewGroup) findViewById(R.id.main_content);
+        topLayout.requestTransparentRegion(topLayout);
+
+        // Add pre-draw listener to start the controller after engine initialization.
+        final ViewTreeObserver observer = getWindow().getDecorView().getViewTreeObserver();
+        observer.addOnPreDrawListener(this);
+
+        mEngineInitializer.initializeResourceExtractor(this);
     }
 
+    @Override
+    public boolean onPreDraw()
+    {
+        final ViewTreeObserver observer = getWindow().getDecorView().getViewTreeObserver();
+        observer.removeOnPreDrawListener(this);
+        mEngineInitializer.onPreDraw();
+        return true;
+    }
+
+
     public static boolean isTablet(Context context) {
         return context.getResources().getBoolean(R.bool.isTablet);
     }
@@ -137,6 +153,11 @@
         return controller;
     }
 
+    public void onEngineInitializationComplete() {
+        Intent intent = (mSavedInstanceState == null) ? getIntent() : null;
+        mController.start(intent);
+    }
+
     @VisibleForTesting
     //public to facilitate testing
     public Controller getController() {
@@ -146,6 +167,12 @@
     @Override
     protected void onNewIntent(Intent intent) {
         if (shouldIgnoreIntents()) return;
+        mEngineInitializer.onNewIntent(intent);
+        // Note: Do not add any more application logic in this method.
+        //       Move any additional app logic into handleOnNewIntent().
+    }
+
+    protected void handleOnNewIntent(Intent intent) {
         if (ACTION_RESTART.equals(intent.getAction())) {
             Bundle outState = new Bundle();
             mController.onSaveInstanceState(outState);
@@ -179,14 +206,24 @@
     }
 
     @Override
+    protected void onStart() {
+        super.onStart();
+        mEngineInitializer.onActivityStart();
+    }
+
+    @Override
     protected void onResume() {
         super.onResume();
         if (LOGV_ENABLED) {
             Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
         }
-        mController.onResume();
+        mEngineInitializer.onActivityResume();
+        // Note: Do not add any more application logic in this method.
+        //       Move any additional app logic into handleOnResume().
+    }
 
-        getTracingController().registerReceiver(this);
+    protected void handleOnResume() {
+        mController.onResume();
     }
 
     @Override
@@ -223,9 +260,14 @@
 
     @Override
     protected void onPause() {
-        mController.onPause();
+        mEngineInitializer.onActivityPause();
         super.onPause();
-        getTracingController().unregisterReceiver(this);
+        // Note: Do not add any more application logic in this method.
+        //       Move any additional app logic into handleOnPause().
+    }
+
+    protected void handleOnPause() {
+        mController.onPause();
     }
 
     @Override
@@ -234,6 +276,7 @@
             Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
         }
         super.onDestroy();
+        mEngineInitializer.onActivityDestroy();
         mController.onDestroy();
         mController = NullController.INSTANCE;
     }
@@ -315,8 +358,12 @@
     }
 
     @Override
-    protected void onActivityResult(int requestCode, int resultCode,
-            Intent intent) {
+    protected void onActivityResult (int requestCode, int resultCode,
+                                     Intent intent) {
+        mEngineInitializer.onActivityResult(requestCode, resultCode, intent);
+    }
+
+    protected void handleOnActivityResult (int requestCode, int resultCode, Intent intent) {
         mController.onActivityResult(requestCode, resultCode, intent);
     }
 
diff --git a/src/com/android/browser/BrowserLauncher.java b/src/com/android/browser/BrowserLauncher.java
new file mode 100644
index 0000000..a50b519
--- /dev/null
+++ b/src/com/android/browser/BrowserLauncher.java
@@ -0,0 +1,52 @@
+/*
+ *  Copyright (c) 2014 The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ *  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+// This is a NoShow activity for chain-loading BrowserActivity.
+// Avoid doing any heavy operations in this activity.
+public class BrowserLauncher extends Activity {
+
+    @Override
+    public void onCreate(Bundle paramBundle)
+    {
+        super.onCreate(paramBundle);
+        Intent localIntent = new Intent(getIntent());
+        localIntent.setClassName(getApplicationContext().getPackageName(), BrowserActivity.class.getName());
+        localIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(localIntent);
+        finish();
+    }
+}
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index b958854..255aaf8 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -130,6 +130,9 @@
 
     private static String sFactoryResetUrl;
 
+    private boolean mEngineInitialized = false;
+    private boolean mSyncManagedSettings = false;
+
     public static synchronized void initialize(final Context context) {
         if (sInstance == null)
             sInstance = new BrowserSettings(context);
@@ -142,7 +145,6 @@
     private BrowserSettings(Context context) {
         mContext = context.getApplicationContext();
         mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
-        mAutofillHandler = new AutofillHandler(mContext);
         mManagedSettings = new LinkedList<WeakReference<WebSettings>>();
         mCustomUserAgents = new WeakHashMap<WebSettings, String>();
         BackgroundHandler.execute(mSetup);
@@ -150,7 +152,16 @@
 
     public void setController(Controller controller) {
         mController = controller;
-        if (sInitialized) {
+        mNeedsSharedSync = true;
+    }
+
+    public void onEngineInitializationComplete() {
+        mEngineInitialized = true;
+        mAutofillHandler = new AutofillHandler(mContext);
+        if (mSyncManagedSettings) {
+            syncManagedSettings();
+        }
+        if (mNeedsSharedSync) {
             syncSharedSettings();
         }
     }
@@ -402,6 +413,11 @@
     }
 
     private void syncManagedSettings() {
+        if (!mEngineInitialized) {
+            mSyncManagedSettings = true;
+            return;
+        }
+        mSyncManagedSettings = false;
         syncSharedSettings();
         synchronized (mManagedSettings) {
             Iterator<WeakReference<WebSettings>> iter = mManagedSettings.iterator();
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 73940be..2bd09ba 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -200,7 +200,7 @@
     // FIXME, temp address onPrepareMenu performance problem.
     // When we move everything out of view, we should rewrite this.
     private int mCurrentMenuState = 0;
-    private int mMenuState = R.id.MAIN_MENU;
+    private int mMenuState = EMPTY_MENU;
     private int mOldMenuState = EMPTY_MENU;
     private Menu mCachedMenu;
 
@@ -279,6 +279,7 @@
 
     @Override
     public void start(final Intent intent) {
+        mMenuState = R.id.MAIN_MENU;
         WebView.setShouldMonitorWebCoreThread();
         // mCrashRecoverHandler has any previously saved state.
         mCrashRecoveryHandler.startRecovery(intent);
diff --git a/src/com/android/browser/EngineInitializer.java b/src/com/android/browser/EngineInitializer.java
new file mode 100644
index 0000000..10cbc0d
--- /dev/null
+++ b/src/com/android/browser/EngineInitializer.java
@@ -0,0 +1,315 @@
+/*
+ *  Copyright (c) 2014 The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ *  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import org.codeaurora.swe.Engine;
+
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+import com.google.common.annotations.VisibleForTesting;
+
+public class EngineInitializer {
+    private final static String LOGTAG = "EngineInitializer";
+
+    private BrowserActivity mActivity;
+
+    private boolean mNotifyActivity = false;
+    private boolean mActivityReady = false;
+    private boolean mActivityDestroyed = false;
+    private boolean mActivityStartPending = false;
+    private boolean mOnResumePending  = false;
+
+    private boolean mFirstDrawCompleted = false;
+    private boolean mLibraryLoaded = false;
+    private boolean mInitializationCompleted = false;
+
+    private Handler mUiThreadHandler;
+
+    class ActivityResult
+    {
+        public Intent data;
+        public int requestCode;
+        public int resultCode;
+
+        public ActivityResult(int requestCode, int resultCode, Intent data)
+        {
+            this.requestCode = requestCode;
+            this.resultCode = resultCode;
+            this.data = data;
+        }
+    }
+    private ArrayList<ActivityResult> mPendingActivityResults = null;
+    private ArrayList<Intent> mPendingIntents = null;
+
+    private static EngineInitializer sEngineInitializer = null;
+    public static  EngineInitializer getInstance() {
+        if (sEngineInitializer == null) {
+            sEngineInitializer = new EngineInitializer();
+        }
+        return sEngineInitializer;
+    }
+
+    private static long sDelayForTesting = 0;
+
+    @VisibleForTesting
+    public static void setDelayForTesting(long delay)
+    {
+        sDelayForTesting = delay;
+    }
+
+    private EngineInitializer() {
+        mUiThreadHandler = new Handler(Looper.getMainLooper());
+    }
+
+    @VisibleForTesting
+    public boolean isInitialized()
+    {
+        return mInitializationCompleted;
+    }
+
+    public boolean runningOnUiThread() {
+        return mUiThreadHandler.getLooper() == Looper.myLooper();
+    }
+
+    public void postOnUiThread(Runnable task) {
+        mUiThreadHandler.post(task);
+    }
+
+    private class InitializeTask extends AsyncTask<Void, Void, Boolean> {
+        public InitializeTask() {
+        }
+        @Override
+        protected Boolean doInBackground(Void... unused) {
+            try
+            {
+                // For testing.
+                if (sDelayForTesting > 0) {
+                    Thread.sleep(sDelayForTesting);
+                }
+
+                Engine.loadNativeLibraries(mActivity.getApplicationContext());
+
+                Engine.warmUpChildProcess(mActivity.getApplicationContext());
+
+                return true;
+            }
+            catch (Exception e)
+            {
+                Log.e(LOGTAG, "Unable to load native library.", e);
+            }
+            return false;
+        }
+
+        @Override
+        protected void onPostExecute (Boolean result) {
+            mLibraryLoaded = true;
+            if (mFirstDrawCompleted) {
+                completeInitializationOnUiThread(mActivity.getApplicationContext());
+            }
+        }
+    }
+    private InitializeTask mInitializeTask = null;
+
+    public void initializeSync(Context ctx) {
+        assert runningOnUiThread() : "Tried to initialize the engine on the wrong thread.";
+
+        if (mInitializeTask != null) {
+            try {
+                // Wait for the InitializeTask to finish.
+                mInitializeTask.get();
+            } catch (CancellationException e1) {
+                Log.e(LOGTAG, "Native library load cancelled", e1);
+            } catch (ExecutionException e2) {
+                Log.e(LOGTAG, "Native library load failed", e2);
+            } catch (InterruptedException e3) {
+                Log.e(LOGTAG, "Native library load interrupted", e3);
+            }
+        }
+        completeInitializationOnUiThread(ctx);
+    }
+
+    private void reset(BrowserActivity newActivity) {
+        mActivity = newActivity;
+        mActivityStartPending = false;
+        mOnResumePending  = false;
+        mNotifyActivity = true;
+        mActivityReady = false;
+        mPendingIntents = null;
+        mPendingActivityResults = null;
+        mFirstDrawCompleted = false;
+        mActivityDestroyed = false;
+    }
+
+    public void onActivityCreate(BrowserActivity activity) {
+        assert runningOnUiThread() : "Tried to initialize the engine on the wrong thread.";
+        reset(activity);
+        if (!mInitializationCompleted) {
+            Engine.initializeCommandLine(mActivity.getApplicationContext());
+            mInitializeTask = new InitializeTask();
+            mInitializeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        }
+    }
+
+    private void completeInitialization() {
+        postOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                completeInitializationOnUiThread(mActivity.getApplicationContext());
+            }
+        });
+    }
+
+    private void completeInitializationOnUiThread(Context ctx) {
+        assert runningOnUiThread() : "Tried to initialize the engine on the wrong thread.";
+
+        if (!mInitializationCompleted) {
+            // TODO: Evaluate the benefit of async Engine.initialize()
+            Engine.initialize(ctx);
+            //Enable remote debugging by default
+            Engine.setWebContentsDebuggingEnabled(true);
+            mInitializationCompleted = true;
+            mLibraryLoaded = true;
+            BrowserSettings.getInstance().onEngineInitializationComplete();
+        }
+        if (mActivity != null && mNotifyActivity) {
+            mNotifyActivity = false;
+            postOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mActivity.onEngineInitializationComplete();
+                    mActivityReady = true;
+                    processPendingEvents();
+                }
+            });
+        }
+
+    }
+
+    private void processPendingEvents() {
+        assert runningOnUiThread() : "Tried to initialize the engine on the wrong thread.";
+
+        if (mActivityStartPending) {
+            mActivityStartPending = false;
+            onActivityStart();
+        }
+        if (mPendingIntents != null) {
+            for (int i = 0; i < mPendingIntents.size(); i++) {
+                mActivity.handleOnNewIntent(mPendingIntents.get(i));
+            }
+            mPendingIntents = null;
+        }
+        if (mPendingActivityResults != null) {
+            for (int i = 0; i < mPendingActivityResults.size(); i++) {
+                ActivityResult result = mPendingActivityResults.get(i);
+                mActivity.handleOnActivityResult(result.requestCode, result.resultCode, result.data);
+            }
+            mPendingActivityResults = null;
+        }
+        if (mOnResumePending && !mActivityDestroyed) {
+            onActivityResume();
+        }
+        mOnResumePending = false;
+    }
+
+    public void onPreDraw() {
+        mFirstDrawCompleted = true;
+        if (mLibraryLoaded) {
+            completeInitialization();
+        }
+    }
+
+    public void initializeResourceExtractor(Context ctx) {
+        Engine.startExtractingResources(ctx);
+    }
+
+    public void onActivityPause() {
+        mOnResumePending = false;
+        if (mActivityReady) {
+            Engine.pauseTracing(mActivity.getApplicationContext());
+        }
+    }
+
+    public void onActivityResume() {
+        if (mActivityReady) {
+            Engine.resumeTracing(mActivity.getApplicationContext());
+            mActivity.handleOnResume();
+            return;
+        }
+        mOnResumePending = true;
+    }
+
+    public void onActivityStart() {
+        if (mActivityReady) {
+            // TODO: We have no reliable mechanism to know when the app goes background.
+            //ChildProcessLauncher.onBroughtToForeground();
+            return;
+        }
+        mActivityStartPending = true;
+    }
+
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (mActivityReady) {
+            mActivity.handleOnActivityResult(requestCode, resultCode, data);
+            return;
+        }
+        if (mPendingActivityResults == null) {
+            mPendingActivityResults = new ArrayList<ActivityResult>(1);
+        }
+        mPendingActivityResults.add(new ActivityResult(requestCode, resultCode, data));
+    }
+
+    public void onNewIntent(Intent intent) {
+        if (mActivityReady) {
+            mActivity.handleOnNewIntent(intent);
+            return;
+        }
+
+        if (mPendingIntents == null) {
+            mPendingIntents = new ArrayList<Intent>(1);
+        }
+        mPendingIntents.add(intent);
+    }
+
+    public void onActivityDestroy() {
+        mActivityDestroyed = true;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/com/android/browser/LocationButton.java b/src/com/android/browser/LocationButton.java
index e805e43..4136d6e 100644
--- a/src/com/android/browser/LocationButton.java
+++ b/src/com/android/browser/LocationButton.java
@@ -74,9 +74,15 @@
         init();
     }
 
-    private void init() {
-        mGeolocationPermissions = GeolocationPermissions.getInstance();
+    private void updateGeolocationPermissions() {
+        mGeolocationPermissions = mCurrentIncognito ?
+                                    GeolocationPermissions.getIncognitoInstance() :
+                                    GeolocationPermissions.getInstance();
         mGeolocationPermissions.registerOnGeolocationPolicyModifiedListener(this);
+    }
+
+    // TODO: Perform this initilalization only after the engine initialization is complete.
+    private void init() {
         mCurrentTabId = -1;
         mCurrentOrigin = null;
         mCurrentIncognito = false;
@@ -86,11 +92,8 @@
             public void onClick(View v) {
                 if (!mCurrentOrigin.isEmpty()) {
                     final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-                    final GeolocationPermissions geolocationPermissions =
-                            (mCurrentIncognito ?
-                                    GeolocationPermissions.getIncognitoInstance() :
-                                    GeolocationPermissions.getInstance());
-
+                    updateGeolocationPermissions();
+                    final GeolocationPermissions geolocationPermissions = mGeolocationPermissions;
                     DialogInterface.OnClickListener alertDialogListener =
                             new AlertDialog.OnClickListener() {
                         public void onClick(DialogInterface dlg, int which) {
@@ -182,16 +185,6 @@
         if (mCurrentTabId != tabId) {
             mCurrentTabId = tabId;
             mCurrentOrigin = origin;
-
-            // Switch GeolocationPermissions if we went from a regular to an
-            // incognito tab or vice versa
-            if (mCurrentIncognito != incognito) {
-                mCurrentIncognito = incognito;
-                mGeolocationPermissions = mCurrentIncognito ?
-                        GeolocationPermissions.getIncognitoInstance() :
-                            GeolocationPermissions.getInstance();
-                mGeolocationPermissions.registerOnGeolocationPolicyModifiedListener(this);
-            }
             update();
         }
         // Update icon if we are in the same tab and origin has changed
@@ -205,6 +198,7 @@
 
     public void update() {
         if (mCurrentOrigin != null) {
+            updateGeolocationPermissions();
             mGeolocationPermissions.hasOrigin(mCurrentOrigin,
                     new ValueCallback<Boolean>() {
                 public void onReceiveValue(Boolean hasOrigin) {
diff --git a/src/com/android/browser/PageProgressView.java b/src/com/android/browser/PageProgressView.java
index f512cef..2b73e45 100644
--- a/src/com/android/browser/PageProgressView.java
+++ b/src/com/android/browser/PageProgressView.java
@@ -114,4 +114,8 @@
         d.draw(canvas);
     }
 
+    public void onProgressStarted() {
+        mCurrentProgress = 0;
+        mTargetProgress = 0;
+    }
 }
diff --git a/src/com/android/browser/PhoneUi.java b/src/com/android/browser/PhoneUi.java
index d74e988..a2799b3 100644
--- a/src/com/android/browser/PhoneUi.java
+++ b/src/com/android/browser/PhoneUi.java
@@ -251,18 +251,21 @@
     }
 
     void showNavScreen() {
-        mShowNav = true;
-        dismissIME();
-        mUiController.setBlockEvents(true);
+        WebView webView = getWebView();
+        if (webView != null) {
+            mShowNav = true;
+            dismissIME();
+            mUiController.setBlockEvents(true);
 
-        getWebView()
-            .getContentBitmapAsync(1.0f,
-                new Rect(),
-                new ValueCallback<Bitmap>() {
-                    @Override
-                    public void onReceiveValue(Bitmap bitmap) {
-                        onShowNavScreenContinue(bitmap);
-                    }});
+            webView.getContentBitmapAsync(1.0f,
+                            new Rect(),
+                            new ValueCallback<Bitmap>() {
+                                @Override
+                                public void onReceiveValue(Bitmap bitmap) {
+                                    onShowNavScreenContinue(bitmap);
+                                }
+                            });
+        }
     }
 
     void onShowNavScreenContinue(Bitmap viewportBitmap) {
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index ea7a511..320e4c8 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -1793,9 +1793,6 @@
      * Get the title of this tab.
      */
     String getTitle() {
-        if (mCurrentState.mTitle == null && mInPageLoad) {
-            return mContext.getString(R.string.title_bar_loading);
-        }
         return mCurrentState.mTitle;
     }
 
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
index 5d5f1e3..4fc56c6 100644
--- a/src/com/android/browser/TitleBar.java
+++ b/src/com/android/browser/TitleBar.java
@@ -263,9 +263,6 @@
      * Update the progress, from 0 to 100.
      */
     public void setProgress(int newProgress) {
-        Tab tab = mBaseUi.getActiveTab();
-        WebView view = tab != null ? tab.getWebView() : null;
-
         if (newProgress >= PROGRESS_MAX) {
             mProgress.setProgress(PageProgressView.MAX_PROGRESS);
             mProgress.setVisibility(View.GONE);
@@ -287,7 +284,7 @@
                 mProgress.setVisibility(View.VISIBLE);
                 mInLoad = true;
                 mNavBar.onProgressStarted();
-
+                mProgress.onProgressStarted();
                 //onPageStarted
                 showTopControls();
             }
diff --git a/src/com/android/browser/test/EngineInitializerTest.java b/src/com/android/browser/test/EngineInitializerTest.java
new file mode 100644
index 0000000..f1d8acc
--- /dev/null
+++ b/src/com/android/browser/test/EngineInitializerTest.java
@@ -0,0 +1,212 @@
+/*
+ *  Copyright (c) 2014 The Linux Foundation. All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *      * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ *  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ *  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser.test;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.test.InstrumentationTestCase;
+import android.view.KeyEvent;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.BrowserPreferencesPage;
+import com.android.browser.EngineInitializer;
+
+public class EngineInitializerTest extends InstrumentationTestCase {
+
+    private Instrumentation mInstrumentation;
+    private Context mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mInstrumentation = getInstrumentation();
+        mContext = getInstrumentation().getTargetContext();
+    }
+
+
+    public void test01() throws Throwable {
+        Intent localIntent = new Intent();
+        localIntent.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        localIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity activity = mInstrumentation.startActivitySync(localIntent);
+
+        Intent restart = new Intent(BrowserActivity.ACTION_RESTART, null);
+        restart.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        restart.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        mInstrumentation.callActivityOnNewIntent(activity, restart);
+
+        Thread.sleep(2000);
+
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(EngineInitializer.getInstance().isInitialized(), true);
+
+    }
+
+
+    public void test02() throws Throwable {
+
+        EngineInitializer.setDelayForTesting(1000);
+
+        Intent localIntent = new Intent();
+        localIntent.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        localIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity browserActivity = mInstrumentation.startActivitySync(localIntent);
+
+        Intent pref = new Intent();
+        pref.setClassName("com.android.swe.browser", BrowserPreferencesPage.class.getName());
+        pref.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity preferencesActivity = mInstrumentation.startActivitySync(pref);
+
+        Thread.sleep(2000);
+
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(EngineInitializer.getInstance().isInitialized(), true);
+    }
+
+
+    public void test03() throws Throwable {
+
+        EngineInitializer.setDelayForTesting(2000);
+
+        Intent pref = new Intent();
+        pref.setClassName("com.android.swe.browser", BrowserPreferencesPage.class.getName());
+        pref.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity preferencesActivity = mInstrumentation.startActivitySync(pref);
+
+        assertEquals(EngineInitializer.getInstance().isInitialized(), true);
+
+        Intent localIntent = new Intent();
+        localIntent.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        localIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity browserActivity = mInstrumentation.startActivitySync(localIntent);
+
+        Thread.sleep(3000);
+
+        mInstrumentation.waitForIdleSync();
+
+    }
+
+
+    public void test04() throws Throwable {
+
+        EngineInitializer.setDelayForTesting(2000);
+
+
+        Intent localIntent = new Intent();
+        localIntent.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        localIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity browserActivity = mInstrumentation.startActivitySync(localIntent);
+
+        final Intent restart = new Intent(BrowserActivity.ACTION_RESTART, null);
+        restart.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        restart.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        runTestOnUiThread(new Runnable () {
+            @Override
+            public void run() {
+                mInstrumentation.callActivityOnNewIntent(browserActivity, restart);
+            }
+        });
+
+        Intent pref = new Intent();
+        pref.setClassName("com.android.swe.browser", BrowserPreferencesPage.class.getName());
+        pref.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+        final Activity preferencesActivity = mInstrumentation.startActivitySync(pref);
+
+        Thread.sleep(3000);
+
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(EngineInitializer.getInstance().isInitialized(), true);
+    }
+
+
+    public void test05() throws Throwable {
+
+        EngineInitializer.setDelayForTesting(2000);
+
+
+        Intent localIntent = new Intent();
+        localIntent.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        localIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity browserActivity = mInstrumentation.startActivitySync(localIntent);
+
+        runTestOnUiThread(new Runnable () {
+            @Override
+            public void run() {
+                Bundle outState = new Bundle();
+                mInstrumentation.callActivityOnSaveInstanceState(browserActivity, outState);
+                mInstrumentation.callActivityOnDestroy(browserActivity);
+            }
+        });
+
+        Thread.sleep(3000);
+
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(EngineInitializer.getInstance().isInitialized(), true);
+    }
+
+
+    public void test06() throws Throwable {
+
+        EngineInitializer.setDelayForTesting(2000);
+
+
+        Intent localIntent = new Intent();
+        localIntent.setClassName("com.android.swe.browser", BrowserActivity.class.getName());
+        localIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Activity browserActivity = mInstrumentation.startActivitySync(localIntent);
+
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+
+        Thread.sleep(3000);
+
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(EngineInitializer.getInstance().isInitialized(), true);
+    }
+
+}