From: Yi-Ling Chuang Date: Tue, 26 Mar 2019 13:16:55 +0000 (+0800) Subject: Add implementation of homepage swipe to dismiss. X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=eeea6676d4c8093ef9966b323e1cb94a85555096;p=android-x86%2Fpackages-apps-Settings.git Add implementation of homepage swipe to dismiss. - Only enable swipe for slice full/half card. - Add isPendingDismiss in ContextualCard to determine if we should show dismissal view. - Take out long press feature. Bug: 126214056 Test: robotests Change-Id: Ib03e605347b2f50d3c62fcd4f95875a21cc9ef1c --- diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCard.java b/src/com/android/settings/homepage/contextualcards/ContextualCard.java index 7b8a0c395f..ede12fb00c 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCard.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCard.java @@ -71,6 +71,7 @@ public class ContextualCard { private final Drawable mIconDrawable; @LayoutRes private final int mViewType; + private final boolean mIsPendingDismiss; public String getName() { return mName; @@ -156,6 +157,10 @@ public class ContextualCard { return mViewType; } + public boolean isPendingDismiss() { + return mIsPendingDismiss; + } + public Builder mutate() { return mBuilder; } @@ -181,6 +186,7 @@ public class ContextualCard { mIconDrawable = builder.mIconDrawable; mIsLargeCard = builder.mIsLargeCard; mViewType = builder.mViewType; + mIsPendingDismiss = builder.mIsPendingDismiss; } ContextualCard(Cursor c) { @@ -226,6 +232,8 @@ public class ContextualCard { mBuilder.setIconDrawable(mIconDrawable); mViewType = getViewTypeByCardType(mCardType); mBuilder.setViewType(mViewType); + mIsPendingDismiss = false; + mBuilder.setIsPendingDismiss(mIsPendingDismiss); } @Override @@ -277,6 +285,7 @@ public class ContextualCard { private boolean mIsLargeCard; @LayoutRes private int mViewType; + private boolean mIsPendingDismiss; public Builder setName(String name) { mName = name; @@ -373,6 +382,11 @@ public class ContextualCard { return this; } + public Builder setIsPendingDismiss(boolean isPendingDismiss) { + mIsPendingDismiss = isPendingDismiss; + return this; + } + public ContextualCard build() { return new ContextualCard(this); } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java index d6df380239..7be0e8e72a 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java @@ -28,7 +28,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer; -import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate.DismissalItemTouchHelperListener; +import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate; import com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer; import java.util.ArrayList; @@ -36,7 +36,7 @@ import java.util.List; import java.util.Map; public class ContextualCardsAdapter extends RecyclerView.Adapter - implements ContextualCardUpdateListener, DismissalItemTouchHelperListener { + implements ContextualCardUpdateListener, SwipeDismissalDelegate.Listener { static final int SPAN_COUNT = 2; private static final String TAG = "ContextualCardsAdapter"; @@ -140,6 +140,9 @@ public class ContextualCardsAdapter extends RecyclerView.Adapter { + if (card.isPendingDismiss()) { flipCardToDismissalView(holder); mFlippedCardSet.add(holder); - return true; - }); + } + } + private void initDismissalActions(RecyclerView.ViewHolder holder, ContextualCard card) { final Button btnKeep = holder.itemView.findViewById(R.id.keep); btnKeep.setOnClickListener(v -> { mFlippedCardSet.remove(holder); diff --git a/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java b/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java index a4186b049c..121d4aadd5 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java @@ -18,31 +18,62 @@ package com.android.settings.homepage.contextualcards.slices; import android.content.Context; import android.graphics.Canvas; +import android.widget.ViewFlipper; import androidx.annotation.NonNull; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.ContextualCard; + public class SwipeDismissalDelegate extends ItemTouchHelper.Callback { private static final String TAG = "DismissItemTouchHelper"; - public interface DismissalItemTouchHelperListener { + public interface Listener { void onSwiped(int position); } private final Context mContext; - private final DismissalItemTouchHelperListener mListener; + private final SwipeDismissalDelegate.Listener mListener; - public SwipeDismissalDelegate(Context context, DismissalItemTouchHelperListener listener) { + public SwipeDismissalDelegate(Context context, SwipeDismissalDelegate.Listener listener) { mContext = context; mListener = listener; } + /** + * Determine whether the ability to drag or swipe should be enabled or not. + * + * Only allow swipe on {@link ContextualCard} built with view type + * {@link SliceContextualCardRenderer#VIEW_TYPE_FULL_WIDTH} or + * {@link SliceContextualCardRenderer#VIEW_TYPE_HALF_WIDTH}. + * + * When the dismissal view is displayed, the swipe will also be disabled. + */ @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { - return 0; + switch (viewHolder.getItemViewType()) { + case SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH: + case SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH: + //TODO(b/129438972): Convert this to a regular view. + final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper); + + // As we are using ViewFlipper to switch between the initial view and + // dismissal view, here we are making sure the current displayed view is the + // initial view of either slice full card or half card, and only allow swipe on + // these two types. + if (viewFlipper.getCurrentView().getId() != getInitialViewId(viewHolder)) { + // Disable swiping when we are in the dismissal view + return 0; + } + return makeMovementFlags(0 /*dragFlags*/, + ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT /*swipeFlags*/); + default: + return 0; + } } @Override @@ -63,4 +94,11 @@ public class SwipeDismissalDelegate extends ItemTouchHelper.Callback { boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } + + private int getInitialViewId(RecyclerView.ViewHolder viewHolder) { + if (viewHolder.getItemViewType() == SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH) { + return R.id.content; + } + return R.id.slice_view; + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java index e08d845196..a53ade2d4f 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java @@ -16,13 +16,11 @@ package com.android.settings.homepage.contextualcards.slices; -import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_DEFERRED_SETUP; import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.Activity; @@ -118,34 +116,25 @@ public class SliceContextualCardRendererTest { } @Test - public void longClick_shouldFlipCard() { + public void bindView_isPendingDismiss_shouldFlipToDismissalView() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper); final View dismissalView = viewHolder.itemView.findViewById(R.id.dismissal_view); - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); + final ContextualCard card = buildContextualCard( + TEST_SLICE_URI).mutate().setIsPendingDismiss(true).build(); - card.performLongClick(); + mRenderer.bindView(viewHolder, card); assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView); } @Test - public void longClick_deferredSetupCard_shouldNotBeClickable() { - final RecyclerView.ViewHolder viewHolder = getDeferredSetupViewHolder(); - final View contentView = viewHolder.itemView.findViewById(R.id.content); - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); - - assertThat(contentView.isLongClickable()).isFalse(); - } - - @Test - public void longClick_shouldAddViewHolderToSet() { + public void bindView_isPendingDismiss_shouldAddViewHolderToSet() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); + final ContextualCard card = buildContextualCard( + TEST_SLICE_URI).mutate().setIsPendingDismiss(true).build(); - card.performLongClick(); + mRenderer.bindView(viewHolder, card); assertThat(mRenderer.mFlippedCardSet).contains(viewHolder); } @@ -232,18 +221,6 @@ public class SliceContextualCardRendererTest { return mRenderer.createViewHolder(view, VIEW_TYPE_FULL_WIDTH); } - private RecyclerView.ViewHolder getDeferredSetupViewHolder() { - final RecyclerView recyclerView = new RecyclerView(mActivity); - recyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); - final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_DEFERRED_SETUP, - recyclerView, false); - final RecyclerView.ViewHolder viewHolder = spy( - mRenderer.createViewHolder(view, VIEW_TYPE_DEFERRED_SETUP)); - doReturn(VIEW_TYPE_DEFERRED_SETUP).when(viewHolder).getItemViewType(); - - return viewHolder; - } - private ContextualCard buildContextualCard(Uri sliceUri) { return new ContextualCard.Builder() .setName("test_name") diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java new file mode 100644 index 0000000000..00b7815745 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.homepage.contextualcards.slices; + +import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_DEFERRED_SETUP; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ViewFlipper; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer; +import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer.ConditionalCardHolder; +import com.android.settings.homepage.contextualcards.slices.SliceDeferredSetupCardRendererHelper.DeferredSetupCardViewHolder; +import com.android.settings.homepage.contextualcards.slices.SliceFullCardRendererHelper.SliceViewHolder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.android.controller.ActivityController; + +@RunWith(RobolectricTestRunner.class) +public class SwipeDismissalDelegateTest { + + @Mock + private SwipeDismissalDelegate.Listener mDismissalDelegateListener; + + private Activity mActivity; + private RecyclerView mRecyclerView; + private SwipeDismissalDelegate mDismissalDelegate; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final ActivityController activityController = Robolectric.buildActivity( + Activity.class); + mActivity = activityController.get(); + mActivity.setTheme(R.style.Theme_Settings_Home); + activityController.create(); + mRecyclerView = new RecyclerView(mActivity); + mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + mDismissalDelegate = new SwipeDismissalDelegate(mActivity, mDismissalDelegateListener); + } + + @Test + public void getMovementFlags_conditionalViewHolder_shouldDisableSwipe() { + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getConditionalViewHolder())) + .isEqualTo(0); + } + + @Test + public void getMovementFlags_deferredSetupViewHolder_shouldDisableSwipe() { + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getDeferredSetupViewHolder())) + .isEqualTo(0); + } + + @Test + public void getMovementFlags_dismissalView_shouldDisableSwipe() { + final RecyclerView.ViewHolder holder = getSliceViewHolder(); + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.showNext(); + final View dismissalView = holder.itemView.findViewById(R.id.dismissal_view); + + assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView); + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, holder)).isEqualTo(0); + } + + @Test + public void getMovementFlags_SliceViewHolder_shouldEnableSwipe() { + final RecyclerView.ViewHolder holder = getSliceViewHolder(); + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.setDisplayedChild(0); + final View sliceView = holder.itemView.findViewById(R.id.slice_view); + + assertThat(viewFlipper.getCurrentView()).isEqualTo(sliceView); + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getSliceViewHolder())) + .isNotEqualTo(0); + } + + @Test + public void onSwipe_shouldNotifyListener() { + mDismissalDelegate.onSwiped(getSliceViewHolder(), 1); + + verify(mDismissalDelegateListener).onSwiped(anyInt()); + } + + private RecyclerView.ViewHolder getSliceViewHolder() { + final View view = LayoutInflater.from(mActivity) + .inflate(SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, mRecyclerView, false); + final RecyclerView.ViewHolder viewHolder = spy(new SliceViewHolder(view)); + doReturn(SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH).when( + viewHolder).getItemViewType(); + + return viewHolder; + } + + private RecyclerView.ViewHolder getConditionalViewHolder() { + final View view = LayoutInflater.from(mActivity) + .inflate(ConditionContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, mRecyclerView, + false); + final RecyclerView.ViewHolder viewHolder = spy(new ConditionalCardHolder(view)); + doReturn(ConditionContextualCardRenderer.VIEW_TYPE_FULL_WIDTH).when( + viewHolder).getItemViewType(); + + return viewHolder; + } + + private RecyclerView.ViewHolder getDeferredSetupViewHolder() { + final View view = LayoutInflater.from(mActivity) + .inflate(VIEW_TYPE_DEFERRED_SETUP, mRecyclerView, false); + final RecyclerView.ViewHolder viewHolder = spy(new DeferredSetupCardViewHolder(view)); + doReturn(VIEW_TYPE_DEFERRED_SETUP).when(viewHolder).getItemViewType(); + + return viewHolder; + } +}