Temporal edge navigation feature

- User can swipe from left and right edge to go
  back and forward in history of a tab

Change-Id: I941f91122510b004bdcb6a718eb6c05730694960
diff --git a/res/drawable-xxhdpi/deco_swipe_spatial.png b/res/drawable-xxhdpi/deco_swipe_spatial.png
new file mode 100644
index 0000000..d95ef9b
--- /dev/null
+++ b/res/drawable-xxhdpi/deco_swipe_spatial.png
Binary files differ
diff --git a/res/drawable-xxhdpi/deco_swipe_temporal.png b/res/drawable-xxhdpi/deco_swipe_temporal.png
new file mode 100644
index 0000000..30529c1
--- /dev/null
+++ b/res/drawable-xxhdpi/deco_swipe_temporal.png
Binary files differ
diff --git a/res/drawable-xxhdpi/left_shade.png b/res/drawable-xxhdpi/left_shade.png
new file mode 100644
index 0000000..94f7c1c
--- /dev/null
+++ b/res/drawable-xxhdpi/left_shade.png
Binary files differ
diff --git a/res/drawable-xxhdpi/right_shade.png b/res/drawable-xxhdpi/right_shade.png
new file mode 100644
index 0000000..3ae50fa
--- /dev/null
+++ b/res/drawable-xxhdpi/right_shade.png
Binary files differ
diff --git a/res/layout/tab.xml b/res/layout/tab.xml
index 8bf3327..0c25901 100755
--- a/res/layout/tab.xml
+++ b/res/layout/tab.xml
@@ -19,21 +19,127 @@
      Currently, the only such element is the Geolocation permissions prompt.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.browser.DraggableFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/draggable_mainframe"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+    <ImageView android:id="@+id/navview_opacity"
+        android:layout_gravity="top"
+        android:scaleType="fitStart"
+        android:background="@android:color/black"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <ImageView android:id="@+id/stationary_navview"
+        android:layout_gravity="top"
+        android:scaleType="fitStart"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <!-- Wrapper layout for the WebView, which must be in a FrameLayout. -->
     <FrameLayout android:id="@+id/webview_wrapper"
         android:layout_width="match_parent"
-        android:layout_height="0dip"
+        android:layout_height="match_parent"
         android:layout_weight="1" />
 
+    <ImageView android:id="@+id/sliding_navview"
+        android:layout_gravity="top"
+        android:scaleType="fitStart"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <ImageView android:id="@+id/sliding_navview_shadow"
+        android:background="@drawable/left_shade"
+        android:layout_gravity="top"
+        android:scaleType="fitStart"
+        android:visibility="gone"
+        android:layout_width="10dp"
+        android:layout_height="match_parent" />
+
+    <LinearLayout android:id="@+id/edge_sliding_settings"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        android:background="#00838f"
+        android:alpha="0.85"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center_horizontal|center_vertical"
+            android:layout_weight=".25"
+            android:textSize="32sp"
+            android:textColor="@color/white"
+            android:text="@string/pref_edge_swipe_title"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textSize="21sp"
+            android:textColor="@color/white"
+            android:text="@string/pref_edge_swipe_option_msg"/>
+
+        <RadioGroup
+            android:id="@+id/edge_sliding_settings_options"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight=".25"
+            android:layout_gravity="center_horizontal"
+            android:padding="10dp"
+            android:orientation="vertical">
+            <RadioButton
+                android:id="@+id/edge_sliding_settings_options_temporal"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/pref_temporal_edge_swipe"
+                android:textColor="@color/white"
+                android:drawableEnd="@drawable/deco_swipe_temporal"
+                android:textSize="18sp"
+                android:padding="10dp"/>
+            <RadioButton
+                android:id="@+id/edge_sliding_settings_options_spatial"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/pref_spatial_edge_swipe"
+                android:textColor="@color/white"
+                android:drawableEnd="@drawable/deco_swipe_spatial"
+                android:textSize="18sp"
+                android:visibility="gone"
+                android:padding="10dp"/>
+            <RadioButton
+                android:id="@+id/edge_sliding_settings_options_disabled"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/pref_disable_edge_swipe"
+                android:textColor="@color/white"
+                android:textSize="18sp"
+                android:padding="10dp"/>
+        </RadioGroup>
+
+        <Button
+            android:id="@+id/edge_sliding_settings_close_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/pref_edge_swipe_option_close"
+            android:padding="10dp"
+            android:gravity="center_horizontal|center_vertical"
+            android:layout_gravity="center"
+            style="@android:style/Widget.Holo.Button.Borderless"/>
+
+    </LinearLayout>
+
     <!-- Geolocation permissions prompt -->
     <ViewStub android:id="@+id/geolocation_permissions_prompt"
         android:layout="@layout/geolocation_permissions_prompt"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 
-</LinearLayout>
+
+</com.android.browser.DraggableFrameLayout>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 7f0e63e..0cb0edf 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -503,4 +503,11 @@
 
     <!-- Toast message displayed when user attempts to modify a setting disabled by the administrator -->
     <string name="mdm_managed_alert">被管理员禁用</string>
+
+    <string name="pref_edge_swipe_title">边缘滑动</string>
+    <string name="pref_edge_swipe_option_msg">滑过屏幕边缘时,您想:</string>
+    <string name="pref_temporal_edge_swipe">上一个/下一个网站</string>
+    <string name="pref_spatial_edge_swipe">更换标签页</string>
+    <string name="pref_disable_edge_swipe">禁用边缘滑动</string>
+    <string name="pref_edge_swipe_option_close">关闭</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 225ba12..77340b0 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -500,4 +500,10 @@
     <string name="accessibility_transition_navscreen" msgid="2734915619351519547">"標籤管理"</string>
     <string name="accessibility_button_bookmarks_folder_up" msgid="9179389954714270505">"上一個資料夾"</string>
     <string name="mdm_managed_alert">被管理員禁用</string>
+    <string name="pref_edge_swipe_title">邊緣滑動</string>
+    <string name="pref_edge_swipe_option_msg">滑過邊緣時,您想:</string>
+    <string name="pref_temporal_edge_swipe">上一個/下一個網站</string>
+    <string name="pref_spatial_edge_swipe">更換標籤頁</string>
+    <string name="pref_disable_edge_swipe">禁用邊緣滑動</string>
+    <string name="pref_edge_swipe_option_close">關閉</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 3ff6708..0c3486d 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -412,4 +412,10 @@
     <string name="help">"幫助"</string>
     <string name="feedback">"反饋"</string>
     <string name="mdm_managed_alert">被管理員禁用</string>
+    <string name="pref_edge_swipe_title">邊緣滑動</string>
+    <string name="pref_edge_swipe_option_msg">滑過邊緣時,您想:</string>
+    <string name="pref_temporal_edge_swipe">上一個/下一個網站</string>
+    <string name="pref_spatial_edge_swipe">更換標籤頁</string>
+    <string name="pref_disable_edge_swipe">禁用邊緣滑動</string>
+    <string name="pref_edge_swipe_option_close">關閉</string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fdfcc9d..7d2ed69 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -638,6 +638,36 @@
         <item>auto</item>
     </string-array>
     <string name="pref_default_text_encoding_default">UTF-8</string>
