diff options
Diffstat (limited to 'java/src')
| -rw-r--r-- | java/src/com/android/intentresolver/ChooserActivity.java | 6 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java | 7 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java | 235 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/MultiProfilePagerAdapter.java (renamed from java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java) | 322 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java | 4 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java | 6 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/ResolverActivity.java | 28 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java | 3 | ||||
| -rw-r--r-- | java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java | 6 |
9 files changed, 266 insertions, 351 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index f455be4c..7b4f4827 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -73,8 +73,8 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.ViewPager; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyState; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyStateProvider; import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.MultiDisplayResolveInfo; @@ -418,7 +418,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } @Override - protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( + protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter( Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, diff --git a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java index ba35ae5d..75ff3a7f 100644 --- a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java @@ -38,7 +38,7 @@ import java.util.function.Supplier; * A {@link PagerAdapter} which describes the work and personal profile share sheet screens. */ @VisibleForTesting -public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAdapter< +public class ChooserMultiProfilePagerAdapter extends MultiProfilePagerAdapter< RecyclerView, ChooserGridAdapter, ChooserListAdapter> { private static final int SINGLE_CELL_SPAN_SIZE = 1; @@ -103,7 +103,6 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier, FeatureFlags featureFlags) { super( - context, gridAdapter -> gridAdapter.getListAdapter(), adapterBinder, gridAdapters, @@ -149,7 +148,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda } @Override - boolean rebuildActiveTab(boolean doPostProcessing) { + public boolean rebuildActiveTab(boolean doPostProcessing) { if (doPostProcessing) { Tracer.INSTANCE.beginAppTargetLoadingSection(getActiveListAdapter().getUserHandle()); } @@ -157,7 +156,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda } @Override - boolean rebuildInactiveTab(boolean doPostProcessing) { + public boolean rebuildInactiveTab(boolean doPostProcessing) { if (getItemCount() != 1 && doPostProcessing) { Tracer.INSTANCE.beginAppTargetLoadingSection(getInactiveListAdapter().getUserHandle()); } diff --git a/java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java deleted file mode 100644 index a1c53402..00000000 --- a/java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2022 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; - -import android.annotation.Nullable; -import android.content.Context; -import android.os.UserHandle; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import com.android.internal.annotations.VisibleForTesting; - -import com.google.common.collect.ImmutableList; - -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * Implementation of {@link AbstractMultiProfilePagerAdapter} that consolidates the variation in - * existing implementations; most overrides were only to vary type signatures (which are better - * represented via generic types), and a few minor behavioral customizations are now implemented - * through small injectable delegate classes. - * TODO: now that the existing implementations are shown to be expressible in terms of this new - * generic type, merge up into the base class and simplify the public APIs. - * TODO: attempt to further restrict visibility in the methods we expose. - * TODO: deprecate and audit/fix usages of any methods that refer to the "active" or "inactive" - * adapters; these were marked {@link VisibleForTesting} and their usage seems like an accident - * waiting to happen since clients seem to make assumptions about which adapter will be "active" in - * a particular context, and more explicit APIs would make sure those were valid. - * TODO: consider renaming legacy methods (e.g. why do we know it's a "list", not just a "page"?) - * - * @param <PageViewT> the type of the widget that represents the contents of a page in this adapter - * @param <SinglePageAdapterT> the type of a "root" adapter class to be instantiated and included in - * the per-profile records. - * @param <ListAdapterT> the concrete type of a {@link ResolverListAdapter} implementation to - * control the contents of a given per-profile list. This is provided for convenience, since it must - * be possible to get the list adapter from the page adapter via our {@link mListAdapterExtractor}. - * - * TODO: this class doesn't make any explicit usage of the {@link ResolverListAdapter} API, so the - * type constraint can probably be dropped once the API is merged upwards and cleaned. - */ -class GenericMultiProfilePagerAdapter< - PageViewT extends ViewGroup, - SinglePageAdapterT, - ListAdapterT extends ResolverListAdapter> extends AbstractMultiProfilePagerAdapter { - - /** Delegate to set up a given adapter and page view to be used together. */ - public interface AdapterBinder<PageViewT, SinglePageAdapterT> { - /** - * The given {@code view} will be associated with the given {@code adapter}. Do any work - * necessary to configure them compatibly, introduce them to each other, etc. - */ - void bind(PageViewT view, SinglePageAdapterT adapter); - } - - private final Function<SinglePageAdapterT, ListAdapterT> mListAdapterExtractor; - private final AdapterBinder<PageViewT, SinglePageAdapterT> mAdapterBinder; - private final Supplier<ViewGroup> mPageViewInflater; - private final Supplier<Optional<Integer>> mContainerBottomPaddingOverrideSupplier; - - private final ImmutableList<GenericProfileDescriptor<PageViewT, SinglePageAdapterT>> mItems; - - GenericMultiProfilePagerAdapter( - Context context, - Function<SinglePageAdapterT, ListAdapterT> listAdapterExtractor, - AdapterBinder<PageViewT, SinglePageAdapterT> adapterBinder, - ImmutableList<SinglePageAdapterT> adapters, - EmptyStateProvider emptyStateProvider, - Supplier<Boolean> workProfileQuietModeChecker, - @Profile int defaultProfile, - UserHandle workProfileUserHandle, - UserHandle cloneProfileUserHandle, - Supplier<ViewGroup> pageViewInflater, - Supplier<Optional<Integer>> containerBottomPaddingOverrideSupplier) { - super( - context, - /* currentPage= */ defaultProfile, - emptyStateProvider, - workProfileQuietModeChecker, - workProfileUserHandle, - cloneProfileUserHandle); - - mListAdapterExtractor = listAdapterExtractor; - mAdapterBinder = adapterBinder; - mPageViewInflater = pageViewInflater; - mContainerBottomPaddingOverrideSupplier = containerBottomPaddingOverrideSupplier; - - ImmutableList.Builder<GenericProfileDescriptor<PageViewT, SinglePageAdapterT>> items = - new ImmutableList.Builder<>(); - for (SinglePageAdapterT adapter : adapters) { - items.add(createProfileDescriptor(adapter)); - } - mItems = items.build(); - } - - private GenericProfileDescriptor<PageViewT, SinglePageAdapterT> - createProfileDescriptor(SinglePageAdapterT adapter) { - return new GenericProfileDescriptor<>(mPageViewInflater.get(), adapter); - } - - @Override - protected GenericProfileDescriptor<PageViewT, SinglePageAdapterT> getItem(int pageIndex) { - return mItems.get(pageIndex); - } - - @Override - public int getItemCount() { - return mItems.size(); - } - - public PageViewT getListViewForIndex(int index) { - return getItem(index).mView; - } - - @Override - @VisibleForTesting - public SinglePageAdapterT getAdapterForIndex(int index) { - return getItem(index).mAdapter; - } - - @Override - protected void setupListAdapter(int pageIndex) { - mAdapterBinder.bind(getListViewForIndex(pageIndex), getAdapterForIndex(pageIndex)); - } - - @Override - public ViewGroup instantiateItem(ViewGroup container, int position) { - setupListAdapter(position); - return super.instantiateItem(container, position); - } - - @Override - @Nullable - protected ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) { - if (getPersonalListAdapter().getUserHandle().equals(userHandle) - || userHandle.equals(getCloneUserHandle())) { - return getPersonalListAdapter(); - } else if (getWorkListAdapter() != null - && getWorkListAdapter().getUserHandle().equals(userHandle)) { - return getWorkListAdapter(); - } - return null; - } - - @Override - @VisibleForTesting - public ListAdapterT getActiveListAdapter() { - return mListAdapterExtractor.apply(getAdapterForIndex(getCurrentPage())); - } - - @Override - @VisibleForTesting - public ListAdapterT getInactiveListAdapter() { - if (getCount() < 2) { - return null; - } - return mListAdapterExtractor.apply(getAdapterForIndex(1 - getCurrentPage())); - } - - @Override - public ListAdapterT getPersonalListAdapter() { - return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_PERSONAL)); - } - - @Override - public ListAdapterT getWorkListAdapter() { - if (!hasAdapterForIndex(PROFILE_WORK)) { - return null; - } - return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK)); - } - - @Override - protected SinglePageAdapterT getCurrentRootAdapter() { - return getAdapterForIndex(getCurrentPage()); - } - - @Override - protected PageViewT getActiveAdapterView() { - return getListViewForIndex(getCurrentPage()); - } - - @Override - protected PageViewT getInactiveAdapterView() { - if (getCount() < 2) { - return null; - } - return getListViewForIndex(1 - getCurrentPage()); - } - - @Override - protected void setupContainerPadding(View container) { - Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get(); - bottomPaddingOverride.ifPresent(paddingBottom -> - container.setPadding( - container.getPaddingLeft(), - container.getPaddingTop(), - container.getPaddingRight(), - paddingBottom)); - } - - private boolean hasAdapterForIndex(int pageIndex) { - return (pageIndex < getCount()); - } - - // TODO: `ChooserActivity` also has a per-profile record type. Maybe the "multi-profile pager" - // should be the owner of all per-profile data (especially now that the API is generic)? - private static class GenericProfileDescriptor<PageViewT, SinglePageAdapterT> extends - ProfileDescriptor { - private final SinglePageAdapterT mAdapter; - private final PageViewT mView; - - GenericProfileDescriptor(ViewGroup rootView, SinglePageAdapterT adapter) { - super(rootView); - mAdapter = adapter; - mView = (PageViewT) rootView.findViewById(com.android.internal.R.id.resolver_list); - } - } -} diff --git a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java index 4b06db3b..cc079a87 100644 --- a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/MultiProfilePagerAdapter.java @@ -36,57 +36,115 @@ import androidx.viewpager.widget.ViewPager; import com.android.internal.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; + import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; /** - * Skeletal {@link PagerAdapter} implementation of a work or personal profile page for - * intent resolution (including share sheet). + * Skeletal {@link PagerAdapter} implementation for a UI with per-profile tabs (as in Sharesheet). + * + * TODO: attempt to further restrict visibility/improve encapsulation in the methods we expose. + * TODO: deprecate and audit/fix usages of any methods that refer to the "active" or "inactive" + * adapters; these were marked {@link VisibleForTesting} and their usage seems like an accident + * waiting to happen since clients seem to make assumptions about which adapter will be "active" in + * a particular context, and more explicit APIs would make sure those were valid. + * TODO: consider renaming legacy methods (e.g. why do we know it's a "list", not just a "page"?) + * + * @param <PageViewT> the type of the widget that represents the contents of a page in this adapter + * @param <SinglePageAdapterT> the type of a "root" adapter class to be instantiated and included in + * the per-profile records. + * @param <ListAdapterT> the concrete type of a {@link ResolverListAdapter} implementation to + * control the contents of a given per-profile list. This is provided for convenience, since it must + * be possible to get the list adapter from the page adapter via our {@link mListAdapterExtractor}. + * + * TODO: this is part of an in-progress refactor to merge with `GenericMultiProfilePagerAdapter`. + * As originally noted there, we've reduced explicit references to the `ResolverListAdapter` base + * type and may be able to drop the type constraint. */ -public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { +public class MultiProfilePagerAdapter< + PageViewT extends ViewGroup, + SinglePageAdapterT, + ListAdapterT extends ResolverListAdapter> extends PagerAdapter { + + /** + * Delegate to set up a given adapter and page view to be used together. + * @param <PageViewT> (as in {@link MultiProfilePagerAdapter}). + * @param <SinglePageAdapterT> (as in {@link MultiProfilePagerAdapter}). + */ + public interface AdapterBinder<PageViewT, SinglePageAdapterT> { + /** + * The given {@code view} will be associated with the given {@code adapter}. Do any work + * necessary to configure them compatibly, introduce them to each other, etc. + */ + void bind(PageViewT view, SinglePageAdapterT adapter); + } - private static final String TAG = "AbstractMultiProfilePagerAdapter"; static final int PROFILE_PERSONAL = 0; static final int PROFILE_WORK = 1; @IntDef({PROFILE_PERSONAL, PROFILE_WORK}) @interface Profile {} - private final Context mContext; - private int mCurrentPage; - private OnProfileSelectedListener mOnProfileSelectedListener; + private final Function<SinglePageAdapterT, ListAdapterT> mListAdapterExtractor; + private final AdapterBinder<PageViewT, SinglePageAdapterT> mAdapterBinder; + private final Supplier<ViewGroup> mPageViewInflater; + private final Supplier<Optional<Integer>> mContainerBottomPaddingOverrideSupplier; + + private final ImmutableList<ProfileDescriptor<PageViewT, SinglePageAdapterT>> mItems; - private Set<Integer> mLoadedPages; private final EmptyStateProvider mEmptyStateProvider; private final UserHandle mWorkProfileUserHandle; private final UserHandle mCloneProfileUserHandle; private final Supplier<Boolean> mWorkProfileQuietModeChecker; // True when work is quiet. - AbstractMultiProfilePagerAdapter( - Context context, - int currentPage, + private Set<Integer> mLoadedPages; + private int mCurrentPage; + private OnProfileSelectedListener mOnProfileSelectedListener; + + protected MultiProfilePagerAdapter( + Function<SinglePageAdapterT, ListAdapterT> listAdapterExtractor, + AdapterBinder<PageViewT, SinglePageAdapterT> adapterBinder, + ImmutableList<SinglePageAdapterT> adapters, EmptyStateProvider emptyStateProvider, Supplier<Boolean> workProfileQuietModeChecker, + @Profile int defaultProfile, UserHandle workProfileUserHandle, - UserHandle cloneProfileUserHandle) { - mContext = Objects.requireNonNull(context); - mCurrentPage = currentPage; + UserHandle cloneProfileUserHandle, + Supplier<ViewGroup> pageViewInflater, + Supplier<Optional<Integer>> containerBottomPaddingOverrideSupplier) { + mCurrentPage = defaultProfile; mLoadedPages = new HashSet<>(); mWorkProfileUserHandle = workProfileUserHandle; mCloneProfileUserHandle = cloneProfileUserHandle; mEmptyStateProvider = emptyStateProvider; mWorkProfileQuietModeChecker = workProfileQuietModeChecker; + + mListAdapterExtractor = listAdapterExtractor; + mAdapterBinder = adapterBinder; + mPageViewInflater = pageViewInflater; + mContainerBottomPaddingOverrideSupplier = containerBottomPaddingOverrideSupplier; + + ImmutableList.Builder<ProfileDescriptor<PageViewT, SinglePageAdapterT>> items = + new ImmutableList.Builder<>(); + for (SinglePageAdapterT adapter : adapters) { + items.add(createProfileDescriptor(adapter)); + } + mItems = items.build(); } - void setOnProfileSelectedListener(OnProfileSelectedListener listener) { - mOnProfileSelectedListener = listener; + private ProfileDescriptor<PageViewT, SinglePageAdapterT> createProfileDescriptor( + SinglePageAdapterT adapter) { + return new ProfileDescriptor<>(mPageViewInflater.get(), adapter); } - Context getContext() { - return mContext; + public void setOnProfileSelectedListener(OnProfileSelectedListener listener) { + mOnProfileSelectedListener = listener; } /** @@ -94,7 +152,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * an {@link ViewPager.OnPageChangeListener} where it keeps track of the currently displayed * page and rebuilds the list. */ - void setupViewPager(ViewPager viewPager) { + public void setupViewPager(ViewPager viewPager) { viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { @@ -120,7 +178,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { mLoadedPages.add(mCurrentPage); } - void clearInactiveProfileCache() { + public void clearInactiveProfileCache() { if (mLoadedPages.size() == 1) { return; } @@ -128,10 +186,11 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } @Override - public ViewGroup instantiateItem(ViewGroup container, int position) { - final ProfileDescriptor profileDescriptor = getItem(position); - container.addView(profileDescriptor.rootView); - return profileDescriptor.rootView; + public final ViewGroup instantiateItem(ViewGroup container, int position) { + setupListAdapter(position); + final ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(position); + container.addView(descriptor.mRootView); + return descriptor.mRootView; } @Override @@ -177,7 +236,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li> * </ul> */ - abstract ProfileDescriptor getItem(int pageIndex); + private ProfileDescriptor<PageViewT, SinglePageAdapterT> getItem(int pageIndex) { + return mItems.get(pageIndex); + } protected ViewGroup getEmptyStateView(int pageIndex) { return getItem(pageIndex).getEmptyStateView(); @@ -188,13 +249,13 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * <p>For a normal consumer device with only one user returns <code>1</code>. * <p>For a device with a work profile returns <code>2</code>. */ - abstract int getItemCount(); + public final int getItemCount() { + return mItems.size(); + } - /** - * Performs view-related initialization procedures for the adapter specified - * by <code>pageIndex</code>. - */ - abstract void setupListAdapter(int pageIndex); + public final PageViewT getListViewForIndex(int index) { + return getItem(index).mView; + } /** * Returns the adapter of the list view for the relevant page specified by @@ -203,54 +264,99 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * depending on the adapter type. */ @VisibleForTesting - public abstract Object getAdapterForIndex(int pageIndex); + public final SinglePageAdapterT getAdapterForIndex(int index) { + return getItem(index).mAdapter; + } + + /** + * Performs view-related initialization procedures for the adapter specified + * by <code>pageIndex</code>. + */ + protected final void setupListAdapter(int pageIndex) { + mAdapterBinder.bind(getListViewForIndex(pageIndex), getAdapterForIndex(pageIndex)); + } /** - * Returns the {@link ResolverListAdapter} instance of the profile that represents + * Returns the {@link ListAdapterT} instance of the profile that represents * <code>userHandle</code>. If there is no such adapter for the specified * <code>userHandle</code>, returns {@code null}. * <p>For example, if there is a work profile on the device with user id 10, calling this method - * with <code>UserHandle.of(10)</code> returns the work profile {@link ResolverListAdapter}. + * with <code>UserHandle.of(10)</code> returns the work profile {@link ListAdapterT}. */ @Nullable - abstract ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle); + protected final ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) { + if (getPersonalListAdapter().getUserHandle().equals(userHandle) + || userHandle.equals(getCloneUserHandle())) { + return getPersonalListAdapter(); + } else if ((getWorkListAdapter() != null) + && getWorkListAdapter().getUserHandle().equals(userHandle)) { + return getWorkListAdapter(); + } + return null; + } /** - * Returns the {@link ResolverListAdapter} instance of the profile that is currently visible + * Returns the {@link ListAdapterT} instance of the profile that is currently visible * to the user. * <p>For example, if the user is viewing the work tab in the share sheet, this method returns - * the work profile {@link ResolverListAdapter}. + * the work profile {@link ListAdapterT}. * @see #getInactiveListAdapter() */ @VisibleForTesting - public abstract ResolverListAdapter getActiveListAdapter(); + protected final ListAdapterT getActiveListAdapter() { + return mListAdapterExtractor.apply(getAdapterForIndex(getCurrentPage())); + } /** - * If this is a device with a work profile, returns the {@link ResolverListAdapter} instance + * If this is a device with a work profile, returns the {@link ListAdapterT} instance * of the profile that is <b><i>not</i></b> currently visible to the user. Otherwise returns * {@code null}. * <p>For example, if the user is viewing the work tab in the share sheet, this method returns - * the personal profile {@link ResolverListAdapter}. + * the personal profile {@link ListAdapterT}. * @see #getActiveListAdapter() */ @VisibleForTesting - public abstract @Nullable ResolverListAdapter getInactiveListAdapter(); + @Nullable + protected final ListAdapterT getInactiveListAdapter() { + if (getCount() < 2) { + return null; + } + return mListAdapterExtractor.apply(getAdapterForIndex(1 - getCurrentPage())); + } - public abstract ResolverListAdapter getPersonalListAdapter(); + public final ListAdapterT getPersonalListAdapter() { + return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_PERSONAL)); + } - public abstract @Nullable ResolverListAdapter getWorkListAdapter(); + @Nullable + public final ListAdapterT getWorkListAdapter() { + if (!hasAdapterForIndex(PROFILE_WORK)) { + return null; + } + return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK)); + } - abstract Object getCurrentRootAdapter(); + protected final SinglePageAdapterT getCurrentRootAdapter() { + return getAdapterForIndex(getCurrentPage()); + } - abstract ViewGroup getActiveAdapterView(); + protected final PageViewT getActiveAdapterView() { + return getListViewForIndex(getCurrentPage()); + } - abstract @Nullable ViewGroup getInactiveAdapterView(); + @Nullable + protected final PageViewT getInactiveAdapterView() { + if (getCount() < 2) { + return null; + } + return getListViewForIndex(1 - getCurrentPage()); + } /** * Rebuilds the tab that is currently visible to the user. * <p>Returns {@code true} if rebuild has completed. */ - boolean rebuildActiveTab(boolean doPostProcessing) { + public boolean rebuildActiveTab(boolean doPostProcessing) { Trace.beginSection("MultiProfilePagerAdapter#rebuildActiveTab"); boolean result = rebuildTab(getActiveListAdapter(), doPostProcessing); Trace.endSection(); @@ -261,7 +367,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * Rebuilds the tab that is not currently visible to the user, if such one exists. * <p>Returns {@code true} if rebuild has completed. */ - boolean rebuildInactiveTab(boolean doPostProcessing) { + public boolean rebuildInactiveTab(boolean doPostProcessing) { Trace.beginSection("MultiProfilePagerAdapter#rebuildInactiveTab"); if (getItemCount() == 1) { Trace.endSection(); @@ -280,7 +386,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } } - private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) { + private boolean rebuildTab(ListAdapterT activeListAdapter, boolean doPostProcessing) { if (shouldSkipRebuild(activeListAdapter)) { activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true); return false; @@ -288,16 +394,20 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { return activeListAdapter.rebuildList(doPostProcessing); } - private boolean shouldSkipRebuild(ResolverListAdapter activeListAdapter) { + private boolean shouldSkipRebuild(ListAdapterT activeListAdapter) { EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter); return emptyState != null && emptyState.shouldSkipDataRebuild(); } + private boolean hasAdapterForIndex(int pageIndex) { + return (pageIndex < getCount()); + } + /** * The empty state screens are shown according to their priority: * <ol> * <li>(highest priority) cross-profile disabled by policy (handled in - * {@link #rebuildTab(ResolverListAdapter, boolean)})</li> + * {@link #rebuildTab(ListAdapterT, boolean)})</li> * <li>no apps available</li> * <li>(least priority) work is off</li> * </ol> @@ -306,7 +416,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * the work profile on if there will not be any apps resolved * anyway. */ - void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) { + public void showEmptyResolverListEmptyState(ListAdapterT listAdapter) { final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter); if (emptyState == null) { @@ -319,9 +429,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { if (emptyState.getButtonClickListener() != null) { clickListener = v -> emptyState.getButtonClickListener().onClick(() -> { - ProfileDescriptor descriptor = getItem( + ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem( userHandleToPageIndex(listAdapter.getUserHandle())); - AbstractMultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView()); + MultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView()); }); } @@ -366,19 +476,24 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } } - protected void showEmptyState(ResolverListAdapter activeListAdapter, EmptyState emptyState, + protected void showEmptyState( + ListAdapterT activeListAdapter, + EmptyState emptyState, View.OnClickListener buttonOnClick) { - ProfileDescriptor descriptor = getItem( + ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem( userHandleToPageIndex(activeListAdapter.getUserHandle())); - descriptor.rootView.findViewById(com.android.internal.R.id.resolver_list).setVisibility(View.GONE); + descriptor.mRootView.findViewById( + com.android.internal.R.id.resolver_list).setVisibility(View.GONE); ViewGroup emptyStateView = descriptor.getEmptyStateView(); resetViewVisibilitiesForEmptyState(emptyStateView); emptyStateView.setVisibility(View.VISIBLE); - View container = emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_container); + View container = emptyStateView.findViewById( + com.android.internal.R.id.resolver_empty_state_container); setupContainerPadding(container); - TextView titleView = emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title); + TextView titleView = emptyStateView.findViewById( + com.android.internal.R.id.resolver_empty_state_title); String title = emptyState.getTitle(); if (title != null) { titleView.setVisibility(View.VISIBLE); @@ -387,7 +502,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { titleView.setVisibility(View.GONE); } - TextView subtitleView = emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_subtitle); + TextView subtitleView = emptyStateView.findViewById( + com.android.internal.R.id.resolver_empty_state_subtitle); String subtitle = emptyState.getSubtitle(); if (subtitle != null) { subtitleView.setVisibility(View.VISIBLE); @@ -399,7 +515,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { View defaultEmptyText = emptyStateView.findViewById(com.android.internal.R.id.empty); defaultEmptyText.setVisibility(emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE); - Button button = emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button); + Button button = emptyStateView.findViewById( + com.android.internal.R.id.resolver_empty_state_button); button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE); button.setOnClickListener(buttonOnClick); @@ -410,44 +527,69 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * Sets up the padding of the view containing the empty state screens. * <p>This method is meant to be overridden so that subclasses can customize the padding. */ - protected void setupContainerPadding(View container) {} + public void setupContainerPadding(View container) { + Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get(); + bottomPaddingOverride.ifPresent(paddingBottom -> + container.setPadding( + container.getPaddingLeft(), + container.getPaddingTop(), + container.getPaddingRight(), + paddingBottom)); + } private void showSpinner(View emptyStateView) { - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress).setVisibility(View.VISIBLE); + emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title) + .setVisibility(View.INVISIBLE); + emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button) + .setVisibility(View.INVISIBLE); + emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress) + .setVisibility(View.VISIBLE); emptyStateView.findViewById(com.android.internal.R.id.empty).setVisibility(View.GONE); } private void resetViewVisibilitiesForEmptyState(View emptyStateView) { - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title).setVisibility(View.VISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE); - emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress).setVisibility(View.GONE); + emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title) + .setVisibility(View.VISIBLE); + emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_subtitle) + .setVisibility(View.VISIBLE); + emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button) + .setVisibility(View.INVISIBLE); + emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress) + .setVisibility(View.GONE); emptyStateView.findViewById(com.android.internal.R.id.empty).setVisibility(View.GONE); } - protected void showListView(ResolverListAdapter activeListAdapter) { - ProfileDescriptor descriptor = getItem( + protected void showListView(ListAdapterT activeListAdapter) { + ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem( userHandleToPageIndex(activeListAdapter.getUserHandle())); - descriptor.rootView.findViewById(com.android.internal.R.id.resolver_list).setVisibility(View.VISIBLE); - View emptyStateView = descriptor.rootView.findViewById(com.android.internal.R.id.resolver_empty_state); + descriptor.mRootView.findViewById( + com.android.internal.R.id.resolver_list).setVisibility(View.VISIBLE); + View emptyStateView = descriptor.mRootView.findViewById( + com.android.internal.R.id.resolver_empty_state); emptyStateView.setVisibility(View.GONE); } - boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) { + public boolean shouldShowEmptyStateScreen(ListAdapterT listAdapter) { int count = listAdapter.getUnfilteredCount(); return (count == 0 && listAdapter.getPlaceholderCount() == 0) || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle) && mWorkProfileQuietModeChecker.get()); } - protected static class ProfileDescriptor { - final ViewGroup rootView; + // TODO: `ChooserActivity` also has a per-profile record type. Maybe the "multi-profile pager" + // should be the owner of all per-profile data (especially now that the API is generic)? + private static class ProfileDescriptor<PageViewT, SinglePageAdapterT> { + final ViewGroup mRootView; private final ViewGroup mEmptyStateView; - ProfileDescriptor(ViewGroup rootView) { - this.rootView = rootView; + + private final SinglePageAdapterT mAdapter; + private final PageViewT mView; + + ProfileDescriptor(ViewGroup rootView, SinglePageAdapterT adapter) { + mRootView = rootView; + mAdapter = adapter; mEmptyStateView = rootView.findViewById(com.android.internal.R.id.resolver_empty_state); + mView = (PageViewT) rootView.findViewById(com.android.internal.R.id.resolver_list); } protected ViewGroup getEmptyStateView() { @@ -455,6 +597,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } } + /** Listener interface for changes between the per-profile UI tabs. */ public interface OnProfileSelectedListener { /** * Callback for when the user changes the active tab from personal to work or vice versa. @@ -528,32 +671,42 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * Title that will be shown on the empty state */ @Nullable - default String getTitle() { return null; } + default String getTitle() { + return null; + } /** * Subtitle that will be shown underneath the title on the empty state */ @Nullable - default String getSubtitle() { return null; } + default String getSubtitle() { + return null; + } /** * If non-null then a button will be shown and this listener will be called * when the button is clicked */ @Nullable - default ClickListener getButtonClickListener() { return null; } + default ClickListener getButtonClickListener() { + return null; + } /** * If true then default text ('No apps can perform this action') and style for the empty * state will be applied, title and subtitle will be ignored. */ - default boolean useDefaultEmptyView() { return false; } + default boolean useDefaultEmptyView() { + return false; + } /** * Returns true if for this empty state we should skip rebuilding of the apps list * for this tab. */ - default boolean shouldSkipDataRebuild() { return false; } + default boolean shouldSkipDataRebuild() { + return false; + } /** * Called when empty state is shown, could be used e.g. to track analytics events @@ -569,11 +722,10 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } } - /** * Listener for when the user switches on the work profile from the work tab. */ - interface OnSwitchOnWorkSelectedListener { + public interface OnSwitchOnWorkSelectedListener { /** * Callback for when the user switches on the work profile from the work tab. */ diff --git a/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java b/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java index a7b50f38..1900abee 100644 --- a/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java @@ -28,8 +28,8 @@ import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.stats.devicepolicy.nano.DevicePolicyEnums; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyState; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyStateProvider; import com.android.internal.R; import java.util.List; diff --git a/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java index 6f72bb00..ad262f0e 100644 --- a/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java @@ -24,9 +24,9 @@ import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.UserHandle; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider; +import com.android.intentresolver.MultiProfilePagerAdapter.CrossProfileIntentsChecker; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyState; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyStateProvider; /** * Empty state provider that does not allow cross profile sharing, it will return a blocker diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java index 1161ca81..d1d86aff 100644 --- a/java/src/com/android/intentresolver/ResolverActivity.java +++ b/java/src/com/android/intentresolver/ResolverActivity.java @@ -98,12 +98,12 @@ import android.widget.Toast; import androidx.fragment.app.FragmentActivity; import androidx.viewpager.widget.ViewPager; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CompositeEmptyStateProvider; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.Profile; +import com.android.intentresolver.MultiProfilePagerAdapter.CompositeEmptyStateProvider; +import com.android.intentresolver.MultiProfilePagerAdapter.CrossProfileIntentsChecker; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyStateProvider; +import com.android.intentresolver.MultiProfilePagerAdapter.MyUserIdProvider; +import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener; +import com.android.intentresolver.MultiProfilePagerAdapter.Profile; import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; @@ -201,7 +201,7 @@ public class ResolverActivity extends FragmentActivity implements private TargetDataLoader mTargetDataLoader; @VisibleForTesting - protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter; + protected MultiProfilePagerAdapter mMultiProfilePagerAdapter; protected WorkProfileAvailabilityManager mWorkProfileAvailability; @@ -228,8 +228,8 @@ public class ResolverActivity extends FragmentActivity implements static final String EXTRA_CALLING_USER = "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"; - protected static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; - protected static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK; + protected static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL; + protected static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK; private UserHandle mHeaderCreatorUser; @@ -496,12 +496,12 @@ public class ResolverActivity extends FragmentActivity implements + (categories != null ? Arrays.toString(categories.toArray()) : "")); } - protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( + protected MultiProfilePagerAdapter createMultiProfilePagerAdapter( Intent[] initialIntents, List<ResolveInfo> resolutionList, boolean filterLastUsed, TargetDataLoader targetDataLoader) { - AbstractMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null; + MultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null; if (shouldShowTabs()) { resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForTwoProfiles( @@ -521,7 +521,7 @@ public class ResolverActivity extends FragmentActivity implements return new EmptyStateProvider() {}; } - final AbstractMultiProfilePagerAdapter.EmptyState + final MultiProfilePagerAdapter.EmptyState noWorkToPersonalEmptyState = new DevicePolicyBlockerEmptyState(/* context= */ this, /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, @@ -533,7 +533,7 @@ public class ResolverActivity extends FragmentActivity implements /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER); - final AbstractMultiProfilePagerAdapter.EmptyState noPersonalToWorkEmptyState = + final MultiProfilePagerAdapter.EmptyState noPersonalToWorkEmptyState = new DevicePolicyBlockerEmptyState(/* context= */ this, /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked, @@ -2080,7 +2080,7 @@ public class ResolverActivity extends FragmentActivity implements viewPager.setVisibility(View.VISIBLE); tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage()); mMultiProfilePagerAdapter.setOnProfileSelectedListener( - new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() { + new MultiProfilePagerAdapter.OnProfileSelectedListener() { @Override public void onProfileSelected(int index) { tabHost.setCurrentTab(index); diff --git a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java index 85d97ad5..9fb35948 100644 --- a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java @@ -36,7 +36,7 @@ import java.util.function.Supplier; */ @VisibleForTesting public class ResolverMultiProfilePagerAdapter extends - GenericMultiProfilePagerAdapter<ListView, ResolverListAdapter, ResolverListAdapter> { + MultiProfilePagerAdapter<ListView, ResolverListAdapter, ResolverListAdapter> { private final BottomPaddingOverrideSupplier mBottomPaddingOverrideSupplier; ResolverMultiProfilePagerAdapter( @@ -86,7 +86,6 @@ public class ResolverMultiProfilePagerAdapter extends UserHandle cloneProfileUserHandle, BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier) { super( - context, listAdapter -> listAdapter, (listView, bindAdapter) -> listView.setAdapter(bindAdapter), listAdapters, diff --git a/java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java b/java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java index 2f3dfbd5..9ea7ceee 100644 --- a/java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java @@ -26,9 +26,9 @@ import android.content.Context; import android.os.UserHandle; import android.stats.devicepolicy.nano.DevicePolicyEnums; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyState; +import com.android.intentresolver.MultiProfilePagerAdapter.EmptyStateProvider; +import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener; /** * Chooser/ResolverActivity empty state provider that returns empty state which is shown when |