diff options
| author | 2022-12-14 17:12:26 +0000 | |
|---|---|---|
| committer | 2022-12-14 17:12:26 +0000 | |
| commit | 92160d6e9fb1caa1142599ee252f53b796298be5 (patch) | |
| tree | 2e1e7cf136f872299ec23efd517175b9dc758f60 /java/src | |
| parent | cd6397be02bb9155a295da5e498858c9121ded7e (diff) | |
| parent | d5eb50ac083b03edf84c904e2ec16acb6ca50fdd (diff) | |
Merge "Extract ChooserGridAdapter." into tm-qpr-dev
Diffstat (limited to 'java/src')
5 files changed, 621 insertions, 595 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index c3864480..4682ec50 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -26,9 +26,6 @@ import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_S import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -58,7 +55,6 @@ import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; -import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.drawable.Drawable; @@ -85,18 +81,14 @@ import android.util.Slog; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; -import android.view.View.MeasureSpec; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.Button; -import android.widget.Space; import android.widget.TextView; import androidx.annotation.MainThread; @@ -111,12 +103,8 @@ import com.android.intentresolver.ResolverListAdapter.ViewHolder; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.MultiDisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.grid.ChooserGridAdapter; import com.android.intentresolver.grid.DirectShareViewHolder; -import com.android.intentresolver.grid.FooterViewHolder; -import com.android.intentresolver.grid.ItemGroupViewHolder; -import com.android.intentresolver.grid.ItemViewHolder; -import com.android.intentresolver.grid.SingleRowViewHolder; -import com.android.intentresolver.grid.ViewHolderBase; import com.android.intentresolver.model.AbstractResolverComparator; import com.android.intentresolver.model.AppPredictionServiceResolverComparator; import com.android.intentresolver.model.ResolverRankerServiceResolverComparator; @@ -130,8 +118,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.FrameworkStatsLog; -import com.google.android.collect.Lists; - import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; @@ -220,12 +206,6 @@ public class ChooserActivity extends ResolverActivity implements @Retention(RetentionPolicy.SOURCE) public @interface ShareTargetType {} - /** - * The transition time between placeholders for direct share to a message - * indicating that non are available. - */ - public static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200; - public static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f; private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7; @@ -2260,565 +2240,6 @@ public class ChooserActivity extends ResolverActivity implements } } - /** - * Adapter for all types of items and targets in ShareSheet. - * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the - * row level by this adapter but not on the item level. Individual targets within the row are - * handled by {@link ChooserListAdapter} - */ - @VisibleForTesting - public static final class ChooserGridAdapter extends - RecyclerView.Adapter<RecyclerView.ViewHolder> { - - /** - * Injectable interface for any considerations that should be delegated to other components - * in the {@link ChooserActivity}. - * TODO: determine whether any of these methods return parameters that can safely be - * precomputed; whether any should be converted to `ChooserGridAdapter` setters to be - * invoked by external callbacks; and whether any reflect requirements that should be moved - * out of `ChooserGridAdapter` altogether. - */ - interface ChooserActivityDelegate { - /** @return whether we're showing a tabbed (multi-profile) UI. */ - boolean shouldShowTabs(); - - /** - * @return a content preview {@link View} that's appropriate for the caller's share - * content, constructed for display in the provided {@code parent} group. - */ - View buildContentPreview(ViewGroup parent); - - /** Notify the client that the item with the selected {@code itemIndex} was selected. */ - void onTargetSelected(int itemIndex); - - /** - * Notify the client that the item with the selected {@code itemIndex} was - * long-pressed. - */ - void onTargetLongPressed(int itemIndex); - - /** - * Notify the client that the provided {@code View} should be configured as the new - * "profile view" button. Callers should attach their own click listeners to implement - * behaviors on this view. - */ - void updateProfileViewButton(View newButtonFromProfileRow); - - /** - * @return the number of "valid" targets in the active list adapter. - * TODO: define "valid." - */ - int getValidTargetCount(); - - /** - * Request that the client update our {@code directShareGroup} to match their desired - * state for the "expansion" UI. - */ - void updateDirectShareExpansion(DirectShareViewHolder directShareGroup); - - /** - * Request that the client handle a scroll event that should be taken as expanding the - * provided {@code directShareGroup}. Note that this currently never happens due to a - * hard-coded condition in {@link #canExpandDirectShare()}. - */ - void handleScrollToExpandDirectShare( - DirectShareViewHolder directShareGroup, int y, int oldy); - } - - private static final int VIEW_TYPE_DIRECT_SHARE = 0; - private static final int VIEW_TYPE_NORMAL = 1; - private static final int VIEW_TYPE_CONTENT_PREVIEW = 2; - private static final int VIEW_TYPE_PROFILE = 3; - private static final int VIEW_TYPE_AZ_LABEL = 4; - private static final int VIEW_TYPE_CALLER_AND_RANK = 5; - private static final int VIEW_TYPE_FOOTER = 6; - - private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20; - - private final ChooserActivityDelegate mChooserActivityDelegate; - private final ChooserListAdapter mChooserListAdapter; - private final LayoutInflater mLayoutInflater; - - private final int mMaxTargetsPerRow; - private final boolean mShouldShowContentPreview; - private final int mChooserWidthPixels; - private final int mChooserRowTextOptionTranslatePixelSize; - private final boolean mShowAzLabelIfPoss; - - private DirectShareViewHolder mDirectShareViewHolder; - private int mChooserTargetWidth = 0; - - private int mFooterHeight = 0; - - ChooserGridAdapter( - Context context, - ChooserActivityDelegate chooserActivityDelegate, - ChooserListAdapter wrappedAdapter, - boolean shouldShowContentPreview, - int maxTargetsPerRow, - int numSheetExpansions) { - super(); - - mChooserActivityDelegate = chooserActivityDelegate; - - mChooserListAdapter = wrappedAdapter; - mLayoutInflater = LayoutInflater.from(context); - - mShouldShowContentPreview = shouldShowContentPreview; - mMaxTargetsPerRow = maxTargetsPerRow; - - mChooserWidthPixels = context.getResources().getDimensionPixelSize( - R.dimen.chooser_width); - mChooserRowTextOptionTranslatePixelSize = context.getResources().getDimensionPixelSize( - R.dimen.chooser_row_text_option_translate); - - mShowAzLabelIfPoss = numSheetExpansions < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL; - - wrappedAdapter.registerDataSetObserver(new DataSetObserver() { - @Override - public void onChanged() { - super.onChanged(); - notifyDataSetChanged(); - } - - @Override - public void onInvalidated() { - super.onInvalidated(); - notifyDataSetChanged(); - } - }); - } - - public void setFooterHeight(int height) { - mFooterHeight = height; - } - - /** - * Calculate the chooser target width to maximize space per item - * - * @param width The new row width to use for recalculation - * @return true if the view width has changed - */ - public boolean calculateChooserTargetWidth(int width) { - if (width == 0) { - return false; - } - - // Limit width to the maximum width of the chooser activity - int maxWidth = mChooserWidthPixels; - width = Math.min(maxWidth, width); - - int newWidth = width / mMaxTargetsPerRow; - if (newWidth != mChooserTargetWidth) { - mChooserTargetWidth = newWidth; - return true; - } - - return false; - } - - public int getRowCount() { - return (int) ( - getSystemRowCount() - + getProfileRowCount() - + getServiceTargetRowCount() - + getCallerAndRankedTargetRowCount() - + getAzLabelRowCount() - + Math.ceil( - (float) mChooserListAdapter.getAlphaTargetCount() - / mMaxTargetsPerRow) - ); - } - - /** - * Whether the "system" row of targets is displayed. - * This area includes the content preview (if present) and action row. - */ - public int getSystemRowCount() { - // For the tabbed case we show the sticky content preview above the tabs, - // please refer to shouldShowStickyContentPreview - if (mChooserActivityDelegate.shouldShowTabs()) { - return 0; - } - - if (!mShouldShowContentPreview) { - return 0; - } - - if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) { - return 0; - } - - return 1; - } - - public int getProfileRowCount() { - if (mChooserActivityDelegate.shouldShowTabs()) { - return 0; - } - return mChooserListAdapter.getOtherProfile() == null ? 0 : 1; - } - - public int getFooterRowCount() { - return 1; - } - - public int getCallerAndRankedTargetRowCount() { - return (int) Math.ceil( - ((float) mChooserListAdapter.getCallerTargetCount() - + mChooserListAdapter.getRankedTargetCount()) / mMaxTargetsPerRow); - } - - // There can be at most one row in the listview, that is internally - // a ViewGroup with 2 rows - public int getServiceTargetRowCount() { - if (mShouldShowContentPreview && !ActivityManager.isLowRamDeviceStatic()) { - return 1; - } - return 0; - } - - public int getAzLabelRowCount() { - // Only show a label if the a-z list is showing - return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0; - } - - @Override - public int getItemCount() { - return (int) ( - getSystemRowCount() - + getProfileRowCount() - + getServiceTargetRowCount() - + getCallerAndRankedTargetRowCount() - + getAzLabelRowCount() - + mChooserListAdapter.getAlphaTargetCount() - + getFooterRowCount() - ); - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_TYPE_CONTENT_PREVIEW: - return new ItemViewHolder( - mChooserActivityDelegate.buildContentPreview(parent), - viewType, - null, - null); - case VIEW_TYPE_PROFILE: - return new ItemViewHolder( - createProfileView(parent), - viewType, - null, - null); - case VIEW_TYPE_AZ_LABEL: - return new ItemViewHolder( - createAzLabelView(parent), - viewType, - null, - null); - case VIEW_TYPE_NORMAL: - return new ItemViewHolder( - mChooserListAdapter.createView(parent), - viewType, - mChooserActivityDelegate::onTargetSelected, - mChooserActivityDelegate::onTargetLongPressed); - case VIEW_TYPE_DIRECT_SHARE: - case VIEW_TYPE_CALLER_AND_RANK: - return createItemGroupViewHolder(viewType, parent); - case VIEW_TYPE_FOOTER: - Space sp = new Space(parent.getContext()); - sp.setLayoutParams(new RecyclerView.LayoutParams( - LayoutParams.MATCH_PARENT, mFooterHeight)); - return new FooterViewHolder(sp, viewType); - default: - // Since we catch all possible viewTypes above, no chance this is being called. - return null; - } - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - int viewType = ((ViewHolderBase) holder).getViewType(); - switch (viewType) { - case VIEW_TYPE_DIRECT_SHARE: - case VIEW_TYPE_CALLER_AND_RANK: - bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder); - break; - case VIEW_TYPE_NORMAL: - bindItemViewHolder(position, (ItemViewHolder) holder); - break; - default: - } - } - - @Override - public int getItemViewType(int position) { - int count; - - int countSum = (count = getSystemRowCount()); - if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW; - - countSum += (count = getProfileRowCount()); - if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE; - - countSum += (count = getServiceTargetRowCount()); - if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE; - - countSum += (count = getCallerAndRankedTargetRowCount()); - if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK; - - countSum += (count = getAzLabelRowCount()); - if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL; - - if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER; - - return VIEW_TYPE_NORMAL; - } - - public int getTargetType(int position) { - return mChooserListAdapter.getPositionTargetType(getListPosition(position)); - } - - private View createProfileView(ViewGroup parent) { - View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false); - mChooserActivityDelegate.updateProfileViewButton(profileRow); - return profileRow; - } - - private View createAzLabelView(ViewGroup parent) { - return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false); - } - - private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) { - final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth, - MeasureSpec.EXACTLY); - int columnCount = holder.getColumnCount(); - - final boolean isDirectShare = holder instanceof DirectShareViewHolder; - - for (int i = 0; i < columnCount; i++) { - final View v = mChooserListAdapter.createView(holder.getRowByIndex(i)); - final int column = i; - v.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mChooserActivityDelegate.onTargetSelected(holder.getItemIndex(column)); - } - }); - - // Show menu for both direct share and app share targets after long click. - v.setOnLongClickListener(v1 -> { - mChooserActivityDelegate.onTargetLongPressed(holder.getItemIndex(column)); - return true; - }); - - holder.addView(i, v); - - // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll = - // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be - // done before measuring. - if (isDirectShare) { - final ViewHolder vh = (ViewHolder) v.getTag(); - vh.text.setLines(2); - vh.text.setHorizontallyScrolling(false); - vh.text2.setVisibility(View.GONE); - } - - // Force height to be a given so we don't have visual disruption during scaling. - v.measure(exactSpec, spec); - setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight()); - } - - final ViewGroup viewGroup = holder.getViewGroup(); - - // Pre-measure and fix height so we can scale later. - holder.measure(); - setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight()); - - if (isDirectShare) { - DirectShareViewHolder dsvh = (DirectShareViewHolder) holder; - setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight()); - setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight()); - } - - viewGroup.setTag(holder); - return holder; - } - - private void setViewBounds(View view, int widthPx, int heightPx) { - LayoutParams lp = view.getLayoutParams(); - if (lp == null) { - lp = new LayoutParams(widthPx, heightPx); - view.setLayoutParams(lp); - } else { - lp.height = heightPx; - lp.width = widthPx; - } - } - - ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) { - if (viewType == VIEW_TYPE_DIRECT_SHARE) { - ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate( - R.layout.chooser_row_direct_share, parent, false); - ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, - parentGroup, false); - ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, - parentGroup, false); - parentGroup.addView(row1); - parentGroup.addView(row2); - - mDirectShareViewHolder = new DirectShareViewHolder(parentGroup, - Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType, - mChooserActivityDelegate::getValidTargetCount); - loadViewsIntoGroup(mDirectShareViewHolder); - - return mDirectShareViewHolder; - } else { - ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent, - false); - ItemGroupViewHolder holder = - new SingleRowViewHolder(row, mMaxTargetsPerRow, viewType); - loadViewsIntoGroup(holder); - - return holder; - } - } - - /** - * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from - * showing on top of the AZ list if the AZ label is visible. All other types are placed into - * their own row as determined by their target type, and dividers are added in the list to - * separate each type. - */ - int getRowType(int rowPosition) { - // Merge caller and ranked standard into a single row - int positionType = mChooserListAdapter.getPositionTargetType(rowPosition); - if (positionType == ChooserListAdapter.TARGET_CALLER) { - return ChooserListAdapter.TARGET_STANDARD; - } - - // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z - // row type the same as the suggestion row type - if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) { - return ChooserListAdapter.TARGET_STANDARD; - } - - return positionType; - } - - void bindItemViewHolder(int position, ItemViewHolder holder) { - View v = holder.itemView; - int listPosition = getListPosition(position); - holder.setListPosition(listPosition); - mChooserListAdapter.bindView(listPosition, v); - } - - void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) { - final ViewGroup viewGroup = (ViewGroup) holder.itemView; - int start = getListPosition(position); - int startType = getRowType(start); - - int columnCount = holder.getColumnCount(); - int end = start + columnCount - 1; - while (getRowType(end) != startType && end >= start) { - end--; - } - - if (end == start && mChooserListAdapter.getItem(start).isEmptyTargetInfo()) { - final TextView textView = viewGroup.findViewById(com.android.internal.R.id.chooser_row_text_option); - - if (textView.getVisibility() != View.VISIBLE) { - textView.setAlpha(0.0f); - textView.setVisibility(View.VISIBLE); - textView.setText(R.string.chooser_no_direct_share_targets); - - ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f); - fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f)); - - textView.setTranslationY(mChooserRowTextOptionTranslatePixelSize); - ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY", - 0.0f); - translateAnim.setInterpolator(new DecelerateInterpolator(1.0f)); - - AnimatorSet animSet = new AnimatorSet(); - animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS); - animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS); - animSet.playTogether(fadeAnim, translateAnim); - animSet.start(); - } - } - - for (int i = 0; i < columnCount; i++) { - final View v = holder.getView(i); - - if (start + i <= end) { - holder.setViewVisibility(i, View.VISIBLE); - holder.setItemIndex(i, start + i); - mChooserListAdapter.bindView(holder.getItemIndex(i), v); - } else { - holder.setViewVisibility(i, View.INVISIBLE); - } - } - } - - int getListPosition(int position) { - position -= getSystemRowCount() + getProfileRowCount(); - - final int serviceCount = mChooserListAdapter.getServiceTargetCount(); - final int serviceRows = (int) Math.ceil((float) serviceCount / mMaxTargetsPerRow); - if (position < serviceRows) { - return position * mMaxTargetsPerRow; - } - - position -= serviceRows; - - final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount() - + mChooserListAdapter.getRankedTargetCount(); - final int callerAndRankedRows = getCallerAndRankedTargetRowCount(); - if (position < callerAndRankedRows) { - return serviceCount + position * mMaxTargetsPerRow; - } - - position -= getAzLabelRowCount() + callerAndRankedRows; - - return callerAndRankedCount + serviceCount + position; - } - - public void handleScroll(View v, int y, int oldy) { - boolean canExpandDirectShare = canExpandDirectShare(); - if (mDirectShareViewHolder != null && canExpandDirectShare) { - mChooserActivityDelegate.handleScrollToExpandDirectShare( - mDirectShareViewHolder, y, oldy); - } - } - - /** - * Only expand direct share area if there is a minimum number of targets. - */ - private boolean canExpandDirectShare() { - // Do not enable until we have confirmed more apps are using sharing shortcuts - // Check git history for enablement logic - return false; - } - - public ChooserListAdapter getListAdapter() { - return mChooserListAdapter; - } - - boolean shouldCellSpan(int position) { - return getItemViewType(position) == VIEW_TYPE_NORMAL; - } - - void updateDirectShareExpansion() { - if (mDirectShareViewHolder == null || !canExpandDirectShare()) { - return; - } - mChooserActivityDelegate.updateDirectShareExpansion(mDirectShareViewHolder); - } - } - static class ChooserTargetRankingInfo { public final List<AppTarget> scores; public final UserHandle userHandle; diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index e968f71d..12a054b9 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -412,7 +412,7 @@ public class ChooserListAdapter extends ResolverListAdapter { return 0; } - int getAlphaTargetCount() { + public int getAlphaTargetCount() { int groupedCount = mSortedList.size(); int ungroupedCount = mCallerTargets.size() + getDisplayResolveInfoCount(); return (ungroupedCount > mMaxRankedTargets) ? groupedCount : 0; diff --git a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java index d0463fff..93daa299 100644 --- a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java @@ -27,6 +27,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.PagerAdapter; +import com.android.intentresolver.grid.ChooserGridAdapter; import com.android.internal.annotations.VisibleForTesting; /** @@ -40,8 +41,9 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd private int mBottomOffset; private int mMaxTargetsPerRow; - ChooserMultiProfilePagerAdapter(Context context, - ChooserActivity.ChooserGridAdapter adapter, + ChooserMultiProfilePagerAdapter( + Context context, + ChooserGridAdapter adapter, EmptyStateProvider emptyStateProvider, QuietModeManager quietModeManager, UserHandle workProfileUserHandle, @@ -54,9 +56,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd mMaxTargetsPerRow = maxTargetsPerRow; } - ChooserMultiProfilePagerAdapter(Context context, - ChooserActivity.ChooserGridAdapter personalAdapter, - ChooserActivity.ChooserGridAdapter workAdapter, + ChooserMultiProfilePagerAdapter( + Context context, + ChooserGridAdapter personalAdapter, + ChooserGridAdapter workAdapter, EmptyStateProvider emptyStateProvider, QuietModeManager quietModeManager, @Profile int defaultProfile, @@ -71,8 +74,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd mMaxTargetsPerRow = maxTargetsPerRow; } - private ChooserProfileDescriptor createProfileDescriptor( - ChooserActivity.ChooserGridAdapter adapter) { + private ChooserProfileDescriptor createProfileDescriptor(ChooserGridAdapter adapter) { final LayoutInflater inflater = LayoutInflater.from(getContext()); final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile, null, false); @@ -103,7 +105,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd @Override @VisibleForTesting - public ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) { + public ChooserGridAdapter getAdapterForIndex(int pageIndex) { return mItems[pageIndex].chooserGridAdapter; } @@ -122,8 +124,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd @Override void setupListAdapter(int pageIndex) { final RecyclerView recyclerView = getItem(pageIndex).recyclerView; - ChooserActivity.ChooserGridAdapter chooserGridAdapter = - getItem(pageIndex).chooserGridAdapter; + ChooserGridAdapter chooserGridAdapter = getItem(pageIndex).chooserGridAdapter; GridLayoutManager glm = (GridLayoutManager) recyclerView.getLayoutManager(); glm.setSpanCount(mMaxTargetsPerRow); glm.setSpanSizeLookup( @@ -164,7 +165,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd } @Override - ChooserActivity.ChooserGridAdapter getCurrentRootAdapter() { + ChooserGridAdapter getCurrentRootAdapter() { return getAdapterForIndex(getCurrentPage()); } @@ -195,9 +196,9 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd } class ChooserProfileDescriptor extends ProfileDescriptor { - private ChooserActivity.ChooserGridAdapter chooserGridAdapter; + private ChooserGridAdapter chooserGridAdapter; private RecyclerView recyclerView; - ChooserProfileDescriptor(ViewGroup rootView, ChooserActivity.ChooserGridAdapter adapter) { + ChooserProfileDescriptor(ViewGroup rootView, ChooserGridAdapter adapter) { super(rootView); chooserGridAdapter = adapter; recyclerView = rootView.findViewById(com.android.internal.R.id.resolver_list); diff --git a/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java new file mode 100644 index 00000000..1cf59316 --- /dev/null +++ b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2008 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.intentresolver.grid; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.content.Context; +import android.database.DataSetObserver; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.animation.DecelerateInterpolator; +import android.widget.Space; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.android.intentresolver.ChooserListAdapter; +import com.android.intentresolver.R; +import com.android.intentresolver.ResolverListAdapter.ViewHolder; +import com.android.internal.annotations.VisibleForTesting; + +import com.google.android.collect.Lists; + +/** + * Adapter for all types of items and targets in ShareSheet. + * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the + * row level by this adapter but not on the item level. Individual targets within the row are + * handled by {@link ChooserListAdapter} + */ +@VisibleForTesting +public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { + + /** + * The transition time between placeholders for direct share to a message + * indicating that none are available. + */ + public static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200; + + /** + * Injectable interface for any considerations that should be delegated to other components + * in the {@link ChooserActivity}. + * TODO: determine whether any of these methods return parameters that can safely be + * precomputed; whether any should be converted to `ChooserGridAdapter` setters to be + * invoked by external callbacks; and whether any reflect requirements that should be moved + * out of `ChooserGridAdapter` altogether. + */ + public interface ChooserActivityDelegate { + /** @return whether we're showing a tabbed (multi-profile) UI. */ + boolean shouldShowTabs(); + + /** + * @return a content preview {@link View} that's appropriate for the caller's share + * content, constructed for display in the provided {@code parent} group. + */ + View buildContentPreview(ViewGroup parent); + + /** Notify the client that the item with the selected {@code itemIndex} was selected. */ + void onTargetSelected(int itemIndex); + + /** + * Notify the client that the item with the selected {@code itemIndex} was + * long-pressed. + */ + void onTargetLongPressed(int itemIndex); + + /** + * Notify the client that the provided {@code View} should be configured as the new + * "profile view" button. Callers should attach their own click listeners to implement + * behaviors on this view. + */ + void updateProfileViewButton(View newButtonFromProfileRow); + + /** + * @return the number of "valid" targets in the active list adapter. + * TODO: define "valid." + */ + int getValidTargetCount(); + + /** + * Request that the client update our {@code directShareGroup} to match their desired + * state for the "expansion" UI. + */ + void updateDirectShareExpansion(DirectShareViewHolder directShareGroup); + + /** + * Request that the client handle a scroll event that should be taken as expanding the + * provided {@code directShareGroup}. Note that this currently never happens due to a + * hard-coded condition in {@link #canExpandDirectShare()}. + */ + void handleScrollToExpandDirectShare( + DirectShareViewHolder directShareGroup, int y, int oldy); + } + + private static final int VIEW_TYPE_DIRECT_SHARE = 0; + private static final int VIEW_TYPE_NORMAL = 1; + private static final int VIEW_TYPE_CONTENT_PREVIEW = 2; + private static final int VIEW_TYPE_PROFILE = 3; + private static final int VIEW_TYPE_AZ_LABEL = 4; + private static final int VIEW_TYPE_CALLER_AND_RANK = 5; + private static final int VIEW_TYPE_FOOTER = 6; + + private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20; + + private final ChooserActivityDelegate mChooserActivityDelegate; + private final ChooserListAdapter mChooserListAdapter; + private final LayoutInflater mLayoutInflater; + + private final int mMaxTargetsPerRow; + private final boolean mShouldShowContentPreview; + private final int mChooserWidthPixels; + private final int mChooserRowTextOptionTranslatePixelSize; + private final boolean mShowAzLabelIfPoss; + + private DirectShareViewHolder mDirectShareViewHolder; + private int mChooserTargetWidth = 0; + + private int mFooterHeight = 0; + + public ChooserGridAdapter( + Context context, + ChooserActivityDelegate chooserActivityDelegate, + ChooserListAdapter wrappedAdapter, + boolean shouldShowContentPreview, + int maxTargetsPerRow, + int numSheetExpansions) { + super(); + + mChooserActivityDelegate = chooserActivityDelegate; + + mChooserListAdapter = wrappedAdapter; + mLayoutInflater = LayoutInflater.from(context); + + mShouldShowContentPreview = shouldShowContentPreview; + mMaxTargetsPerRow = maxTargetsPerRow; + + mChooserWidthPixels = context.getResources().getDimensionPixelSize(R.dimen.chooser_width); + mChooserRowTextOptionTranslatePixelSize = context.getResources().getDimensionPixelSize( + R.dimen.chooser_row_text_option_translate); + + mShowAzLabelIfPoss = numSheetExpansions < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL; + + wrappedAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + super.onChanged(); + notifyDataSetChanged(); + } + + @Override + public void onInvalidated() { + super.onInvalidated(); + notifyDataSetChanged(); + } + }); + } + + public void setFooterHeight(int height) { + mFooterHeight = height; + } + + /** + * Calculate the chooser target width to maximize space per item + * + * @param width The new row width to use for recalculation + * @return true if the view width has changed + */ + public boolean calculateChooserTargetWidth(int width) { + if (width == 0) { + return false; + } + + // Limit width to the maximum width of the chooser activity + int maxWidth = mChooserWidthPixels; + width = Math.min(maxWidth, width); + + int newWidth = width / mMaxTargetsPerRow; + if (newWidth != mChooserTargetWidth) { + mChooserTargetWidth = newWidth; + return true; + } + + return false; + } + + public int getRowCount() { + return (int) ( + getSystemRowCount() + + getProfileRowCount() + + getServiceTargetRowCount() + + getCallerAndRankedTargetRowCount() + + getAzLabelRowCount() + + Math.ceil( + (float) mChooserListAdapter.getAlphaTargetCount() + / mMaxTargetsPerRow) + ); + } + + /** + * Whether the "system" row of targets is displayed. + * This area includes the content preview (if present) and action row. + */ + public int getSystemRowCount() { + // For the tabbed case we show the sticky content preview above the tabs, + // please refer to shouldShowStickyContentPreview + if (mChooserActivityDelegate.shouldShowTabs()) { + return 0; + } + + if (!mShouldShowContentPreview) { + return 0; + } + + if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) { + return 0; + } + + return 1; + } + + public int getProfileRowCount() { + if (mChooserActivityDelegate.shouldShowTabs()) { + return 0; + } + return mChooserListAdapter.getOtherProfile() == null ? 0 : 1; + } + + public int getFooterRowCount() { + return 1; + } + + public int getCallerAndRankedTargetRowCount() { + return (int) Math.ceil( + ((float) mChooserListAdapter.getCallerTargetCount() + + mChooserListAdapter.getRankedTargetCount()) / mMaxTargetsPerRow); + } + + // There can be at most one row in the listview, that is internally + // a ViewGroup with 2 rows + public int getServiceTargetRowCount() { + if (mShouldShowContentPreview && !ActivityManager.isLowRamDeviceStatic()) { + return 1; + } + return 0; + } + + public int getAzLabelRowCount() { + // Only show a label if the a-z list is showing + return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0; + } + + @Override + public int getItemCount() { + return (int) ( + getSystemRowCount() + + getProfileRowCount() + + getServiceTargetRowCount() + + getCallerAndRankedTargetRowCount() + + getAzLabelRowCount() + + mChooserListAdapter.getAlphaTargetCount() + + getFooterRowCount() + ); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case VIEW_TYPE_CONTENT_PREVIEW: + return new ItemViewHolder( + mChooserActivityDelegate.buildContentPreview(parent), + viewType, + null, + null); + case VIEW_TYPE_PROFILE: + return new ItemViewHolder( + createProfileView(parent), + viewType, + null, + null); + case VIEW_TYPE_AZ_LABEL: + return new ItemViewHolder( + createAzLabelView(parent), + viewType, + null, + null); + case VIEW_TYPE_NORMAL: + return new ItemViewHolder( + mChooserListAdapter.createView(parent), + viewType, + mChooserActivityDelegate::onTargetSelected, + mChooserActivityDelegate::onTargetLongPressed); + case VIEW_TYPE_DIRECT_SHARE: + case VIEW_TYPE_CALLER_AND_RANK: + return createItemGroupViewHolder(viewType, parent); + case VIEW_TYPE_FOOTER: + Space sp = new Space(parent.getContext()); + sp.setLayoutParams(new RecyclerView.LayoutParams( + LayoutParams.MATCH_PARENT, mFooterHeight)); + return new FooterViewHolder(sp, viewType); + default: + // Since we catch all possible viewTypes above, no chance this is being called. + return null; + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + int viewType = ((ViewHolderBase) holder).getViewType(); + switch (viewType) { + case VIEW_TYPE_DIRECT_SHARE: + case VIEW_TYPE_CALLER_AND_RANK: + bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder); + break; + case VIEW_TYPE_NORMAL: + bindItemViewHolder(position, (ItemViewHolder) holder); + break; + default: + } + } + + @Override + public int getItemViewType(int position) { + int count; + + int countSum = (count = getSystemRowCount()); + if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW; + + countSum += (count = getProfileRowCount()); + if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE; + + countSum += (count = getServiceTargetRowCount()); + if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE; + + countSum += (count = getCallerAndRankedTargetRowCount()); + if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK; + + countSum += (count = getAzLabelRowCount()); + if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL; + + if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER; + + return VIEW_TYPE_NORMAL; + } + + public int getTargetType(int position) { + return mChooserListAdapter.getPositionTargetType(getListPosition(position)); + } + + private View createProfileView(ViewGroup parent) { + View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false); + mChooserActivityDelegate.updateProfileViewButton(profileRow); + return profileRow; + } + + private View createAzLabelView(ViewGroup parent) { + return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false); + } + + private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) { + final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth, MeasureSpec.EXACTLY); + int columnCount = holder.getColumnCount(); + + final boolean isDirectShare = holder instanceof DirectShareViewHolder; + + for (int i = 0; i < columnCount; i++) { + final View v = mChooserListAdapter.createView(holder.getRowByIndex(i)); + final int column = i; + v.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mChooserActivityDelegate.onTargetSelected(holder.getItemIndex(column)); + } + }); + + // Show menu for both direct share and app share targets after long click. + v.setOnLongClickListener(v1 -> { + mChooserActivityDelegate.onTargetLongPressed(holder.getItemIndex(column)); + return true; + }); + + holder.addView(i, v); + + // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll = + // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be + // done before measuring. + if (isDirectShare) { + final ViewHolder vh = (ViewHolder) v.getTag(); + vh.text.setLines(2); + vh.text.setHorizontallyScrolling(false); + vh.text2.setVisibility(View.GONE); + } + + // Force height to be a given so we don't have visual disruption during scaling. + v.measure(exactSpec, spec); + setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight()); + } + + final ViewGroup viewGroup = holder.getViewGroup(); + + // Pre-measure and fix height so we can scale later. + holder.measure(); + setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight()); + + if (isDirectShare) { + DirectShareViewHolder dsvh = (DirectShareViewHolder) holder; + setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight()); + setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight()); + } + + viewGroup.setTag(holder); + return holder; + } + + private void setViewBounds(View view, int widthPx, int heightPx) { + LayoutParams lp = view.getLayoutParams(); + if (lp == null) { + lp = new LayoutParams(widthPx, heightPx); + view.setLayoutParams(lp); + } else { + lp.height = heightPx; + lp.width = widthPx; + } + } + + ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) { + if (viewType == VIEW_TYPE_DIRECT_SHARE) { + ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate( + R.layout.chooser_row_direct_share, parent, false); + ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate( + R.layout.chooser_row, parentGroup, false); + ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate( + R.layout.chooser_row, parentGroup, false); + parentGroup.addView(row1); + parentGroup.addView(row2); + + mDirectShareViewHolder = new DirectShareViewHolder(parentGroup, + Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType, + mChooserActivityDelegate::getValidTargetCount); + loadViewsIntoGroup(mDirectShareViewHolder); + + return mDirectShareViewHolder; + } else { + ViewGroup row = (ViewGroup) mLayoutInflater.inflate( + R.layout.chooser_row, parent, false); + ItemGroupViewHolder holder = + new SingleRowViewHolder(row, mMaxTargetsPerRow, viewType); + loadViewsIntoGroup(holder); + + return holder; + } + } + + /** + * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from + * showing on top of the AZ list if the AZ label is visible. All other types are placed into + * their own row as determined by their target type, and dividers are added in the list to + * separate each type. + */ + int getRowType(int rowPosition) { + // Merge caller and ranked standard into a single row + int positionType = mChooserListAdapter.getPositionTargetType(rowPosition); + if (positionType == ChooserListAdapter.TARGET_CALLER) { + return ChooserListAdapter.TARGET_STANDARD; + } + + // If an A-Z label is shown, prevent a separator from appearing by making the A-Z + // row type the same as the suggestion row type + if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) { + return ChooserListAdapter.TARGET_STANDARD; + } + + return positionType; + } + + void bindItemViewHolder(int position, ItemViewHolder holder) { + View v = holder.itemView; + int listPosition = getListPosition(position); + holder.setListPosition(listPosition); + mChooserListAdapter.bindView(listPosition, v); + } + + void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) { + final ViewGroup viewGroup = (ViewGroup) holder.itemView; + int start = getListPosition(position); + int startType = getRowType(start); + + int columnCount = holder.getColumnCount(); + int end = start + columnCount - 1; + while (getRowType(end) != startType && end >= start) { + end--; + } + + if (end == start && mChooserListAdapter.getItem(start).isEmptyTargetInfo()) { + final TextView textView = viewGroup.findViewById( + com.android.internal.R.id.chooser_row_text_option); + + if (textView.getVisibility() != View.VISIBLE) { + textView.setAlpha(0.0f); + textView.setVisibility(View.VISIBLE); + textView.setText(R.string.chooser_no_direct_share_targets); + + ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f); + fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f)); + + textView.setTranslationY(mChooserRowTextOptionTranslatePixelSize); + ValueAnimator translateAnim = + ObjectAnimator.ofFloat(textView, "translationY", 0.0f); + translateAnim.setInterpolator(new DecelerateInterpolator(1.0f)); + + AnimatorSet animSet = new AnimatorSet(); + animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS); + animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS); + animSet.playTogether(fadeAnim, translateAnim); + animSet.start(); + } + } + + for (int i = 0; i < columnCount; i++) { + final View v = holder.getView(i); + + if (start + i <= end) { + holder.setViewVisibility(i, View.VISIBLE); + holder.setItemIndex(i, start + i); + mChooserListAdapter.bindView(holder.getItemIndex(i), v); + } else { + holder.setViewVisibility(i, View.INVISIBLE); + } + } + } + + int getListPosition(int position) { + position -= getSystemRowCount() + getProfileRowCount(); + + final int serviceCount = mChooserListAdapter.getServiceTargetCount(); + final int serviceRows = (int) Math.ceil((float) serviceCount / mMaxTargetsPerRow); + if (position < serviceRows) { + return position * mMaxTargetsPerRow; + } + + position -= serviceRows; + + final int callerAndRankedCount = + mChooserListAdapter.getCallerTargetCount() + + mChooserListAdapter.getRankedTargetCount(); + final int callerAndRankedRows = getCallerAndRankedTargetRowCount(); + if (position < callerAndRankedRows) { + return serviceCount + position * mMaxTargetsPerRow; + } + + position -= getAzLabelRowCount() + callerAndRankedRows; + + return callerAndRankedCount + serviceCount + position; + } + + public void handleScroll(View v, int y, int oldy) { + boolean canExpandDirectShare = canExpandDirectShare(); + if (mDirectShareViewHolder != null && canExpandDirectShare) { + mChooserActivityDelegate.handleScrollToExpandDirectShare( + mDirectShareViewHolder, y, oldy); + } + } + + /** Only expand direct share area if there is a minimum number of targets. */ + private boolean canExpandDirectShare() { + // Do not enable until we have confirmed more apps are using sharing shortcuts + // Check git history for enablement logic + return false; + } + + public ChooserListAdapter getListAdapter() { + return mChooserListAdapter; + } + + public boolean shouldCellSpan(int position) { + return getItemViewType(position) == VIEW_TYPE_NORMAL; + } + + public void updateDirectShareExpansion() { + if (mDirectShareViewHolder == null || !canExpandDirectShare()) { + return; + } + mChooserActivityDelegate.updateDirectShareExpansion(mDirectShareViewHolder); + } +} diff --git a/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java b/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java index cfd54697..316c9f07 100644 --- a/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java +++ b/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java @@ -113,7 +113,7 @@ public class DirectShareViewHolder extends ItemGroupViewHolder { mCellVisibility[i] = false; ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f); - fadeAnim.setDuration(ChooserActivity.NO_DIRECT_SHARE_ANIM_IN_MILLIS); + fadeAnim.setDuration(ChooserGridAdapter.NO_DIRECT_SHARE_ANIM_IN_MILLIS); fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f)); fadeAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { |