summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java394
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java56
-rw-r--r--java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java136
-rw-r--r--java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java154
-rw-r--r--java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java137
-rw-r--r--java/src/com/android/intentresolver/ResolverActivity.java171
-rw-r--r--java/src/com/android/intentresolver/ResolverListAdapter.java1
-rw-r--r--java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java112
-rw-r--r--java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java114
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java42
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java42
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java65
-rw-r--r--java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java455
13 files changed, 1371 insertions, 508 deletions
diff --git a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java
index 0f0d1797..3e1084f4 100644
--- a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java
@@ -17,18 +17,15 @@ package com.android.intentresolver;
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.AppGlobals;
-import android.app.admin.DevicePolicyEventLogger;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.stats.devicepolicy.DevicePolicyEnums;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@@ -60,73 +57,32 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
private final Context mContext;
private int mCurrentPage;
private OnProfileSelectedListener mOnProfileSelectedListener;
- private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+
private Set<Integer> mLoadedPages;
- private final UserHandle mPersonalProfileUserHandle;
+ private final EmptyStateProvider mEmptyStateProvider;
private final UserHandle mWorkProfileUserHandle;
- private Injector mInjector;
- private boolean mIsWaitingToEnableWorkProfile;
+ private final QuietModeManager mQuietModeManager;
AbstractMultiProfilePagerAdapter(Context context, int currentPage,
- UserHandle personalProfileUserHandle,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
UserHandle workProfileUserHandle) {
mContext = Objects.requireNonNull(context);
mCurrentPage = currentPage;
mLoadedPages = new HashSet<>();
- mPersonalProfileUserHandle = personalProfileUserHandle;
mWorkProfileUserHandle = workProfileUserHandle;
- UserManager userManager = context.getSystemService(UserManager.class);
- mInjector = new Injector() {
- @Override
- public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
- int targetUserId) {
- return AbstractMultiProfilePagerAdapter.this
- .hasCrossProfileIntents(intents, sourceUserId, targetUserId);
- }
-
- @Override
- public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
- return userManager.isQuietModeEnabled(workProfileUserHandle);
- }
-
- @Override
- public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
- AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
- userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
- });
- mIsWaitingToEnableWorkProfile = true;
- }
- };
- }
-
- protected void markWorkProfileEnabledBroadcastReceived() {
- mIsWaitingToEnableWorkProfile = false;
- }
-
- protected boolean isWaitingToEnableWorkProfile() {
- return mIsWaitingToEnableWorkProfile;
- }
-
- /**
- * Overrides the default {@link Injector} for testing purposes.
- */
- @VisibleForTesting
- public void setInjector(Injector injector) {
- mInjector = injector;
+ mEmptyStateProvider = emptyStateProvider;
+ mQuietModeManager = quietModeManager;
}
- protected boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
- return mInjector.isQuietModeEnabled(workProfileUserHandle);
+ private boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return mQuietModeManager.isQuietModeEnabled(workProfileUserHandle);
}
void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
mOnProfileSelectedListener = listener;
}
- void setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener) {
- mOnSwitchOnWorkSelectedListener = listener;
- }
-
Context getContext() {
return mContext;
}
@@ -280,8 +236,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
abstract @Nullable ViewGroup getInactiveAdapterView();
- abstract String getMetricsCategory();
-
/**
* Rebuilds the tab that is currently visible to the user.
* <p>Returns {@code true} if rebuild has completed.
@@ -317,41 +271,18 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
}
private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
- if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) {
+ if (shouldSkipRebuild(activeListAdapter)) {
activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
return false;
}
return activeListAdapter.rebuildList(doPostProcessing);
}
- private boolean shouldShowNoCrossProfileIntentsEmptyState(
- ResolverListAdapter activeListAdapter) {
- UserHandle listUserHandle = activeListAdapter.getUserHandle();
- return UserHandle.myUserId() != listUserHandle.getIdentifier()
- && allowShowNoCrossProfileIntentsEmptyState()
- && !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
- UserHandle.myUserId(), listUserHandle.getIdentifier());
+ private boolean shouldSkipRebuild(ResolverListAdapter activeListAdapter) {
+ EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter);
+ return emptyState != null && emptyState.shouldSkipDataRebuild();
}
- boolean allowShowNoCrossProfileIntentsEmptyState() {
- return true;
- }
-
- protected abstract void showWorkProfileOffEmptyState(
- ResolverListAdapter activeListAdapter, View.OnClickListener listener);
-
- protected abstract void showNoPersonalToWorkIntentsEmptyState(
- ResolverListAdapter activeListAdapter);
-
- protected abstract void showNoPersonalAppsAvailableEmptyState(
- ResolverListAdapter activeListAdapter);
-
- protected abstract void showNoWorkAppsAvailableEmptyState(
- ResolverListAdapter activeListAdapter);
-
- protected abstract void showNoWorkToPersonalIntentsEmptyState(
- ResolverListAdapter activeListAdapter);
-
/**
* The empty state screens are shown according to their priority:
* <ol>
@@ -366,103 +297,88 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
* anyway.
*/
void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
- if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) {
+ final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
+
+ if (emptyState == null) {
return;
}
- if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
- return;
+
+ emptyState.onEmptyStateShown();
+
+ View.OnClickListener clickListener = null;
+
+ if (emptyState.getButtonClickListener() != null) {
+ clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
+ ProfileDescriptor descriptor = getItem(
+ userHandleToPageIndex(listAdapter.getUserHandle()));
+ AbstractMultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView());
+ });
}
- maybeShowNoAppsAvailableEmptyState(listAdapter);
+
+ showEmptyState(listAdapter, emptyState, clickListener);
}
- private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) {
- if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
- return false;
- }
- if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
- DevicePolicyEventLogger.createEvent(
- DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL)
- .setStrings(getMetricsCategory())
- .write();
- showNoWorkToPersonalIntentsEmptyState(listAdapter);
- } else {
- DevicePolicyEventLogger.createEvent(
- DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK)
- .setStrings(getMetricsCategory())
- .write();
- showNoPersonalToWorkIntentsEmptyState(listAdapter);
+ /**
+ * 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();
}
- return true;
}
/**
- * Returns {@code true} if the work profile off empty state screen is shown.
+ * Utility class to check if there are cross profile intents, it is in a separate class so
+ * it could be mocked in tests
*/
- private boolean maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter) {
- UserHandle listUserHandle = listAdapter.getUserHandle();
- if (!listUserHandle.equals(mWorkProfileUserHandle)
- || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle)
- || listAdapter.getCount() == 0) {
- return false;
- }
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
- .setStrings(getMetricsCategory())
- .write();
- showWorkProfileOffEmptyState(listAdapter,
- v -> {
- ProfileDescriptor descriptor = getItem(
- userHandleToPageIndex(listAdapter.getUserHandle()));
- showSpinner(descriptor.getEmptyStateView());
- if (mOnSwitchOnWorkSelectedListener != null) {
- mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
- }
- mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle);
- });
- return true;
- }
-
- private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- UserHandle listUserHandle = listAdapter.getUserHandle();
- if (mWorkProfileUserHandle != null
- && (UserHandle.myUserId() == listUserHandle.getIdentifier()
- || !hasAppsInOtherProfile(listAdapter))) {
- DevicePolicyEventLogger.createEvent(
- DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
- .setStrings(getMetricsCategory())
- .setBoolean(/*isPersonalProfile*/ listUserHandle == mPersonalProfileUserHandle)
- .write();
- if (listUserHandle == mPersonalProfileUserHandle) {
- showNoPersonalAppsAvailableEmptyState(listAdapter);
- } else {
- showNoWorkAppsAvailableEmptyState(listAdapter);
- }
- } else if (mWorkProfileUserHandle == null) {
- showConsumerUserNoAppsAvailableEmptyState(listAdapter);
+ public static class CrossProfileIntentsChecker {
+
+ private final ContentResolver mContentResolver;
+
+ public CrossProfileIntentsChecker(@NonNull ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
}
- }
- protected void showEmptyState(ResolverListAdapter activeListAdapter, String title,
- String subtitle) {
- showEmptyState(activeListAdapter, title, subtitle, /* buttonOnClick */ null);
+ /**
+ * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
+ * from {@code source} (user id) to {@code target} (user id).
+ */
+ public boolean hasCrossProfileIntents(List<Intent> intents, @UserIdInt int source,
+ @UserIdInt int target) {
+ IPackageManager packageManager = AppGlobals.getPackageManager();
+
+ return intents.stream().anyMatch(intent ->
+ null != IntentForwarderActivity.canForward(intent, source, target,
+ packageManager, mContentResolver));
+ }
}
- protected void showEmptyState(ResolverListAdapter activeListAdapter,
- String title, String subtitle, View.OnClickListener buttonOnClick) {
+ protected void showEmptyState(ResolverListAdapter activeListAdapter, EmptyState emptyState,
+ View.OnClickListener buttonOnClick) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
descriptor.rootView.findViewById(com.android.internal.R.id.resolver_list).setVisibility(View.GONE);
ViewGroup emptyStateView = descriptor.getEmptyStateView();
- resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
+ resetViewVisibilitiesForEmptyState(emptyStateView);
emptyStateView.setVisibility(View.VISIBLE);
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);
- titleView.setText(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);
@@ -470,6 +386,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
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);
@@ -483,22 +402,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
*/
protected void setupContainerPadding(View container) {}
- private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
- ProfileDescriptor descriptor = getItem(
- userHandleToPageIndex(activeListAdapter.getUserHandle()));
- descriptor.rootView.findViewById(com.android.internal.R.id.resolver_list).setVisibility(View.GONE);
- View emptyStateView = descriptor.getEmptyStateView();
- resetViewVisibilitiesForConsumerUserEmptyState(emptyStateView);
- emptyStateView.setVisibility(View.VISIBLE);
-
- activeListAdapter.markTabLoaded();
- }
-
- private boolean isSpinnerShowing(View emptyStateView) {
- return emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress).getVisibility()
- == View.VISIBLE;
- }
-
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);
@@ -506,7 +409,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
emptyStateView.findViewById(com.android.internal.R.id.empty).setVisibility(View.GONE);
}
- private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
+ 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);
@@ -514,14 +417,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
emptyStateView.findViewById(com.android.internal.R.id.empty).setVisibility(View.GONE);
}
- private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
- emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_title).setVisibility(View.GONE);
- emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
- emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_button).setVisibility(View.GONE);
- emptyStateView.findViewById(com.android.internal.R.id.resolver_empty_state_progress).setVisibility(View.GONE);
- emptyStateView.findViewById(com.android.internal.R.id.empty).setVisibility(View.VISIBLE);
- }
-
protected void showListView(ResolverListAdapter activeListAdapter) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
@@ -530,33 +425,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
emptyStateView.setVisibility(View.GONE);
}
- private boolean hasCrossProfileIntents(List<Intent> intents, int source, int target) {
- IPackageManager packageManager = AppGlobals.getPackageManager();
- ContentResolver contentResolver = mContext.getContentResolver();
- for (Intent intent : intents) {
- if (IntentForwarderActivity.canForward(intent, source, target, packageManager,
- contentResolver) != null) {
- return true;
- }
- }
- return false;
- }
-
- private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
- if (mWorkProfileUserHandle == null) {
- return false;
- }
- List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
- adapter.getResolversForUser(UserHandle.of(UserHandle.myUserId()));
- for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
- ResolveInfo resolveInfo = info.getResolveInfoAt(0);
- if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
- return true;
- }
- }
- return false;
- }
-
boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
int count = listAdapter.getUnfilteredCount();
return (count == 0 && listAdapter.getPlaceholderCount() == 0)
@@ -600,6 +468,99 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
}
/**
+ * Returns an empty state to show for the current profile page (tab) if necessary.
+ * This could be used e.g. to show a blocker on a tab if device management policy doesn't
+ * allow to use it or there are no apps available.
+ */
+ public interface EmptyStateProvider {
+ /**
+ * When a non-null empty state is returned the corresponding profile page will show
+ * this empty state
+ * @param resolverListAdapter the current adapter
+ */
+ @Nullable
+ default EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ return null;
+ }
+ }
+
+ /**
+ * Empty state provider that combines multiple providers. Providers earlier in the list have
+ * priority, that is if there is a provider that returns non-null empty state then all further
+ * providers will be ignored.
+ */
+ public static class CompositeEmptyStateProvider implements EmptyStateProvider {
+
+ private final EmptyStateProvider[] mProviders;
+
+ public CompositeEmptyStateProvider(EmptyStateProvider... providers) {
+ mProviders = providers;
+ }
+
+ @Nullable
+ @Override
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ for (EmptyStateProvider provider : mProviders) {
+ EmptyState emptyState = provider.getEmptyState(resolverListAdapter);
+ if (emptyState != null) {
+ return emptyState;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Describes how the blocked empty state should look like for a profile tab
+ */
+ public interface EmptyState {
+ /**
+ * Title that will be shown on the empty state
+ */
+ @Nullable
+ default String getTitle() { return null; }
+
+ /**
+ * Subtitle that will be shown underneath the title on the empty state
+ */
+ @Nullable
+ 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; }
+
+ /**
+ * 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; }
+
+ /**
+ * Returns true if for this empty state we should skip rebuilding of the apps list
+ * for this tab.
+ */
+ default boolean shouldSkipDataRebuild() { return false; }
+
+ /**
+ * Called when empty state is shown, could be used e.g. to track analytics events
+ */
+ default void onEmptyStateShown() {}
+
+ interface ClickListener {
+ void onClick(TabControl currentTab);
+ }
+
+ interface TabControl {
+ void showSpinner();
+ }
+ }
+
+
+ /**
* Listener for when the user switches on the work profile from the work tab.
*/
interface OnSwitchOnWorkSelectedListener {
@@ -612,14 +573,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
/**
* Describes an injector to be used for cross profile functionality. Overridable for testing.
*/
- @VisibleForTesting
- public interface Injector {
- /**
- * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
- * from {@code sourceUserId} to {@code targetUserId}.
- */
- boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
-
+ public interface QuietModeManager {
/**
* Returns whether the given profile is in quiet mode or not.
*/
@@ -629,5 +583,15 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
* Enables or disables quiet mode for a managed profile.
*/
void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
+
+ /**
+ * Should be called when the work profile enabled broadcast received
+ */
+ void markWorkProfileEnabledBroadcastReceived();
+
+ /**
+ * Returns true if enabling of work profile is in progress
+ */
+ boolean isWaitingToEnableWorkProfile();
}
}
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index d5a0c32c..776d34a9 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -16,6 +16,14 @@
package com.android.intentresolver;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
+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 com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
import android.animation.Animator;
@@ -99,6 +107,9 @@ 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.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
import com.android.intentresolver.ResolverListAdapter.ViewHolder;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
@@ -594,6 +605,41 @@ public class ChooserActivity extends ResolverActivity implements
return mChooserMultiProfilePagerAdapter;
}
+ @Override
+ protected EmptyStateProvider createBlockerEmptyStateProvider() {
+ final boolean isSendAction = isSendAction(getTargetIntent());
+
+ final EmptyState noWorkToPersonalEmptyState =
+ new DevicePolicyBlockerEmptyState(
+ /* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */
+ isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
+ /* defaultSubtitleResource= */
+ isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
+ : R.string.resolver_cant_access_personal_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+ final EmptyState noPersonalToWorkEmptyState =
+ new DevicePolicyBlockerEmptyState(
+ /* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */
+ isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
+ /* defaultSubtitleResource= */
+ isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
+ : R.string.resolver_cant_access_work_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+ return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
+ noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
+ createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ }
+
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
Intent[] initialIntents,
List<ResolveInfo> rList,
@@ -608,9 +654,10 @@ public class ChooserActivity extends ResolverActivity implements
return new ChooserMultiProfilePagerAdapter(
/* context */ this,
adapter,
- getPersonalProfileUserHandle(),
+ createEmptyStateProvider(/* workProfileUserHandle= */ null),
+ mQuietModeManager,
/* workProfileUserHandle= */ null,
- isSendAction(getTargetIntent()), mMaxTargetsPerRow);
+ mMaxTargetsPerRow);
}
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
@@ -636,10 +683,11 @@ public class ChooserActivity extends ResolverActivity implements
/* context */ this,
personalAdapter,
workAdapter,
+ createEmptyStateProvider(/* workProfileUserHandle= */ getWorkProfileUserHandle()),
+ mQuietModeManager,
selectedProfile,
- getPersonalProfileUserHandle(),
getWorkProfileUserHandle(),
- isSendAction(getTargetIntent()), mMaxTargetsPerRow);
+ mMaxTargetsPerRow);
}
private int findSelectedProfile() {
diff --git a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
index 62c14866..d0463fff 100644
--- a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java
@@ -16,17 +16,7 @@
package com.android.intentresolver;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
-
import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.UserHandle;
import android.view.LayoutInflater;
@@ -47,37 +37,37 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
private static final int SINGLE_CELL_SPAN_SIZE = 1;
private final ChooserProfileDescriptor[] mItems;
- private final boolean mIsSendAction;
private int mBottomOffset;
private int mMaxTargetsPerRow;
ChooserMultiProfilePagerAdapter(Context context,
ChooserActivity.ChooserGridAdapter adapter,
- UserHandle personalProfileUserHandle,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
UserHandle workProfileUserHandle,
- boolean isSendAction, int maxTargetsPerRow) {
- super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
+ int maxTargetsPerRow) {
+ super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
+ workProfileUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(adapter)
};
- mIsSendAction = isSendAction;
mMaxTargetsPerRow = maxTargetsPerRow;
}
ChooserMultiProfilePagerAdapter(Context context,
ChooserActivity.ChooserGridAdapter personalAdapter,
ChooserActivity.ChooserGridAdapter workAdapter,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
@Profile int defaultProfile,
- UserHandle personalProfileUserHandle,
UserHandle workProfileUserHandle,
- boolean isSendAction, int maxTargetsPerRow) {
- super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
- workProfileUserHandle);
+ int maxTargetsPerRow) {
+ super(context, /* currentPage */ defaultProfile, emptyStateProvider,
+ quietModeManager, workProfileUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
};
- mIsSendAction = isSendAction;
mMaxTargetsPerRow = maxTargetsPerRow;
}
@@ -192,112 +182,6 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
return getListViewForIndex(1 - getCurrentPage());
}
- @Override
- String getMetricsCategory() {
- return ResolverActivity.METRICS_CATEGORY_CHOOSER;
- }
-
- @Override
- protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
- View.OnClickListener listener) {
- showEmptyState(activeListAdapter,
- getWorkAppPausedTitle(),
- /* subtitle = */ null,
- listener);
- }
-
- @Override
- protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- if (mIsSendAction) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantShareWithWorkMessage());
- } else {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessWorkMessage());
- }
- }
-
- @Override
- protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- if (mIsSendAction) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantShareWithPersonalMessage());
- } else {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessPersonalMessage());
- }
- }
-
- @Override
- protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter, getNoPersonalAppsAvailableMessage(), /* subtitle= */ null);
-
- }
-
- @Override
- protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter, getNoWorkAppsAvailableMessage(), /* subtitle = */ null);
- }
-
- private String getWorkAppPausedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_WORK_PAUSED_TITLE,
- () -> getContext().getString(R.string.resolver_turn_on_work_apps));
- }
-
- private String getCrossProfileBlockedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- () -> getContext().getString(R.string.resolver_cross_profile_blocked));
- }
-
- private String getCantShareWithWorkMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_SHARE_WITH_WORK,
- () -> getContext().getString(
- R.string.resolver_cant_share_with_work_apps_explanation));
- }
-
- private String getCantShareWithPersonalMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_SHARE_WITH_PERSONAL,
- () -> getContext().getString(
- R.string.resolver_cant_share_with_personal_apps_explanation));
- }
-
- private String getCantAccessWorkMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_WORK,
- () -> getContext().getString(
- R.string.resolver_cant_access_work_apps_explanation));
- }
-
- private String getCantAccessPersonalMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_PERSONAL,
- () -> getContext().getString(
- R.string.resolver_cant_access_personal_apps_explanation));
- }
-
- private String getNoWorkAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_WORK_APPS,
- () -> getContext().getString(
- R.string.resolver_no_work_apps_available));
- }
-
- private String getNoPersonalAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_PERSONAL_APPS,
- () -> getContext().getString(
- R.string.resolver_no_personal_apps_available));
- }
-
-
void setEmptyStateBottomOffset(int bottomOffset) {
mBottomOffset = bottomOffset;
}
diff --git a/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java b/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java
new file mode 100644
index 00000000..5bf994d6
--- /dev/null
+++ b/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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 static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+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.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.R;
+
+import java.util.List;
+
+/**
+ * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
+ * there are no apps available.
+ */
+public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
+
+ @NonNull
+ private final Context mContext;
+ @Nullable
+ private final UserHandle mWorkProfileUserHandle;
+ @Nullable
+ private final UserHandle mPersonalProfileUserHandle;
+ @NonNull
+ private final String mMetricsCategory;
+ @NonNull
+ private final MyUserIdProvider mMyUserIdProvider;
+
+ public NoAppsAvailableEmptyStateProvider(Context context, UserHandle workProfileUserHandle,
+ UserHandle personalProfileUserHandle, String metricsCategory,
+ MyUserIdProvider myUserIdProvider) {
+ mContext = context;
+ mWorkProfileUserHandle = workProfileUserHandle;
+ mPersonalProfileUserHandle = personalProfileUserHandle;
+ mMetricsCategory = metricsCategory;
+ mMyUserIdProvider = myUserIdProvider;
+ }
+
+ @Nullable
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ UserHandle listUserHandle = resolverListAdapter.getUserHandle();
+
+ if (mWorkProfileUserHandle != null
+ && (mMyUserIdProvider.getMyUserId() == listUserHandle.getIdentifier()
+ || !hasAppsInOtherProfile(resolverListAdapter))) {
+
+ String title;
+ if (listUserHandle == mPersonalProfileUserHandle) {
+ title = mContext.getSystemService(
+ DevicePolicyManager.class).getResources().getString(
+ RESOLVER_NO_PERSONAL_APPS,
+ () -> mContext.getString(R.string.resolver_no_personal_apps_available));
+ } else {
+ title = mContext.getSystemService(
+ DevicePolicyManager.class).getResources().getString(
+ RESOLVER_NO_WORK_APPS,
+ () -> mContext.getString(R.string.resolver_no_work_apps_available));
+ }
+
+ return new NoAppsAvailableEmptyState(
+ title, mMetricsCategory,
+ /* isPersonalProfile= */ listUserHandle == mPersonalProfileUserHandle
+ );
+ } else if (mWorkProfileUserHandle == null) {
+ // Return default empty state without tracking
+ return new DefaultEmptyState();
+ }
+
+ return null;
+ }
+
+ private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
+ if (mWorkProfileUserHandle == null) {
+ return false;
+ }
+ List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
+ adapter.getResolversForUser(UserHandle.of(mMyUserIdProvider.getMyUserId()));
+ for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
+ ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+ if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static class DefaultEmptyState implements EmptyState {
+ @Override
+ public boolean useDefaultEmptyView() {
+ return true;
+ }
+ }
+
+ public static class NoAppsAvailableEmptyState implements EmptyState {
+
+ @NonNull
+ private String mTitle;
+
+ @NonNull
+ private String mMetricsCategory;
+
+ private boolean mIsPersonalProfile;
+
+ public NoAppsAvailableEmptyState(String title, String metricsCategory,
+ boolean isPersonalProfile) {
+ mTitle = title;
+ mMetricsCategory = metricsCategory;
+ mIsPersonalProfile = isPersonalProfile;
+ }
+
+ @Nullable
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ @Override
+ public void onEmptyStateShown() {
+ DevicePolicyEventLogger.createEvent(
+ DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
+ .setStrings(mMetricsCategory)
+ .setBoolean(/*isPersonalProfile*/ mIsPersonalProfile)
+ .write();
+ }
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java
new file mode 100644
index 00000000..420d26c5
--- /dev/null
+++ b/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java
@@ -0,0 +1,137 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.admin.DevicePolicyEventLogger;
+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.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+
+/**
+ * Empty state provider that does not allow cross profile sharing, it will return a blocker
+ * in case if the profile of the current tab is not the same as the profile of the calling app.
+ */
+public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
+
+ private final UserHandle mPersonalProfileUserHandle;
+ private final EmptyState mNoWorkToPersonalEmptyState;
+ private final EmptyState mNoPersonalToWorkEmptyState;
+ private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
+ private final MyUserIdProvider mUserIdProvider;
+
+ public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
+ EmptyState noWorkToPersonalEmptyState,
+ EmptyState noPersonalToWorkEmptyState,
+ CrossProfileIntentsChecker crossProfileIntentsChecker,
+ MyUserIdProvider myUserIdProvider) {
+ mPersonalProfileUserHandle = personalUserHandle;
+ mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
+ mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
+ mCrossProfileIntentsChecker = crossProfileIntentsChecker;
+ mUserIdProvider = myUserIdProvider;
+ }
+
+ @Nullable
+ @Override
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ boolean shouldShowBlocker =
+ mUserIdProvider.getMyUserId() != resolverListAdapter.getUserHandle().getIdentifier()
+ && !mCrossProfileIntentsChecker
+ .hasCrossProfileIntents(resolverListAdapter.getIntents(),
+ mUserIdProvider.getMyUserId(),
+ resolverListAdapter.getUserHandle().getIdentifier());
+
+ if (!shouldShowBlocker) {
+ return null;
+ }
+
+ if (resolverListAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
+ return mNoWorkToPersonalEmptyState;
+ } else {
+ return mNoPersonalToWorkEmptyState;
+ }
+ }
+
+
+ /**
+ * Empty state that gets strings from the device policy manager and tracks events into
+ * event logger of the device policy events.
+ */
+ public static class DevicePolicyBlockerEmptyState implements EmptyState {
+
+ @NonNull
+ private final Context mContext;
+ private final String mDevicePolicyStringTitleId;
+ @StringRes
+ private final int mDefaultTitleResource;
+ private final String mDevicePolicyStringSubtitleId;
+ @StringRes
+ private final int mDefaultSubtitleResource;
+ private final int mEventId;
+ @NonNull
+ private final String mEventCategory;
+
+ public DevicePolicyBlockerEmptyState(Context context, String devicePolicyStringTitleId,
+ @StringRes int defaultTitleResource, String devicePolicyStringSubtitleId,
+ @StringRes int defaultSubtitleResource,
+ int devicePolicyEventId, String devicePolicyEventCategory) {
+ mContext = context;
+ mDevicePolicyStringTitleId = devicePolicyStringTitleId;
+ mDefaultTitleResource = defaultTitleResource;
+ mDevicePolicyStringSubtitleId = devicePolicyStringSubtitleId;
+ mDefaultSubtitleResource = defaultSubtitleResource;
+ mEventId = devicePolicyEventId;
+ mEventCategory = devicePolicyEventCategory;
+ }
+
+ @Nullable
+ @Override
+ public String getTitle() {
+ return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+ mDevicePolicyStringTitleId,
+ () -> mContext.getString(mDefaultTitleResource));
+ }
+
+ @Nullable
+ @Override
+ public String getSubtitle() {
+ return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+ mDevicePolicyStringSubtitleId,
+ () -> mContext.getString(mDefaultSubtitleResource));
+ }
+
+ @Override
+ public void onEmptyStateShown() {
+ DevicePolicyEventLogger.createEvent(mEventId)
+ .setStrings(mEventCategory)
+ .write();
+ }
+
+ @Override
+ public boolean shouldSkipDataRebuild() {
+ return true;
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java
index 46a41b50..fece8d3d 100644
--- a/java/src/com/android/intentresolver/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/ResolverActivity.java
@@ -19,6 +19,9 @@ package com.android.intentresolver;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
@@ -26,6 +29,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
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 android.annotation.Nullable;
@@ -56,6 +61,7 @@ import android.content.res.TypedArray;
import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
@@ -93,7 +99,15 @@ 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.AbstractMultiProfilePagerAdapter.QuietModeManager;
+import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
+import com.android.intentresolver.chooser.ChooserTargetInfo;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.widget.ResolverDrawerLayout;
@@ -178,6 +192,8 @@ public class ResolverActivity extends FragmentActivity implements
@VisibleForTesting
protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter;
+ protected QuietModeManager mQuietModeManager;
+
// Intent extra for connected audio devices
public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
@@ -209,6 +225,9 @@ public class ResolverActivity extends FragmentActivity implements
private UserHandle mWorkProfileUserHandle;
+ @Nullable
+ private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
private LatencyTracker getLatencyTracker() {
@@ -366,6 +385,8 @@ public class ResolverActivity extends FragmentActivity implements
setTheme(appliedThemeResId());
super.onCreate(savedInstanceState);
+ mQuietModeManager = createQuietModeManager();
+
// Determine whether we should show that intent is forwarded
// from managed profile to owner or other way around.
setProfileSwitchMessage(intent.getContentUserHint());
@@ -466,6 +487,111 @@ public class ResolverActivity extends FragmentActivity implements
return resolverMultiProfilePagerAdapter;
}
+ @VisibleForTesting
+ protected MyUserIdProvider createMyUserIdProvider() {
+ return new MyUserIdProvider();
+ }
+
+ @VisibleForTesting
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ return new CrossProfileIntentsChecker(getContentResolver());
+ }
+
+ @VisibleForTesting
+ protected QuietModeManager createQuietModeManager() {
+ UserManager userManager = getSystemService(UserManager.class);
+ return new QuietModeManager() {
+
+ private boolean mIsWaitingToEnableWorkProfile = false;
+
+ @Override
+ public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return userManager.isQuietModeEnabled(workProfileUserHandle);
+ }
+
+ @Override
+ public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+ userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
+ });
+ mIsWaitingToEnableWorkProfile = true;
+ }
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ mIsWaitingToEnableWorkProfile = false;
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return mIsWaitingToEnableWorkProfile;
+ }
+ };
+ }
+
+ protected EmptyStateProvider createBlockerEmptyStateProvider() {
+ final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
+
+ if (!shouldShowNoCrossProfileIntentsEmptyState) {
+ // Implementation that doesn't show any blockers
+ return new EmptyStateProvider() {};
+ }
+
+ final AbstractMultiProfilePagerAdapter.EmptyState
+ noWorkToPersonalEmptyState =
+ new DevicePolicyBlockerEmptyState(/* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL,
+ /* defaultSubtitleResource= */
+ R.string.resolver_cant_access_personal_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+ final AbstractMultiProfilePagerAdapter.EmptyState noPersonalToWorkEmptyState =
+ new DevicePolicyBlockerEmptyState(/* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK,
+ /* defaultSubtitleResource= */
+ R.string.resolver_cant_access_work_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+ return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
+ noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
+ createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ }
+
+ protected EmptyStateProvider createEmptyStateProvider(
+ @Nullable UserHandle workProfileUserHandle) {
+ final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
+
+ final EmptyStateProvider workProfileOffEmptyStateProvider =
+ new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
+ mQuietModeManager,
+ /* onSwitchOnWorkSelectedListener= */
+ () -> { if (mOnSwitchOnWorkSelectedListener != null) {
+ mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+ }},
+ getMetricsCategory());
+
+ final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
+ this,
+ workProfileUserHandle,
+ getPersonalProfileUserHandle(),
+ getMetricsCategory(),
+ createMyUserIdProvider()
+ );
+
+ // Return composite provider, the order matters (the higher, the more priority)
+ return new CompositeEmptyStateProvider(
+ blockerEmptyStateProvider,
+ workProfileOffEmptyStateProvider,
+ noAppsEmptyStateProvider
+ );
+ }
+
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
Intent[] initialIntents,
List<ResolveInfo> rList, boolean filterLastUsed) {
@@ -476,13 +602,21 @@ public class ResolverActivity extends FragmentActivity implements
rList,
filterLastUsed,
/* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
adapter,
- getPersonalProfileUserHandle(),
+ createEmptyStateProvider(/* workProfileUserHandle= */ null),
+ quietModeManager,
/* workProfileUserHandle= */ null);
}
+ private UserHandle getIntentUser() {
+ return getIntent().hasExtra(EXTRA_CALLING_USER)
+ ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
+ : getUser();
+ }
+
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
Intent[] initialIntents,
List<ResolveInfo> rList,
@@ -491,9 +625,7 @@ public class ResolverActivity extends FragmentActivity implements
// the intent resolver is started in the other profile. Since this is the only case when
// this happens, we check for it here and set the current profile's tab.
int selectedProfile = getCurrentProfile();
- UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER)
- ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
- : getUser();
+ UserHandle intentUser = getIntentUser();
if (!getUser().equals(intentUser)) {
if (getPersonalProfileUserHandle().equals(intentUser)) {
selectedProfile = PROFILE_PERSONAL;
@@ -526,14 +658,15 @@ public class ResolverActivity extends FragmentActivity implements
(filterLastUsed && UserHandle.myUserId()
== workProfileUserHandle.getIdentifier()),
/* userHandle */ workProfileUserHandle);
+ QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
personalAdapter,
workAdapter,
+ createEmptyStateProvider(getWorkProfileUserHandle()),
+ quietModeManager,
selectedProfile,
- getPersonalProfileUserHandle(),
- getWorkProfileUserHandle(),
- /* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser));
+ getWorkProfileUserHandle());
}
protected int appliedThemeResId() {
@@ -840,9 +973,9 @@ public class ResolverActivity extends FragmentActivity implements
}
mRegistered = true;
}
- if (shouldShowTabs() && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
- if (mMultiProfilePagerAdapter.isQuietModeEnabled(getWorkProfileUserHandle())) {
- mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
+ if (shouldShowTabs() && mQuietModeManager.isWaitingToEnableWorkProfile()) {
+ if (mQuietModeManager.isQuietModeEnabled(getWorkProfileUserHandle())) {
+ mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
}
}
mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
@@ -1799,13 +1932,12 @@ public class ResolverActivity extends FragmentActivity implements
onHorizontalSwipeStateChanged(state);
}
});
- mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener(
- () -> {
- final View workTab = tabHost.getTabWidget().getChildAt(1);
- workTab.setFocusable(true);
- workTab.setFocusableInTouchMode(true);
- workTab.requestFocus();
- });
+ mOnSwitchOnWorkSelectedListener = () -> {
+ final View workTab = tabHost.getTabWidget().getChildAt(1);
+ workTab.setFocusable(true);
+ workTab.setFocusableInTouchMode(true);
+ workTab.requestFocus();
+ };
}
private String getPersonalTabLabel() {
@@ -2066,7 +2198,7 @@ public class ResolverActivity extends FragmentActivity implements
public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
- && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
+ && mQuietModeManager.isWaitingToEnableWorkProfile()) {
// We have just turned on the work profile and entered the pass code to start it,
// now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
// point in reloading the list now, since the work profile user is still
@@ -2118,7 +2250,7 @@ public class ResolverActivity extends FragmentActivity implements
}
mWorkProfileHasBeenEnabled = true;
- mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
+ mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
} else {
// Must be an UNAVAILABLE broadcast, so we watch for the next availability
mWorkProfileHasBeenEnabled = false;
@@ -2134,7 +2266,6 @@ public class ResolverActivity extends FragmentActivity implements
};
}
- @VisibleForTesting
public static final class ResolvedComponentInfo {
public final ComponentName name;
private final List<Intent> mIntents = new ArrayList<>();
diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java
index f74c33c0..d97191c6 100644
--- a/java/src/com/android/intentresolver/ResolverListAdapter.java
+++ b/java/src/com/android/intentresolver/ResolverListAdapter.java
@@ -764,7 +764,6 @@ public class ResolverListAdapter extends BaseAdapter {
}
}
- @VisibleForTesting
public UserHandle getUserHandle() {
return mResolverListController.getUserHandle();
}
diff --git a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
index 7cd38a7e..8cf65529 100644
--- a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java
@@ -16,15 +16,7 @@
package com.android.intentresolver;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
-
import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.UserHandle;
import android.view.LayoutInflater;
@@ -43,34 +35,33 @@ import com.android.internal.annotations.VisibleForTesting;
public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter {
private final ResolverProfileDescriptor[] mItems;
- private final boolean mShouldShowNoCrossProfileIntentsEmptyState;
private boolean mUseLayoutWithDefault;
ResolverMultiProfilePagerAdapter(Context context,
ResolverListAdapter adapter,
- UserHandle personalProfileUserHandle,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
UserHandle workProfileUserHandle) {
- super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
+ super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
+ workProfileUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(adapter)
};
- mShouldShowNoCrossProfileIntentsEmptyState = true;
}
ResolverMultiProfilePagerAdapter(Context context,
ResolverListAdapter personalAdapter,
ResolverListAdapter workAdapter,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
@Profile int defaultProfile,
- UserHandle personalProfileUserHandle,
- UserHandle workProfileUserHandle,
- boolean shouldShowNoCrossProfileIntentsEmptyState) {
- super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
+ UserHandle workProfileUserHandle) {
+ super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
workProfileUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
};
- mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState;
}
private ResolverProfileDescriptor createProfileDescriptor(
@@ -170,93 +161,6 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
return getListViewForIndex(1 - getCurrentPage());
}
- @Override
- String getMetricsCategory() {
- return ResolverActivity.METRICS_CATEGORY_RESOLVER;
- }
-
- @Override
- boolean allowShowNoCrossProfileIntentsEmptyState() {
- return mShouldShowNoCrossProfileIntentsEmptyState;
- }
-
- @Override
- protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
- View.OnClickListener listener) {
- showEmptyState(activeListAdapter,
- getWorkAppPausedTitle(),
- /* subtitle = */ null,
- listener);
- }
-
- @Override
- protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessWorkMessage());
- }
-
- @Override
- protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessPersonalMessage());
- }
-
- @Override
- protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter,
- getNoPersonalAppsAvailableMessage(),
- /* subtitle = */ null);
- }
-
- @Override
- protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter,
- getNoWorkAppsAvailableMessage(),
- /* subtitle= */ null);
- }
-
- private String getWorkAppPausedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_WORK_PAUSED_TITLE,
- () -> getContext().getString(R.string.resolver_turn_on_work_apps));
- }
-
- private String getCrossProfileBlockedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- () -> getContext().getString(R.string.resolver_cross_profile_blocked));
- }
-
- private String getCantAccessWorkMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_WORK,
- () -> getContext().getString(
- R.string.resolver_cant_access_work_apps_explanation));
- }
-
- private String getCantAccessPersonalMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_PERSONAL,
- () -> getContext().getString(
- R.string.resolver_cant_access_personal_apps_explanation));
- }
-
- private String getNoWorkAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_WORK_APPS,
- () -> getContext().getString(
- R.string.resolver_no_work_apps_available));
- }
-
- private String getNoPersonalAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_PERSONAL_APPS,
- () -> getContext().getString(
- R.string.resolver_no_personal_apps_available));
- }
-
void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
mUseLayoutWithDefault = useLayoutWithDefault;
}
diff --git a/java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java b/java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java
new file mode 100644
index 00000000..b7c89907
--- /dev/null
+++ b/java/src/com/android/intentresolver/WorkProfilePausedEmptyStateProvider.java
@@ -0,0 +1,114 @@
+/*
+ * 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 static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.stats.devicepolicy.nano.DevicePolicyEnums;
+
+import com.android.internal.R;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
+
+/**
+ * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
+ * work profile is paused and we need to show a button to enable it.
+ */
+public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider {
+
+ private final UserHandle mWorkProfileUserHandle;
+ private final QuietModeManager mQuietModeManager;
+ private final String mMetricsCategory;
+ private final OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+ private final Context mContext;
+
+ public WorkProfilePausedEmptyStateProvider(@NonNull Context context,
+ @Nullable UserHandle workProfileUserHandle,
+ @NonNull QuietModeManager quietModeManager,
+ @Nullable OnSwitchOnWorkSelectedListener onSwitchOnWorkSelectedListener,
+ @NonNull String metricsCategory) {
+ mContext = context;
+ mWorkProfileUserHandle = workProfileUserHandle;
+ mQuietModeManager = quietModeManager;
+ mMetricsCategory = metricsCategory;
+ mOnSwitchOnWorkSelectedListener = onSwitchOnWorkSelectedListener;
+ }
+
+ @Nullable
+ @Override
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ if (!resolverListAdapter.getUserHandle().equals(mWorkProfileUserHandle)
+ || !mQuietModeManager.isQuietModeEnabled(mWorkProfileUserHandle)
+ || resolverListAdapter.getCount() == 0) {
+ return null;
+ }
+
+ final String title = mContext.getSystemService(DevicePolicyManager.class)
+ .getResources().getString(RESOLVER_WORK_PAUSED_TITLE,
+ () -> mContext.getString(R.string.resolver_turn_on_work_apps));
+
+ return new WorkProfileOffEmptyState(title, (tab) -> {
+ tab.showSpinner();
+ if (mOnSwitchOnWorkSelectedListener != null) {
+ mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+ }
+ mQuietModeManager.requestQuietModeEnabled(false, mWorkProfileUserHandle);
+ }, mMetricsCategory);
+ }
+
+ public static class WorkProfileOffEmptyState implements EmptyState {
+
+ private final String mTitle;
+ private final ClickListener mOnClick;
+ private final String mMetricsCategory;
+
+ public WorkProfileOffEmptyState(String title, @NonNull ClickListener onClick,
+ @NonNull String metricsCategory) {
+ mTitle = title;
+ mOnClick = onClick;
+ mMetricsCategory = metricsCategory;
+ }
+
+ @Nullable
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ @Nullable
+ @Override
+ public ClickListener getButtonClickListener() {
+ return mOnClick;
+ }
+
+ @Override
+ public void onEmptyStateShown() {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
+ .setStrings(mMetricsCategory)
+ .write();
+ }
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
index dd78b69e..5acdb42c 100644
--- a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
+++ b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
@@ -16,20 +16,24 @@
package com.android.intentresolver;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.shortcuts.ShortcutLoader;
import com.android.internal.logging.MetricsLogger;
-import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -69,7 +73,10 @@ public class ChooserActivityOverrideData {
public UserHandle workProfileUserHandle;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
- public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
+ public Integer myUserId;
+ public QuietModeManager mQuietModeManager;
+ public MyUserIdProvider mMyUserIdProvider;
+ public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager;
public void reset() {
@@ -89,14 +96,9 @@ public class ChooserActivityOverrideData {
workProfileUserHandle = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
+ myUserId = null;
packageManager = null;
- multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
- @Override
- public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
- int targetUserId) {
- return hasCrossProfileIntents;
- }
-
+ mQuietModeManager = new QuietModeManager() {
@Override
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return isQuietModeEnabled;
@@ -107,8 +109,28 @@ public class ChooserActivityOverrideData {
UserHandle workProfileUserHandle) {
isQuietModeEnabled = enabled;
}
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return false;
+ }
};
shortcutLoaderFactory = ((userHandle, resultConsumer) -> null);
+
+ mMyUserIdProvider = new MyUserIdProvider() {
+ @Override
+ public int getMyUserId() {
+ return myUserId != null ? myUserId : UserHandle.myUserId();
+ }
+ };
+
+ mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+ when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+ .thenAnswer(invocation -> hasCrossProfileIntents);
}
private ChooserActivityOverrideData() {}
diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
index 6b74fcd4..56e583bb 100644
--- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
+++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
@@ -35,13 +35,10 @@ import android.net.Uri;
import android.os.UserHandle;
import android.util.Size;
-import com.android.intentresolver.AbstractMultiProfilePagerAdapter;
-import com.android.intentresolver.ChooserActivityLogger;
-import com.android.intentresolver.ChooserActivityOverrideData;
-import com.android.intentresolver.ChooserListAdapter;
-import com.android.intentresolver.IChooserWrapper;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
-import com.android.intentresolver.ResolverListController;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.NotSelectableTargetInfo;
import com.android.intentresolver.chooser.TargetInfo;
@@ -69,15 +66,6 @@ public class ChooserWrapperActivity
}
@Override
- protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
- AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
- super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
- multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
- return multiProfilePagerAdapter;
- }
-
- @Override
public ChooserListAdapter createChooserListAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
ResolverListController resolverListController) {
@@ -150,6 +138,30 @@ public class ChooserWrapperActivity
}
@Override
+ protected MyUserIdProvider createMyUserIdProvider() {
+ if (sOverrides.mMyUserIdProvider != null) {
+ return sOverrides.mMyUserIdProvider;
+ }
+ return super.createMyUserIdProvider();
+ }
+
+ @Override
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ if (sOverrides.mCrossProfileIntentsChecker != null) {
+ return sOverrides.mCrossProfileIntentsChecker;
+ }
+ return super.createCrossProfileIntentsChecker();
+ }
+
+ @Override
+ protected QuietModeManager createQuietModeManager() {
+ if (sOverrides.mQuietModeManager != null) {
+ return sOverrides.mQuietModeManager;
+ }
+ return super.createQuietModeManager();
+ }
+
+ @Override
public void safelyStartActivity(com.android.intentresolver.chooser.TargetInfo cti) {
if (sOverrides.onSafelyStartCallback != null
&& sOverrides.onSafelyStartCallback.apply(cti)) {
diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
index b6d63265..7d4b07d8 100644
--- a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
+++ b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
@@ -16,6 +16,8 @@
package com.android.intentresolver;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -27,6 +29,9 @@ import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.intentresolver.chooser.TargetInfo;
import java.util.List;
@@ -59,12 +64,27 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
@Override
- protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
- AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
- super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
- multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
- return multiProfilePagerAdapter;
+ protected MyUserIdProvider createMyUserIdProvider() {
+ if (sOverrides.mMyUserIdProvider != null) {
+ return sOverrides.mMyUserIdProvider;
+ }
+ return super.createMyUserIdProvider();
+ }
+
+ @Override
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ if (sOverrides.mCrossProfileIntentsChecker != null) {
+ return sOverrides.mCrossProfileIntentsChecker;
+ }
+ return super.createCrossProfileIntentsChecker();
+ }
+
+ @Override
+ protected QuietModeManager createQuietModeManager() {
+ if (sOverrides.mQuietModeManager != null) {
+ return sOverrides.mQuietModeManager;
+ }
+ return super.createQuietModeManager();
}
ResolverWrapperAdapter getAdapter() {
@@ -144,9 +164,12 @@ public class ResolverWrapperActivity extends ResolverActivity {
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
+ public Integer myUserId;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
- public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
+ public QuietModeManager mQuietModeManager;
+ public MyUserIdProvider mMyUserIdProvider;
+ public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public void reset() {
onSafelyStartCallback = null;
@@ -155,15 +178,11 @@ public class ResolverWrapperActivity extends ResolverActivity {
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
+ myUserId = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
- multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
- @Override
- public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
- int targetUserId) {
- return hasCrossProfileIntents;
- }
+ mQuietModeManager = new QuietModeManager() {
@Override
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return isQuietModeEnabled;
@@ -174,7 +193,27 @@ public class ResolverWrapperActivity extends ResolverActivity {
UserHandle workProfileUserHandle) {
isQuietModeEnabled = enabled;
}
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return false;
+ }
};
+
+ mMyUserIdProvider = new MyUserIdProvider() {
+ @Override
+ public int getMyUserId() {
+ return myUserId != null ? myUserId : UserHandle.myUserId();
+ }
+ };
+
+ mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+ when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+ .thenAnswer(invocation -> hasCrossProfileIntents);
}
}
}
diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
new file mode 100644
index 00000000..b7eecb3f
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
@@ -0,0 +1,455 @@
+/*
+ * 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 static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.WORK;
+import static com.android.intentresolver.ChooserWrapperActivity.sOverrides;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.companion.DeviceFilter;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.internal.R;
+import com.android.intentresolver.ResolverActivity.ResolvedComponentInfo;
+import com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@DeviceFilter.MediumType
+@RunWith(Parameterized.class)
+public class UnbundledChooserActivityWorkProfileTest {
+
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
+ private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+
+ @Rule
+ public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(ChooserWrapperActivity.class, false,
+ false);
+ private final TestCase mTestCase;
+
+ public UnbundledChooserActivityWorkProfileTest(TestCase testCase) {
+ mTestCase = testCase;
+ }
+
+ @Before
+ public void cleanOverrideData() {
+ // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
+ // permissions we require (which we'll read from the manifest at runtime).
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+
+ sOverrides.reset();
+ }
+
+ @Test
+ public void testBlocker() {
+ setUpPersonalAndWorkComponentInfos();
+ sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
+ sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+
+ launchActivity(mTestCase.getIsSendAction());
+ switchToTab(mTestCase.getTab());
+
+ switch (mTestCase.getExpectedBlocker()) {
+ case NO_BLOCKER:
+ assertNoBlockerDisplayed();
+ break;
+ case PERSONAL_PROFILE_SHARE_BLOCKER:
+ assertCantSharePersonalAppsBlockerDisplayed();
+ break;
+ case WORK_PROFILE_SHARE_BLOCKER:
+ assertCantShareWorkAppsBlockerDisplayed();
+ break;
+ case PERSONAL_PROFILE_ACCESS_BLOCKER:
+ assertCantAccessPersonalAppsBlockerDisplayed();
+ break;
+ case WORK_PROFILE_ACCESS_BLOCKER:
+ assertCantAccessWorkAppsBlockerDisplayed();
+ break;
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection tests() {
+ return Arrays.asList(
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+// TODO(b/256869196) ChooserActivity goes into requestLayout loop
+// new TestCase(
+// /* isSendAction= */ true,
+// /* hasCrossProfileIntents= */ false,
+// /* myUserHandle= */ WORK_USER_HANDLE,
+// /* tab= */ WORK,
+// /* expectedBlocker= */ NO_BLOCKER
+// ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+// TODO(b/256869196) ChooserActivity goes into requestLayout loop
+// new TestCase(
+// /* isSendAction= */ true,
+// /* hasCrossProfileIntents= */ false,
+// /* myUserHandle= */ WORK_USER_HANDLE,
+// /* tab= */ PERSONAL,
+// /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER
+// ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ )
+ );
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults, int userId) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ return infoList;
+ }
+
+ private void setUpPersonalAndWorkComponentInfos() {
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3,
+ /* userId */ WORK_USER_HANDLE.getIdentifier());
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ }
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> personalResolvedComponentInfos,
+ List<ResolvedComponentInfo> workResolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private void markWorkProfileUserAvailable() {
+ ChooserWrapperActivity.sOverrides.workProfileUserHandle = WORK_USER_HANDLE;
+ }
+
+ private void assertCantAccessWorkAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_work_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantAccessPersonalAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantShareWorkAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_share_with_work_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantSharePersonalAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertNoBlockerDisplayed() {
+ try {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(not(isDisplayed())));
+ } catch (NoMatchingViewException ignored) {
+ }
+ }
+
+ private void switchToTab(Tab tab) {
+ final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
+ : R.string.resolver_personal_tab;
+
+ onView(withText(stringId)).perform(click());
+ waitForIdle();
+
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ waitForIdle();
+ }
+
+ private Intent createTextIntent(boolean isSendAction) {
+ Intent sendIntent = new Intent();
+ if (isSendAction) {
+ sendIntent.setAction(Intent.ACTION_SEND);
+ }
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+ sendIntent.setType("text/plain");
+ return sendIntent;
+ }
+
+ private void launchActivity(boolean isSendAction) {
+ Intent sendIntent = createTextIntent(isSendAction);
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
+ waitForIdle();
+ }
+
+ public static class TestCase {
+ private final boolean mIsSendAction;
+ private final boolean mHasCrossProfileIntents;
+ private final UserHandle mMyUserHandle;
+ private final Tab mTab;
+ private final ExpectedBlocker mExpectedBlocker;
+
+ public enum ExpectedBlocker {
+ NO_BLOCKER,
+ PERSONAL_PROFILE_SHARE_BLOCKER,
+ WORK_PROFILE_SHARE_BLOCKER,
+ PERSONAL_PROFILE_ACCESS_BLOCKER,
+ WORK_PROFILE_ACCESS_BLOCKER
+ }
+
+ public enum Tab {
+ WORK,
+ PERSONAL
+ }
+
+ public TestCase(boolean isSendAction, boolean hasCrossProfileIntents,
+ UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
+ mIsSendAction = isSendAction;
+ mHasCrossProfileIntents = hasCrossProfileIntents;
+ mMyUserHandle = myUserHandle;
+ mTab = tab;
+ mExpectedBlocker = expectedBlocker;
+ }
+
+ public boolean getIsSendAction() {
+ return mIsSendAction;
+ }
+
+ public boolean hasCrossProfileIntents() {
+ return mHasCrossProfileIntents;
+ }
+
+ public UserHandle getMyUserHandle() {
+ return mMyUserHandle;
+ }
+
+ public Tab getTab() {
+ return mTab;
+ }
+
+ public ExpectedBlocker getExpectedBlocker() {
+ return mExpectedBlocker;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("test");
+
+ if (mTab == WORK) {
+ result.append("WorkTab_");
+ } else {
+ result.append("PersonalTab_");
+ }
+
+ if (mIsSendAction) {
+ result.append("sendAction_");
+ } else {
+ result.append("notSendAction_");
+ }
+
+ if (mHasCrossProfileIntents) {
+ result.append("hasCrossProfileIntents_");
+ } else {
+ result.append("doesNotHaveCrossProfileIntents_");
+ }
+
+ if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
+ result.append("myUserIsPersonal_");
+ } else {
+ result.append("myUserIsWork_");
+ }
+
+ if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
+ result.append("thenNoBlocker");
+ } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) {
+ result.append("thenAccessBlockerOnPersonalProfile");
+ } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) {
+ result.append("thenShareBlockerOnPersonalProfile");
+ } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) {
+ result.append("thenAccessBlockerOnWorkProfile");
+ } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) {
+ result.append("thenShareBlockerOnWorkProfile");
+ }
+
+ return result.toString();
+ }
+ }
+}