+    <string name="pref_edge_swipe_title">Edge Swipe</string>
+    <string name="pref_edge_swipe_option_msg">When swiping, would you like:</string>
+    <string name="pref_temporal_edge_swipe">Previous/Next Web Site</string>
+    <string name="pref_spatial_edge_swipe">Change Tab</string>
+    <string name="pref_disable_edge_swipe">Disable edge swipe</string>
+    <string name="pref_edge_swipe_option_close">Close</string>
+    <string name="pref_temporal_edge_swipe_enabled_toast">Swipe from edge to navigate</string>
+    <string name="pref_spatial_edge_swipe_enabled_toast">Swipe from edge to change tabs</string>
+    <string name="pref_edge_swipe_disabled_toast">Swiping from edge disabled</string>
+    <string-array name="pref_edge_swiping_choices">
+        <item>@string/pref_temporal_edge_swipe</item>
+<!--
+        <item>@string/pref_spatial_edge_swipe</item>
+-->
+        <item>@string/pref_disable_edge_swipe</item>
+    </string-array>
+
+    <string name="value_temporal_edge_swipe"  translatable="false">temporal_edge_swipe</string>
+    <string name="value_spatial_edge_swipe"  translatable="false">spatial_edge_swipe</string>
+    <string name="value_disable_edge_swipe"  translatable="false">disable_edge_swipe</string>
+    <string name="value_unknown_edge_swipe"  translatable="false">unknown_edge_swipe</string>
+
+    <string-array name="pref_edge_swiping_values" translatable="false">
+        <item>@string/value_temporal_edge_swipe</item>
+<!--
+        <item>@string/value_spatial_edge_swipe</item>
+-->
+        <item>@string/value_disable_edge_swipe</item>
+    </string-array>
+
     <!-- Title for accessibility settings [CHAR LIMIT=25] -->
     <string name="pref_accessibility_title">Accessibility</string>
     <!-- Font size settings category under accessibility settings [CHAR LIMIT=50] -->
diff --git a/res/xml/general_preferences.xml b/res/xml/general_preferences.xml
index 662ef30..abd7b73 100644
--- a/res/xml/general_preferences.xml
+++ b/res/xml/general_preferences.xml
@@ -200,6 +200,13 @@
             android:summary="@string/pref_extras_website_settings_summary"
             android:title="@string/pref_extras_website_settings" />
 
+        <ListPreference
+            android:dialogTitle="@string/pref_edge_swipe_option_msg"
+            android:entries="@array/pref_edge_swiping_choices"
+            android:entryValues="@array/pref_edge_swiping_values"
+            android:key="edge_swiping_action"
+            android:title="@string/pref_edge_swipe_title" />
+
         <com.android.browser.BrowserYesNoPreference
             android:key="reset_default_preferences"
             android:title="@string/pref_extras_reset_default"
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index 089c7f0..c24cc48 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -109,6 +109,9 @@
     private NavigationBarBase mNavigationBar;
     private boolean mBlockFocusAnimations;
 
+    private EdgeSwipeController mEdgeSwipeController;
+    private EdgeSwipeSettings mEdgeSwipeSettings;
+
     public BaseUi(Activity browser, UiController controller) {
         mActivity = browser;
         mUiController = controller;
@@ -205,6 +208,12 @@
     }
 
     public void onConfigurationChanged(Configuration config) {
+        if (mEdgeSwipeController != null) {
+            mEdgeSwipeController.onConfigurationChanged();
+        }
+        if (mEdgeSwipeSettings != null) {
+            mEdgeSwipeSettings.onConfigurationChanged();
+        }
     }
 
     public Activity getActivity() {
@@ -442,9 +451,49 @@
             }
             mContentView.addView(container, COVER_SCREEN_PARAMS);
         }
+
+        refreshEdgeSwipeController(container);
+
         mUiController.attachSubWindow(tab);
     }
 
+    public void refreshEdgeSwipeController(View container) {
+        if (mEdgeSwipeController != null) {
+            mEdgeSwipeController.cleanup();
+        }
+
+        mEdgeSwipeSettings = null;
+
+        String action = BrowserSettings.getInstance().getEdgeSwipeAction();
+
+        if (action.equalsIgnoreCase(
+                mActivity.getResources().getString(R.string.value_temporal_edge_swipe))) {
+            mEdgeSwipeController = new EdgeSwipeController(
+                    container,
+                    R.id.stationary_navview,
+                    R.id.sliding_navview,
+                    R.id.sliding_navview_shadow,
+                    R.id.navview_opacity,
+                    R.id.webview_wrapper,
+                    R.id.draggable_mainframe,
+                    this);
+        } else if (action.equalsIgnoreCase(
+                mActivity.getResources().getString(R.string.value_unknown_edge_swipe))) {
+            mEdgeSwipeSettings = new EdgeSwipeSettings(
+                    container,
+                    R.id.stationary_navview,
+                    R.id.edge_sliding_settings,
+                    R.id.sliding_navview_shadow,
+                    R.id.webview_wrapper,
+                    R.id.draggable_mainframe,
+                    this);
+        } else {
+            DraggableFrameLayout draggableView = (DraggableFrameLayout)
+                    container.findViewById(R.id.draggable_mainframe);
+            draggableView.setDragHelper(null);
+        }
+    }
+
     private void removeTabFromContentView(Tab tab) {
         hideTitleBar();
         // Remove the container that contains the main WebView.
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 36f28b1..a60a109 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -221,6 +221,12 @@
                 mPrefs.edit().putString(PREF_DEFAULT_TEXT_ENCODING, "auto").apply();
             }
 
+            if (!mPrefs.contains(PREF_EDGE_SWIPE)) {
+                mPrefs.edit().putString(PREF_EDGE_SWIPE,
+                        mContext.getResources().getString(
+                                R.string.value_unknown_edge_swipe)).apply();
+            }
+
             if (sFactoryResetUrl.indexOf("{CID}") != -1) {
                 sFactoryResetUrl = sFactoryResetUrl.replace("{CID}",
                     BrowserProvider.getClientId(mContext.getContentResolver()));
@@ -759,6 +765,26 @@
         return mPrefs.getString(PREF_DEFAULT_TEXT_ENCODING, "auto");
     }
 
+    public String getEdgeSwipeAction() {
+        return mPrefs.getString(PREF_EDGE_SWIPE,
+                mContext.getResources().getString(R.string.value_unknown_edge_swipe));
+    }
+
+    public void setEdgeSwipeTemporal() {
+        mPrefs.edit().putString(PREF_EDGE_SWIPE,
+                mContext.getResources().getString(R.string.value_temporal_edge_swipe)).apply();
+    }
+
+    public void setEdgeSwipeSpatial() {
+        mPrefs.edit().putString(PREF_EDGE_SWIPE,
+                mContext.getResources().getString(R.string.value_spatial_edge_swipe)).apply();
+    }
+
+    public void setEdgeSwipeDisabled() {
+        mPrefs.edit().putString(PREF_EDGE_SWIPE,
+                mContext.getResources().getString(R.string.value_disable_edge_swipe)).apply();
+    }
+
     // -----------------------------
     // getter/setters for general_preferences.xml
     // -----------------------------
