[Partial Screensharing] Add abstraction to show custom device policy blockers in ChooserActivity

Adds an interface that controls the behavior of
the blocked empty state of personal/work profile
tabs in the ChooserActivity/ResolverActivity.
This state is displayed when the device policy
doesn't allow sharing between apps.
The interface allows to customize in which cases
we block the sharing, what text we display there
and send custom analytics events.

This CL should not change any behaviour.

Default behaviour is to not allow cross profile
sharing which could be overriden by using
different implementation of the ProfileBlockerEmptyStateProvider.
E.g. in partial screensharing app selector we could
decide whether we want to block the tab based
on the screen capturing device policies.

Bug: 233348916
Test: atest com.android.internal.app.ChooserActivityTest
Test: atest com.android.internal.app.ResolverActivityTest
Test: atest com.android.internal.app.ChooserActivityWorkProfileTest
Test: atest com.android.internal.app.ResolverActivityWorkProfileTest
Change-Id: I6b46c9424644016d7e2f472a95b0b5583c58c2cb
This commit is contained in:
Nick Chameyev
2022-10-07 16:07:26 +00:00
parent 68888b39a6
commit a722553826
13 changed files with 1790 additions and 500 deletions

View File

@@ -17,18 +17,15 @@ package com.android.internal.app;
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,31 @@ 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;
}
};
mEmptyStateProvider = emptyStateProvider;
mQuietModeManager = quietModeManager;
}
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;
}
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 +235,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 +270,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 +296,88 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
* anyway.
*/
void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) {
return;
}
if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
return;
}
maybeShowNoAppsAvailableEmptyState(listAdapter);
}
final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) {
if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
return false;
if (emptyState == null) {
return;
}
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);
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());
});
}
return true;
showEmptyState(listAdapter, emptyState, clickListener);
}
/**
* Returns {@code true} if the work profile off empty state screen is shown.
* Class to get user id of the current process
*/
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 MyUserIdProvider {
/**
* @return user id of the current process
*/
public int getMyUserId() {
return UserHandle.myUserId();
}
}
protected void showEmptyState(ResolverListAdapter activeListAdapter, String title,
String subtitle) {
showEmptyState(activeListAdapter, title, subtitle, /* buttonOnClick */ null);
/**
* Utility class to check if there are cross profile intents, it is in a separate class so
* it could be mocked in tests
*/
public static class CrossProfileIntentsChecker {
private final ContentResolver mContentResolver;
public CrossProfileIntentsChecker(@NonNull ContentResolver contentResolver) {
mContentResolver = contentResolver;
}
/**
* 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(R.id.resolver_list).setVisibility(View.GONE);
ViewGroup emptyStateView = descriptor.getEmptyStateView();
resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
resetViewVisibilitiesForEmptyState(emptyStateView);
emptyStateView.setVisibility(View.VISIBLE);
View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
setupContainerPadding(container);
TextView titleView = emptyStateView.findViewById(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(R.id.resolver_empty_state_subtitle);
String subtitle = emptyState.getSubtitle();
if (subtitle != null) {
subtitleView.setVisibility(View.VISIBLE);
subtitleView.setText(subtitle);
@@ -470,6 +385,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
subtitleView.setVisibility(View.GONE);
}
View defaultEmptyText = emptyStateView.findViewById(R.id.empty);
defaultEmptyText.setVisibility(emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
button.setOnClickListener(buttonOnClick);
@@ -483,22 +401,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(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(R.id.resolver_empty_state_progress).getVisibility()
== View.VISIBLE;
}
private void showSpinner(View emptyStateView) {
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
@@ -506,7 +408,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
}
private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
private void resetViewVisibilitiesForEmptyState(View emptyStateView) {
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
@@ -514,14 +416,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
}
private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.GONE);
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.GONE);
emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
emptyStateView.findViewById(R.id.empty).setVisibility(View.VISIBLE);
}
protected void showListView(ResolverListAdapter activeListAdapter) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
@@ -530,33 +424,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)
@@ -599,6 +466,98 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
void onProfilePageStateChanged(int state);
}
/**
* 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.
*/
@@ -612,14 +571,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 +581,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();
}
}

View File

@@ -16,6 +16,14 @@
package com.android.internal.app;
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.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -114,6 +122,9 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
import com.android.internal.app.ResolverListAdapter.ViewHolder;
import com.android.internal.app.chooser.ChooserTargetInfo;
@@ -830,6 +841,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,
@@ -844,9 +890,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(
@@ -872,10 +919,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() {

View File

@@ -16,17 +16,7 @@
package com.android.internal.app;
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;
}

View File

@@ -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.internal.app;
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.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
import com.android.internal.app.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();
}
}
}

