/* * 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.WebView; 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, mModel.getColor(mCurrIndex)); mbCurrBMSynced = true; } } } private void showCurrBMInSlidingView() { if (!mbCurrBMSynced) { Bitmap currBM = mModel.readSnapshot(mCurrIndex); mView.setSlidingViewBitmap(currBM, mModel.getColor(mCurrIndex)); 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(mState == ViewDragHelper.STATE_DRAGGING) { if (mGrayBM) { return; } switch (mFromEdge) { case ViewDragHelper.EDGE_LEFT: mView.setSlidingViewBitmap( getGrayscale(getSnapshotOrFavicon(pageIndex)), mModel.getColor(pageIndex)); mGrayBM = true; break; case ViewDragHelper.EDGE_RIGHT: mView.setStationaryViewBitmap( getGrayscale(getSnapshotOrFavicon(pageIndex)), mModel.getColor(pageIndex)); mGrayBM = true; break; } } else { if (mGrayBM) { return; } mView.setStationaryViewBitmap( getGrayscale(getSnapshotOrFavicon(pageIndex)), mModel.getColor(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 int lastCommittedHistoryIndex() { WebView wv = mActiveTab.getWebView(); if (wv == null || wv.getLastCommittedHistoryIndex() == -1) return 0; // WebView is null or No History has been committed for this tab else return wv.getLastCommittedHistoryIndex(); } private void monitorProgressAtHistoryUpdate(final int pageIndex) { if (mCommitTimer != null) { mCommitTimer.cancel(); } if (mTitleBar.getProgressView().getProgressPercent() >= mMinProgress && lastCommittedHistoryIndex() == 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 (mbNavigated) { mView.setStationaryViewBitmap(getSnapshotOrFavicon(mCurrIndex), mModel.getColor(mCurrIndex)); } else { swipeSessionCleanup(); } mView.setStationaryViewAlpha(1.0f); 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; } mView.setStationaryViewAlpha(mMinAlpha + alpha * (1 - mMinAlpha)); if (mState != ViewDragHelper.STATE_IDLE) { mView.moveShadowView(left); } showCurrBMInSlidingView(); if (mPrevIndex >= 0) { if (!mView.stationaryViewHasImage()) { mView.setStationaryViewBitmap(getSnapshotOrFavicon(mPrevIndex), mModel.getColor(mPrevIndex)); } } break; case ViewDragHelper.EDGE_RIGHT: mView.setStationaryViewAlpha(mMinAlpha + (1 - alpha) * (1 - mMinAlpha)); if (mState != ViewDragHelper.STATE_IDLE) { mView.moveShadowView(mView.getMeasuredWidth() + left); if (!mView.slidingViewHasImage() && mNextIndex < mMaxIndex) { mView.setSlidingViewBitmap(getSnapshotOrFavicon(mNextIndex), mModel.getColor(mNextIndex)); } showCurrBMInStationaryView(); if (mbCurrBMSynced) { mView.goDormant(); } } 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), mModel.getColor(mPrevIndex)); showCurrBMInSlidingView(); break; case ViewDragHelper.EDGE_RIGHT: mView.showSlidingViews(); mNextIndex = mCurrIndex + 1; mView.setSlidingViewBitmap(getSnapshotOrFavicon(mNextIndex), mModel.getColor(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.getWebView() == null || mActiveTab.isPrivateBrowsingEnabled() || mActiveTab.isKeyboardShowing()) { 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 = lastCommittedHistoryIndex(); } 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; } }