diff --git a/src/com/android/browser/DraggableFrameLayout.java b/src/com/android/browser/DraggableFrameLayout.java
new file mode 100644
index 0000000..e2f9699
--- /dev/null
+++ b/src/com/android/browser/DraggableFrameLayout.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015, 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.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+public class DraggableFrameLayout extends FrameLayout {
+    private ViewDragHelper mDragger;
+    public DraggableFrameLayout(Context ctx) {
+        super(ctx);
+    }
+    public DraggableFrameLayout(Context ctx, AttributeSet set) {
+        super(ctx, set);
+    }
+    public DraggableFrameLayout(Context ctx, AttributeSet set, int defStyleAttr) {
+        super(ctx, set, defStyleAttr);
+    }
+    public DraggableFrameLayout(Context ctx, AttributeSet set, int defStyleAttr, int defStyleRes) {
+        super(ctx, set, defStyleAttr, defStyleRes);
+    }
+
+    public void setDragHelper(ViewDragHelper dragger) {
+        mDragger = dragger;
+    }
+
+    public final ViewGroup getParentViewGroup() {
+        return (ViewGroup) DraggableFrameLayout.this.getParent();
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (mDragger == null)
+            return super.onInterceptTouchEvent(event);
+
+        return mDragger.shouldInterceptTouchEvent(event) ?
+                true : super.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mDragger == null)
+            return super.onTouchEvent(event);
+
+        mDragger.processTouchEvent(event);
+        return true;
+    }
+
+    @Override
+    public void computeScroll() {
+        super.computeScroll();
+        if (mDragger != null && mDragger.continueSettling(true)) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+}
+
diff --git a/src/com/android/browser/EdgeSwipeController.java b/src/com/android/browser/EdgeSwipeController.java
new file mode 100644
index 0000000..87ad2e7
--- /dev/null
+++ b/src/com/android/browser/EdgeSwipeController.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2015, 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.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.os.CountDownTimer;
+import android.support.v4.widget.ViewDragHelper;
+import android.view.View;
+
+import org.codeaurora.swe.WebHistoryItem;
+import org.codeaurora.swe.util.Activator;
+import org.codeaurora.swe.util.Observable;
+
+public class EdgeSwipeController extends ViewDragHelper.Callback {
+    private ViewDragHelper mDragHelper;
+    private int mState = ViewDragHelper.STATE_IDLE;
+    private int mFromEdge = ViewDragHelper.EDGE_LEFT;
+    private boolean mbNavigated = false;
+    private int mOldX = 0;
+    private int mOldDx = 0;
+    private Observable mPageLoadTarget;
+    private Observable mPageLoadObservable;
+
+    private boolean mbCurrBMSynced = false;
+
+    private Tab mActiveTab;
+    private TitleBar mTitleBar;
+
+    private static final float mMinAlpha = 0.5f;
+    private static final int mMinProgress = 85;
+    private static final int mProgressWaitMS = 1000;
+    private static final int EDGE_SWIPE_INVALID_INDEX = -2;
+
+    private CountDownTimer mLoadTimer, mCommitTimer;
+
+    private int mCurrIndex = EDGE_SWIPE_INVALID_INDEX;
+    private int mPrevIndex;
+    private int mNextIndex;
+    private int mMaxIndex;
+
+    private EdgeSwipeModel mModel;
+    private EdgeSwipeView mView;
+
+    public EdgeSwipeController(View container,
+                               int stationaryViewId,
+                               int slidingViewId,
+                               int slidingViewShadowId,
+                               int opacityViewId,
+                               int liveViewId,
+                               int viewGroupId,
+                               BaseUi ui) {
+        DraggableFrameLayout viewGroup = (DraggableFrameLayout)
+                container.findViewById(viewGroupId);
+
+        mActiveTab = ui.getActiveTab();
+        mTitleBar = ui.getTitleBar();
+
+        mModel = new EdgeSwipeModel(mActiveTab, mTitleBar);
+        mView = new EdgeSwipeView(
+                container,
+                stationaryViewId,
+                slidingViewId,
+                slidingViewShadowId,
+                opacityViewId,
+                liveViewId,
+                viewGroupId,
+                mTitleBar);
+
+        mPageLoadTarget = mActiveTab.getTabHistoryUpdateObservable();
+        mPageLoadObservable = Activator.activate(
+                new Observable.Observer() {
+                    @Override
+                    public void onChange(Object... params) {
+                        if (mDragHelper == null ||
+                                mPageLoadTarget == null) {
+                            return;
+                        }
+
+                        synchronized (this) {
+                            int index = (int) params[0];
+                            if (mState == ViewDragHelper.STATE_IDLE && index == mCurrIndex) {
+                                monitorProgressAtHistoryUpdate(index);
+                            }
+                        }
+                    }
+                },
+                mPageLoadTarget
+        );
+
+        mDragHelper = ViewDragHelper.create(viewGroup, 0.5f, this);
+        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_RIGHT);
+        viewGroup.setDragHelper(mDragHelper);
+    }
+
+    private void swipeSessionCleanup() {
+        mView.goLive();
+        mModel.cleanup();
+        mCurrIndex = EDGE_SWIPE_INVALID_INDEX;
+        mState = ViewDragHelper.STATE_IDLE;
+    }
+
+    private boolean setState(int curState, int newState) {
+        if (mState == curState) {
+            mState = newState;
+            return true;
+        }
+        return false;
+    }
+
+    public void cleanup() {
+        if (mPageLoadObservable != null) {
+            mPageLoadObservable.onOff(false);
+            synchronized (this) {
+                mDragHelper.cancel();
+                swipeSessionCleanup();
+            }
+        }
+    }
+
+    public void onConfigurationChanged() {
+        synchronized (this) {
+            swipeSessionCleanup();
+        }
+    }
+
+    private void showCurrBMInStationaryView() {
+        if (!mbCurrBMSynced) {
+            Bitmap currBM = mModel.readSnapshot(mCurrIndex);
+            if (currBM != null) {
+                mView.setStationaryViewBitmap(currBM);
+                mbCurrBMSynced = true;
+            }
+        }
+    }
+
+    private void showCurrBMInSlidingView() {
+        if (!mbCurrBMSynced) {
+            Bitmap currBM = mModel.readSnapshot(mCurrIndex);
+            mView.setSlidingViewBitmap(currBM);
+            if (currBM != null) {
+                mbCurrBMSynced = true;
+            }
+        }
+    }
+
+    private Bitmap getGrayscale(Bitmap bitmap)
+    {
+        if (bitmap == null)
+            return null;
+
+        int height = bitmap.getHeight();
+        int width = bitmap.getWidth();
+
+        Bitmap gray = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(gray);
+        Paint paint = new Paint();
+        ColorMatrix cm = new ColorMatrix();
+
+        cm.setSaturation(0);
+
+        ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
+
+        paint.setColorFilter(f);
+
+        c.drawBitmap(bitmap, 0, 0, paint);
+        return gray;
+    }
+
+    private void monitorProgressAtLoad(final int pageIndex) {
+        if (mLoadTimer != null) {
+            mLoadTimer.cancel();
+        }
+
+        mLoadTimer = new CountDownTimer(mProgressWaitMS * 5, mProgressWaitMS) {
+            boolean mGrayBM = false;
+
+            public void onTick(long msRemain) {
+                if (msRemain > mProgressWaitMS * 4) {
+                    return;
+                }
+                synchronized (this) {
+                    if (mTitleBar.getProgressView().getProgressPercent() >= mMinProgress) {
+                        if (mState == ViewDragHelper.STATE_IDLE && pageIndex == mCurrIndex) {
+                            swipeSessionCleanup();
+
+                        }
+                        cancel();
+                    } else {
+                        if (mGrayBM) {
+                            return;
+                        }
+                        mView.setStationaryViewBitmap(
+                                getGrayscale(getSnapshotOrFavicon(pageIndex)));
+                        mGrayBM = true;
+                    }
+                }
+            }
+
+            public void onFinish() {
+                mGrayBM = false;
+                synchronized (this) {
+                    if (mTitleBar.getProgressView().getProgressPercent() >= mMinProgress) {
+                        if (mState == ViewDragHelper.STATE_IDLE && pageIndex == mCurrIndex) {
+                            swipeSessionCleanup();
+                        }
+                        cancel();
+                    }
+                }
+            }
+        }.start();
+    }
+
+    private void monitorProgressAtHistoryUpdate(final int pageIndex) {
+        if (mCommitTimer != null) {
+            mCommitTimer.cancel();
+        }
+
+        if (mTitleBar.getProgressView().getProgressPercent() >= mMinProgress
+                && mActiveTab.getWebView().getLastCommittedHistoryIndex() == pageIndex) {
+            swipeSessionCleanup();
+            return;
+        }
+
+        mCommitTimer = new CountDownTimer(mProgressWaitMS * 5, mProgressWaitMS) {
+            public void onTick(long msRemain) {
+                synchronized (this) {
+                    if (mTitleBar.getProgressView().getProgressPercent() >= mMinProgress) {
+                        if (mState == ViewDragHelper.STATE_IDLE && pageIndex == mCurrIndex) {
+                            swipeSessionCleanup();
+
+                        }
+                        cancel();
+                    }
+                }
+            }
+
+            public void onFinish() {
+                synchronized (this) {
+                    if (mState == ViewDragHelper.STATE_IDLE && pageIndex == mCurrIndex) {
+                        swipeSessionCleanup();
+                    }
+                }
+            }
+        }.start();
+    }
+
+    private boolean isPortrait(Bitmap bitmap) {
+        return (bitmap.getHeight() < bitmap.getWidth());
+    }
+
+    private Bitmap getSnapshotOrFavicon(int index) {
+        Bitmap bm = mModel.readSnapshot(index);
+        if (bm == null || mView.isPortrait() != isPortrait(bm))  {
+            WebHistoryItem item = mActiveTab.getWebView()
+                    .copyBackForwardList().getItemAtIndex(index);
+            if (item != null) {
+                bm = item.getFavicon();
+            }
+        }
+        return bm;
+    }
+
+    public void onViewDragStateChanged(int state) {
+        synchronized (this) {
+            if (mState != ViewDragHelper.STATE_SETTLING || state != ViewDragHelper.STATE_IDLE) {
+                return;
+            }
+
+            mView.hideSlidingViews();
+
+            if (mView.isLive()) {
+                return;
+            }
+
+            if (mbNavigated) {
+                mView.setStationaryViewBitmap(getSnapshotOrFavicon(mCurrIndex));
+                mView.setStationaryViewAlpha(1.0f);
+            } else {
+                swipeSessionCleanup();
+            }
+
+            mView.invalidate();
+
+            setState(ViewDragHelper.STATE_SETTLING, ViewDragHelper.STATE_IDLE);
+        }
+    }
+
+    public void onViewReleased(View releasedChild, float xvel, float yvel) {
+        synchronized (this) {
+            if (!setState(ViewDragHelper.STATE_DRAGGING, ViewDragHelper.STATE_SETTLING)) {
+                mOldX = 0;
+                mOldDx = 0;
+                return;
+            }
+
+            mbNavigated = true;
+
+            boolean bCrossedEventHorizon = Math.abs(mOldX) > mView.getWidth() / 2;
+
+            if (mCurrIndex >= 0) {
+                if ((xvel > 0 || (xvel == 0 && mOldX > 0 && bCrossedEventHorizon))
+                        && mFromEdge == ViewDragHelper.EDGE_LEFT
+                        && mActiveTab.getWebView().canGoToHistoryIndex(mCurrIndex - 1)) {
+                    mCurrIndex -= 1;
+                    mActiveTab.getWebView().stopLoading();
+                    mActiveTab.getWebView().goToHistoryIndex(mCurrIndex);
+                    monitorProgressAtLoad(mCurrIndex);
+                    mDragHelper.settleCapturedViewAt(
+                            releasedChild.getMeasuredWidth(),
+                            releasedChild.getTop());
+                } else if ((xvel < 0 || (xvel == 0 && mOldX < 0 && bCrossedEventHorizon))
+                        && mFromEdge == ViewDragHelper.EDGE_RIGHT
+                        && mActiveTab.getWebView().canGoToHistoryIndex(mCurrIndex + 1)) {
+                    mCurrIndex += 1;
+                    mActiveTab.getWebView().stopLoading();
+                    mActiveTab.getWebView().goToHistoryIndex(mCurrIndex);
+                    monitorProgressAtLoad(mCurrIndex);
+                    mDragHelper.settleCapturedViewAt(
+                            -releasedChild.getMeasuredWidth(),
+                            releasedChild.getTop());
+                    mView.goDormant();
+                } else {
+                    mbNavigated = false;
+                    mDragHelper.settleCapturedViewAt(0, releasedChild.getTop());
+                }
+            }
+            mOldX = 0;
+            mOldDx = 0;
+
+            mView.invalidate();
+        }
+    }
+
+    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+        float alpha = ((float) Math.abs(left)) / mView.getMeasuredWidth();
+
+        synchronized (this) {
+            switch (mFromEdge) {
+                case ViewDragHelper.EDGE_LEFT:
+                    if (mView.isLive()) {
+                        return;
+                    }
+
+                    if (mState != ViewDragHelper.STATE_IDLE) {
+                        mView.moveShadowView(left);
+                    }
+
+                    showCurrBMInSlidingView();
+
+                    if (mPrevIndex >= 0) {
+                        if (!mView.stationaryViewHasImage()) {
+                            mView.setStationaryViewBitmap(getSnapshotOrFavicon(mPrevIndex));
+                        }
+
+                        if (mActiveTab.getWebView().canGoToHistoryIndex(mPrevIndex)) {
+                            mView.setStationaryViewAlpha(mMinAlpha + alpha * (1 - mMinAlpha));
+                        }
+                    }
+                    break;
+                case ViewDragHelper.EDGE_RIGHT:
+                    if (mState != ViewDragHelper.STATE_IDLE) {
+                        mView.moveShadowView(mView.getMeasuredWidth() + left);
+
+                        if (!mView.slidingViewHasImage() && mNextIndex < mMaxIndex) {
+                            mView.setSlidingViewBitmap(getSnapshotOrFavicon(mNextIndex));
+                        }
+
+                        showCurrBMInStationaryView();
+                        if (mbCurrBMSynced) {
+                            mView.goDormant();
+                        }
+                    }
+                    if (mNextIndex < mMaxIndex &&
+                            mActiveTab.getWebView().canGoToHistoryIndex(mNextIndex)) {
+                        mView.setStationaryViewAlpha(mMinAlpha + (1 - alpha) * (1 - mMinAlpha));
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    public void onEdgeDragStarted(int edgeFlags, int pointerId) {
+        synchronized (this) {
+            if (mActiveTab.isPrivateBrowsingEnabled()) {
+                mDragHelper.abort();
+                return;
+            }
+
+            if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE ||
+                    !setState(ViewDragHelper.STATE_IDLE, ViewDragHelper.STATE_DRAGGING)) {
+                mDragHelper.abort();
+                return;
+            }
+
+            if ((edgeFlags & mFromEdge) != mFromEdge || mCurrIndex == EDGE_SWIPE_INVALID_INDEX) {
+                onEdgeTouched(edgeFlags, pointerId);
+            }
+
+            mbCurrBMSynced = false;
+
+            switch (mFromEdge) {
+                case ViewDragHelper.EDGE_LEFT:
+                    mView.showSlidingViews();
+                    mView.goDormant();
+                    mPrevIndex = mCurrIndex - 1;
+                    mView.setStationaryViewBitmap(getSnapshotOrFavicon(mPrevIndex));
+                    showCurrBMInSlidingView();
+                    break;
+                case ViewDragHelper.EDGE_RIGHT:
+                    mView.showSlidingViews();
+                    mNextIndex = mCurrIndex + 1;
+                    mView.setSlidingViewBitmap(getSnapshotOrFavicon(mNextIndex));
+                    showCurrBMInStationaryView();
+                    if (mbCurrBMSynced)
+                        mView.goDormant();
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    public int getOrderedChildIndex(int index) {
+        return mView.slidingViewIndex();
+    }
+
+    public void onEdgeTouched (int edgeFlags, int pointerId) {
+        synchronized (this) {
+            if (mActiveTab.isPrivateBrowsingEnabled()) {
+                mDragHelper.abort();
+                return;
+            }
+
+            if (mState != ViewDragHelper.STATE_IDLE && mCurrIndex != EDGE_SWIPE_INVALID_INDEX) {
+                mDragHelper.abort();
+                return;
+            }
+
+            mView.init();
+
+            if (mCurrIndex == EDGE_SWIPE_INVALID_INDEX) {
+                mCurrIndex = mActiveTab.getWebView().getLastCommittedHistoryIndex();
+            }
+
+            mMaxIndex = mActiveTab.getWebView().copyBackForwardList().getSize() - 1;
+            mModel.updateSnapshot(mCurrIndex);
+
+            if (ViewDragHelper.EDGE_LEFT == (edgeFlags & ViewDragHelper.EDGE_LEFT)) {
+                mFromEdge = ViewDragHelper.EDGE_LEFT;
+                mView.slidingViewTouched(mFromEdge);
+                if (mCurrIndex > 0) {
+                    mModel.fetchSnapshot(mCurrIndex - 1);
+                }
+            } else if (ViewDragHelper.EDGE_RIGHT == (edgeFlags & ViewDragHelper.EDGE_RIGHT)) {
+                mFromEdge = ViewDragHelper.EDGE_RIGHT;
+                mView.slidingViewTouched(mFromEdge);
+                if (mCurrIndex < mMaxIndex) {
+                    mModel.fetchSnapshot(mCurrIndex + 1);
+                }
+            }
+        }
+    }
+
+    public int getViewHorizontalDragRange(View child) {
+        return child.getMeasuredWidth();
+    }
+
+    public boolean tryCaptureView(View child, int pointerId) {
+        return (mState == ViewDragHelper.STATE_DRAGGING && mView.allowCapture(child));
+    }
+
+    public int clampViewPositionHorizontal(View child, int left, int dx) {
+        if (mOldX != 0 && Math.signum(dx) != Math.signum(mOldDx)) {
+            mOldDx = dx;
+            return mOldX;
+        }
+
+        switch (mFromEdge) {
+            case ViewDragHelper.EDGE_LEFT:
+                if (left < 0) {
+                    mOldDx = dx;
+                    return mOldX;
+                }
+                if (!mActiveTab.getWebView().canGoToHistoryIndex(mPrevIndex)) {
+                    if (Math.abs(left) >= child.getMeasuredWidth() / 3) {
+                        return child.getMeasuredWidth() / 3;
+                    }
+                }
+                break;
+            case ViewDragHelper.EDGE_RIGHT:
+                if (left > 0) {
+                    mOldDx = dx;
+                    return mOldX;
+                }
+                if (!mActiveTab.getWebView().canGoToHistoryIndex(mNextIndex)) {
+                    if (Math.abs(left) >= child.getMeasuredWidth() / 3) {
+                        return -child.getMeasuredWidth() / 3;
+                    }
+                }
+                break;
+            default:
+                break;
+        }
+
+        mOldX = left;
+        mOldDx = dx;
+        return left;
+    }
+}
+
diff --git a/src/com/android/browser/EdgeSwipeModel.java b/src/com/android/browser/EdgeSwipeModel.java
new file mode 100644
index 0000000..51a5dc9
--- /dev/null
+++ b/src/com/android/browser/EdgeSwipeModel.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2015, 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.graphics.Bitmap;
+import android.util.SparseArray;
+import android.webkit.ValueCallback;
+
+public class EdgeSwipeModel {
+    private SparseArray<Bitmap> mBitmaps;
+    private Tab mTab;
+    private TitleBar mBar;
+
+    private static final int mMinProgress = 85;
+
+    private static final int mMaxBitmaps = 5;
+
+    public EdgeSwipeModel(Tab tab, TitleBar bar) {
+        mTab = tab;
+        mBar = bar;
+        mBitmaps = new SparseArray<>();
+    }
+
+    public void updateSnapshot(final int index) {
+        if (mBitmaps.get(index) != null) {
+            return;
+        }
+
+        int captureIndex = mTab.getCaptureIndex(index);
+
+        boolean bitmapExists = mTab.getWebView().hasSnapshot(captureIndex);
+
+        if (!mTab.isFirstVisualPixelPainted()) {
+            fetchSnapshot(index);
+            return;
+        }
+
+        int progress = mBar.getProgressView().getProgressPercent();
+
+        if (bitmapExists && progress < mMinProgress) {
+            fetchSnapshot(index);
+            return;
+        }
+
+        mTab.getWebView().captureSnapshot(captureIndex,
+                new ValueCallback<Bitmap>() {
+                    @Override
+                    public void onReceiveValue(Bitmap value) {
+                        mBitmaps.put(index, value);
+                    }
+                }
+        );
+    }
+
+    public void fetchSnapshot(final int index) {
+        if (mBitmaps.get(index) != null) {
+            return;
+        }
+
+        int captureIndex = mTab.getCaptureIndex(index);
+
+        mTab.getWebView().getSnapshot(captureIndex,
+                new ValueCallback<Bitmap>() {
+                    @Override
+                    public void onReceiveValue(Bitmap bitmap) {
+                        mBitmaps.put(index, bitmap);
+                    }
+                }
+        );
+    }
+
+    public Bitmap readSnapshot(int index) {
+        if (index < 0) {
+            return null;
+        }
+
+        if (index > (mTab.getWebView().copyBackForwardList().getSize() - 1)) {
+            return null;
+        }
+
+        return mBitmaps.get(index);
+    }
+
+    public void deleteSnapshot(int index) {
+        mBitmaps.delete(index);
+    }
+
+    public void cleanup() {
+        mBitmaps.clear();
+    }
+}
diff --git a/src/com/android/browser/EdgeSwipeSettings.java b/src/com/android/browser/EdgeSwipeSettings.java
new file mode 100644
index 0000000..3425142
--- /dev/null
+++ b/src/com/android/browser/EdgeSwipeSettings.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2015, 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.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.support.v4.widget.ViewDragHelper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Toast;
+
+public class EdgeSwipeSettings extends ViewDragHelper.Callback {
+    private ViewDragHelper mDragHelper;
+    private int mFromEdge = ViewDragHelper.EDGE_TOP;
+    private int mLeft = 0;
+
+    private ImageView mSlidingViewShadow;
+    private LinearLayout mSettingsView;
+    private DraggableFrameLayout mViewGroup;
+    private View mLiveView;
+    private ImageView mStationaryView;
+
+    private int mSlidingViewIndex;
+    private int mCurrIndex;
+
+    private EdgeSwipeModel mModel;
+    private Tab mActiveTab;
+    private TitleBar mTitleBar;
+
+    private boolean mbWaitForSettings = false;
+
+    public EdgeSwipeSettings(final View container,
+                             int stationaryViewId,
+                             int settingViewId,
+                             int slidingViewShadowId,
+                             int liveViewId,
+                             int viewGroupId,
+                             final BaseUi ui) {
+        DraggableFrameLayout viewGroup = (DraggableFrameLayout)
+                container.findViewById(viewGroupId);
+
+        mSlidingViewShadow = (ImageView) container.findViewById(slidingViewShadowId);
+        mSettingsView = (LinearLayout) container.findViewById(settingViewId);
+        mStationaryView = (ImageView) container.findViewById(stationaryViewId);
+        mLiveView = container.findViewById(liveViewId);
+        mViewGroup = (DraggableFrameLayout) container.findViewById(viewGroupId);
+
+        final int childCount = mViewGroup.getChildCount();
+
+        for (int i = childCount - 1; i >= 0; i--) {
+            final View child = mViewGroup.getChildAt(i);
+            if (mSettingsView == child) {
+                mSlidingViewIndex = i;
+                break;
+            }
+        }
+
+        mActiveTab = ui.getActiveTab();
+        mTitleBar = ui.getTitleBar();
+        mModel = new EdgeSwipeModel(mActiveTab, mTitleBar);
+
+        mDragHelper = ViewDragHelper.create(viewGroup, 0.5f, this);
+        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT | ViewDragHelper.EDGE_RIGHT);
+        mViewGroup.setDragHelper(mDragHelper);
+
+        final Button closeBtn =
+                (Button) container.findViewById(R.id.edge_sliding_settings_close_btn);
+        closeBtn.setOnClickListener(
+            new View.OnClickListener() {
+                public void onClick(View v) {
+                    mbWaitForSettings = false;
+                    mSettingsView.setVisibility(View.GONE);
+                    goLive();
+                }
+            }
+        );
+
+        final RadioButton temporalNavButton =
+                (RadioButton) container.findViewById(R.id.edge_sliding_settings_options_temporal);
+        temporalNavButton.setOnClickListener(
+            new View.OnClickListener() {
+                public void onClick(View v) {
+                    mbWaitForSettings = false;
+                    mSettingsView.setVisibility(View.GONE);
+                    BrowserSettings.getInstance().setEdgeSwipeTemporal();
+                    goLive();
+                    applySettingsAndRefresh(ui, container);
+                    Toast toast = Toast.makeText(ui.getActivity().getApplicationContext(),
+                            R.string.pref_temporal_edge_swipe_enabled_toast, Toast.LENGTH_SHORT);
+                    toast.show();
+                }
+            }
+        );
+
+        final RadioButton spatialNavButton =
+                (RadioButton) container.findViewById(R.id.edge_sliding_settings_options_spatial);
+        spatialNavButton.setOnClickListener(
+            new View.OnClickListener() {
+                public void onClick(View v) {
+                    mbWaitForSettings = false;
+                    mSettingsView.setVisibility(View.GONE);
+                    BrowserSettings.getInstance().setEdgeSwipeSpatial();
+                    goLive();
+                    applySettingsAndRefresh(ui, container);
+                    Toast toast = Toast.makeText(ui.getActivity().getApplicationContext(),
+                            R.string.pref_spatial_edge_swipe_enabled_toast, Toast.LENGTH_SHORT);
+                    toast.show();
+                }
+            }
+        );
+
+        final RadioButton disabledNavButton =
+                (RadioButton) container.findViewById(R.id.edge_sliding_settings_options_disabled);
+        disabledNavButton.setOnClickListener(
+            new View.OnClickListener() {
+                public void onClick(View v) {
+                    mbWaitForSettings = false;
+                    mSettingsView.setVisibility(View.GONE);
+                    BrowserSettings.getInstance().setEdgeSwipeDisabled();
+                    goLive();
+                    applySettingsAndRefresh(ui, container);
+                    Toast toast = Toast.makeText(ui.getActivity().getApplicationContext(),
+                            R.string.pref_edge_swipe_disabled_toast, Toast.LENGTH_SHORT);
+                    toast.show();
+                }
+            }
+        );
+    }
+
+    private void applySettingsAndRefresh(final BaseUi ui, final View container) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... unused) {
+                mDragHelper = null;
+                ui.refreshEdgeSwipeController(container);
+                return null;
+            }
+        }.execute();
+    }
+
+    private void goLive() {
+        mFromEdge = ViewDragHelper.EDGE_TOP;
+        mLiveView.setVisibility(View.VISIBLE);
+        mStationaryView.setVisibility(View.GONE);
+        mSlidingViewShadow.setVisibility(View.GONE);
+        mViewGroup.invalidate();
+    }
+
+    private void goDormant() {
+        mLiveView.setVisibility(View.GONE);
+        mStationaryView.setVisibility(View.VISIBLE);
+        mViewGroup.invalidate();
+    }
+
+    public void onConfigurationChanged() {
+        mSettingsView.setVisibility(View.GONE);
+        goLive();
+    }
+
+    private void showCurrBitmap() {
+        if (mStationaryView.getVisibility() == View.VISIBLE) {
+            return;
+        }
+
+        Bitmap currBM = mModel.readSnapshot(mCurrIndex);
+        if (currBM != null) {
+            clampViewIfNeeded(mStationaryView);
+            mStationaryView.setImageBitmap(currBM);
+            goDormant();
+            mModel.deleteSnapshot(mCurrIndex);
+        }
+    }
+
+    private void clampViewIfNeeded(View view) {
+        int offset = 0;
+        if (mTitleBar.getY() >= 0) {
+            offset = mTitleBar.getNavigationBar().getMeasuredHeight();
+        }
+        view.setPadding(0, offset - view.getTop(), 0, 0);
+    }
+
+    public void onViewDragStateChanged(int state) {
+        if (ViewDragHelper.STATE_IDLE == state && !mbWaitForSettings) {
+            mSettingsView.setVisibility(View.GONE);
+            goLive();
+        }
+    }
+
+    public void onViewReleased(View releasedChild, float xvel, float yvel) {
+        boolean bCrossedEventHorizon = Math.abs(mLeft) > mViewGroup.getWidth() / 2;
+
+        switch (mFromEdge) {
+            case ViewDragHelper.EDGE_LEFT:
+                if (xvel > 0 || (xvel == 0 && mLeft > 0 && bCrossedEventHorizon)) {
+                    showCurrBitmap();
+                    mbWaitForSettings = true;
+                    mDragHelper.settleCapturedViewAt(
+                            releasedChild.getMeasuredWidth(),
+                            releasedChild.getTop());
+                    break;
+                }
+                mDragHelper.settleCapturedViewAt(0, releasedChild.getTop());
+                break;
+            case ViewDragHelper.EDGE_RIGHT:
+                if (xvel < 0 || (xvel == 0 && mLeft < 0 && bCrossedEventHorizon)) {
+                    showCurrBitmap();
+                    mbWaitForSettings = true;
+                    mDragHelper.settleCapturedViewAt(
+                            -releasedChild.getMeasuredWidth(),
+                            releasedChild.getTop());
+                    break;
+                }
+                mDragHelper.settleCapturedViewAt(0, releasedChild.getTop());
+                break;
+            default:
+                mDragHelper.settleCapturedViewAt(0, releasedChild.getTop());
+                break;
+        }
+        mLeft = 0;
+        mViewGroup.invalidate();
+    }
+
+    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+        showCurrBitmap();
+        switch (mFromEdge) {
+            case ViewDragHelper.EDGE_LEFT:
+                mSlidingViewShadow.setX(left);
+                mViewGroup.invalidate();
+                break;
+            case ViewDragHelper.EDGE_RIGHT:
+                mSlidingViewShadow.setX(mViewGroup.getMeasuredWidth() + left
+                        - mSlidingViewShadow.getMeasuredWidth());
+                mViewGroup.invalidate();
+                break;
+            default:
+                break;
+        }
+    }
+
+    public void onEdgeDragStarted(int edgeFlags, int pointerId) {
+        if (mFromEdge != ViewDragHelper.EDGE_TOP) {
+            return;
+        }
+
+        mCurrIndex = mActiveTab.getWebView().copyBackForwardList().getCurrentIndex();
+
+        mModel.updateSnapshot(mCurrIndex);
+
+        clampViewIfNeeded(mSettingsView);
+
+        if (ViewDragHelper.EDGE_LEFT == (edgeFlags & ViewDragHelper.EDGE_LEFT)) {
+            mFromEdge = ViewDragHelper.EDGE_LEFT;
+
+            mSettingsView.setTranslationX(-mViewGroup.getMeasuredWidth());
+            mSlidingViewShadow.setBackgroundResource(R.drawable.right_shade);
+        } else if (ViewDragHelper.EDGE_RIGHT == (edgeFlags & ViewDragHelper.EDGE_RIGHT)) {
+            mFromEdge = ViewDragHelper.EDGE_RIGHT;
+
+            mSettingsView.setTranslationX(mViewGroup.getMeasuredWidth());
+            mSlidingViewShadow.setBackgroundResource(R.drawable.left_shade);
+        }
+
+        mSettingsView.setVisibility(View.VISIBLE);
+        mSlidingViewShadow.setVisibility(View.VISIBLE);
+
+        showCurrBitmap();
+
+        mViewGroup.invalidate();
+    }
+
+    public int getOrderedChildIndex(int index) {
+        return mSlidingViewIndex;
+    }
+
+    public int getViewHorizontalDragRange(View child) {
+        return child.getMeasuredWidth();
+    }
+
+    public boolean tryCaptureView(View child, int pointerId) {
+        return (child == mSettingsView);
+    }
+
+    public int clampViewPositionHorizontal(View child, int left, int dx) {
+        mLeft = left;
+        return left;
+    }
+}
+
diff --git a/src/com/android/browser/EdgeSwipeView.java b/src/com/android/browser/EdgeSwipeView.java
new file mode 100644
index 0000000..a777c26
--- /dev/null
+++ b/src/com/android/browser/EdgeSwipeView.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2015, 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.graphics.Bitmap;
+import android.graphics.Color;
+import android.support.v4.widget.ViewDragHelper;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+public class EdgeSwipeView {
+    private ImageView mStationaryView;
+    private ImageView mSlidingView;
+    private ImageView mSlidingViewShadow;
+    private View mOpacityView;
+    private FrameLayout mLiveView;
+    private DraggableFrameLayout mViewGroup;
+
+    private int mSlidingViewIndex;
+
+    private boolean mbShowingLive = true;
+
+    private boolean mbStationaryViewBMSet = false;
+    private boolean mbSlidingViewBMSet = false;
+
+    private TitleBar mTitleBar;
+
+    public EdgeSwipeView(
+            View container,
+            int stationaryViewId,
+            int slidingViewId,
+            int slidingViewShadowId,
+            int opacityViewId,
+            int liveViewId,
+            int viewGroupId,
+            TitleBar titleBar) {
+        mStationaryView = (ImageView) container.findViewById(stationaryViewId);
+        mSlidingView = (ImageView) container.findViewById(slidingViewId);
+        mSlidingViewShadow = (ImageView) container.findViewById(slidingViewShadowId);
+        mOpacityView = container.findViewById(opacityViewId);
+        mLiveView = (FrameLayout) container.findViewById(liveViewId);
+        mViewGroup = (DraggableFrameLayout) container.findViewById(viewGroupId);
+        mSlidingViewShadow.setBackgroundResource(R.drawable.left_shade);
+
+        mSlidingView.setVisibility(View.GONE);
+        mSlidingViewShadow.setVisibility(View.GONE);
+        mOpacityView.setVisibility(View.GONE);
+
+        final int childCount = mViewGroup.getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            final View child = mViewGroup.getChildAt(i);
+            if (mSlidingView == child) {
+                mSlidingViewIndex = i;
+                break;
+            }
+        }
+
+        mTitleBar = titleBar;
+    }
+
+    public void goLive() {
+        if (mbShowingLive)
+            return;
+
+        mLiveView.setVisibility(View.VISIBLE);
+        mStationaryView.setVisibility(View.GONE);
+        mSlidingView.setVisibility(View.GONE);
+        mSlidingViewShadow.setVisibility(View.GONE);
+        mOpacityView.setVisibility(View.GONE);
+        mbShowingLive = true;
+    }
+
+    public void goDormant() {
+        if (!mbShowingLive)
+            return;
+
+        mSlidingView.setVisibility(View.VISIBLE);
+        mStationaryView.setVisibility(View.VISIBLE);
+        mOpacityView.setVisibility(View.VISIBLE);
+        mLiveView.setVisibility(View.GONE);
+        mbShowingLive = false;
+    }
+
+    public boolean isLive() {
+        return mbShowingLive;
+    }
+
+    private Bitmap getColorBitmap(int color)
+    {
+        int height = mViewGroup.getMeasuredHeight();
+        int width = mViewGroup.getMeasuredWidth();
+        height -= (mTitleBar.getY()>= 0) ? mTitleBar.getNavigationBar().getMeasuredHeight() : 0;
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
+        bitmap.eraseColor(color);
+        return bitmap;
+    }
+
+    private void clampViewIfNeeded(View view) {
+        int offset = 0;
+        if (mTitleBar.getY() >= 0) {
+            offset = mTitleBar.getNavigationBar().getMeasuredHeight();
+        }
+        view.setPadding(0, offset - view.getTop(), 0, 0);
+    }
+
+    public boolean isPortrait() {
+        return (mViewGroup.getHeight() < mViewGroup.getWidth());
+    }
+
+    private void setBitmap(ImageView view, Bitmap bitmap) {
+        clampViewIfNeeded(view);
+        if (bitmap == null) {
+            bitmap = getColorBitmap(Color.DKGRAY);
+        }
+
+        int offset = 0;
+        if (mTitleBar.getY() >= 0) {
+            offset = mTitleBar.getNavigationBar().getMeasuredHeight();
+        }
+
+        int bitmap_height = bitmap.getHeight();
+
+        if (view.getMeasuredHeight() != 0) {
+            bitmap_height = (view.getMeasuredHeight() - offset) * bitmap.getWidth() /
+                    view.getMeasuredWidth();
+        }
+
+        if ((bitmap.getHeight() - bitmap_height)  > 5) {
+            Bitmap cropped = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap_height);
+            view.setImageBitmap(cropped);
+        } else {
+            view.setImageBitmap(bitmap);
+        }
+    }
+
+    public void setStationaryViewBitmap(Bitmap bitmap) {
+        mbStationaryViewBMSet = null != bitmap;
+        setBitmap(mStationaryView, bitmap);
+    }
+
+    public void setStationaryViewAlpha(float alpha) {
+        mStationaryView.setAlpha(alpha);
+    }
+
+    public void setSlidingViewBitmap(Bitmap bitmap) {
+        mbSlidingViewBMSet = null != bitmap;
+        setBitmap(mSlidingView, bitmap);
+    }
+
+    public boolean slidingViewHasImage() {
+        return mbSlidingViewBMSet;
+    }
+
+    public boolean stationaryViewHasImage() {
+        return mbStationaryViewBMSet;
+    }
+
+    public void slidingViewTouched(int edge) {
+        if (edge == ViewDragHelper.EDGE_RIGHT) {
+            mSlidingView.setTranslationX(mViewGroup.getMeasuredWidth());
+        } else {
+            mSlidingView.setTranslationX(0);
+        }
+    }
+
+    public void hideSlidingViews() {
+        mSlidingViewShadow.setVisibility(View.GONE);
+        mSlidingView.setVisibility(View.GONE);
+    }
+
+    public void showSlidingViews() {
+        mSlidingViewShadow.setVisibility(View.VISIBLE);
+        mSlidingView.setVisibility(View.VISIBLE);
+    }
+
+    public int slidingViewIndex() {
+        return mSlidingViewIndex;
+    }
+
+    public void moveShadowView(float x) {
+        x -= mSlidingViewShadow.getMeasuredWidth();
+        mSlidingViewShadow.setX(x);
+        mSlidingViewShadow.setVisibility(View.VISIBLE);
+        mOpacityView.setVisibility(View.VISIBLE);
+    }
+
+    public boolean allowCapture(View view) {
+        return (view == mSlidingView);
+    }
+
+    public int getMeasuredWidth() {
+        return mViewGroup.getMeasuredWidth();
+    }
+
+    public int getWidth() {
+        return mViewGroup.getWidth();
+    }
+
+    public void init() {
+        clampViewIfNeeded(mSlidingView);
+        clampViewIfNeeded(mStationaryView);
+    }
+
+    public void invalidate() {
+        mViewGroup.invalidate();
+    }
+}
diff --git a/src/com/android/browser/PageProgressView.java b/src/com/android/browser/PageProgressView.java
index 2b73e45..c316c68 100644
--- a/src/com/android/browser/PageProgressView.java
+++ b/src/com/android/browser/PageProgressView.java
@@ -106,6 +106,10 @@
         mHandler.sendEmptyMessage(MSG_UPDATE);
     }
 