View File

@@ -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.internal.app;
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.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
import com.android.internal.app.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;
}
}
}

View File

@@ -19,6 +19,9 @@ package com.android.internal.app;
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;
@@ -57,6 +62,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,14 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CompositeEmptyStateProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.Profile;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
@@ -186,6 +199,8 @@ public class ResolverActivity extends Activity 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";
@@ -217,6 +232,9 @@ public class ResolverActivity extends Activity implements
private UserHandle mWorkProfileUserHandle;
@Nullable
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
private LatencyTracker getLatencyTracker() {
@@ -375,6 +393,8 @@ public class ResolverActivity extends Activity 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());
@@ -475,6 +495,111 @@ public class ResolverActivity extends Activity 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) {
@@ -485,10 +610,12 @@ public class ResolverActivity extends Activity implements
rList,
filterLastUsed,
/* userHandle */ UserHandle.of(UserHandle.myUserId()));
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
adapter,
getPersonalProfileUserHandle(),
createEmptyStateProvider(/* workProfileUserHandle= */ null),
quietModeManager,
/* workProfileUserHandle= */ null);
}
@@ -539,14 +666,15 @@ public class ResolverActivity extends Activity 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() {
@@ -853,9 +981,9 @@ public class ResolverActivity extends Activity 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();
@@ -1815,13 +1943,12 @@ public class ResolverActivity extends Activity 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() {
@@ -2082,7 +2209,7 @@ public class ResolverActivity extends Activity 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
@@ -2134,7 +2261,7 @@ public class ResolverActivity extends Activity implements
}
mWorkProfileHasBeenEnabled = true;
mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
} else {
// Must be an UNAVAILABLE broadcast, so we watch for the next availability
mWorkProfileHasBeenEnabled = false;
@@ -2150,7 +2277,6 @@ public class ResolverActivity extends Activity implements
};
}
@VisibleForTesting
public static final class ResolvedComponentInfo {
public final ComponentName name;
private final List<Intent> mIntents = new ArrayList<>();

View File

@@ -16,15 +16,7 @@
package com.android.internal.app;
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.widget.PagerAdapter;
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;
}

View File

@@ -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.internal.app;
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.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
import com.android.internal.app.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();
}
}
}

View File

