Merge "[Partial Screensharing] Add abstraction to show custom device policy blockers in ChooserActivity" into tm-qpr-dev

This commit is contained in:
Nick Chameyev
2022-11-24 11:30:47 +00:00
committed by Android (Google) Code Review
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.IntDef;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AppGlobals; import android.app.AppGlobals;
import android.app.admin.DevicePolicyEventLogger;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.IPackageManager; import android.content.pm.IPackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import android.os.Trace; import android.os.Trace;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
@@ -60,73 +57,31 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
private final Context mContext; private final Context mContext;
private int mCurrentPage; private int mCurrentPage;
private OnProfileSelectedListener mOnProfileSelectedListener; private OnProfileSelectedListener mOnProfileSelectedListener;
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
private Set<Integer> mLoadedPages; private Set<Integer> mLoadedPages;
private final UserHandle mPersonalProfileUserHandle; private final EmptyStateProvider mEmptyStateProvider;
private final UserHandle mWorkProfileUserHandle; private final UserHandle mWorkProfileUserHandle;
private Injector mInjector; private final QuietModeManager mQuietModeManager;
private boolean mIsWaitingToEnableWorkProfile;
AbstractMultiProfilePagerAdapter(Context context, int currentPage, AbstractMultiProfilePagerAdapter(Context context, int currentPage,
UserHandle personalProfileUserHandle, EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
UserHandle workProfileUserHandle) { UserHandle workProfileUserHandle) {
mContext = Objects.requireNonNull(context); mContext = Objects.requireNonNull(context);
mCurrentPage = currentPage; mCurrentPage = currentPage;
mLoadedPages = new HashSet<>(); mLoadedPages = new HashSet<>();
mPersonalProfileUserHandle = personalProfileUserHandle;
mWorkProfileUserHandle = workProfileUserHandle; mWorkProfileUserHandle = workProfileUserHandle;
UserManager userManager = context.getSystemService(UserManager.class); mEmptyStateProvider = emptyStateProvider;
mInjector = new Injector() { mQuietModeManager = quietModeManager;
@Override
public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
int targetUserId) {
return AbstractMultiProfilePagerAdapter.this
.hasCrossProfileIntents(intents, sourceUserId, targetUserId);
} }
@Override private boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) { return mQuietModeManager.isQuietModeEnabled(workProfileUserHandle);
return userManager.isQuietModeEnabled(workProfileUserHandle);
}
@Override
public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
});
mIsWaitingToEnableWorkProfile = true;
}
};
}
protected void markWorkProfileEnabledBroadcastReceived() {
mIsWaitingToEnableWorkProfile = false;
}
protected boolean isWaitingToEnableWorkProfile() {
return mIsWaitingToEnableWorkProfile;
}
/**
* Overrides the default {@link Injector} for testing purposes.
*/
@VisibleForTesting
public void setInjector(Injector injector) {
mInjector = injector;
}
protected boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return mInjector.isQuietModeEnabled(workProfileUserHandle);
} }
void setOnProfileSelectedListener(OnProfileSelectedListener listener) { void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
mOnProfileSelectedListener = listener; mOnProfileSelectedListener = listener;
} }
void setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener) {
mOnSwitchOnWorkSelectedListener = listener;
}
Context getContext() { Context getContext() {
return mContext; return mContext;
} }
@@ -280,8 +235,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
abstract @Nullable ViewGroup getInactiveAdapterView(); abstract @Nullable ViewGroup getInactiveAdapterView();
abstract String getMetricsCategory();
/** /**
* Rebuilds the tab that is currently visible to the user. * Rebuilds the tab that is currently visible to the user.
* <p>Returns {@code true} if rebuild has completed. * <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) { private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) { if (shouldSkipRebuild(activeListAdapter)) {
activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true); activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
return false; return false;
} }
return activeListAdapter.rebuildList(doPostProcessing); return activeListAdapter.rebuildList(doPostProcessing);
} }
private boolean shouldShowNoCrossProfileIntentsEmptyState( private boolean shouldSkipRebuild(ResolverListAdapter activeListAdapter) {
ResolverListAdapter activeListAdapter) { EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter);
UserHandle listUserHandle = activeListAdapter.getUserHandle(); return emptyState != null && emptyState.shouldSkipDataRebuild();
return UserHandle.myUserId() != listUserHandle.getIdentifier()
&& allowShowNoCrossProfileIntentsEmptyState()
&& !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
UserHandle.myUserId(), listUserHandle.getIdentifier());
} }
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: * The empty state screens are shown according to their priority:
* <ol> * <ol>
@@ -366,103 +296,88 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
* anyway. * anyway.
*/ */
void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) { void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) { final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
if (emptyState == null) {
return; return;
} }
if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
return;
}
maybeShowNoAppsAvailableEmptyState(listAdapter);
}
private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) { emptyState.onEmptyStateShown();
if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
return false; View.OnClickListener clickListener = null;
if (emptyState.getButtonClickListener() != null) {
clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(listAdapter.getUserHandle()));
AbstractMultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView());
});
} }
if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
DevicePolicyEventLogger.createEvent( showEmptyState(listAdapter, emptyState, clickListener);
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);
}
return true;
} }
/** /**
* 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) { public static class MyUserIdProvider {
UserHandle listUserHandle = listAdapter.getUserHandle(); /**
if (!listUserHandle.equals(mWorkProfileUserHandle) * @return user id of the current process
|| !mInjector.isQuietModeEnabled(mWorkProfileUserHandle) */
|| listAdapter.getCount() == 0) { public int getMyUserId() {
return false; return UserHandle.myUserId();
}
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);
} }
} }
protected void showEmptyState(ResolverListAdapter activeListAdapter, String title, /**
String subtitle) { * Utility class to check if there are cross profile intents, it is in a separate class so
showEmptyState(activeListAdapter, title, subtitle, /* buttonOnClick */ null); * it could be mocked in tests
*/
public static class CrossProfileIntentsChecker {
private final ContentResolver mContentResolver;
public CrossProfileIntentsChecker(@NonNull ContentResolver contentResolver) {
mContentResolver = contentResolver;
} }
protected void showEmptyState(ResolverListAdapter activeListAdapter, /**
String title, String subtitle, View.OnClickListener buttonOnClick) { * 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, EmptyState emptyState,
View.OnClickListener buttonOnClick) {
ProfileDescriptor descriptor = getItem( ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle())); userHandleToPageIndex(activeListAdapter.getUserHandle()));
descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE); descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
ViewGroup emptyStateView = descriptor.getEmptyStateView(); ViewGroup emptyStateView = descriptor.getEmptyStateView();
resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView); resetViewVisibilitiesForEmptyState(emptyStateView);
emptyStateView.setVisibility(View.VISIBLE); emptyStateView.setVisibility(View.VISIBLE);
View container = emptyStateView.findViewById(R.id.resolver_empty_state_container); View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
setupContainerPadding(container); setupContainerPadding(container);
TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title); TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title);
String title = emptyState.getTitle();
if (title != null) {
titleView.setVisibility(View.VISIBLE);
titleView.setText(title); titleView.setText(title);
} else {
titleView.setVisibility(View.GONE);
}
TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle); TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
String subtitle = emptyState.getSubtitle();
if (subtitle != null) { if (subtitle != null) {
subtitleView.setVisibility(View.VISIBLE); subtitleView.setVisibility(View.VISIBLE);
subtitleView.setText(subtitle); subtitleView.setText(subtitle);
@@ -470,6 +385,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
subtitleView.setVisibility(View.GONE); 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 button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE); button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
button.setOnClickListener(buttonOnClick); button.setOnClickListener(buttonOnClick);
@@ -483,22 +401,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
*/ */
protected void setupContainerPadding(View container) {} 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) { private void showSpinner(View emptyStateView) {
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE); emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).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); 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_title).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).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); 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); 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) { protected void showListView(ResolverListAdapter activeListAdapter) {
ProfileDescriptor descriptor = getItem( ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle())); userHandleToPageIndex(activeListAdapter.getUserHandle()));
@@ -530,33 +424,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
emptyStateView.setVisibility(View.GONE); 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) { boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
int count = listAdapter.getUnfilteredCount(); int count = listAdapter.getUnfilteredCount();
return (count == 0 && listAdapter.getPlaceholderCount() == 0) return (count == 0 && listAdapter.getPlaceholderCount() == 0)
@@ -599,6 +466,98 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
void onProfilePageStateChanged(int state); 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. * 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. * Describes an injector to be used for cross profile functionality. Overridable for testing.
*/ */
@VisibleForTesting public interface QuietModeManager {
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);
/** /**
* Returns whether the given profile is in quiet mode or not. * 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. * Enables or disables quiet mode for a managed profile.
*/ */
void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle); 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; 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 com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -114,6 +122,9 @@ import android.widget.TextView;
import com.android.internal.R; import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting; 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.ActivityInfoPresentationGetter;
import com.android.internal.app.ResolverListAdapter.ViewHolder; import com.android.internal.app.ResolverListAdapter.ViewHolder;
import com.android.internal.app.chooser.ChooserTargetInfo; import com.android.internal.app.chooser.ChooserTargetInfo;
@@ -830,6 +841,41 @@ public class ChooserActivity extends ResolverActivity implements
return mChooserMultiProfilePagerAdapter; 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( private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
Intent[] initialIntents, Intent[] initialIntents,
List<ResolveInfo> rList, List<ResolveInfo> rList,
@@ -844,9 +890,10 @@ public class ChooserActivity extends ResolverActivity implements
return new ChooserMultiProfilePagerAdapter( return new ChooserMultiProfilePagerAdapter(
/* context */ this, /* context */ this,
adapter, adapter,
getPersonalProfileUserHandle(), createEmptyStateProvider(/* workProfileUserHandle= */ null),
mQuietModeManager,
/* workProfileUserHandle= */ null, /* workProfileUserHandle= */ null,
isSendAction(getTargetIntent()), mMaxTargetsPerRow); mMaxTargetsPerRow);
} }
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles( private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
@@ -872,10 +919,11 @@ public class ChooserActivity extends ResolverActivity implements
/* context */ this, /* context */ this,
personalAdapter, personalAdapter,
workAdapter, workAdapter,
createEmptyStateProvider(/* workProfileUserHandle= */ getWorkProfileUserHandle()),
mQuietModeManager,
selectedProfile, selectedProfile,
getPersonalProfileUserHandle(),
getWorkProfileUserHandle(), getWorkProfileUserHandle(),
isSendAction(getTargetIntent()), mMaxTargetsPerRow); mMaxTargetsPerRow);
} }
private int findSelectedProfile() { private int findSelectedProfile() {

View File

@@ -16,17 +16,7 @@
package com.android.internal.app; 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.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.os.UserHandle; import android.os.UserHandle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -47,37 +37,37 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
private static final int SINGLE_CELL_SPAN_SIZE = 1; private static final int SINGLE_CELL_SPAN_SIZE = 1;
private final ChooserProfileDescriptor[] mItems; private final ChooserProfileDescriptor[] mItems;
private final boolean mIsSendAction;
private int mBottomOffset; private int mBottomOffset;
private int mMaxTargetsPerRow; private int mMaxTargetsPerRow;
ChooserMultiProfilePagerAdapter(Context context, ChooserMultiProfilePagerAdapter(Context context,
ChooserActivity.ChooserGridAdapter adapter, ChooserActivity.ChooserGridAdapter adapter,
UserHandle personalProfileUserHandle, EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
UserHandle workProfileUserHandle, UserHandle workProfileUserHandle,
boolean isSendAction, int maxTargetsPerRow) { int maxTargetsPerRow) {
super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle); super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
workProfileUserHandle);
mItems = new ChooserProfileDescriptor[] { mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(adapter) createProfileDescriptor(adapter)
}; };
mIsSendAction = isSendAction;
mMaxTargetsPerRow = maxTargetsPerRow; mMaxTargetsPerRow = maxTargetsPerRow;
} }
ChooserMultiProfilePagerAdapter(Context context, ChooserMultiProfilePagerAdapter(Context context,
ChooserActivity.ChooserGridAdapter personalAdapter, ChooserActivity.ChooserGridAdapter personalAdapter,
ChooserActivity.ChooserGridAdapter workAdapter, ChooserActivity.ChooserGridAdapter workAdapter,
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
@Profile int defaultProfile, @Profile int defaultProfile,
UserHandle personalProfileUserHandle,
UserHandle workProfileUserHandle, UserHandle workProfileUserHandle,
boolean isSendAction, int maxTargetsPerRow) { int maxTargetsPerRow) {
super(context, /* currentPage */ defaultProfile, personalProfileUserHandle, super(context, /* currentPage */ defaultProfile, emptyStateProvider,
workProfileUserHandle); quietModeManager, workProfileUserHandle);
mItems = new ChooserProfileDescriptor[] { mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(personalAdapter), createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter) createProfileDescriptor(workAdapter)
}; };
mIsSendAction = isSendAction;
mMaxTargetsPerRow = maxTargetsPerRow; mMaxTargetsPerRow = maxTargetsPerRow;
} }
@@ -192,112 +182,6 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
return getListViewForIndex(1 - getCurrentPage()); 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) { void setEmptyStateBottomOffset(int bottomOffset) {
mBottomOffset = 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.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_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; 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;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY; 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; 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.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.PermissionChecker.PID_UNKNOWN; import static android.content.PermissionChecker.PID_UNKNOWN;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -57,6 +62,7 @@ import android.content.res.TypedArray;
import android.graphics.Insets; import android.graphics.Insets;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.PatternMatcher; import android.os.PatternMatcher;
@@ -93,7 +99,14 @@ import android.widget.Toast;
import com.android.internal.R; import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting; 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.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.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo; import com.android.internal.app.chooser.TargetInfo;
@@ -186,6 +199,8 @@ public class ResolverActivity extends Activity implements
@VisibleForTesting @VisibleForTesting
protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter; protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter;
protected QuietModeManager mQuietModeManager;
// Intent extra for connected audio devices // Intent extra for connected audio devices
public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device"; 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; private UserHandle mWorkProfileUserHandle;
@Nullable
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
protected final LatencyTracker mLatencyTracker = getLatencyTracker(); protected final LatencyTracker mLatencyTracker = getLatencyTracker();
private LatencyTracker getLatencyTracker() { private LatencyTracker getLatencyTracker() {
@@ -375,6 +393,8 @@ public class ResolverActivity extends Activity implements
setTheme(appliedThemeResId()); setTheme(appliedThemeResId());
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mQuietModeManager = createQuietModeManager();
// Determine whether we should show that intent is forwarded // Determine whether we should show that intent is forwarded
// from managed profile to owner or other way around. // from managed profile to owner or other way around.
setProfileSwitchMessage(intent.getContentUserHint()); setProfileSwitchMessage(intent.getContentUserHint());
@@ -475,6 +495,111 @@ public class ResolverActivity extends Activity implements
return resolverMultiProfilePagerAdapter; 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( private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
Intent[] initialIntents, Intent[] initialIntents,
List<ResolveInfo> rList, boolean filterLastUsed) { List<ResolveInfo> rList, boolean filterLastUsed) {
@@ -485,10 +610,12 @@ public class ResolverActivity extends Activity implements
rList, rList,
filterLastUsed, filterLastUsed,
/* userHandle */ UserHandle.of(UserHandle.myUserId())); /* userHandle */ UserHandle.of(UserHandle.myUserId()));
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter( return new ResolverMultiProfilePagerAdapter(
/* context */ this, /* context */ this,
adapter, adapter,
getPersonalProfileUserHandle(), createEmptyStateProvider(/* workProfileUserHandle= */ null),
quietModeManager,
/* workProfileUserHandle= */ null); /* workProfileUserHandle= */ null);
} }
@@ -539,14 +666,15 @@ public class ResolverActivity extends Activity implements
(filterLastUsed && UserHandle.myUserId() (filterLastUsed && UserHandle.myUserId()
== workProfileUserHandle.getIdentifier()), == workProfileUserHandle.getIdentifier()),
/* userHandle */ workProfileUserHandle); /* userHandle */ workProfileUserHandle);
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter( return new ResolverMultiProfilePagerAdapter(
/* context */ this, /* context */ this,
personalAdapter, personalAdapter,
workAdapter, workAdapter,
createEmptyStateProvider(getWorkProfileUserHandle()),
quietModeManager,
selectedProfile, selectedProfile,
getPersonalProfileUserHandle(), getWorkProfileUserHandle());
getWorkProfileUserHandle(),
/* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser));
} }
protected int appliedThemeResId() { protected int appliedThemeResId() {
@@ -853,9 +981,9 @@ public class ResolverActivity extends Activity implements
} }
mRegistered = true; mRegistered = true;
} }
if (shouldShowTabs() && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) { if (shouldShowTabs() && mQuietModeManager.isWaitingToEnableWorkProfile()) {
if (mMultiProfilePagerAdapter.isQuietModeEnabled(getWorkProfileUserHandle())) { if (mQuietModeManager.isQuietModeEnabled(getWorkProfileUserHandle())) {
mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived(); mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
} }
} }
mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
@@ -1815,13 +1943,12 @@ public class ResolverActivity extends Activity implements
onHorizontalSwipeStateChanged(state); onHorizontalSwipeStateChanged(state);
} }
}); });
mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener( mOnSwitchOnWorkSelectedListener = () -> {
() -> {
final View workTab = tabHost.getTabWidget().getChildAt(1); final View workTab = tabHost.getTabWidget().getChildAt(1);
workTab.setFocusable(true); workTab.setFocusable(true);
workTab.setFocusableInTouchMode(true); workTab.setFocusableInTouchMode(true);
workTab.requestFocus(); workTab.requestFocus();
}); };
} }
private String getPersonalTabLabel() { private String getPersonalTabLabel() {
@@ -2082,7 +2209,7 @@ public class ResolverActivity extends Activity implements
public void onHandlePackagesChanged(ResolverListAdapter listAdapter) { public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) { if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle()) 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, // 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 // 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 // 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; mWorkProfileHasBeenEnabled = true;
mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived(); mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
} else { } else {
// Must be an UNAVAILABLE broadcast, so we watch for the next availability // Must be an UNAVAILABLE broadcast, so we watch for the next availability
mWorkProfileHasBeenEnabled = false; mWorkProfileHasBeenEnabled = false;
@@ -2150,7 +2277,6 @@ public class ResolverActivity extends Activity implements
}; };
} }
@VisibleForTesting
public static final class ResolvedComponentInfo { public static final class ResolvedComponentInfo {
public final ComponentName name; public final ComponentName name;
private final List<Intent> mIntents = new ArrayList<>(); private final List<Intent> mIntents = new ArrayList<>();

View File

@@ -16,15 +16,7 @@
package com.android.internal.app; 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.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.os.UserHandle; import android.os.UserHandle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -43,34 +35,33 @@ import com.android.internal.widget.PagerAdapter;
public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter { public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter {
private final ResolverProfileDescriptor[] mItems; private final ResolverProfileDescriptor[] mItems;
private final boolean mShouldShowNoCrossProfileIntentsEmptyState;
private boolean mUseLayoutWithDefault; private boolean mUseLayoutWithDefault;
ResolverMultiProfilePagerAdapter(Context context, ResolverMultiProfilePagerAdapter(Context context,
ResolverListAdapter adapter, ResolverListAdapter adapter,
UserHandle personalProfileUserHandle, EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
UserHandle workProfileUserHandle) { UserHandle workProfileUserHandle) {
super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle); super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
workProfileUserHandle);
mItems = new ResolverProfileDescriptor[] { mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(adapter) createProfileDescriptor(adapter)
}; };
mShouldShowNoCrossProfileIntentsEmptyState = true;
} }
ResolverMultiProfilePagerAdapter(Context context, ResolverMultiProfilePagerAdapter(Context context,
ResolverListAdapter personalAdapter, ResolverListAdapter personalAdapter,
ResolverListAdapter workAdapter, ResolverListAdapter workAdapter,
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
@Profile int defaultProfile, @Profile int defaultProfile,
UserHandle personalProfileUserHandle, UserHandle workProfileUserHandle) {
UserHandle workProfileUserHandle, super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
boolean shouldShowNoCrossProfileIntentsEmptyState) {
super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
workProfileUserHandle); workProfileUserHandle);
mItems = new ResolverProfileDescriptor[] { mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(personalAdapter), createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter) createProfileDescriptor(workAdapter)
}; };
mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState;
} }
private ResolverProfileDescriptor createProfileDescriptor( private ResolverProfileDescriptor createProfileDescriptor(
@@ -170,93 +161,6 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
return getListViewForIndex(1 - getCurrentPage()); 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) { void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
mUseLayoutWithDefault = 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; 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.mock;
import static org.mockito.Mockito.when;
import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
@@ -26,10 +28,12 @@ import android.graphics.Bitmap;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.Pair; 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.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger;
import java.util.List;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
@@ -71,7 +75,10 @@ public class ChooserActivityOverrideData {
public boolean isQuietModeEnabled; public boolean isQuietModeEnabled;
public boolean isWorkProfileUserRunning; public boolean isWorkProfileUserRunning;
public boolean isWorkProfileUserUnlocked; public boolean isWorkProfileUserUnlocked;
public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector; public Integer myUserId;
public QuietModeManager mQuietModeManager;
public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager; public PackageManager packageManager;
public void reset() { public void reset() {
@@ -95,14 +102,9 @@ public class ChooserActivityOverrideData {
isQuietModeEnabled = false; isQuietModeEnabled = false;
isWorkProfileUserRunning = true; isWorkProfileUserRunning = true;
isWorkProfileUserUnlocked = true; isWorkProfileUserUnlocked = true;
myUserId = null;
packageManager = null; packageManager = null;
multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() { mQuietModeManager = new QuietModeManager() {
@Override
public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
int targetUserId) {
return hasCrossProfileIntents;
}
@Override @Override
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) { public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return isQuietModeEnabled; return isQuietModeEnabled;
@@ -113,7 +115,27 @@ public class ChooserActivityOverrideData {
UserHandle workProfileUserHandle) { UserHandle workProfileUserHandle) {
isQuietModeEnabled = enabled; 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() {} 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; 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 static org.mockito.Mockito.when;
import android.annotation.Nullable; import android.annotation.Nullable;
@@ -34,6 +38,8 @@ import android.os.UserHandle;
import android.util.Pair; import android.util.Pair;
import android.util.Size; 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.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo; import com.android.internal.app.chooser.TargetInfo;
@@ -59,15 +65,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
return 1234; 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 @Override
public ChooserListAdapter createChooserListAdapter(Context context, List<Intent> payloadIntents, public ChooserListAdapter createChooserListAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
@@ -134,6 +131,30 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
return super.isVoiceInteraction(); 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 @Override
public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) { public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
if (sOverrides.onSafelyStartCallback != null && 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; 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.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -27,6 +29,9 @@ import android.content.pm.ResolveInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; 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 com.android.internal.app.chooser.TargetInfo;
import java.util.List; import java.util.List;
@@ -52,12 +57,27 @@ public class ResolverWrapperActivity extends ResolverActivity {
} }
@Override @Override
protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( protected MyUserIdProvider createMyUserIdProvider() {
Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) { if (sOverrides.mMyUserIdProvider != null) {
AbstractMultiProfilePagerAdapter multiProfilePagerAdapter = return sOverrides.mMyUserIdProvider;
super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed); }
multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector); return super.createMyUserIdProvider();
return multiProfilePagerAdapter; }
@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() { ResolverWrapperAdapter getAdapter() {
@@ -137,9 +157,12 @@ public class ResolverWrapperActivity extends ResolverActivity {
public ResolverListController workResolverListController; public ResolverListController workResolverListController;
public Boolean isVoiceInteraction; public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle; public UserHandle workProfileUserHandle;
public Integer myUserId;
public boolean hasCrossProfileIntents; public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled; public boolean isQuietModeEnabled;
public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector; public QuietModeManager mQuietModeManager;
public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public void reset() { public void reset() {
onSafelyStartCallback = null; onSafelyStartCallback = null;
@@ -148,15 +171,11 @@ public class ResolverWrapperActivity extends ResolverActivity {
resolverListController = mock(ResolverListController.class); resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class); workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null; workProfileUserHandle = null;
myUserId = null;
hasCrossProfileIntents = true; hasCrossProfileIntents = true;
isQuietModeEnabled = false; isQuietModeEnabled = false;
multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
@Override
public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
int targetUserId) {
return hasCrossProfileIntents;
}
mQuietModeManager = new QuietModeManager() {
@Override @Override
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) { public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return isQuietModeEnabled; return isQuietModeEnabled;
@@ -167,7 +186,27 @@ public class ResolverWrapperActivity extends ResolverActivity {
UserHandle workProfileUserHandle) { UserHandle workProfileUserHandle) {
isQuietModeEnabled = enabled; 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);
} }
} }
} }