+    public int getProgressPercent() {
+        return (100* mCurrentProgress) / MAX_PROGRESS;
+    }
+
     @Override
     public void onDraw(Canvas canvas) {
 //        super.onDraw(canvas);
diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java
index 2df0ceb..ab11940 100644
--- a/src/com/android/browser/PreferenceKeys.java
+++ b/src/com/android/browser/PreferenceKeys.java
@@ -131,4 +131,6 @@
     static final String PREF_USER_AGENT = "user_agent";
     static final String PREF_HELP = "help_about";
     static final String PREF_FEEDBACK = "feedback";
+
+    static final String PREF_EDGE_SWIPE = "edge_swiping_action";
 }
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 5e1f614..ccaa522 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
@@ -48,7 +49,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewStub;
-import android.view.View.OnClickListener;
 import android.webkit.ConsoleMessage;
 import android.webkit.GeolocationPermissions;
 import android.webkit.URLUtil;
@@ -58,8 +58,6 @@
 import android.webkit.ValueCallback;
 import android.widget.CheckBox;
 import android.widget.Toast;
-import android.widget.FrameLayout;
-import android.widget.Button;
 
 import com.android.browser.TabControl.OnThumbnailUpdatedListener;
 import com.android.browser.homepages.HomeProvider;
@@ -73,21 +71,19 @@
 import org.codeaurora.swe.SslErrorHandler;
 import org.codeaurora.swe.WebBackForwardList;
 import org.codeaurora.swe.WebChromeClient;
