Merge "[Partial Screensharing] Add abstraction to show custom device policy blockers in ChooserActivity" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
814ef64f49
@@ -17,18 +17,15 @@ package com.android.internal.app;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.admin.DevicePolicyEventLogger;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Trace;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.stats.devicepolicy.DevicePolicyEnums;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
@@ -60,73 +57,31 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
private final Context mContext;
|
||||
private int mCurrentPage;
|
||||
private OnProfileSelectedListener mOnProfileSelectedListener;
|
||||
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
|
||||
private Set<Integer> mLoadedPages;
|
||||
private final UserHandle mPersonalProfileUserHandle;
|
||||
private final EmptyStateProvider mEmptyStateProvider;
|
||||
private final UserHandle mWorkProfileUserHandle;
|
||||
private Injector mInjector;
|
||||
private boolean mIsWaitingToEnableWorkProfile;
|
||||
private final QuietModeManager mQuietModeManager;
|
||||
|
||||
AbstractMultiProfilePagerAdapter(Context context, int currentPage,
|
||||
UserHandle personalProfileUserHandle,
|
||||
EmptyStateProvider emptyStateProvider,
|
||||
QuietModeManager quietModeManager,
|
||||
UserHandle workProfileUserHandle) {
|
||||
mContext = Objects.requireNonNull(context);
|
||||
mCurrentPage = currentPage;
|
||||
mLoadedPages = new HashSet<>();
|
||||
mPersonalProfileUserHandle = personalProfileUserHandle;
|
||||
mWorkProfileUserHandle = workProfileUserHandle;
|
||||
UserManager userManager = context.getSystemService(UserManager.class);
|
||||
mInjector = new Injector() {
|
||||
@Override
|
||||
public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
|
||||
int targetUserId) {
|
||||
return AbstractMultiProfilePagerAdapter.this
|
||||
.hasCrossProfileIntents(intents, sourceUserId, targetUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
|
||||
return userManager.isQuietModeEnabled(workProfileUserHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
|
||||
});
|
||||
mIsWaitingToEnableWorkProfile = true;
|
||||
}
|
||||
};
|
||||
mEmptyStateProvider = emptyStateProvider;
|
||||
mQuietModeManager = quietModeManager;
|
||||
}
|
||||
|
||||
protected void markWorkProfileEnabledBroadcastReceived() {
|
||||
mIsWaitingToEnableWorkProfile = false;
|
||||
}
|
||||
|
||||
protected boolean isWaitingToEnableWorkProfile() {
|
||||
return mIsWaitingToEnableWorkProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default {@link Injector} for testing purposes.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void setInjector(Injector injector) {
|
||||
mInjector = injector;
|
||||
}
|
||||
|
||||
protected boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
|
||||
return mInjector.isQuietModeEnabled(workProfileUserHandle);
|
||||
private boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
|
||||
return mQuietModeManager.isQuietModeEnabled(workProfileUserHandle);
|
||||
}
|
||||
|
||||
void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
|
||||
mOnProfileSelectedListener = listener;
|
||||
}
|
||||
|
||||
void setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener) {
|
||||
mOnSwitchOnWorkSelectedListener = listener;
|
||||
}
|
||||
|
||||
Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
@@ -280,8 +235,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
|
||||
abstract @Nullable ViewGroup getInactiveAdapterView();
|
||||
|
||||
abstract String getMetricsCategory();
|
||||
|
||||
/**
|
||||
* Rebuilds the tab that is currently visible to the user.
|
||||
* <p>Returns {@code true} if rebuild has completed.
|
||||
@@ -317,41 +270,18 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
}
|
||||
|
||||
private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
|
||||
if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) {
|
||||
if (shouldSkipRebuild(activeListAdapter)) {
|
||||
activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
|
||||
return false;
|
||||
}
|
||||
return activeListAdapter.rebuildList(doPostProcessing);
|
||||
}
|
||||
|
||||
private boolean shouldShowNoCrossProfileIntentsEmptyState(
|
||||
ResolverListAdapter activeListAdapter) {
|
||||
UserHandle listUserHandle = activeListAdapter.getUserHandle();
|
||||
return UserHandle.myUserId() != listUserHandle.getIdentifier()
|
||||
&& allowShowNoCrossProfileIntentsEmptyState()
|
||||
&& !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
|
||||
UserHandle.myUserId(), listUserHandle.getIdentifier());
|
||||
private boolean shouldSkipRebuild(ResolverListAdapter activeListAdapter) {
|
||||
EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter);
|
||||
return emptyState != null && emptyState.shouldSkipDataRebuild();
|
||||
}
|
||||
|
||||
boolean allowShowNoCrossProfileIntentsEmptyState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract void showWorkProfileOffEmptyState(
|
||||
ResolverListAdapter activeListAdapter, View.OnClickListener listener);
|
||||
|
||||
protected abstract void showNoPersonalToWorkIntentsEmptyState(
|
||||
ResolverListAdapter activeListAdapter);
|
||||
|
||||
protected abstract void showNoPersonalAppsAvailableEmptyState(
|
||||
ResolverListAdapter activeListAdapter);
|
||||
|
||||
protected abstract void showNoWorkAppsAvailableEmptyState(
|
||||
ResolverListAdapter activeListAdapter);
|
||||
|
||||
protected abstract void showNoWorkToPersonalIntentsEmptyState(
|
||||
ResolverListAdapter activeListAdapter);
|
||||
|
||||
/**
|
||||
* The empty state screens are shown according to their priority:
|
||||
* <ol>
|
||||
@@ -366,103 +296,88 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
* anyway.
|
||||
*/
|
||||
void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
|
||||
if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) {
|
||||
return;
|
||||
}
|
||||
if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
|
||||
return;
|
||||
}
|
||||
maybeShowNoAppsAvailableEmptyState(listAdapter);
|
||||
}
|
||||
final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
|
||||
|
||||
private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) {
|
||||
if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
|
||||
return false;
|
||||
if (emptyState == null) {
|
||||
return;
|
||||
}
|
||||
if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
|
||||
DevicePolicyEventLogger.createEvent(
|
||||
DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL)
|
||||
.setStrings(getMetricsCategory())
|
||||
.write();
|
||||
showNoWorkToPersonalIntentsEmptyState(listAdapter);
|
||||
} else {
|
||||
DevicePolicyEventLogger.createEvent(
|
||||
DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK)
|
||||
.setStrings(getMetricsCategory())
|
||||
.write();
|
||||
showNoPersonalToWorkIntentsEmptyState(listAdapter);
|
||||
|
||||
emptyState.onEmptyStateShown();
|
||||
|
||||
View.OnClickListener clickListener = null;
|
||||
|
||||
if (emptyState.getButtonClickListener() != null) {
|
||||
clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
|
||||
ProfileDescriptor descriptor = getItem(
|
||||
userHandleToPageIndex(listAdapter.getUserHandle()));
|
||||
AbstractMultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView());
|
||||
});
|
||||
}
|
||||
return true;
|
||||
|
||||
showEmptyState(listAdapter, emptyState, clickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the work profile off empty state screen is shown.
|
||||
* Class to get user id of the current process
|
||||
*/
|
||||
private boolean maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter) {
|
||||
UserHandle listUserHandle = listAdapter.getUserHandle();
|
||||
if (!listUserHandle.equals(mWorkProfileUserHandle)
|
||||
|| !mInjector.isQuietModeEnabled(mWorkProfileUserHandle)
|
||||
|| listAdapter.getCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
DevicePolicyEventLogger
|
||||
.createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
|
||||
.setStrings(getMetricsCategory())
|
||||
.write();
|
||||
showWorkProfileOffEmptyState(listAdapter,
|
||||
v -> {
|
||||
ProfileDescriptor descriptor = getItem(
|
||||
userHandleToPageIndex(listAdapter.getUserHandle()));
|
||||
showSpinner(descriptor.getEmptyStateView());
|
||||
if (mOnSwitchOnWorkSelectedListener != null) {
|
||||
mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
|
||||
}
|
||||
mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
|
||||
UserHandle listUserHandle = listAdapter.getUserHandle();
|
||||
if (mWorkProfileUserHandle != null
|
||||
&& (UserHandle.myUserId() == listUserHandle.getIdentifier()
|
||||
|| !hasAppsInOtherProfile(listAdapter))) {
|
||||
DevicePolicyEventLogger.createEvent(
|
||||
DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
|
||||
.setStrings(getMetricsCategory())
|
||||
.setBoolean(/*isPersonalProfile*/ listUserHandle == mPersonalProfileUserHandle)
|
||||
.write();
|
||||
if (listUserHandle == mPersonalProfileUserHandle) {
|
||||
showNoPersonalAppsAvailableEmptyState(listAdapter);
|
||||
} else {
|
||||
showNoWorkAppsAvailableEmptyState(listAdapter);
|
||||
}
|
||||
} else if (mWorkProfileUserHandle == null) {
|
||||
showConsumerUserNoAppsAvailableEmptyState(listAdapter);
|
||||
public static class MyUserIdProvider {
|
||||
/**
|
||||
* @return user id of the current process
|
||||
*/
|
||||
public int getMyUserId() {
|
||||
return UserHandle.myUserId();
|
||||
}
|
||||
}
|
||||
|
||||
protected void showEmptyState(ResolverListAdapter activeListAdapter, String title,
|
||||
String subtitle) {
|
||||
showEmptyState(activeListAdapter, title, subtitle, /* buttonOnClick */ null);
|
||||
/**
|
||||
* Utility class to check if there are cross profile intents, it is in a separate class so
|
||||
* it could be mocked in tests
|
||||
*/
|
||||
public static class CrossProfileIntentsChecker {
|
||||
|
||||
private final ContentResolver mContentResolver;
|
||||
|
||||
public CrossProfileIntentsChecker(@NonNull ContentResolver contentResolver) {
|
||||
mContentResolver = contentResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if at least one of the provided {@code intents} can be forwarded
|
||||
* from {@code source} (user id) to {@code target} (user id).
|
||||
*/
|
||||
public boolean hasCrossProfileIntents(List<Intent> intents, @UserIdInt int source,
|
||||
@UserIdInt int target) {
|
||||
IPackageManager packageManager = AppGlobals.getPackageManager();
|
||||
|
||||
return intents.stream().anyMatch(intent ->
|
||||
null != IntentForwarderActivity.canForward(intent, source, target,
|
||||
packageManager, mContentResolver));
|
||||
}
|
||||
}
|
||||
|
||||
protected void showEmptyState(ResolverListAdapter activeListAdapter,
|
||||
String title, String subtitle, View.OnClickListener buttonOnClick) {
|
||||
protected void showEmptyState(ResolverListAdapter activeListAdapter, EmptyState emptyState,
|
||||
View.OnClickListener buttonOnClick) {
|
||||
ProfileDescriptor descriptor = getItem(
|
||||
userHandleToPageIndex(activeListAdapter.getUserHandle()));
|
||||
descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
|
||||
ViewGroup emptyStateView = descriptor.getEmptyStateView();
|
||||
resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
|
||||
resetViewVisibilitiesForEmptyState(emptyStateView);
|
||||
emptyStateView.setVisibility(View.VISIBLE);
|
||||
|
||||
View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
|
||||
setupContainerPadding(container);
|
||||
|
||||
TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title);
|
||||
titleView.setText(title);
|
||||
String title = emptyState.getTitle();
|
||||
if (title != null) {
|
||||
titleView.setVisibility(View.VISIBLE);
|
||||
titleView.setText(title);
|
||||
} else {
|
||||
titleView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
|
||||
String subtitle = emptyState.getSubtitle();
|
||||
if (subtitle != null) {
|
||||
subtitleView.setVisibility(View.VISIBLE);
|
||||
subtitleView.setText(subtitle);
|
||||
@@ -470,6 +385,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
subtitleView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
View defaultEmptyText = emptyStateView.findViewById(R.id.empty);
|
||||
defaultEmptyText.setVisibility(emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
|
||||
|
||||
Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
|
||||
button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
|
||||
button.setOnClickListener(buttonOnClick);
|
||||
@@ -483,22 +401,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
*/
|
||||
protected void setupContainerPadding(View container) {}
|
||||
|
||||
private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
|
||||
ProfileDescriptor descriptor = getItem(
|
||||
userHandleToPageIndex(activeListAdapter.getUserHandle()));
|
||||
descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
|
||||
View emptyStateView = descriptor.getEmptyStateView();
|
||||
resetViewVisibilitiesForConsumerUserEmptyState(emptyStateView);
|
||||
emptyStateView.setVisibility(View.VISIBLE);
|
||||
|
||||
activeListAdapter.markTabLoaded();
|
||||
}
|
||||
|
||||
private boolean isSpinnerShowing(View emptyStateView) {
|
||||
return emptyStateView.findViewById(R.id.resolver_empty_state_progress).getVisibility()
|
||||
== View.VISIBLE;
|
||||
}
|
||||
|
||||
private void showSpinner(View emptyStateView) {
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
|
||||
@@ -506,7 +408,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
|
||||
private void resetViewVisibilitiesForEmptyState(View emptyStateView) {
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
|
||||
@@ -514,14 +416,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.GONE);
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.GONE);
|
||||
emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
|
||||
emptyStateView.findViewById(R.id.empty).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
protected void showListView(ResolverListAdapter activeListAdapter) {
|
||||
ProfileDescriptor descriptor = getItem(
|
||||
userHandleToPageIndex(activeListAdapter.getUserHandle()));
|
||||
@@ -530,33 +424,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
emptyStateView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private boolean hasCrossProfileIntents(List<Intent> intents, int source, int target) {
|
||||
IPackageManager packageManager = AppGlobals.getPackageManager();
|
||||
ContentResolver contentResolver = mContext.getContentResolver();
|
||||
for (Intent intent : intents) {
|
||||
if (IntentForwarderActivity.canForward(intent, source, target, packageManager,
|
||||
contentResolver) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
|
||||
if (mWorkProfileUserHandle == null) {
|
||||
return false;
|
||||
}
|
||||
List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
|
||||
adapter.getResolversForUser(UserHandle.of(UserHandle.myUserId()));
|
||||
for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
|
||||
ResolveInfo resolveInfo = info.getResolveInfoAt(0);
|
||||
if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
|
||||
int count = listAdapter.getUnfilteredCount();
|
||||
return (count == 0 && listAdapter.getPlaceholderCount() == 0)
|
||||
@@ -599,6 +466,98 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
void onProfilePageStateChanged(int state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty state to show for the current profile page (tab) if necessary.
|
||||
* This could be used e.g. to show a blocker on a tab if device management policy doesn't
|
||||
* allow to use it or there are no apps available.
|
||||
*/
|
||||
public interface EmptyStateProvider {
|
||||
/**
|
||||
* When a non-null empty state is returned the corresponding profile page will show
|
||||
* this empty state
|
||||
* @param resolverListAdapter the current adapter
|
||||
*/
|
||||
@Nullable
|
||||
default EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty state provider that combines multiple providers. Providers earlier in the list have
|
||||
* priority, that is if there is a provider that returns non-null empty state then all further
|
||||
* providers will be ignored.
|
||||
*/
|
||||
public static class CompositeEmptyStateProvider implements EmptyStateProvider {
|
||||
|
||||
private final EmptyStateProvider[] mProviders;
|
||||
|
||||
public CompositeEmptyStateProvider(EmptyStateProvider... providers) {
|
||||
mProviders = providers;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
|
||||
for (EmptyStateProvider provider : mProviders) {
|
||||
EmptyState emptyState = provider.getEmptyState(resolverListAdapter);
|
||||
if (emptyState != null) {
|
||||
return emptyState;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes how the blocked empty state should look like for a profile tab
|
||||
*/
|
||||
public interface EmptyState {
|
||||
/**
|
||||
* Title that will be shown on the empty state
|
||||
*/
|
||||
@Nullable
|
||||
default String getTitle() { return null; }
|
||||
|
||||
/**
|
||||
* Subtitle that will be shown underneath the title on the empty state
|
||||
*/
|
||||
@Nullable
|
||||
default String getSubtitle() { return null; }
|
||||
|
||||
/**
|
||||
* If non-null then a button will be shown and this listener will be called
|
||||
* when the button is clicked
|
||||
*/
|
||||
@Nullable
|
||||
default ClickListener getButtonClickListener() { return null; }
|
||||
|
||||
/**
|
||||
* If true then default text ('No apps can perform this action') and style for the empty
|
||||
* state will be applied, title and subtitle will be ignored.
|
||||
*/
|
||||
default boolean useDefaultEmptyView() { return false; }
|
||||
|
||||
/**
|
||||
* Returns true if for this empty state we should skip rebuilding of the apps list
|
||||
* for this tab.
|
||||
*/
|
||||
default boolean shouldSkipDataRebuild() { return false; }
|
||||
|
||||
/**
|
||||
* Called when empty state is shown, could be used e.g. to track analytics events
|
||||
*/
|
||||
default void onEmptyStateShown() {}
|
||||
|
||||
interface ClickListener {
|
||||
void onClick(TabControl currentTab);
|
||||
}
|
||||
|
||||
interface TabControl {
|
||||
void showSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for when the user switches on the work profile from the work tab.
|
||||
*/
|
||||
@@ -612,14 +571,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
/**
|
||||
* Describes an injector to be used for cross profile functionality. Overridable for testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public interface Injector {
|
||||
/**
|
||||
* Returns {@code true} if at least one of the provided {@code intents} can be forwarded
|
||||
* from {@code sourceUserId} to {@code targetUserId}.
|
||||
*/
|
||||
boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
|
||||
|
||||
public interface QuietModeManager {
|
||||
/**
|
||||
* Returns whether the given profile is in quiet mode or not.
|
||||
*/
|
||||
@@ -629,5 +581,15 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
|
||||
* Enables or disables quiet mode for a managed profile.
|
||||
*/
|
||||
void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
|
||||
|
||||
/**
|
||||
* Should be called when the work profile enabled broadcast received
|
||||
*/
|
||||
void markWorkProfileEnabledBroadcastReceived();
|
||||
|
||||
/**
|
||||
* Returns true if enabling of work profile is in progress
|
||||
*/
|
||||
boolean isWaitingToEnableWorkProfile();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,14 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
|
||||
import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
|
||||
import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
|
||||
|
||||
import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
@@ -114,6 +122,9 @@ import android.widget.TextView;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
|
||||
import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
|
||||
import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
|
||||
import com.android.internal.app.ResolverListAdapter.ViewHolder;
|
||||
import com.android.internal.app.chooser.ChooserTargetInfo;
|
||||
@@ -830,6 +841,41 @@ public class ChooserActivity extends ResolverActivity implements
|
||||
return mChooserMultiProfilePagerAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EmptyStateProvider createBlockerEmptyStateProvider() {
|
||||
final boolean isSendAction = isSendAction(getTargetIntent());
|
||||
|
||||
final EmptyState noWorkToPersonalEmptyState =
|
||||
new DevicePolicyBlockerEmptyState(
|
||||
/* context= */ this,
|
||||
/* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
|
||||
/* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
|
||||
/* devicePolicyStringSubtitleId= */
|
||||
isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
|
||||
/* defaultSubtitleResource= */
|
||||
isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
|
||||
: R.string.resolver_cant_access_personal_apps_explanation,
|
||||
/* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
|
||||
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
|
||||
|
||||
final EmptyState noPersonalToWorkEmptyState =
|
||||
new DevicePolicyBlockerEmptyState(
|
||||
/* context= */ this,
|
||||
/* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
|
||||
/* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
|
||||
/* devicePolicyStringSubtitleId= */
|
||||
isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
|
||||
/* defaultSubtitleResource= */
|
||||
isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
|
||||
: R.string.resolver_cant_access_work_apps_explanation,
|
||||
/* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
|
||||
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
|
||||
|
||||
return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
|
||||
noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
|
||||
createCrossProfileIntentsChecker(), createMyUserIdProvider());
|
||||
}
|
||||
|
||||
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
|
||||
Intent[] initialIntents,
|
||||
List<ResolveInfo> rList,
|
||||
@@ -844,9 +890,10 @@ public class ChooserActivity extends ResolverActivity implements
|
||||
return new ChooserMultiProfilePagerAdapter(
|
||||
/* context */ this,
|
||||
adapter,
|
||||
getPersonalProfileUserHandle(),
|
||||
createEmptyStateProvider(/* workProfileUserHandle= */ null),
|
||||
mQuietModeManager,
|
||||
/* workProfileUserHandle= */ null,
|
||||
isSendAction(getTargetIntent()), mMaxTargetsPerRow);
|
||||
mMaxTargetsPerRow);
|
||||
}
|
||||
|
||||
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
|
||||
@@ -872,10 +919,11 @@ public class ChooserActivity extends ResolverActivity implements
|
||||
/* context */ this,
|
||||
personalAdapter,
|
||||
workAdapter,
|
||||
createEmptyStateProvider(/* workProfileUserHandle= */ getWorkProfileUserHandle()),
|
||||
mQuietModeManager,
|
||||
selectedProfile,
|
||||
getPersonalProfileUserHandle(),
|
||||
getWorkProfileUserHandle(),
|
||||
isSendAction(getTargetIntent()), mMaxTargetsPerRow);
|
||||
mMaxTargetsPerRow);
|
||||
}
|
||||
|
||||
private int findSelectedProfile() {
|
||||
|
||||
@@ -16,17 +16,7 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -47,37 +37,37 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
|
||||
private static final int SINGLE_CELL_SPAN_SIZE = 1;
|
||||
|
||||
private final ChooserProfileDescriptor[] mItems;
|
||||
private final boolean mIsSendAction;
|
||||
private int mBottomOffset;
|
||||
private int mMaxTargetsPerRow;
|
||||
|
||||
ChooserMultiProfilePagerAdapter(Context context,
|
||||
ChooserActivity.ChooserGridAdapter adapter,
|
||||
UserHandle personalProfileUserHandle,
|
||||
EmptyStateProvider emptyStateProvider,
|
||||
QuietModeManager quietModeManager,
|
||||
UserHandle workProfileUserHandle,
|
||||
boolean isSendAction, int maxTargetsPerRow) {
|
||||
super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
|
||||
int maxTargetsPerRow) {
|
||||
super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
|
||||
workProfileUserHandle);
|
||||
mItems = new ChooserProfileDescriptor[] {
|
||||
createProfileDescriptor(adapter)
|
||||
};
|
||||
mIsSendAction = isSendAction;
|
||||
mMaxTargetsPerRow = maxTargetsPerRow;
|
||||
}
|
||||
|
||||
ChooserMultiProfilePagerAdapter(Context context,
|
||||
ChooserActivity.ChooserGridAdapter personalAdapter,
|
||||
ChooserActivity.ChooserGridAdapter workAdapter,
|
||||
EmptyStateProvider emptyStateProvider,
|
||||
QuietModeManager quietModeManager,
|
||||
@Profile int defaultProfile,
|
||||
UserHandle personalProfileUserHandle,
|
||||
UserHandle workProfileUserHandle,
|
||||
boolean isSendAction, int maxTargetsPerRow) {
|
||||
super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
|
||||
workProfileUserHandle);
|
||||
int maxTargetsPerRow) {
|
||||
super(context, /* currentPage */ defaultProfile, emptyStateProvider,
|
||||
quietModeManager, workProfileUserHandle);
|
||||
mItems = new ChooserProfileDescriptor[] {
|
||||
createProfileDescriptor(personalAdapter),
|
||||
createProfileDescriptor(workAdapter)
|
||||
};
|
||||
mIsSendAction = isSendAction;
|
||||
mMaxTargetsPerRow = maxTargetsPerRow;
|
||||
}
|
||||
|
||||
@@ -192,112 +182,6 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
|
||||
return getListViewForIndex(1 - getCurrentPage());
|
||||
}
|
||||
|
||||
@Override
|
||||
String getMetricsCategory() {
|
||||
return ResolverActivity.METRICS_CATEGORY_CHOOSER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
|
||||
View.OnClickListener listener) {
|
||||
showEmptyState(activeListAdapter,
|
||||
getWorkAppPausedTitle(),
|
||||
/* subtitle = */ null,
|
||||
listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
|
||||
if (mIsSendAction) {
|
||||
showEmptyState(activeListAdapter,
|
||||
getCrossProfileBlockedTitle(),
|
||||
getCantShareWithWorkMessage());
|
||||
} else {
|
||||
showEmptyState(activeListAdapter,
|
||||
getCrossProfileBlockedTitle(),
|
||||
getCantAccessWorkMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
|
||||
if (mIsSendAction) {
|
||||
showEmptyState(activeListAdapter,
|
||||
getCrossProfileBlockedTitle(),
|
||||
getCantShareWithPersonalMessage());
|
||||
} else {
|
||||
showEmptyState(activeListAdapter,
|
||||
getCrossProfileBlockedTitle(),
|
||||
getCantAccessPersonalMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
|
||||
showEmptyState(listAdapter, getNoPersonalAppsAvailableMessage(), /* subtitle= */ null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
|
||||
showEmptyState(listAdapter, getNoWorkAppsAvailableMessage(), /* subtitle = */ null);
|
||||
}
|
||||
|
||||
private String getWorkAppPausedTitle() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_WORK_PAUSED_TITLE,
|
||||
() -> getContext().getString(R.string.resolver_turn_on_work_apps));
|
||||
}
|
||||
|
||||
private String getCrossProfileBlockedTitle() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
|
||||
() -> getContext().getString(R.string.resolver_cross_profile_blocked));
|
||||
}
|
||||
|
||||
private String getCantShareWithWorkMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CANT_SHARE_WITH_WORK,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_cant_share_with_work_apps_explanation));
|
||||
}
|
||||
|
||||
private String getCantShareWithPersonalMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CANT_SHARE_WITH_PERSONAL,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_cant_share_with_personal_apps_explanation));
|
||||
}
|
||||
|
||||
private String getCantAccessWorkMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CANT_ACCESS_WORK,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_cant_access_work_apps_explanation));
|
||||
}
|
||||
|
||||
private String getCantAccessPersonalMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CANT_ACCESS_PERSONAL,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_cant_access_personal_apps_explanation));
|
||||
}
|
||||
|
||||
private String getNoWorkAppsAvailableMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_NO_WORK_APPS,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_no_work_apps_available));
|
||||
}
|
||||
|
||||
private String getNoPersonalAppsAvailableMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_NO_PERSONAL_APPS,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_no_personal_apps_available));
|
||||
}
|
||||
|
||||
|
||||
void setEmptyStateBottomOffset(int bottomOffset) {
|
||||
mBottomOffset = bottomOffset;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ package com.android.internal.app;
|
||||
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
|
||||
@@ -26,6 +29,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.PermissionChecker.PID_UNKNOWN;
|
||||
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
|
||||
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
|
||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
@@ -57,6 +62,7 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PatternMatcher;
|
||||
@@ -93,7 +99,14 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CompositeEmptyStateProvider;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.Profile;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
|
||||
import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
|
||||
import com.android.internal.app.chooser.ChooserTargetInfo;
|
||||
import com.android.internal.app.chooser.DisplayResolveInfo;
|
||||
import com.android.internal.app.chooser.TargetInfo;
|
||||
@@ -186,6 +199,8 @@ public class ResolverActivity extends Activity implements
|
||||
@VisibleForTesting
|
||||
protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter;
|
||||
|
||||
protected QuietModeManager mQuietModeManager;
|
||||
|
||||
// Intent extra for connected audio devices
|
||||
public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
|
||||
|
||||
@@ -217,6 +232,9 @@ public class ResolverActivity extends Activity implements
|
||||
|
||||
private UserHandle mWorkProfileUserHandle;
|
||||
|
||||
@Nullable
|
||||
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
|
||||
|
||||
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
|
||||
|
||||
private LatencyTracker getLatencyTracker() {
|
||||
@@ -375,6 +393,8 @@ public class ResolverActivity extends Activity implements
|
||||
setTheme(appliedThemeResId());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mQuietModeManager = createQuietModeManager();
|
||||
|
||||
// Determine whether we should show that intent is forwarded
|
||||
// from managed profile to owner or other way around.
|
||||
setProfileSwitchMessage(intent.getContentUserHint());
|
||||
@@ -475,6 +495,111 @@ public class ResolverActivity extends Activity implements
|
||||
return resolverMultiProfilePagerAdapter;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected MyUserIdProvider createMyUserIdProvider() {
|
||||
return new MyUserIdProvider();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
|
||||
return new CrossProfileIntentsChecker(getContentResolver());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected QuietModeManager createQuietModeManager() {
|
||||
UserManager userManager = getSystemService(UserManager.class);
|
||||
return new QuietModeManager() {
|
||||
|
||||
private boolean mIsWaitingToEnableWorkProfile = false;
|
||||
|
||||
@Override
|
||||
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
|
||||
return userManager.isQuietModeEnabled(workProfileUserHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
|
||||
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
|
||||
userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
|
||||
});
|
||||
mIsWaitingToEnableWorkProfile = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markWorkProfileEnabledBroadcastReceived() {
|
||||
mIsWaitingToEnableWorkProfile = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWaitingToEnableWorkProfile() {
|
||||
return mIsWaitingToEnableWorkProfile;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected EmptyStateProvider createBlockerEmptyStateProvider() {
|
||||
final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
|
||||
|
||||
if (!shouldShowNoCrossProfileIntentsEmptyState) {
|
||||
// Implementation that doesn't show any blockers
|
||||
return new EmptyStateProvider() {};
|
||||
}
|
||||
|
||||
final AbstractMultiProfilePagerAdapter.EmptyState
|
||||
noWorkToPersonalEmptyState =
|
||||
new DevicePolicyBlockerEmptyState(/* context= */ this,
|
||||
/* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
|
||||
/* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
|
||||
/* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL,
|
||||
/* defaultSubtitleResource= */
|
||||
R.string.resolver_cant_access_personal_apps_explanation,
|
||||
/* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
|
||||
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
|
||||
|
||||
final AbstractMultiProfilePagerAdapter.EmptyState noPersonalToWorkEmptyState =
|
||||
new DevicePolicyBlockerEmptyState(/* context= */ this,
|
||||
/* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
|
||||
/* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
|
||||
/* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK,
|
||||
/* defaultSubtitleResource= */
|
||||
R.string.resolver_cant_access_work_apps_explanation,
|
||||
/* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
|
||||
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
|
||||
|
||||
return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
|
||||
noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
|
||||
createCrossProfileIntentsChecker(), createMyUserIdProvider());
|
||||
}
|
||||
|
||||
protected EmptyStateProvider createEmptyStateProvider(
|
||||
@Nullable UserHandle workProfileUserHandle) {
|
||||
final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
|
||||
|
||||
final EmptyStateProvider workProfileOffEmptyStateProvider =
|
||||
new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
|
||||
mQuietModeManager,
|
||||
/* onSwitchOnWorkSelectedListener= */
|
||||
() -> { if (mOnSwitchOnWorkSelectedListener != null) {
|
||||
mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
|
||||
}},
|
||||
getMetricsCategory());
|
||||
|
||||
final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
|
||||
this,
|
||||
workProfileUserHandle,
|
||||
getPersonalProfileUserHandle(),
|
||||
getMetricsCategory(),
|
||||
createMyUserIdProvider()
|
||||
);
|
||||
|
||||
// Return composite provider, the order matters (the higher, the more priority)
|
||||
return new CompositeEmptyStateProvider(
|
||||
blockerEmptyStateProvider,
|
||||
workProfileOffEmptyStateProvider,
|
||||
noAppsEmptyStateProvider
|
||||
);
|
||||
}
|
||||
|
||||
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
|
||||
Intent[] initialIntents,
|
||||
List<ResolveInfo> rList, boolean filterLastUsed) {
|
||||
@@ -485,10 +610,12 @@ public class ResolverActivity extends Activity implements
|
||||
rList,
|
||||
filterLastUsed,
|
||||
/* userHandle */ UserHandle.of(UserHandle.myUserId()));
|
||||
QuietModeManager quietModeManager = createQuietModeManager();
|
||||
return new ResolverMultiProfilePagerAdapter(
|
||||
/* context */ this,
|
||||
adapter,
|
||||
getPersonalProfileUserHandle(),
|
||||
createEmptyStateProvider(/* workProfileUserHandle= */ null),
|
||||
quietModeManager,
|
||||
/* workProfileUserHandle= */ null);
|
||||
}
|
||||
|
||||
@@ -539,14 +666,15 @@ public class ResolverActivity extends Activity implements
|
||||
(filterLastUsed && UserHandle.myUserId()
|
||||
== workProfileUserHandle.getIdentifier()),
|
||||
/* userHandle */ workProfileUserHandle);
|
||||
QuietModeManager quietModeManager = createQuietModeManager();
|
||||
return new ResolverMultiProfilePagerAdapter(
|
||||
/* context */ this,
|
||||
personalAdapter,
|
||||
workAdapter,
|
||||
createEmptyStateProvider(getWorkProfileUserHandle()),
|
||||
quietModeManager,
|
||||
selectedProfile,
|
||||
getPersonalProfileUserHandle(),
|
||||
getWorkProfileUserHandle(),
|
||||
/* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser));
|
||||
getWorkProfileUserHandle());
|
||||
}
|
||||
|
||||
protected int appliedThemeResId() {
|
||||
@@ -853,9 +981,9 @@ public class ResolverActivity extends Activity implements
|
||||
}
|
||||
mRegistered = true;
|
||||
}
|
||||
if (shouldShowTabs() && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
|
||||
if (mMultiProfilePagerAdapter.isQuietModeEnabled(getWorkProfileUserHandle())) {
|
||||
mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
|
||||
if (shouldShowTabs() && mQuietModeManager.isWaitingToEnableWorkProfile()) {
|
||||
if (mQuietModeManager.isQuietModeEnabled(getWorkProfileUserHandle())) {
|
||||
mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
|
||||
}
|
||||
}
|
||||
mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
|
||||
@@ -1815,13 +1943,12 @@ public class ResolverActivity extends Activity implements
|
||||
onHorizontalSwipeStateChanged(state);
|
||||
}
|
||||
});
|
||||
mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener(
|
||||
() -> {
|
||||
final View workTab = tabHost.getTabWidget().getChildAt(1);
|
||||
workTab.setFocusable(true);
|
||||
workTab.setFocusableInTouchMode(true);
|
||||
workTab.requestFocus();
|
||||
});
|
||||
mOnSwitchOnWorkSelectedListener = () -> {
|
||||
final View workTab = tabHost.getTabWidget().getChildAt(1);
|
||||
workTab.setFocusable(true);
|
||||
workTab.setFocusableInTouchMode(true);
|
||||
workTab.requestFocus();
|
||||
};
|
||||
}
|
||||
|
||||
private String getPersonalTabLabel() {
|
||||
@@ -2082,7 +2209,7 @@ public class ResolverActivity extends Activity implements
|
||||
public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
|
||||
if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
|
||||
if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
|
||||
&& mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
|
||||
&& mQuietModeManager.isWaitingToEnableWorkProfile()) {
|
||||
// We have just turned on the work profile and entered the pass code to start it,
|
||||
// now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
|
||||
// point in reloading the list now, since the work profile user is still
|
||||
@@ -2134,7 +2261,7 @@ public class ResolverActivity extends Activity implements
|
||||
}
|
||||
|
||||
mWorkProfileHasBeenEnabled = true;
|
||||
mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
|
||||
mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
|
||||
} else {
|
||||
// Must be an UNAVAILABLE broadcast, so we watch for the next availability
|
||||
mWorkProfileHasBeenEnabled = false;
|
||||
@@ -2150,7 +2277,6 @@ public class ResolverActivity extends Activity implements
|
||||
};
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static final class ResolvedComponentInfo {
|
||||
public final ComponentName name;
|
||||
private final List<Intent> mIntents = new ArrayList<>();
|
||||
|
||||
@@ -16,15 +16,7 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -43,34 +35,33 @@ import com.android.internal.widget.PagerAdapter;
|
||||
public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter {
|
||||
|
||||
private final ResolverProfileDescriptor[] mItems;
|
||||
private final boolean mShouldShowNoCrossProfileIntentsEmptyState;
|
||||
private boolean mUseLayoutWithDefault;
|
||||
|
||||
ResolverMultiProfilePagerAdapter(Context context,
|
||||
ResolverListAdapter adapter,
|
||||
UserHandle personalProfileUserHandle,
|
||||
EmptyStateProvider emptyStateProvider,
|
||||
QuietModeManager quietModeManager,
|
||||
UserHandle workProfileUserHandle) {
|
||||
super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
|
||||
super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
|
||||
workProfileUserHandle);
|
||||
mItems = new ResolverProfileDescriptor[] {
|
||||
createProfileDescriptor(adapter)
|
||||
};
|
||||
mShouldShowNoCrossProfileIntentsEmptyState = true;
|
||||
}
|
||||
|
||||
ResolverMultiProfilePagerAdapter(Context context,
|
||||
ResolverListAdapter personalAdapter,
|
||||
ResolverListAdapter workAdapter,
|
||||
EmptyStateProvider emptyStateProvider,
|
||||
QuietModeManager quietModeManager,
|
||||
@Profile int defaultProfile,
|
||||
UserHandle personalProfileUserHandle,
|
||||
UserHandle workProfileUserHandle,
|
||||
boolean shouldShowNoCrossProfileIntentsEmptyState) {
|
||||
super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
|
||||
UserHandle workProfileUserHandle) {
|
||||
super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
|
||||
workProfileUserHandle);
|
||||
mItems = new ResolverProfileDescriptor[] {
|
||||
createProfileDescriptor(personalAdapter),
|
||||
createProfileDescriptor(workAdapter)
|
||||
};
|
||||
mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState;
|
||||
}
|
||||
|
||||
private ResolverProfileDescriptor createProfileDescriptor(
|
||||
@@ -170,93 +161,6 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
|
||||
return getListViewForIndex(1 - getCurrentPage());
|
||||
}
|
||||
|
||||
@Override
|
||||
String getMetricsCategory() {
|
||||
return ResolverActivity.METRICS_CATEGORY_RESOLVER;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean allowShowNoCrossProfileIntentsEmptyState() {
|
||||
return mShouldShowNoCrossProfileIntentsEmptyState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
|
||||
View.OnClickListener listener) {
|
||||
showEmptyState(activeListAdapter,
|
||||
getWorkAppPausedTitle(),
|
||||
/* subtitle = */ null,
|
||||
listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
|
||||
showEmptyState(activeListAdapter,
|
||||
getCrossProfileBlockedTitle(),
|
||||
getCantAccessWorkMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
|
||||
showEmptyState(activeListAdapter,
|
||||
getCrossProfileBlockedTitle(),
|
||||
getCantAccessPersonalMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
|
||||
showEmptyState(listAdapter,
|
||||
getNoPersonalAppsAvailableMessage(),
|
||||
/* subtitle = */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
|
||||
showEmptyState(listAdapter,
|
||||
getNoWorkAppsAvailableMessage(),
|
||||
/* subtitle= */ null);
|
||||
}
|
||||
|
||||
private String getWorkAppPausedTitle() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_WORK_PAUSED_TITLE,
|
||||
() -> getContext().getString(R.string.resolver_turn_on_work_apps));
|
||||
}
|
||||
|
||||
private String getCrossProfileBlockedTitle() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
|
||||
() -> getContext().getString(R.string.resolver_cross_profile_blocked));
|
||||
}
|
||||
|
||||
private String getCantAccessWorkMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CANT_ACCESS_WORK,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_cant_access_work_apps_explanation));
|
||||
}
|
||||
|
||||
private String getCantAccessPersonalMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_CANT_ACCESS_PERSONAL,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_cant_access_personal_apps_explanation));
|
||||
}
|
||||
|
||||
private String getNoWorkAppsAvailableMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_NO_WORK_APPS,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_no_work_apps_available));
|
||||
}
|
||||
|
||||
private String getNoPersonalAppsAvailableMessage() {
|
||||
return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
|
||||
RESOLVER_NO_PERSONAL_APPS,
|
||||
() -> getContext().getString(
|
||||
R.string.resolver_no_personal_apps_available));
|
||||
}
|
||||
|
||||
void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
|
||||
mUseLayoutWithDefault = useLayoutWithDefault;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
@@ -26,10 +28,12 @@ import android.graphics.Bitmap;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
|
||||
import com.android.internal.app.chooser.TargetInfo;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -71,7 +75,10 @@ public class ChooserActivityOverrideData {
|
||||
public boolean isQuietModeEnabled;
|
||||
public boolean isWorkProfileUserRunning;
|
||||
public boolean isWorkProfileUserUnlocked;
|
||||
public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
|
||||
public Integer myUserId;
|
||||
public QuietModeManager mQuietModeManager;
|
||||
public MyUserIdProvider mMyUserIdProvider;
|
||||
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
|
||||
public PackageManager packageManager;
|
||||
|
||||
public void reset() {
|
||||
@@ -95,14 +102,9 @@ public class ChooserActivityOverrideData {
|
||||
isQuietModeEnabled = false;
|
||||
isWorkProfileUserRunning = true;
|
||||
isWorkProfileUserUnlocked = true;
|
||||
myUserId = null;
|
||||
packageManager = null;
|
||||
multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
|
||||
@Override
|
||||
public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
|
||||
int targetUserId) {
|
||||
return hasCrossProfileIntents;
|
||||
}
|
||||
|
||||
mQuietModeManager = new QuietModeManager() {
|
||||
@Override
|
||||
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
|
||||
return isQuietModeEnabled;
|
||||
@@ -113,7 +115,27 @@ public class ChooserActivityOverrideData {
|
||||
UserHandle workProfileUserHandle) {
|
||||
isQuietModeEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markWorkProfileEnabledBroadcastReceived() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWaitingToEnableWorkProfile() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
mMyUserIdProvider = new MyUserIdProvider() {
|
||||
@Override
|
||||
public int getMyUserId() {
|
||||
return myUserId != null ? myUserId : UserHandle.myUserId();
|
||||
}
|
||||
};
|
||||
|
||||
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
|
||||
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
|
||||
.thenAnswer(invocation -> hasCrossProfileIntents);
|
||||
}
|
||||
|
||||
private ChooserActivityOverrideData() {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
@@ -34,6 +38,8 @@ import android.os.UserHandle;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
|
||||
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
|
||||
import com.android.internal.app.chooser.DisplayResolveInfo;
|
||||
import com.android.internal.app.chooser.TargetInfo;
|
||||
@@ -59,15 +65,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
|
||||
return 1234;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
|
||||
Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
|
||||
AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
|
||||
super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
|
||||
multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
|
||||
return multiProfilePagerAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChooserListAdapter createChooserListAdapter(Context context, List<Intent> payloadIntents,
|
||||
Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
|
||||
@@ -134,6 +131,30 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
|
||||
return super.isVoiceInteraction();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MyUserIdProvider createMyUserIdProvider() {
|
||||
if (sOverrides.mMyUserIdProvider != null) {
|
||||
return sOverrides.mMyUserIdProvider;
|
||||
}
|
||||
return super.createMyUserIdProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
|
||||
if (sOverrides.mCrossProfileIntentsChecker != null) {
|
||||
return sOverrides.mCrossProfileIntentsChecker;
|
||||
}
|
||||
return super.createCrossProfileIntentsChecker();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMultiProfilePagerAdapter.QuietModeManager createQuietModeManager() {
|
||||
if (sOverrides.mQuietModeManager != null) {
|
||||
return sOverrides.mQuietModeManager;
|
||||
}
|
||||
return super.createQuietModeManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
|
||||
if (sOverrides.onSafelyStartCallback != null &&
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -27,6 +29,9 @@ import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
|
||||
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
|
||||
import com.android.internal.app.chooser.TargetInfo;
|
||||
|
||||
import java.util.List;
|
||||
@@ -52,12 +57,27 @@ public class ResolverWrapperActivity extends ResolverActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
|
||||
Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
|
||||
AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
|
||||
super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
|
||||
multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
|
||||
return multiProfilePagerAdapter;
|
||||
protected MyUserIdProvider createMyUserIdProvider() {
|
||||
if (sOverrides.mMyUserIdProvider != null) {
|
||||
return sOverrides.mMyUserIdProvider;
|
||||
}
|
||||
return super.createMyUserIdProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
|
||||
if (sOverrides.mCrossProfileIntentsChecker != null) {
|
||||
return sOverrides.mCrossProfileIntentsChecker;
|
||||
}
|
||||
return super.createCrossProfileIntentsChecker();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuietModeManager createQuietModeManager() {
|
||||
if (sOverrides.mQuietModeManager != null) {
|
||||
return sOverrides.mQuietModeManager;
|
||||
}
|
||||
return super.createQuietModeManager();
|
||||
}
|
||||
|
||||
ResolverWrapperAdapter getAdapter() {
|
||||
@@ -137,9 +157,12 @@ public class ResolverWrapperActivity extends ResolverActivity {
|
||||
public ResolverListController workResolverListController;
|
||||
public Boolean isVoiceInteraction;
|
||||
public UserHandle workProfileUserHandle;
|
||||
public Integer myUserId;
|
||||
public boolean hasCrossProfileIntents;
|
||||
public boolean isQuietModeEnabled;
|
||||
public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
|
||||
public QuietModeManager mQuietModeManager;
|
||||
public MyUserIdProvider mMyUserIdProvider;
|
||||
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
|
||||
|
||||
public void reset() {
|
||||
onSafelyStartCallback = null;
|
||||
@@ -148,15 +171,11 @@ public class ResolverWrapperActivity extends ResolverActivity {
|
||||
resolverListController = mock(ResolverListController.class);
|
||||
workResolverListController = mock(ResolverListController.class);
|
||||
workProfileUserHandle = null;
|
||||
myUserId = null;
|
||||
hasCrossProfileIntents = true;
|
||||
isQuietModeEnabled = false;
|
||||
multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
|
||||
@Override
|
||||
public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
|
||||
int targetUserId) {
|
||||
return hasCrossProfileIntents;
|
||||
}
|
||||
|
||||
mQuietModeManager = new QuietModeManager() {
|
||||
@Override
|
||||
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
|
||||
return isQuietModeEnabled;
|
||||
@@ -167,7 +186,27 @@ public class ResolverWrapperActivity extends ResolverActivity {
|
||||
UserHandle workProfileUserHandle) {
|
||||
isQuietModeEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markWorkProfileEnabledBroadcastReceived() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWaitingToEnableWorkProfile() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
mMyUserIdProvider = new MyUserIdProvider() {
|
||||
@Override
|
||||
public int getMyUserId() {
|
||||
return myUserId != null ? myUserId : UserHandle.myUserId();
|
||||
}
|
||||
};
|
||||
|
||||
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
|
||||
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
|
||||
.thenAnswer(invocation -> hasCrossProfileIntents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user