summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2023-10-04 15:31:47 +0000
committer Joshua Trask <joshtrask@google.com> 2023-10-04 15:37:31 +0000
commit989de76169699beee2c92a8ce41bc636896a66b6 (patch)
treed51d1394d24abb724185ecab406f1745bb0315f6 /java/src
parent2bcce186d5b4879e33aedd6bbac7f5b0b4ec9cd8 (diff)
`v2` boilerplate for ongoing empty-state work.
Just cutting over to our new experiment infrastructure before I continue porting over the changes that were originally prototyped in ag/24516421. Bug: 302311217 Test: IntentResolverUnitTests Change-Id: Ied1843bee2be6ffb42ba4f539f6168a9d07a77d9
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java2
-rw-r--r--java/src/com/android/intentresolver/ChooserRecyclerViewAccessibilityDelegate.java4
-rw-r--r--java/src/com/android/intentresolver/ResolverListAdapter.java6
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java5
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserMultiProfilePagerAdapter.java218
-rw-r--r--java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java582
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverActivity.java9
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverMultiProfilePagerAdapter.java122
-rw-r--r--java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java63
9 files changed, 998 insertions, 13 deletions
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index ec8800b8..35258317 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -293,7 +293,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
@Override
- protected boolean rebuildList(boolean doPostProcessing) {
+ public boolean rebuildList(boolean doPostProcessing) {
mAnimationTracker.reset();
mSortedList.clear();
boolean result = super.rebuildList(doPostProcessing);
diff --git a/java/src/com/android/intentresolver/ChooserRecyclerViewAccessibilityDelegate.java b/java/src/com/android/intentresolver/ChooserRecyclerViewAccessibilityDelegate.java
index 250b6827..3f6a3437 100644
--- a/java/src/com/android/intentresolver/ChooserRecyclerViewAccessibilityDelegate.java
+++ b/java/src/com/android/intentresolver/ChooserRecyclerViewAccessibilityDelegate.java
@@ -25,11 +25,11 @@ import android.view.accessibility.AccessibilityEvent;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
-class ChooserRecyclerViewAccessibilityDelegate extends RecyclerViewAccessibilityDelegate {
+public class ChooserRecyclerViewAccessibilityDelegate extends RecyclerViewAccessibilityDelegate {
private final Rect mTempRect = new Rect();
private final int[] mConsumed = new int[2];
- ChooserRecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
+ public ChooserRecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
super(recyclerView);
}
diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java
index 8c0d414c..d1e8c15b 100644
--- a/java/src/com/android/intentresolver/ResolverListAdapter.java
+++ b/java/src/com/android/intentresolver/ResolverListAdapter.java
@@ -252,7 +252,7 @@ public class ResolverListAdapter extends BaseAdapter {
* with {@code rebuildCompleted} true at the end of some newly-launched asynchronous work.
* Otherwise the callback is only queued once, with {@code rebuildCompleted} true.
*/
- protected boolean rebuildList(boolean doPostProcessing) {
+ public boolean rebuildList(boolean doPostProcessing) {
Trace.beginSection("ResolverListAdapter#rebuildList");
mDisplayList.clear();
mIsTabLoaded = false;
@@ -545,7 +545,7 @@ public class ResolverListAdapter extends BaseAdapter {
* after the list has been rebuilt
* @param rebuildCompleted Whether the list has been completely rebuilt
*/
- void postListReadyRunnable(boolean doPostProcessing, boolean rebuildCompleted) {
+ public void postListReadyRunnable(boolean doPostProcessing, boolean rebuildCompleted) {
Runnable listReadyRunnable = new Runnable() {
@Override
public void run() {
@@ -838,7 +838,7 @@ public class ResolverListAdapter extends BaseAdapter {
return mIsTabLoaded;
}
- protected void markTabLoaded() {
+ public void markTabLoaded() {
mIsTabLoaded = true;
}
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java
index 9e437010..a755b9e9 100644
--- a/java/src/com/android/intentresolver/v2/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java
@@ -23,7 +23,9 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
+
import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
+
import static com.android.intentresolver.v2.ResolverActivity.PROFILE_PERSONAL;
import static com.android.intentresolver.v2.ResolverActivity.PROFILE_WORK;
import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
@@ -78,7 +80,6 @@ import com.android.intentresolver.ChooserActionFactory;
import com.android.intentresolver.ChooserGridLayoutManager;
import com.android.intentresolver.ChooserIntegratedDeviceComponents;
import com.android.intentresolver.ChooserListAdapter;
-import com.android.intentresolver.ChooserMultiProfilePagerAdapter;
import com.android.intentresolver.ChooserRefinementManager;
import com.android.intentresolver.ChooserRequestParameters;
import com.android.intentresolver.ChooserStackedAppDialogFragment;
@@ -112,8 +113,8 @@ import com.android.intentresolver.model.AppPredictionServiceResolverComparator;
import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
import com.android.intentresolver.shortcuts.AppPredictorFactory;
import com.android.intentresolver.shortcuts.ShortcutLoader;
-import com.android.intentresolver.widget.ImagePreviewView;
import com.android.intentresolver.v2.Hilt_ChooserActivity;
+import com.android.intentresolver.widget.ImagePreviewView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
diff --git a/java/src/com/android/intentresolver/v2/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/v2/ChooserMultiProfilePagerAdapter.java
new file mode 100644
index 00000000..d3c9efea
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/ChooserMultiProfilePagerAdapter.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.v2;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager.widget.PagerAdapter;
+
+import com.android.intentresolver.ChooserListAdapter;
+import com.android.intentresolver.ChooserRecyclerViewAccessibilityDelegate;
+import com.android.intentresolver.FeatureFlags;
+import com.android.intentresolver.R;
+import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.intentresolver.grid.ChooserGridAdapter;
+import com.android.intentresolver.measurements.Tracer;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * A {@link PagerAdapter} which describes the work and personal profile share sheet screens.
+ */
+@VisibleForTesting
+public class ChooserMultiProfilePagerAdapter extends MultiProfilePagerAdapter<
+ RecyclerView, ChooserGridAdapter, ChooserListAdapter> {
+ private static final int SINGLE_CELL_SPAN_SIZE = 1;
+
+ private final ChooserProfileAdapterBinder mAdapterBinder;
+ private final BottomPaddingOverrideSupplier mBottomPaddingOverrideSupplier;
+
+ public ChooserMultiProfilePagerAdapter(
+ Context context,
+ ChooserGridAdapter adapter,
+ EmptyStateProvider emptyStateProvider,
+ Supplier<Boolean> workProfileQuietModeChecker,
+ UserHandle workProfileUserHandle,
+ UserHandle cloneProfileUserHandle,
+ int maxTargetsPerRow,
+ FeatureFlags featureFlags) {
+ this(
+ context,
+ new ChooserProfileAdapterBinder(maxTargetsPerRow),
+ ImmutableList.of(adapter),
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ /* defaultProfile= */ 0,
+ workProfileUserHandle,
+ cloneProfileUserHandle,
+ new BottomPaddingOverrideSupplier(context),
+ featureFlags);
+ }
+
+ public ChooserMultiProfilePagerAdapter(
+ Context context,
+ ChooserGridAdapter personalAdapter,
+ ChooserGridAdapter workAdapter,
+ EmptyStateProvider emptyStateProvider,
+ Supplier<Boolean> workProfileQuietModeChecker,
+ @Profile int defaultProfile,
+ UserHandle workProfileUserHandle,
+ UserHandle cloneProfileUserHandle,
+ int maxTargetsPerRow,
+ FeatureFlags featureFlags) {
+ this(
+ context,
+ new ChooserProfileAdapterBinder(maxTargetsPerRow),
+ ImmutableList.of(personalAdapter, workAdapter),
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ defaultProfile,
+ workProfileUserHandle,
+ cloneProfileUserHandle,
+ new BottomPaddingOverrideSupplier(context),
+ featureFlags);
+ }
+
+ private ChooserMultiProfilePagerAdapter(
+ Context context,
+ ChooserProfileAdapterBinder adapterBinder,
+ ImmutableList<ChooserGridAdapter> gridAdapters,
+ EmptyStateProvider emptyStateProvider,
+ Supplier<Boolean> workProfileQuietModeChecker,
+ @Profile int defaultProfile,
+ UserHandle workProfileUserHandle,
+ UserHandle cloneProfileUserHandle,
+ BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier,
+ FeatureFlags featureFlags) {
+ super(
+ gridAdapter -> gridAdapter.getListAdapter(),
+ adapterBinder,
+ gridAdapters,
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ defaultProfile,
+ workProfileUserHandle,
+ cloneProfileUserHandle,
+ () -> makeProfileView(context, featureFlags),
+ bottomPaddingOverrideSupplier);
+ mAdapterBinder = adapterBinder;
+ mBottomPaddingOverrideSupplier = bottomPaddingOverrideSupplier;
+ }
+
+ public void setMaxTargetsPerRow(int maxTargetsPerRow) {
+ mAdapterBinder.setMaxTargetsPerRow(maxTargetsPerRow);
+ }
+
+ public void setEmptyStateBottomOffset(int bottomOffset) {
+ mBottomPaddingOverrideSupplier.setEmptyStateBottomOffset(bottomOffset);
+ }
+
+ /**
+ * Notify adapter about the drawer's collapse state. This will affect the app divider's
+ * visibility.
+ */
+ public void setIsCollapsed(boolean isCollapsed) {
+ for (int i = 0, size = getItemCount(); i < size; i++) {
+ getAdapterForIndex(i).setAzLabelVisibility(!isCollapsed);
+ }
+ }
+
+ private static ViewGroup makeProfileView(
+ Context context, FeatureFlags featureFlags) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ ViewGroup rootView = featureFlags.scrollablePreview()
+ ? (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile_wrap, null, false)
+ : (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile, null, false);
+ RecyclerView recyclerView = rootView.findViewById(com.android.internal.R.id.resolver_list);
+ recyclerView.setAccessibilityDelegateCompat(
+ new ChooserRecyclerViewAccessibilityDelegate(recyclerView));
+ return rootView;
+ }
+
+ @Override
+ public boolean rebuildActiveTab(boolean doPostProcessing) {
+ if (doPostProcessing) {
+ Tracer.INSTANCE.beginAppTargetLoadingSection(getActiveListAdapter().getUserHandle());
+ }
+ return super.rebuildActiveTab(doPostProcessing);
+ }
+
+ @Override
+ public boolean rebuildInactiveTab(boolean doPostProcessing) {
+ if (getItemCount() != 1 && doPostProcessing) {
+ Tracer.INSTANCE.beginAppTargetLoadingSection(getInactiveListAdapter().getUserHandle());
+ }
+ return super.rebuildInactiveTab(doPostProcessing);
+ }
+
+ private static class BottomPaddingOverrideSupplier implements Supplier<Optional<Integer>> {
+ private final Context mContext;
+ private int mBottomOffset;
+
+ BottomPaddingOverrideSupplier(Context context) {
+ mContext = context;
+ }
+
+ public void setEmptyStateBottomOffset(int bottomOffset) {
+ mBottomOffset = bottomOffset;
+ }
+
+ public Optional<Integer> get() {
+ int initialBottomPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.resolver_empty_state_container_padding_bottom);
+ return Optional.of(initialBottomPadding + mBottomOffset);
+ }
+ }
+
+ private static class ChooserProfileAdapterBinder implements
+ AdapterBinder<RecyclerView, ChooserGridAdapter> {
+ private int mMaxTargetsPerRow;
+
+ ChooserProfileAdapterBinder(int maxTargetsPerRow) {
+ mMaxTargetsPerRow = maxTargetsPerRow;
+ }
+
+ public void setMaxTargetsPerRow(int maxTargetsPerRow) {
+ mMaxTargetsPerRow = maxTargetsPerRow;
+ }
+
+ @Override
+ public void bind(
+ RecyclerView recyclerView, ChooserGridAdapter chooserGridAdapter) {
+ GridLayoutManager glm = (GridLayoutManager) recyclerView.getLayoutManager();
+ glm.setSpanCount(mMaxTargetsPerRow);
+ glm.setSpanSizeLookup(
+ new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ return chooserGridAdapter.shouldCellSpan(position)
+ ? SINGLE_CELL_SPAN_SIZE
+ : glm.getSpanCount();
+ }
+ });
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java
new file mode 100644
index 00000000..b2a167e1
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.intentresolver.v2;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
+import com.android.intentresolver.ResolverListAdapter;
+import com.android.intentresolver.emptystate.EmptyState;
+import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.intentresolver.v2.emptystate.EmptyStateUiHelper;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * 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 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);
+ }
+
+ public static final int PROFILE_PERSONAL = 0;
+ public static final int PROFILE_WORK = 1;
+
+ @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
+ public @interface Profile {}
+
+ 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 final EmptyStateProvider mEmptyStateProvider;
+ private final UserHandle mWorkProfileUserHandle;
+ private final UserHandle mCloneProfileUserHandle;
+ private final Supplier<Boolean> mWorkProfileQuietModeChecker; // True when work is quiet.
+
+ 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,
+ 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();
+ }
+
+ private ProfileDescriptor<PageViewT, SinglePageAdapterT> createProfileDescriptor(
+ SinglePageAdapterT adapter) {
+ return new ProfileDescriptor<>(mPageViewInflater.get(), adapter);
+ }
+
+ public void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
+ mOnProfileSelectedListener = listener;
+ }
+
+ /**
+ * Sets this instance of this class as {@link ViewPager}'s {@link PagerAdapter} and sets
+ * an {@link ViewPager.OnPageChangeListener} where it keeps track of the currently displayed
+ * page and rebuilds the list.
+ */
+ public void setupViewPager(ViewPager viewPager) {
+ viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ mCurrentPage = position;
+ if (!mLoadedPages.contains(position)) {
+ rebuildActiveTab(true);
+ mLoadedPages.add(position);
+ }
+ if (mOnProfileSelectedListener != null) {
+ mOnProfileSelectedListener.onProfileSelected(position);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ if (mOnProfileSelectedListener != null) {
+ mOnProfileSelectedListener.onProfilePageStateChanged(state);
+ }
+ }
+ });
+ viewPager.setAdapter(this);
+ viewPager.setCurrentItem(mCurrentPage);
+ mLoadedPages.add(mCurrentPage);
+ }
+
+ public void clearInactiveProfileCache() {
+ if (mLoadedPages.size() == 1) {
+ return;
+ }
+ mLoadedPages.remove(1 - mCurrentPage);
+ }
+
+ @Override
+ 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
+ public void destroyItem(ViewGroup container, int position, Object view) {
+ container.removeView((View) view);
+ }
+
+ @Override
+ public int getCount() {
+ return getItemCount();
+ }
+
+ public int getCurrentPage() {
+ return mCurrentPage;
+ }
+
+ @VisibleForTesting
+ public UserHandle getCurrentUserHandle() {
+ return getActiveListAdapter().getUserHandle();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return null;
+ }
+
+ public UserHandle getCloneUserHandle() {
+ return mCloneProfileUserHandle;
+ }
+
+ /**
+ * Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
+ * <ul>
+ * <li>For a device with only one user, <code>pageIndex</code> value of
+ * <code>0</code> would return the personal profile {@link ProfileDescriptor}.</li>
+ * <li>For a device with a work profile, <code>pageIndex</code> value of <code>0</code> would
+ * return the personal profile {@link ProfileDescriptor}, and <code>pageIndex</code> value of
+ * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
+ * </ul>
+ */
+ private ProfileDescriptor<PageViewT, SinglePageAdapterT> getItem(int pageIndex) {
+ return mItems.get(pageIndex);
+ }
+
+ public ViewGroup getEmptyStateView(int pageIndex) {
+ return getItem(pageIndex).getEmptyStateView();
+ }
+
+ /**
+ * Returns the number of {@link ProfileDescriptor} objects.
+ * <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>.
+ */
+ public final int getItemCount() {
+ return mItems.size();
+ }
+
+ public final PageViewT getListViewForIndex(int index) {
+ return getItem(index).mView;
+ }
+
+ /**
+ * Returns the adapter of the list view for the relevant page specified by
+ * <code>pageIndex</code>.
+ * <p>This method is meant to be implemented with an implementation-specific return type
+ * depending on the adapter type.
+ */
+ @VisibleForTesting
+ public final SinglePageAdapterT getAdapterForIndex(int index) {
+ return getItem(index).mAdapter;
+ }
+
+ /**
+ * Performs view-related initialization procedures for the adapter specified
+ * by <code>pageIndex</code>.
+ */
+ public final void setupListAdapter(int pageIndex) {
+ mAdapterBinder.bind(getListViewForIndex(pageIndex), getAdapterForIndex(pageIndex));
+ }
+
+ /**
+ * 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 ListAdapterT}.
+ */
+ @Nullable
+ public 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 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 ListAdapterT}.
+ * @see #getInactiveListAdapter()
+ */
+ @VisibleForTesting
+ public final ListAdapterT getActiveListAdapter() {
+ return mListAdapterExtractor.apply(getAdapterForIndex(getCurrentPage()));
+ }
+
+ /**
+ * 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 ListAdapterT}.
+ * @see #getActiveListAdapter()
+ */
+ @VisibleForTesting
+ @Nullable
+ public final ListAdapterT getInactiveListAdapter() {
+ if (getCount() < 2) {
+ return null;
+ }
+ return mListAdapterExtractor.apply(getAdapterForIndex(1 - getCurrentPage()));
+ }
+
+ public final ListAdapterT getPersonalListAdapter() {
+ return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_PERSONAL));
+ }
+
+ @Nullable
+ public final ListAdapterT getWorkListAdapter() {
+ if (!hasAdapterForIndex(PROFILE_WORK)) {
+ return null;
+ }
+ return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK));
+ }
+
+ public final SinglePageAdapterT getCurrentRootAdapter() {
+ return getAdapterForIndex(getCurrentPage());
+ }
+
+ public final PageViewT getActiveAdapterView() {
+ return getListViewForIndex(getCurrentPage());
+ }
+
+ @Nullable
+ public 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.
+ */
+ public boolean rebuildActiveTab(boolean doPostProcessing) {
+ Trace.beginSection("MultiProfilePagerAdapter#rebuildActiveTab");
+ boolean result = rebuildTab(getActiveListAdapter(), doPostProcessing);
+ Trace.endSection();
+ return result;
+ }
+
+ /**
+ * Rebuilds the tab that is not currently visible to the user, if such one exists.
+ * <p>Returns {@code true} if rebuild has completed.
+ */
+ public boolean rebuildInactiveTab(boolean doPostProcessing) {
+ Trace.beginSection("MultiProfilePagerAdapter#rebuildInactiveTab");
+ if (getItemCount() == 1) {
+ Trace.endSection();
+ return false;
+ }
+ boolean result = rebuildTab(getInactiveListAdapter(), doPostProcessing);
+ Trace.endSection();
+ return result;
+ }
+
+ private int userHandleToPageIndex(UserHandle userHandle) {
+ if (userHandle.equals(getPersonalListAdapter().getUserHandle())) {
+ return PROFILE_PERSONAL;
+ } else {
+ return PROFILE_WORK;
+ }
+ }
+
+ private boolean rebuildTab(ListAdapterT activeListAdapter, boolean doPostProcessing) {
+ if (shouldSkipRebuild(activeListAdapter)) {
+ activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
+ return false;
+ }
+ return activeListAdapter.rebuildList(doPostProcessing);
+ }
+
+ 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(ListAdapterT, boolean)})</li>
+ * <li>no apps available</li>
+ * <li>(least priority) work is off</li>
+ * </ol>
+ *
+ * The intention is to prevent the user from having to turn
+ * the work profile on if there will not be any apps resolved
+ * anyway.
+ */
+ public void showEmptyResolverListEmptyState(ListAdapterT listAdapter) {
+ final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
+
+ if (emptyState == null) {
+ return;
+ }
+
+ emptyState.onEmptyStateShown();
+
+ View.OnClickListener clickListener = null;
+
+ if (emptyState.getButtonClickListener() != null) {
+ clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
+ ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(
+ userHandleToPageIndex(listAdapter.getUserHandle()));
+ descriptor.mEmptyStateUi.showSpinner();
+ });
+ }
+
+ showEmptyState(listAdapter, emptyState, clickListener);
+ }
+
+ /**
+ * Class to get user id of the current process
+ */
+ public static class MyUserIdProvider {
+ /**
+ * @return user id of the current process
+ */
+ public int getMyUserId() {
+ return UserHandle.myUserId();
+ }
+ }
+
+ protected void showEmptyState(
+ ListAdapterT activeListAdapter,
+ EmptyState emptyState,
+ View.OnClickListener buttonOnClick) {
+ ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(
+ userHandleToPageIndex(activeListAdapter.getUserHandle()));
+ descriptor.mRootView.findViewById(
+ com.android.internal.R.id.resolver_list).setVisibility(View.GONE);
+ descriptor.mEmptyStateUi.resetViewVisibilities();
+
+ ViewGroup emptyStateView = descriptor.getEmptyStateView();
+
+ 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);
+ String title = emptyState.getTitle();
+ if (title != null) {
+ titleView.setVisibility(View.VISIBLE);
+ titleView.setText(title);
+ } else {
+ titleView.setVisibility(View.GONE);
+ }
+
+ TextView subtitleView = emptyStateView.findViewById(
+ com.android.internal.R.id.resolver_empty_state_subtitle);
+ String subtitle = emptyState.getSubtitle();
+ if (subtitle != null) {
+ subtitleView.setVisibility(View.VISIBLE);
+ subtitleView.setText(subtitle);
+ } else {
+ subtitleView.setVisibility(View.GONE);
+ }
+
+ 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.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
+ button.setOnClickListener(buttonOnClick);
+
+ activeListAdapter.markTabLoaded();
+ }
+
+ /**
+ * 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.
+ */
+ public void setupContainerPadding(View container) {
+ Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get();
+ bottomPaddingOverride.ifPresent(paddingBottom ->
+ container.setPadding(
+ container.getPaddingLeft(),
+ container.getPaddingTop(),
+ container.getPaddingRight(),
+ paddingBottom));
+ }
+
+ public void showListView(ListAdapterT activeListAdapter) {
+ ProfileDescriptor<PageViewT, SinglePageAdapterT> descriptor = getItem(
+ userHandleToPageIndex(activeListAdapter.getUserHandle()));
+ descriptor.mRootView.findViewById(
+ com.android.internal.R.id.resolver_list).setVisibility(View.VISIBLE);
+ descriptor.mEmptyStateUi.hide();
+ }
+
+ public boolean shouldShowEmptyStateScreen(ListAdapterT listAdapter) {
+ int count = listAdapter.getUnfilteredCount();
+ return (count == 0 && listAdapter.getPlaceholderCount() == 0)
+ || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle)
+ && mWorkProfileQuietModeChecker.get());
+ }
+
+ // 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;
+ final EmptyStateUiHelper mEmptyStateUi;
+
+ // TODO: post-refactoring, we may not need to retain these ivars directly (since they may
+ // be encapsulated within the `EmptyStateUiHelper`?).
+ private final ViewGroup mEmptyStateView;
+
+ 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);
+ mEmptyStateUi = new EmptyStateUiHelper(rootView);
+ }
+
+ protected ViewGroup getEmptyStateView() {
+ return mEmptyStateView;
+ }
+ }
+
+ /** 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.
+ * <p>This callback is only called when the intent resolver or share sheet shows
+ * the work and personal profiles.
+ * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or
+ * {@link #PROFILE_WORK} if the work profile was selected.
+ */
+ void onProfileSelected(int profileIndex);
+
+
+ /**
+ * Callback for when the scroll state changes. Useful for discovering when the user begins
+ * dragging, when the pager is automatically settling to the current page, or when it is
+ * fully stopped/idle.
+ * @param state {@link ViewPager#SCROLL_STATE_IDLE}, {@link ViewPager#SCROLL_STATE_DRAGGING}
+ * or {@link ViewPager#SCROLL_STATE_SETTLING}
+ * @see ViewPager.OnPageChangeListener#onPageScrollStateChanged
+ */
+ void onProfilePageStateChanged(int state);
+ }
+
+ /**
+ * Listener for when the user switches on the work profile from the work tab.
+ */
+ public interface OnSwitchOnWorkSelectedListener {
+ /**
+ * Callback for when the user switches on the work profile from the work tab.
+ */
+ void onSwitchOnWorkSelected();
+ }
+}
diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java
index dd6842aa..03221c6c 100644
--- a/java/src/com/android/intentresolver/v2/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java
@@ -33,6 +33,7 @@ import static android.content.PermissionChecker.PID_UNKNOWN;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
import android.annotation.Nullable;
@@ -99,14 +100,9 @@ import androidx.fragment.app.FragmentActivity;
import androidx.viewpager.widget.ViewPager;
import com.android.intentresolver.AnnotatedUserHandles;
-import com.android.intentresolver.MultiProfilePagerAdapter;
-import com.android.intentresolver.MultiProfilePagerAdapter.MyUserIdProvider;
-import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
-import com.android.intentresolver.MultiProfilePagerAdapter.Profile;
import com.android.intentresolver.R;
import com.android.intentresolver.ResolverListAdapter;
import com.android.intentresolver.ResolverListController;
-import com.android.intentresolver.ResolverMultiProfilePagerAdapter;
import com.android.intentresolver.WorkProfileAvailabilityManager;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
@@ -121,6 +117,9 @@ import com.android.intentresolver.emptystate.WorkProfilePausedEmptyStateProvider
import com.android.intentresolver.icons.DefaultTargetDataLoader;
import com.android.intentresolver.icons.TargetDataLoader;
import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
+import com.android.intentresolver.v2.MultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.v2.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
+import com.android.intentresolver.v2.MultiProfilePagerAdapter.Profile;
import com.android.intentresolver.widget.ResolverDrawerLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
diff --git a/java/src/com/android/intentresolver/v2/ResolverMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/v2/ResolverMultiProfilePagerAdapter.java
new file mode 100644
index 00000000..dadc9c0f
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/ResolverMultiProfilePagerAdapter.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.v2;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import androidx.viewpager.widget.PagerAdapter;
+
+import com.android.intentresolver.R;
+import com.android.intentresolver.ResolverListAdapter;
+import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * A {@link PagerAdapter} which describes the work and personal profile intent resolver screens.
+ */
+@VisibleForTesting
+public class ResolverMultiProfilePagerAdapter extends
+ MultiProfilePagerAdapter<ListView, ResolverListAdapter, ResolverListAdapter> {
+ private final BottomPaddingOverrideSupplier mBottomPaddingOverrideSupplier;
+
+ public ResolverMultiProfilePagerAdapter(
+ Context context,
+ ResolverListAdapter adapter,
+ EmptyStateProvider emptyStateProvider,
+ Supplier<Boolean> workProfileQuietModeChecker,
+ UserHandle workProfileUserHandle,
+ UserHandle cloneProfileUserHandle) {
+ this(
+ context,
+ ImmutableList.of(adapter),
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ /* defaultProfile= */ 0,
+ workProfileUserHandle,
+ cloneProfileUserHandle,
+ new BottomPaddingOverrideSupplier());
+ }
+
+ public ResolverMultiProfilePagerAdapter(Context context,
+ ResolverListAdapter personalAdapter,
+ ResolverListAdapter workAdapter,
+ EmptyStateProvider emptyStateProvider,
+ Supplier<Boolean> workProfileQuietModeChecker,
+ @Profile int defaultProfile,
+ UserHandle workProfileUserHandle,
+ UserHandle cloneProfileUserHandle) {
+ this(
+ context,
+ ImmutableList.of(personalAdapter, workAdapter),
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ defaultProfile,
+ workProfileUserHandle,
+ cloneProfileUserHandle,
+ new BottomPaddingOverrideSupplier());
+ }
+
+ private ResolverMultiProfilePagerAdapter(
+ Context context,
+ ImmutableList<ResolverListAdapter> listAdapters,
+ EmptyStateProvider emptyStateProvider,
+ Supplier<Boolean> workProfileQuietModeChecker,
+ @Profile int defaultProfile,
+ UserHandle workProfileUserHandle,
+ UserHandle cloneProfileUserHandle,
+ BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier) {
+ super(
+ listAdapter -> listAdapter,
+ (listView, bindAdapter) -> listView.setAdapter(bindAdapter),
+ listAdapters,
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ defaultProfile,
+ workProfileUserHandle,
+ cloneProfileUserHandle,
+ () -> (ViewGroup) LayoutInflater.from(context).inflate(
+ R.layout.resolver_list_per_profile, null, false),
+ bottomPaddingOverrideSupplier);
+ mBottomPaddingOverrideSupplier = bottomPaddingOverrideSupplier;
+ }
+
+ public void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
+ mBottomPaddingOverrideSupplier.setUseLayoutWithDefault(useLayoutWithDefault);
+ }
+
+ private static class BottomPaddingOverrideSupplier implements Supplier<Optional<Integer>> {
+ private boolean mUseLayoutWithDefault;
+
+ public void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
+ mUseLayoutWithDefault = useLayoutWithDefault;
+ }
+
+ @Override
+ public Optional<Integer> get() {
+ return mUseLayoutWithDefault ? Optional.empty() : Optional.of(0);
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java b/java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java
new file mode 100644
index 00000000..7230b042
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelper.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.v2.emptystate;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Helper for building `MultiProfilePagerAdapter` tab UIs for profile tabs that are "blocked" by
+ * some empty-state status.
+ */
+public class EmptyStateUiHelper {
+ private final View mEmptyStateView;
+
+ public EmptyStateUiHelper(ViewGroup rootView) {
+ mEmptyStateView =
+ rootView.requireViewById(com.android.internal.R.id.resolver_empty_state);
+ }
+
+ public void resetViewVisibilities() {
+ mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_title)
+ .setVisibility(View.VISIBLE);
+ mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_subtitle)
+ .setVisibility(View.VISIBLE);
+ mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_button)
+ .setVisibility(View.INVISIBLE);
+ mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_progress)
+ .setVisibility(View.GONE);
+ mEmptyStateView.requireViewById(com.android.internal.R.id.empty)
+ .setVisibility(View.GONE);
+ mEmptyStateView.setVisibility(View.VISIBLE);
+ }
+
+ public void showSpinner() {
+ mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_title)
+ .setVisibility(View.INVISIBLE);
+ // TODO: subtitle?
+ mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_button)
+ .setVisibility(View.INVISIBLE);
+ mEmptyStateView.requireViewById(com.android.internal.R.id.resolver_empty_state_progress)
+ .setVisibility(View.VISIBLE);
+ mEmptyStateView.requireViewById(com.android.internal.R.id.empty)
+ .setVisibility(View.GONE);
+ }
+
+ public void hide() {
+ mEmptyStateView.setVisibility(View.GONE);
+ }
+}
+