-import org.codeaurora.swe.WebHistoryItem;
 import org.codeaurora.swe.WebView;
 import org.codeaurora.swe.WebView.PictureListener;
 import org.codeaurora.swe.WebView.CreateWindowParams;
 import org.codeaurora.swe.WebViewClient;
+import org.codeaurora.swe.util.Observable;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
-import java.util.LinkedList;
 import java.util.Map;
 import java.util.UUID;
 import java.util.Vector;
-import java.util.regex.Pattern;
 import java.sql.Timestamp;
 import java.util.Date;
 
@@ -198,6 +194,16 @@
     // determine if webview is destroyed to MemoryMonitor
     private boolean mWebViewDestroyedByMemoryMonitor;
 
+    private Observable mFirstPixelObservable;
+    private Observable mTabHistoryUpdateObservable;
+
+    Observable getFirstPixelObservable() {
+        return mFirstPixelObservable;
+    }
+
+    Observable getTabHistoryUpdateObservable() {
+        return mTabHistoryUpdateObservable;
+    }
 
 
     private static synchronized Bitmap getDefaultFavicon(Context context) {
@@ -264,6 +270,35 @@
         }
     }
 
+    public boolean isFirstVisualPixelPainted() {
+        return mFirstVisualPixelPainted;
+    }
+
+    public int getCaptureIndex(int navIndex) {
+        int tabPosition = mWebViewController.getTabControl().getCurrentPosition();
+        int orientation = mWebViewController.getActivity().
+                getResources().getConfiguration().orientation;
+
+        int orientationBit = (orientation == Configuration.ORIENTATION_LANDSCAPE) ? 0 : 1;
+
+        int index = orientationBit << 31 | ((tabPosition & 0x7f) << 24) | (navIndex & 0xffffff);
+        return index;
+    }
+
+    public int getTabIdxFromCaptureIdx(int index) {
+        return (index & 0x7f000000) >> 24;
+    }
+
+    public int getOrientationFromCaptureIdx(int index) {
+        return ((index & 0x80000000) == 0) ? Configuration.ORIENTATION_LANDSCAPE :
+                Configuration.ORIENTATION_PORTRAIT;
+
+    }
+
+    public int getNavIdxFromCaptureIdx(int index) {
+        return (index & 0xffffff);
+    }
+
     // -------------------------------------------------------------------------
     // WebViewClient implementation for the main WebView
     // -------------------------------------------------------------------------
