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/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;
+    }
+}
+