summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Andrey Epin <ayepin@google.com> 2023-09-20 15:02:21 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-09-20 15:02:21 +0000
commit07c906b2c45e406effb57cc70c51e4fa12dc4c59 (patch)
tree3a9c196a46423d815a52cb19f0655cbb580252c5 /java/src
parentc7bcd3858d5aa3b7750890abfec73f40e175357b (diff)
parent5e4370b5de1347909d514e279e1aada582458820 (diff)
Merge "Make preview scrollable under a feature flag." into main
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java29
-rw-r--r--java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java25
-rw-r--r--java/src/com/android/intentresolver/grid/ChooserGridAdapter.java34
-rw-r--r--java/src/com/android/intentresolver/widget/ChooserNestedScrollView.kt90
-rw-r--r--java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java96
5 files changed, 252 insertions, 22 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 26dbd224..f455be4c 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -491,7 +491,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
/* workProfileQuietModeChecker= */ () -> false,
/* workProfileUserHandle= */ null,
getAnnotatedUserHandles().cloneProfileUserHandle,
- mMaxTargetsPerRow);
+ mMaxTargetsPerRow,
+ mFeatureFlags);
}
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
@@ -525,7 +526,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
selectedProfile,
getAnnotatedUserHandles().workProfileUserHandle,
getAnnotatedUserHandles().cloneProfileUserHandle,
- mMaxTargetsPerRow);
+ mMaxTargetsPerRow,
+ mFeatureFlags);
}
private int findSelectedProfile() {
@@ -667,7 +669,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
getResources(),
getLayoutInflater(),
parent,
- /*headlineViewParent=*/null);
+ mFeatureFlags.scrollablePreview()
+ ? findViewById(R.id.chooser_headline_row_container)
+ : null);
if (layout != null) {
adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
@@ -788,7 +792,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
@Override
public int getLayoutResource() {
- return R.layout.chooser_grid;
+ return mFeatureFlags.scrollablePreview()
+ ? R.layout.chooser_grid_scrollable_preview
+ : R.layout.chooser_grid;
}
@Override // ResolverListCommunicator
@@ -1208,7 +1214,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
},
chooserListAdapter,
shouldShowContentPreview(),
- mMaxTargetsPerRow);
+ mMaxTargetsPerRow,
+ mFeatureFlags);
}
@VisibleForTesting
@@ -1639,11 +1646,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
- return shouldShowTabs()
- && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
- UserHandle.of(UserHandle.myUserId())).getCount() > 0
- || shouldShowContentPreviewWhenEmpty())
- && shouldShowContentPreview();
+ if (!shouldShowContentPreview()) {
+ return false;
+ }
+ boolean isEmpty = mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ UserHandle.of(UserHandle.myUserId())).getCount() == 0;
+ return (mFeatureFlags.scrollablePreview() || shouldShowTabs())
+ && (!isEmpty || shouldShowContentPreviewWhenEmpty());
}
/**
diff --git a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
index c159243e..ba35ae5d 100644
--- a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
@@ -52,7 +52,8 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda
Supplier<Boolean> workProfileQuietModeChecker,
UserHandle workProfileUserHandle,
UserHandle cloneProfileUserHandle,
- int maxTargetsPerRow) {
+ int maxTargetsPerRow,
+ FeatureFlags featureFlags) {
this(
context,
new ChooserProfileAdapterBinder(maxTargetsPerRow),
@@ -62,7 +63,8 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda
/* defaultProfile= */ 0,
workProfileUserHandle,
cloneProfileUserHandle,
- new BottomPaddingOverrideSupplier(context));
+ new BottomPaddingOverrideSupplier(context),
+ featureFlags);
}
ChooserMultiProfilePagerAdapter(
@@ -74,7 +76,8 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda
@Profile int defaultProfile,
UserHandle workProfileUserHandle,
UserHandle cloneProfileUserHandle,
- int maxTargetsPerRow) {
+ int maxTargetsPerRow,
+ FeatureFlags featureFlags) {
this(
context,
new ChooserProfileAdapterBinder(maxTargetsPerRow),
@@ -84,7 +87,8 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda
defaultProfile,
workProfileUserHandle,
cloneProfileUserHandle,
- new BottomPaddingOverrideSupplier(context));
+ new BottomPaddingOverrideSupplier(context),
+ featureFlags);
}
private ChooserMultiProfilePagerAdapter(
@@ -96,7 +100,8 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda
@Profile int defaultProfile,
UserHandle workProfileUserHandle,
UserHandle cloneProfileUserHandle,
- BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier) {
+ BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier,
+ FeatureFlags featureFlags) {
super(
context,
gridAdapter -> gridAdapter.getListAdapter(),
@@ -107,7 +112,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda
defaultProfile,
workProfileUserHandle,
cloneProfileUserHandle,
- () -> makeProfileView(context),
+ () -> makeProfileView(context, featureFlags),
bottomPaddingOverrideSupplier);
mAdapterBinder = adapterBinder;
mBottomPaddingOverrideSupplier = bottomPaddingOverrideSupplier;
@@ -131,10 +136,12 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda
}
}
- private static ViewGroup makeProfileView(Context context) {
+ private static ViewGroup makeProfileView(
+ Context context, FeatureFlags featureFlags) {
LayoutInflater inflater = LayoutInflater.from(context);
- ViewGroup rootView = (ViewGroup) inflater.inflate(
- R.layout.chooser_list_per_profile, null, false);
+ ViewGroup rootView = featureFlags.scrollablePreview()
+ ? (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile_wrap, null, false)
+ : (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile, null, false);
RecyclerView recyclerView = rootView.findViewById(com.android.internal.R.id.resolver_list);
recyclerView.setAccessibilityDelegateCompat(
new ChooserRecyclerViewAccessibilityDelegate(recyclerView));
diff --git a/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java
index fadea934..2ab38f30 100644
--- a/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java
+++ b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java
@@ -32,9 +32,12 @@ import android.view.animation.DecelerateInterpolator;
import android.widget.Space;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.intentresolver.ChooserListAdapter;
+import com.android.intentresolver.FeatureFlags;
import com.android.intentresolver.R;
import com.android.intentresolver.ResolverListAdapter.ViewHolder;
import com.android.internal.annotations.VisibleForTesting;
@@ -107,6 +110,9 @@ public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.
private final boolean mShouldShowContentPreview;
private final int mChooserWidthPixels;
private final int mChooserRowTextOptionTranslatePixelSize;
+ private final FeatureFlags mFeatureFlags;
+ @Nullable
+ private RecyclerView mRecyclerView;
private int mChooserTargetWidth = 0;
@@ -119,7 +125,8 @@ public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.
ChooserActivityDelegate chooserActivityDelegate,
ChooserListAdapter wrappedAdapter,
boolean shouldShowContentPreview,
- int maxTargetsPerRow) {
+ int maxTargetsPerRow,
+ FeatureFlags featureFlags) {
super();
mChooserActivityDelegate = chooserActivityDelegate;
@@ -133,6 +140,7 @@ public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.
mChooserWidthPixels = context.getResources().getDimensionPixelSize(R.dimen.chooser_width);
mChooserRowTextOptionTranslatePixelSize = context.getResources().getDimensionPixelSize(
R.dimen.chooser_row_text_option_translate);
+ mFeatureFlags = featureFlags;
wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
@@ -149,6 +157,18 @@ public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.
});
}
+ @Override
+ public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+ if (mFeatureFlags.scrollablePreview()) {
+ mRecyclerView = recyclerView;
+ }
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+ mRecyclerView = null;
+ }
+
public void setFooterHeight(int height) {
mFooterHeight = height;
}
@@ -198,7 +218,8 @@ public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.
public int getSystemRowCount() {
// For the tabbed case we show the sticky content preview above the tabs,
// please refer to shouldShowStickyContentPreview
- if (mChooserActivityDelegate.shouldShowTabs()) {
+ if (mChooserActivityDelegate.shouldShowTabs()
+ || mFeatureFlags.scrollablePreview()) {
return 0;
}
@@ -318,6 +339,15 @@ public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.
mAzLabelVisibility = isVisible;
int azRowPos = getAzLabelRowPosition();
if (azRowPos >= 0) {
+ if (mRecyclerView != null) {
+ for (int i = 0, size = mRecyclerView.getChildCount(); i < size; i++) {
+ View child = mRecyclerView.getChildAt(i);
+ if (mRecyclerView.getChildAdapterPosition(child) == azRowPos) {
+ child.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ }
+ }
+ return;
+ }
notifyItemChanged(azRowPos);
}
}
diff --git a/java/src/com/android/intentresolver/widget/ChooserNestedScrollView.kt b/java/src/com/android/intentresolver/widget/ChooserNestedScrollView.kt
new file mode 100644
index 00000000..26464ca1
--- /dev/null
+++ b/java/src/com/android/intentresolver/widget/ChooserNestedScrollView.kt
@@ -0,0 +1,90 @@
+package com.android.intentresolver.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.LinearLayout
+import androidx.core.view.ScrollingView
+import androidx.core.view.marginBottom
+import androidx.core.view.marginLeft
+import androidx.core.view.marginRight
+import androidx.core.view.marginTop
+import androidx.core.widget.NestedScrollView
+
+/**
+ * A narrowly tailored [NestedScrollView] to be used inside [ResolverDrawerLayout] and help to
+ * orchestrate content preview scrolling. It expects one [LinearLayout] child with
+ * [LinearLayout.VERTICAL] orientation. If the child has more than one child, the first its child
+ * will be made scrollable (it is expected to be a content preview view).
+ */
+class ChooserNestedScrollView : NestedScrollView {
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int
+ ) : super(context, attrs, defStyleAttr)
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val content =
+ getChildAt(0) as? LinearLayout ?: error("Exactly one child, LinerLayout, is expected")
+ require(content.orientation == LinearLayout.VERTICAL) { "VERTICAL orientation is expected" }
+ require(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
+ "Expected to have an exact width"
+ }
+
+ val lp = content.layoutParams ?: error("LayoutParams is missing")
+ val contentWidthSpec =
+ getChildMeasureSpec(
+ widthMeasureSpec,
+ paddingLeft + content.marginLeft + content.marginRight + paddingRight,
+ lp.width
+ )
+ val contentHeightSpec =
+ getChildMeasureSpec(
+ heightMeasureSpec,
+ paddingTop + content.marginTop + content.marginBottom + paddingBottom,
+ lp.height
+ )
+ content.measure(contentWidthSpec, contentHeightSpec)
+
+ if (content.childCount > 1) {
+ // We expect that the first child should be scrollable up
+ val child = content.getChildAt(0)
+ val height =
+ MeasureSpec.getSize(heightMeasureSpec) +
+ child.measuredHeight +
+ child.marginTop +
+ child.marginBottom
+
+ content.measure(
+ contentWidthSpec,
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))
+ )
+ }
+ setMeasuredDimension(
+ MeasureSpec.getSize(widthMeasureSpec),
+ minOf(
+ MeasureSpec.getSize(heightMeasureSpec),
+ paddingTop +
+ content.marginTop +
+ content.measuredHeight +
+ content.marginBottom +
+ paddingBottom
+ )
+ )
+ }
+
+ override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
+ // let the parent scroll
+ super.onNestedPreScroll(target, dx, dy, consumed, type)
+ // scroll ourselves, if recycler has not scrolled
+ val delta = dy - consumed[1]
+ if (delta > 0 && target is ScrollingView && !target.canScrollVertically(-1)) {
+ val preScrollY = scrollY
+ scrollBy(0, delta)
+ consumed[1] += scrollY - preScrollY
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java b/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java
index de76a1d2..b8fbedbf 100644
--- a/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java
+++ b/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java
@@ -45,6 +45,8 @@ import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.OverScroller;
+import androidx.annotation.Nullable;
+import androidx.core.view.ScrollingView;
import androidx.recyclerview.widget.RecyclerView;
import com.android.intentresolver.R;
@@ -131,6 +133,9 @@ public class ResolverDrawerLayout extends ViewGroup {
private AbsListView mNestedListChild;
private RecyclerView mNestedRecyclerChild;
+ @Nullable
+ private final ScrollablePreviewFlingLogicDelegate mFlingLogicDelegate;
+
private final ViewTreeObserver.OnTouchModeChangeListener mTouchModeChangeListener =
new ViewTreeObserver.OnTouchModeChangeListener() {
@Override
@@ -167,6 +172,12 @@ public class ResolverDrawerLayout extends ViewGroup {
mIgnoreOffsetTopLimitViewId = a.getResourceId(
R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit, ID_NULL);
}
+ mFlingLogicDelegate =
+ a.getBoolean(
+ R.styleable.ResolverDrawerLayout_useScrollablePreviewNestedFlingLogic,
+ false)
+ ? new ScrollablePreviewFlingLogicDelegate() {}
+ : null;
a.recycle();
mScrollIndicatorDrawable = mContext.getDrawable(
@@ -832,6 +843,9 @@ public class ResolverDrawerLayout extends ViewGroup {
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+ if (mFlingLogicDelegate != null) {
+ return mFlingLogicDelegate.onNestedPreFling(this, target, velocityX, velocityY);
+ }
if (!getShowAtTop() && velocityY > mMinFlingVelocity && mCollapseOffset != 0) {
smoothScrollTo(0, velocityY);
return true;
@@ -841,9 +855,12 @@ public class ResolverDrawerLayout extends ViewGroup {
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (mFlingLogicDelegate != null) {
+ return mFlingLogicDelegate.onNestedFling(this, target, velocityX, velocityY, consumed);
+ }
// TODO: find a more suitable way to fix it.
// RecyclerView started reporting `consumed` as true whenever a scrolling is enabled,
- // previously the value was based whether the fling can be performed in given direction
+ // previously the value was based on whether the fling can be performed in given direction
// i.e. whether it is at the top or at the bottom. isRecyclerViewAtTheTop method is a
// workaround that restores the legacy functionality.
boolean shouldConsume = (Math.abs(velocityY) > mMinFlingVelocity)
@@ -885,6 +902,13 @@ public class ResolverDrawerLayout extends ViewGroup {
&& firstChild.getTop() >= recyclerView.getPaddingTop();
}
+ private static boolean isFlingTargetAtTop(View target) {
+ if (target instanceof ScrollingView) {
+ return !target.canScrollVertically(-1);
+ }
+ return false;
+ }
+
private boolean performAccessibilityActionCommon(int action) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
@@ -1299,4 +1323,74 @@ public class ResolverDrawerLayout extends ViewGroup {
}
return mMetricsLogger;
}
+
+ /**
+ * Controlled by
+ * {@link com.android.intentresolver.Flags#FLAG_SCROLLABLE_PREVIEW}
+ */
+ private interface ScrollablePreviewFlingLogicDelegate {
+ default boolean onNestedPreFling(
+ ResolverDrawerLayout drawer, View target, float velocityX, float velocityY) {
+ boolean shouldScroll = !drawer.getShowAtTop() && velocityY > drawer.mMinFlingVelocity
+ && drawer.mCollapseOffset != 0;
+ if (shouldScroll) {
+ drawer.smoothScrollTo(0, velocityY);
+ return true;
+ }
+ boolean shouldDismiss = (Math.abs(velocityY) > drawer.mMinFlingVelocity)
+ && velocityY < 0
+ && isFlingTargetAtTop(target);
+ if (shouldDismiss) {
+ if (drawer.getShowAtTop()) {
+ drawer.smoothScrollTo(drawer.mCollapsibleHeight, velocityY);
+ } else {
+ if (drawer.isDismissable()
+ && drawer.mCollapseOffset > drawer.mCollapsibleHeight) {
+ drawer.smoothScrollTo(drawer.mHeightUsed, velocityY);
+ drawer.mDismissOnScrollerFinished = true;
+ } else {
+ drawer.smoothScrollTo(drawer.mCollapsibleHeight, velocityY);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ default boolean onNestedFling(
+ ResolverDrawerLayout drawer,
+ View target,
+ float velocityX,
+ float velocityY,
+ boolean consumed) {
+ // TODO: find a more suitable way to fix it.
+ // RecyclerView started reporting `consumed` as true whenever a scrolling is enabled,
+ // previously the value was based on whether the fling can be performed in given
+ // direction i.e. whether it is at the top or at the bottom. isRecyclerViewAtTheTop
+ // method is a workaround that restores the legacy functionality.
+ boolean shouldConsume = (Math.abs(velocityY) > drawer.mMinFlingVelocity) && !consumed;
+ if (shouldConsume) {
+ if (drawer.getShowAtTop()) {
+ if (drawer.isDismissable() && velocityY > 0) {
+ drawer.abortAnimation();
+ drawer.dismiss();
+ } else {
+ drawer.smoothScrollTo(
+ velocityY < 0 ? drawer.mCollapsibleHeight : 0, velocityY);
+ }
+ } else {
+ if (drawer.isDismissable()
+ && velocityY < 0
+ && drawer.mCollapseOffset > drawer.mCollapsibleHeight) {
+ drawer.smoothScrollTo(drawer.mHeightUsed, velocityY);
+ drawer.mDismissOnScrollerFinished = true;
+ } else {
+ drawer.smoothScrollTo(
+ velocityY > 0 ? 0 : drawer.mCollapsibleHeight, velocityY);
+ }
+ }
+ }
+ return shouldConsume;
+ }
+ }
}