@@ -283,6 +318,7 @@
             mInPageLoad = true;
             mPageFinished = false;
             mFirstVisualPixelPainted = false;
+            mFirstPixelObservable.set(false);
             mReceivedError = false;
             mUpdateThumbnail = true;
             mPageLoadProgress = INITIAL_PROGRESS;
@@ -324,6 +360,7 @@
         @Override
         public void onFirstVisualPixel(WebView view) {
             mFirstVisualPixelPainted = true;
+            mFirstPixelObservable.set(true);
         }
 
         // return true if want to hijack the url to let another app to handle it
@@ -597,6 +634,50 @@
                 super.onUnhandledKeyEvent(view, event);
             }
         }
+
+        @Override
+        public void beforeNavigation(WebView view, String url) {
+            if (isPrivateBrowsingEnabled()) {
+                return;
+            }
+
+            if (!mFirstVisualPixelPainted) {
+                return;
+            }
+
+            final int idx = view.copyBackForwardList().getCurrentIndex();
+            boolean bitmapExists = view.hasSnapshot(idx);
+
+            int progress = 100;
+            Controller controller = (Controller)mWebViewController;
+            UI ui = controller.getUi();
+            if (ui instanceof BaseUi) {
+                BaseUi baseUi = (BaseUi) ui;
+                TitleBar titleBar = baseUi.getTitleBar();
+                progress = titleBar.getProgressView().getProgressPercent();
+            }
+
+            if (bitmapExists && progress < 85) {
+                return;
+            }
+
+            int index = getCaptureIndex(view.getLastCommittedHistoryIndex());
+            view.captureSnapshot(index , null);
+        }
+
+        @Override
+        public void onHistoryItemCommit(WebView view, int index) {
+            mTabHistoryUpdateObservable.set(index);
+            int maxIdx = view.copyBackForwardList().getSize();
+            int[] ids = view.getSnapshotIds();
+            int currentTabIdx = mWebViewController.getTabControl().getCurrentPosition();
+            for (int id : ids) {
+                if (getTabIdxFromCaptureIdx(id) == currentTabIdx &&
+                        getNavIdxFromCaptureIdx(id) >= maxIdx) {
+                    view.deleteSnapshot(id);
+                }
+            }
+        }
     };
 
     private void syncCurrentState(WebView view, String url) {
@@ -1174,6 +1255,10 @@
                 }
             }
         };