@@ -16,9 +16,11 @@
package com.android.internal.app;
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;
@@ -26,10 +28,12 @@ import android.graphics.Bitmap;
import android.os.UserHandle;
import android.util.Pair;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -71,7 +75,10 @@ public class ChooserActivityOverrideData {
public boolean isQuietModeEnabled;
public boolean isWorkProfileUserRunning;
public boolean isWorkProfileUserUnlocked;
public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
public Integer myUserId;
public QuietModeManager mQuietModeManager;
public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager;
public void reset() {
@@ -95,14 +102,9 @@ public class ChooserActivityOverrideData {
isQuietModeEnabled = false;
isWorkProfileUserRunning = true;
isWorkProfileUserUnlocked = true;
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;
@@ -113,7 +115,27 @@ public class ChooserActivityOverrideData {
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);
}
private ChooserActivityOverrideData() {}

View File

@@ -0,0 +1,450 @@
/*
* 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.internal.app;
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.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab.WORK;
import static com.android.internal.app.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.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.ChooserActivityWorkProfileTest.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 ChooserActivityWorkProfileTest {
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 ChooserActivityWorkProfileTest(TestCase testCase) {
mTestCase = testCase;
}
@Before
public void cleanOverrideData() {
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() {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
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();
}
}
}

View File

@@ -16,6 +16,10 @@
package com.android.internal.app;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
@@ -34,6 +38,8 @@ import android.os.UserHandle;
import android.util.Pair;
import android.util.Size;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
@@ -59,15 +65,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
return 1234;
}
@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,
@@ -134,6 +131,30 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
return super.isVoiceInteraction();
}
@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 AbstractMultiProfilePagerAdapter.QuietModeManager createQuietModeManager() {
if (sOverrides.mQuietModeManager != null) {
return sOverrides.mQuietModeManager;
}
return super.createQuietModeManager();
}
@Override
public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
if (sOverrides.onSafelyStartCallback != null &&

View File

@@ -0,0 +1,429 @@
/*
* 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.internal.app;
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.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_BLOCKER;
import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_BLOCKER;
import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab.PERSONAL;
import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab.WORK;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
import static org.hamcrest.CoreMatchers.not;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
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.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.ResolverActivityWorkProfileTest.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 ResolverActivityWorkProfileTest {
private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
.getInstrumentation().getTargetContext().getUser();
private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
@Rule
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false,
false);
private final TestCase mTestCase;
public ResolverActivityWorkProfileTest(TestCase testCase) {
mTestCase = testCase;
}
@Before
public void cleanOverrideData() {
sOverrides.reset();
}
@Test
public void testBlocker() {
setUpPersonalAndWorkComponentInfos();
sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
launchActivity(/* callingUser= */ mTestCase.getExtraCallingUser());
switchToTab(mTestCase.getTab());
switch (mTestCase.getExpectedBlocker()) {
case NO_BLOCKER:
assertNoBlockerDisplayed();
break;
case PERSONAL_PROFILE_BLOCKER:
assertCantAccessPersonalAppsBlockerDisplayed();
break;
case WORK_PROFILE_BLOCKER:
assertCantAccessWorkAppsBlockerDisplayed();
break;
}
}
@Parameterized.Parameters(name = "{0}")
public static Collection tests() {
return Arrays.asList(
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ false,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ PERSONAL_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ false,
/* myUserHandle= */ PERSONAL_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ WORK_PROFILE_BLOCKER
),
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ PERSONAL,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ false,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ PERSONAL,
/* expectedBlocker= */ PERSONAL_PROFILE_BLOCKER
),
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ PERSONAL_USER_HANDLE,
/* tab= */ PERSONAL,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ null,
/* hasCrossProfileIntents= */ false,
/* myUserHandle= */ PERSONAL_USER_HANDLE,
/* tab= */ PERSONAL,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* hasCrossProfileIntents= */ false,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ PERSONAL_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* hasCrossProfileIntents= */ false,
/* myUserHandle= */ PERSONAL_USER_HANDLE,
/* tab= */ WORK,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ PERSONAL,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* hasCrossProfileIntents= */ false,
/* myUserHandle= */ WORK_USER_HANDLE,
/* tab= */ PERSONAL,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* hasCrossProfileIntents= */ true,
/* myUserHandle= */ PERSONAL_USER_HANDLE,
/* tab= */ PERSONAL,
/* expectedBlocker= */ NO_BLOCKER
),
new TestCase(
/* extraCallingUser= */ WORK_USER_HANDLE,
/* 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() {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
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() {
ResolverWrapperActivity.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 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 createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
sendIntent.setType("image/jpeg");
return sendIntent;
}
private void launchActivity(UserHandle callingUser) {
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
if (callingUser != null) {
sendIntent.putExtra(ResolverActivity.EXTRA_CALLING_USER, callingUser);
}
mActivityRule.launchActivity(sendIntent);
waitForIdle();
}
public static class TestCase {
@Nullable
private final UserHandle mExtraCallingUser;
private final boolean mHasCrossProfileIntents;
private final UserHandle mMyUserHandle;
private final Tab mTab;
private final ExpectedBlocker mExpectedBlocker;
public enum ExpectedBlocker {
NO_BLOCKER,
PERSONAL_PROFILE_BLOCKER,
WORK_PROFILE_BLOCKER
}
public enum Tab {
WORK,
PERSONAL
}
public TestCase(@Nullable UserHandle extraCallingUser, boolean hasCrossProfileIntents,
UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
mExtraCallingUser = extraCallingUser;
mHasCrossProfileIntents = hasCrossProfileIntents;
mMyUserHandle = myUserHandle;
mTab = tab;
mExpectedBlocker = expectedBlocker;
}
@Nullable
public UserHandle getExtraCallingUser() {
return mExtraCallingUser;
}
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 (mExtraCallingUser != null
&& !mExtraCallingUser.equals(PERSONAL_USER_HANDLE)) {
result.append("callingUserIsNonPersonal_");
} else {
result.append("callingUserIsPersonal_");
}
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 == ExpectedBlocker.PERSONAL_PROFILE_BLOCKER) {
result.append("thenBlockerOnPersonalProfile");
} else if (mExpectedBlocker == WORK_PROFILE_BLOCKER) {
result.append("thenBlockerOnWorkProfile");
}
return result.toString();
}
}
}

View File

@@ -16,6 +16,8 @@
package com.android.internal.app;
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.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
import java.util.List;
@@ -52,12 +57,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() {
@@ -137,9 +157,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;
@@ -148,15 +171,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;
@@ -167,7 +186,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);
}
}
}