+
+        mFirstPixelObservable = new Observable();
+        mFirstPixelObservable.set(false);
+        mTabHistoryUpdateObservable = new Observable();
     }
 
     public boolean shouldUpdateThumbnail() {
@@ -1319,6 +1404,15 @@
             dismissSubWindow();
             // save the WebView to call destroy() after detach it from the tab
             WebView webView = mMainView;
+            if (!mWebViewDestroyedByMemoryMonitor) {
+                int[] ids = webView.getSnapshotIds();
+                int currentTabIdx = mWebViewController.getTabControl().getCurrentPosition();
+                for (int id : ids) {
+                    if (getTabIdxFromCaptureIdx(id) == currentTabIdx) {
+                        webView.deleteSnapshot(id);
+                    }
+                }
+            }
             setWebView(null);
             webView.destroy();
         }
diff --git a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
index 4c715ce..9853128 100644
--- a/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
+++ b/src/com/android/browser/preferences/AdvancedPreferencesFragment.java
@@ -100,6 +100,21 @@
                     downloadPath);
             downloadPathPreset.setSummary(downloadPathForUser);
         }
+
+        ListPreference edgeSwipePref =
+                (ListPreference) mFragment.findPreference("edge_swiping_action");
+
+        String[] options = mFragment.getResources().getStringArray(
+                R.array.pref_edge_swiping_values);
+
+        String value = BrowserSettings.getInstance().getEdgeSwipeAction();
+
+        for (int i = 0; i < options.length; i++) {
+            if (value.equals(options[i])) {
+                edgeSwipePref.setValueIndex(i);
+                break;
+            }
+        }
     }
 
     private Preference.OnPreferenceClickListener onClickDownloadPathSettings() {