summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--java/src/com/android/intentresolver/grid/ChooserGridAdapter.java2
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java302
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserHelper.kt40
-rw-r--r--java/src/com/android/intentresolver/v2/ProfileAvailability.kt7
-rw-r--r--java/src/com/android/intentresolver/v2/ProfileHelper.kt68
-rw-r--r--java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java59
-rw-r--r--java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java9
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java4
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt9
9 files changed, 253 insertions, 247 deletions
diff --git a/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java
index 036b686b..ba76a4a0 100644
--- a/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java
+++ b/java/src/com/android/intentresolver/grid/ChooserGridAdapter.java
@@ -40,7 +40,6 @@ import com.android.intentresolver.ChooserListAdapter;
import com.android.intentresolver.FeatureFlags;
import com.android.intentresolver.R;
import com.android.intentresolver.ResolverListAdapter.ViewHolder;
-import com.android.internal.annotations.VisibleForTesting;
import com.google.android.collect.Lists;
@@ -50,7 +49,6 @@ import com.google.android.collect.Lists;
* row level by this adapter but not on the item level. Individual targets within the row are
* handled by {@link ChooserListAdapter}
*/
-@VisibleForTesting
public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java
index 8fe64da7..a95caddc 100644
--- a/java/src/com/android/intentresolver/v2/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java
@@ -31,12 +31,12 @@ import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION;
import static com.android.intentresolver.v2.ext.CreationExtrasExtKt.addDefaultArgs;
+import static com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL;
+import static com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.PROFILE_WORK;
import static com.android.intentresolver.v2.ui.model.ActivityModel.ACTIVITY_MODEL_KEY;
import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
-import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
-import static java.util.Objects.requireNonNullElse;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -143,13 +143,13 @@ import com.android.intentresolver.v2.platform.AppPredictionAvailable;
import com.android.intentresolver.v2.platform.ImageEditor;
import com.android.intentresolver.v2.platform.NearbyShare;
import com.android.intentresolver.v2.profiles.ChooserMultiProfilePagerAdapter;
-import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter;
import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.ProfileType;
import com.android.intentresolver.v2.profiles.OnProfileSelectedListener;
import com.android.intentresolver.v2.profiles.OnSwitchOnWorkSelectedListener;
import com.android.intentresolver.v2.profiles.TabConfig;
import com.android.intentresolver.v2.shared.model.Profile;
import com.android.intentresolver.v2.ui.ActionTitle;
+import com.android.intentresolver.v2.ui.ProfilePagerResources;
import com.android.intentresolver.v2.ui.ShareResultSender;
import com.android.intentresolver.v2.ui.ShareResultSenderFactory;
import com.android.intentresolver.v2.ui.model.ActivityModel;
@@ -182,6 +182,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import javax.inject.Inject;
@@ -226,8 +227,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
private int mLayoutId;
private UserHandle mHeaderCreatorUser;
- protected static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL;
- protected static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK;
private boolean mRegistered;
private PackageMonitor mPersonalPackageMonitor;
private PackageMonitor mWorkPackageMonitor;
@@ -276,6 +275,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
@Inject @NearbyShare public Optional<ComponentName> mNearbyShare;
@Inject public TargetDataLoader mTargetDataLoader;
@Inject public DevicePolicyResources mDevicePolicyResources;
+ @Inject public ProfilePagerResources mProfilePagerResources;
@Inject public PackageManager mPackageManager;
@Inject public ClipboardManager mClipboardManager;
@Inject public IntentForwarding mIntentForwarding;
@@ -353,7 +353,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
setTheme(R.style.Theme_DeviceDefault_Chooser);
// Initializer is invoked when this function returns, via Lifecycle.
- mChooserHelper.setInitializer(this::initialize);
+ mChooserHelper.setInitializer(this::initializeWith);
}
@Override
@@ -429,7 +429,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
getMainLooper(),
mProfiles.getPersonalHandle(),
false);
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
if (mWorkPackageMonitor == null) {
mWorkPackageMonitor = createPackageMonitor(
mChooserMultiProfilePagerAdapter.getWorkListAdapter());
@@ -465,14 +465,24 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
/** DO NOT CALL. Only for use from ChooserHelper as a callback. */
- private void initialize(Profile launchedAs, List<Profile> profiles) {
+ private void initializeWith(InitialState initialState) {
+ Log.d(TAG, "initializeWith: " + initialState);
+
mViewModel = new ViewModelProvider(this).get(ChooserViewModel.class);
mRequest = mViewModel.getRequest().getValue();
mActivityModel = mViewModel.getActivityModel();
- mProfiles = new ProfileHelper(mUserInteractor, mFeatureFlags, profiles, launchedAs);
- mProfileAvailability =
- new ProfileAvailability(getCoroutineScope(getLifecycle()), mUserInteractor);
+ mProfiles = new ProfileHelper(
+ mUserInteractor,
+ mFeatureFlags,
+ initialState.getProfiles(),
+ initialState.getLaunchedAs());
+
+ mProfileAvailability = new ProfileAvailability(
+ getCoroutineScope(getLifecycle()),
+ mUserInteractor,
+ initialState.getAvailability());
+
mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated);
mIntentReceivedTime.set(System.currentTimeMillis());
@@ -501,15 +511,17 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mRequest.getShareTargetFilter()
);
- Intent intent = mRequest.getTargetIntent();
- List<Intent> initialIntents = mRequest.getInitialIntents();
- Log.d(TAG, "createMultiProfilePagerAdapter");
mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
- requireNonNullElse(initialIntents, emptyList()).toArray(new Intent[0]),
- /* resolutionList = */ null,
- false
- );
+ /* context = */ this,
+ mProfilePagerResources,
+ mViewModel.getRequest().getValue(),
+ mProfiles,
+ mProfileAvailability,
+ mRequest.getInitialIntents(),
+ mMaxTargetsPerRow,
+ mFeatureFlags);
+
if (!configureContentView(mTargetDataLoader)) {
mPersonalPackageMonitor = createPackageMonitor(
mChooserMultiProfilePagerAdapter.getPersonalListAdapter());
@@ -519,7 +531,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mProfiles.getPersonalHandle(),
false
);
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
mWorkPackageMonitor = createPackageMonitor(
mChooserMultiProfilePagerAdapter.getWorkListAdapter());
mWorkPackageMonitor.register(
@@ -553,6 +565,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mResolverDrawerLayout = rdl;
}
+
+ Intent intent = mRequest.getTargetIntent();
final Set<String> categories = intent.getCategories();
MetricsLogger.action(this,
mChooserMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
@@ -702,7 +716,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return false;
}
-
private boolean isTwoPagePersonalAndWorkConfiguration() {
return (mChooserMultiProfilePagerAdapter.getCount() == 2)
&& mChooserMultiProfilePagerAdapter.hasPageForProfile(PROFILE_PERSONAL)
@@ -844,7 +857,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
&& !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) {
return;
}
- if (!hasWorkProfile()
+ if (!mProfiles.getWorkProfilePresent()
&& listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) {
final TextView titleView = findViewById(com.android.internal.R.id.title);
if (titleView != null) {
@@ -928,22 +941,17 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) {
- if (!hasWorkProfile() || currentUserHandle.equals(getUser())) {
+ if (!mProfiles.getWorkProfilePresent() || currentUserHandle.equals(getUser())) {
return;
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED)
- .setBoolean(
- currentUserHandle.equals(
- mProfiles.getPersonalHandle()))
+ .setBoolean(currentUserHandle.equals(mProfiles.getPersonalHandle()))
.setStrings(getMetricsCategory(),
cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target")
.write();
}
- private boolean hasWorkProfile() {
- return mProfiles.getWorkHandle() != null;
- }
private LatencyTracker getLatencyTracker() {
return LatencyTracker.getInstance(this);
}
@@ -963,15 +971,16 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
protected final EmptyStateProvider createEmptyStateProvider(
- @Nullable UserHandle workProfileUserHandle) {
- final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
+ ProfileHelper profileHelper,
+ ProfileAvailability profileAvailability) {
+ EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
- final EmptyStateProvider workProfileOffEmptyStateProvider =
+ EmptyStateProvider workProfileOffEmptyStateProvider =
new WorkProfilePausedEmptyStateProvider(
this,
- mProfiles,
- mProfileAvailability,
- /* onSwitchOnWorkSelectedListener= */
+ profileHelper,
+ profileAvailability,
+ /* onSwitchOnWorkSelectedListener = */
() -> {
if (mOnSwitchOnWorkSelectedListener != null) {
mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
@@ -979,12 +988,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
},
getMetricsCategory());
- final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
+ EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
this,
- workProfileUserHandle,
- mProfiles.getPersonalHandle(),
+ profileHelper.getWorkHandle(),
+ profileHelper.getPersonalHandle(),
getMetricsCategory(),
- mProfiles.getTabOwnerUserHandleForLaunch()
+ profileHelper.getTabOwnerUserHandleForLaunch()
);
// Return composite provider, the order matters (the higher, the more priority)
@@ -1003,8 +1012,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return getResolverRankerServiceUserHandleListInternal(userHandle);
}
- private List<UserHandle> getResolverRankerServiceUserHandleListInternal(
- UserHandle userHandle) {
+ private List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle userHandle) {
List<UserHandle> userList = new ArrayList<>();
userList.add(userHandle);
// Add clonedProfileUserHandle to the list only if we are:
@@ -1090,7 +1098,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
Trace.beginSection("configureContentView");
// We partially rebuild the inactive adapter to determine if we should auto launch
// isTabLoaded will be true here if the empty state screen is shown instead of the list.
- boolean rebuildCompleted = mChooserMultiProfilePagerAdapter.rebuildTabs(hasWorkProfile());
+ boolean rebuildCompleted = mChooserMultiProfilePagerAdapter.rebuildTabs(
+ mProfiles.getWorkProfilePresent());
mLayoutId = mFeatureFlags.scrollablePreview()
? R.layout.chooser_grid_scrollable_preview
@@ -1125,7 +1134,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
stub.setVisibility(View.VISIBLE);
TextView textView = (TextView) LayoutInflater.from(this).inflate(
R.layout.resolver_different_item_header, null, false);
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
textView.setGravity(Gravity.CENTER);
}
stub.addView(textView);
@@ -1154,7 +1163,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
setupViewVisibilities();
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()
+ || (mProfiles.getPrivateProfilePresent()
+ && mProfileAvailability.isAvailable(
+ requireNonNull(mProfiles.getPrivateProfile())))) {
setupProfileTabs();
}
@@ -1182,8 +1194,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
});
mOnSwitchOnWorkSelectedListener = () -> {
- final View workTab =
- tabHost.getTabWidget().getChildAt(
+ View workTab = tabHost.getTabWidget().getChildAt(
mChooserMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK));
workTab.setFocusable(true);
workTab.setFocusableInTouchMode(true);
@@ -1265,18 +1276,72 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return context.getSharedPreferences(PINNED_SHARED_PREFS_NAME, MODE_PRIVATE);
}
- protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed) {
- if (hasWorkProfile()) {
- mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
- initialIntents, rList, filterLastUsed);
- } else {
- mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
- initialIntents, rList, filterLastUsed);
+ protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter() {
+ return createMultiProfilePagerAdapter(
+ /* context = */ this,
+ mProfilePagerResources,
+ mViewModel.getRequest().getValue(),
+ mProfiles,
+ mProfileAvailability,
+ mRequest.getInitialIntents(),
+ mMaxTargetsPerRow,
+ mFeatureFlags);
+ }
+
+ private ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+ Context context,
+ ProfilePagerResources profilePagerResources,
+ ChooserRequest request,
+ ProfileHelper profileHelper,
+ ProfileAvailability profileAvailability,
+ List<Intent> initialIntents,
+ int maxTargetsPerRow,
+ FeatureFlags featureFlags) {
+ Log.d(TAG, "createMultiProfilePagerAdapter");
+
+ Profile launchedAs = profileHelper.getLaunchedAsProfile();
+
+ Intent[] initialIntentArray = initialIntents.toArray(new Intent[0]);
+ List<Intent> payloadIntents = request.getPayloadIntents();
+
+ List<TabConfig<ChooserGridAdapter>> tabs = new ArrayList<>();
+ for (Profile profile : profileHelper.getProfiles()) {
+ if (profile.getType() == Profile.Type.PRIVATE
+ && !profileAvailability.isAvailable(profile)) {
+ continue;
+ }
+ ChooserGridAdapter adapter = createChooserGridAdapter(
+ context,
+ payloadIntents,
+ profile.equals(launchedAs) ? initialIntentArray : null,
+ profile.getPrimary().getHandle()
+ );
+ tabs.add(new TabConfig<>(
+ /* profile = */ profile.getType().ordinal(),
+ profilePagerResources.profileTabLabel(profile.getType()),
+ profilePagerResources.profileTabAccessibilityLabel(profile.getType()),
+ /* tabTag = */ profile.getType().name(),
+ adapter));
}
- return mChooserMultiProfilePagerAdapter;
+
+ EmptyStateProvider emptyStateProvider =
+ createEmptyStateProvider(profileHelper, profileAvailability);
+
+ Supplier<Boolean> workProfileQuietModeChecker =
+ () -> !(profileHelper.getWorkProfilePresent()
+ && profileAvailability.isAvailable(
+ requireNonNull(profileHelper.getWorkProfile())));
+
+ return new ChooserMultiProfilePagerAdapter(
+ /* context */ this,
+ ImmutableList.copyOf(tabs),
+ emptyStateProvider,
+ workProfileQuietModeChecker,
+ launchedAs.getType().ordinal(),
+ profileHelper.getWorkHandle(),
+ profileHelper.getCloneHandle(),
+ maxTargetsPerRow,
+ featureFlags);
}
protected EmptyStateProvider createBlockerEmptyStateProvider() {
@@ -1309,93 +1374,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
return new NoCrossProfileEmptyStateProvider(
- mProfiles.getPersonalHandle(),
+ mProfiles,
noWorkToPersonalEmptyState,
noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(),
- mProfiles.getTabOwnerUserHandleForLaunch());
- }
-
- private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed) {
- ChooserGridAdapter adapter = createChooserGridAdapter(
- /* context */ this,
- mRequest.getPayloadIntents(),
- initialIntents,
- rList,
- filterLastUsed,
- /* userHandle */ mProfiles.getPersonalHandle()
- );
- return new ChooserMultiProfilePagerAdapter(
- /* context */ this,
- ImmutableList.of(
- new TabConfig<>(
- PROFILE_PERSONAL,
- mDevicePolicyResources.getPersonalTabLabel(),
- mDevicePolicyResources.getPersonalTabAccessibilityLabel(),
- TAB_TAG_PERSONAL,
- adapter)),
- createEmptyStateProvider(/* workProfileUserHandle= */ null),
- /* workProfileQuietModeChecker= */ () -> false,
- /* defaultProfile= */ PROFILE_PERSONAL,
- /* workProfileUserHandle= */ null,
- mProfiles.getCloneHandle(),
- mMaxTargetsPerRow,
- mFeatureFlags);
- }
-
- private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed) {
- int selectedProfile = findSelectedProfile();
- ChooserGridAdapter personalAdapter = createChooserGridAdapter(
- /* context */ this,
- mRequest.getPayloadIntents(),
- selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
- rList,
- filterLastUsed,
- /* userHandle */ mProfiles.getPersonalHandle()
- );
- ChooserGridAdapter workAdapter = createChooserGridAdapter(
- /* context */ this,
- mRequest.getPayloadIntents(),
- selectedProfile == PROFILE_WORK ? initialIntents : null,
- rList,
- filterLastUsed,
- /* userHandle */ mProfiles.getWorkHandle()
- );
- return new ChooserMultiProfilePagerAdapter(
- /* context */ this,
- ImmutableList.of(
- new TabConfig<>(
- PROFILE_PERSONAL,
- mDevicePolicyResources.getPersonalTabLabel(),
- mDevicePolicyResources.getPersonalTabAccessibilityLabel(),
- TAB_TAG_PERSONAL,
- personalAdapter),
- new TabConfig<>(
- PROFILE_WORK,
- mDevicePolicyResources.getWorkTabLabel(),
- mDevicePolicyResources.getWorkTabAccessibilityLabel(),
- TAB_TAG_WORK,
- workAdapter)),
- createEmptyStateProvider(mProfiles.getWorkHandle()),
- /* Supplier<Boolean> (QuietMode enabled) == !(available) */
- () -> !(mProfiles.getWorkProfilePresent()
- && mProfileAvailability.isAvailable(
- requireNonNull(mProfiles.getWorkProfile()))),
- selectedProfile,
- mProfiles.getWorkHandle(),
- mProfiles.getCloneHandle(),
- mMaxTargetsPerRow,
- mFeatureFlags);
+ createCrossProfileIntentsChecker());
}
private int findSelectedProfile() {
- return getProfileForUser(mProfiles.getTabOwnerUserHandleForLaunch());
+ return mProfiles.getLaunchedAsProfileType().ordinal();
}
/**
@@ -1475,7 +1461,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
private void updateTabPadding() {
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
View tabs = findViewById(com.android.internal.R.id.tabs);
float iconSize = getResources().getDimension(R.dimen.chooser_icon_size);
// The entire width consists of icons or padding. Divide the item padding in half to get
@@ -1895,20 +1881,17 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return mEventLog;
}
- @VisibleForTesting
- public ChooserGridAdapter createChooserGridAdapter(
+ private ChooserGridAdapter createChooserGridAdapter(
Context context,
List<Intent> payloadIntents,
Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
UserHandle userHandle) {
ChooserListAdapter chooserListAdapter = createChooserListAdapter(
context,
payloadIntents,
initialIntents,
- rList,
- filterLastUsed,
+ /* TODO: not used, remove. rList= */ null,
+ /* TODO: not used, remove. filterLastUsed= */ false,
createListController(userHandle),
userHandle,
mRequest.getTargetIntent(),
@@ -1921,7 +1904,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
new ChooserGridAdapter.ChooserActivityDelegate() {
@Override
public boolean shouldShowTabs() {
- return hasWorkProfile();
+ return mProfiles.getWorkProfilePresent();
}
@Override
@@ -2185,7 +2168,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
offset += stickyContentPreview.getHeight();
}
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
offset += findViewById(com.android.internal.R.id.tabs).getHeight();
}
@@ -2229,19 +2212,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
.shouldShowEmptyStateScreenInAnyInactiveAdapter();
}
- /**
- * Returns {@link #PROFILE_WORK}, if the given user handle matches work user handle.
- * Returns {@link #PROFILE_PERSONAL}, otherwise.
- **/
- private int getProfileForUser(UserHandle currentUserHandle) {
- if (currentUserHandle.equals(mProfiles.getWorkHandle())) {
- return PROFILE_WORK;
- }
- // We return personal profile, as it is the default when there is no work profile, personal
- // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
- return PROFILE_PERSONAL;
- }
-
protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
setupScrollListener();
maybeSetupGlobalLayoutListener();
@@ -2326,8 +2296,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (mResolverDrawerLayout == null) {
return;
}
- int elevatedViewResId = hasWorkProfile() ?
- com.android.internal.R.id.tabs : com.android.internal.R.id.chooser_header;
+ int elevatedViewResId = mProfiles.getWorkProfilePresent()
+ ? com.android.internal.R.id.tabs : com.android.internal.R.id.chooser_header;
final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
final float defaultElevation = elevatedView.getElevation();
final float chooserHeaderScrollElevation =
@@ -2365,7 +2335,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
private void maybeSetupGlobalLayoutListener() {
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
return;
}
final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
@@ -2401,7 +2371,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
boolean isEmpty = mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
UserHandle.of(UserHandle.myUserId())).getCount() == 0;
- return (mFeatureFlags.scrollablePreview() || hasWorkProfile())
+ return (mFeatureFlags.scrollablePreview() || mProfiles.getWorkProfilePresent())
&& (!isEmpty || shouldShowContentPreviewWhenEmpty());
}
@@ -2471,7 +2441,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
protected void onProfileTabSelected(int currentPage) {
setupViewVisibilities();
maybeLogProfileChange();
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
// The device policy logger is only concerned with sessions that include a work profile.
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
@@ -2490,7 +2460,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- if (hasWorkProfile()) {
+ if (mProfiles.getWorkProfilePresent()) {
mChooserMultiProfilePagerAdapter
.setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
}
diff --git a/java/src/com/android/intentresolver/v2/ChooserHelper.kt b/java/src/com/android/intentresolver/v2/ChooserHelper.kt
index 5b514614..1498453b 100644
--- a/java/src/com/android/intentresolver/v2/ChooserHelper.kt
+++ b/java/src/com/android/intentresolver/v2/ChooserHelper.kt
@@ -24,7 +24,7 @@ import androidx.activity.viewModels
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
+import com.android.intentresolver.inject.Background
import com.android.intentresolver.v2.annotation.JavaInterop
import com.android.intentresolver.v2.domain.interactor.UserInteractor
import com.android.intentresolver.v2.shared.model.Profile
@@ -35,8 +35,9 @@ import com.android.intentresolver.v2.validation.Valid
import com.android.intentresolver.v2.validation.log
import dagger.hilt.android.scopes.ActivityScoped
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
private const val TAG: String = "ChooserHelper"
@@ -48,14 +49,23 @@ private const val TAG: String = "ChooserHelper"
*/
@JavaInterop
fun interface ChooserInitializer {
- /**
- * @param launchedAs the profile which launched this instance
- * @param initialProfiles a snapshot of the launching user's profile group
- */
- fun initialize(launchedAs: Profile, initialProfiles: List<Profile>)
+ /** @param initialState the initial state to provide to initialization */
+ fun initializeWith(initialState: InitialState)
}
/**
+ * A parameter object for Initialize which contains all the values which are required "early", on
+ * the main thread and outside of any coroutines. This supports code which expects to be called by
+ * the system on the main thread only. (This includes everything originally called from onCreate).
+ */
+@JavaInterop
+data class InitialState(
+ val profiles: List<Profile>,
+ val availability: Map<Profile, Boolean>,
+ val launchedAs: Profile
+)
+
+/**
* __Purpose__
*
* Cleanup aid. Provides a pathway to cleaner code.
@@ -90,11 +100,11 @@ class ChooserHelper
constructor(
hostActivity: Activity,
private val userInteractor: UserInteractor,
+ @Background private val background: CoroutineDispatcher,
) : DefaultLifecycleObserver {
// This is guaranteed by Hilt, since only a ComponentActivity is injectable.
private val activity: ComponentActivity = hostActivity as ComponentActivity
private val viewModel by activity.viewModels<ChooserViewModel>()
- private val lifecycleScope = activity.lifecycleScope
private lateinit var activityInitializer: ChooserInitializer
@@ -159,11 +169,13 @@ constructor(
private fun initializeActivity(request: Valid<ChooserRequest>) {
request.warnings.forEach { it.log(TAG) }
- // Note: Activity lifecycleScope uses Dispatchers.Main.immediate
- lifecycleScope.launch {
- val initialProfiles = userInteractor.profiles.first()
- val launchedAsProfile = userInteractor.launchedAsProfile.first()
- activityInitializer.initialize(launchedAsProfile, initialProfiles)
- }
+ val initialState =
+ runBlocking(background) {
+ val initialProfiles = userInteractor.profiles.first()
+ val initialAvailability = userInteractor.availability.first()
+ val launchedAsProfile = userInteractor.launchedAsProfile.first()
+ InitialState(initialProfiles, initialAvailability, launchedAsProfile)
+ }
+ activityInitializer.initializeWith(initialState)
}
}
diff --git a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt b/java/src/com/android/intentresolver/v2/ProfileAvailability.kt
index 2df25c41..4b183ecb 100644
--- a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt
+++ b/java/src/com/android/intentresolver/v2/ProfileAvailability.kt
@@ -29,10 +29,11 @@ import kotlinx.coroutines.launch
/** Provides availability status for profiles */
class ProfileAvailability(
private val scope: CoroutineScope,
- private val userInteractor: UserInteractor
+ private val userInteractor: UserInteractor,
+ initialState: Map<Profile, Boolean>
) {
private val availability =
- userInteractor.availability.stateIn(scope, SharingStarted.Eagerly, mapOf())
+ userInteractor.availability.stateIn(scope, SharingStarted.Eagerly, initialState)
/** Used by WorkProfilePausedEmptyStateProvider */
var waitingToEnableProfile = false
@@ -62,7 +63,7 @@ class ProfileAvailability(
val job =
scope.launch {
// Wait for the profile to become available
- userInteractor.availability.filter { it[profile] == true }.first()
+ availability.filter { it[profile] == true }.first()
}
job.invokeOnCompletion {
waitingToEnableProfile = false
diff --git a/java/src/com/android/intentresolver/v2/ProfileHelper.kt b/java/src/com/android/intentresolver/v2/ProfileHelper.kt
index 784096b4..29aab770 100644
--- a/java/src/com/android/intentresolver/v2/ProfileHelper.kt
+++ b/java/src/com/android/intentresolver/v2/ProfileHelper.kt
@@ -1,18 +1,18 @@
/*
-* Copyright (C) 2024 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.
-*/
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.intentresolver.v2
@@ -23,20 +23,23 @@ import com.android.intentresolver.v2.shared.model.Profile
import com.android.intentresolver.v2.shared.model.User
import javax.inject.Inject
-class ProfileHelper @Inject constructor(
+class ProfileHelper
+@Inject
+constructor(
interactor: UserInteractor,
private val flags: IntentResolverFlags,
- profiles: List<Profile>,
- launchedAsProfile: Profile,
+ val profiles: List<Profile>,
+ val launchedAsProfile: Profile,
) {
private val launchedByHandle: UserHandle = interactor.launchedAs
// Map UserHandle back to a user within launchedByProfile
- private val launchedByUser = when (launchedByHandle) {
- launchedAsProfile.primary.handle -> launchedAsProfile.primary
- launchedAsProfile.clone?.handle -> launchedAsProfile.clone
- else -> error("launchedByUser must be a member of launchedByProfile")
- }
+ private val launchedByUser =
+ when (launchedByHandle) {
+ launchedAsProfile.primary.handle -> launchedAsProfile.primary
+ launchedAsProfile.clone?.handle -> launchedAsProfile.clone
+ else -> error("launchedByUser must be a member of launchedByProfile")
+ }
val launchedAsProfileType: Profile.Type = launchedAsProfile.type
val personalProfile = profiles.single { it.type == Profile.Type.PERSONAL }
@@ -45,7 +48,7 @@ class ProfileHelper @Inject constructor(
val personalHandle = personalProfile.primary.handle
val workHandle = workProfile?.primary?.handle
- val privateHandle = privateProfile?.primary?.handle?.takeIf { flags.enablePrivateProfile() }
+ val privateHandle = privateProfile?.primary?.handle
val cloneHandle = personalProfile.clone?.handle
val isLaunchedAsCloneProfile = launchedByUser == launchedAsProfile.clone
@@ -55,12 +58,19 @@ class ProfileHelper @Inject constructor(
val privateProfilePresent = privateProfile != null
// Name retained for ease of review, to be renamed later
- val tabOwnerUserHandleForLaunch = if (launchedByUser.role == User.Role.CLONE) {
- // When started by clone user, return the profile owner instead
- launchedAsProfile.primary.handle
- } else {
- // Otherwise the launched user is used
- launchedByUser.handle
+ val tabOwnerUserHandleForLaunch =
+ if (launchedByUser.role == User.Role.CLONE) {
+ // When started by clone user, return the profile owner instead
+ launchedAsProfile.primary.handle
+ } else {
+ // Otherwise the launched user is used
+ launchedByUser.handle
+ }
+
+ fun findProfileType(handle: UserHandle): Profile.Type? {
+ val matched =
+ profiles.firstOrNull { it.primary.handle == handle || it.clone?.handle == handle }
+ return matched?.type
}
// Name retained for ease of review, to be renamed later
diff --git a/java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java
index b744c589..d52015bf 100644
--- a/java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java
+++ b/java/src/com/android/intentresolver/v2/emptystate/NoCrossProfileEmptyStateProvider.java
@@ -19,6 +19,7 @@ package com.android.intentresolver.v2.emptystate;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.Intent;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -29,6 +30,11 @@ import com.android.intentresolver.ResolverListAdapter;
import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
import com.android.intentresolver.emptystate.EmptyState;
import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.intentresolver.v2.ProfileHelper;
+import com.android.intentresolver.v2.shared.model.Profile;
+import com.android.intentresolver.v2.shared.model.User;
+
+import java.util.List;
/**
* Empty state provider that does not allow cross profile sharing, it will return a blocker
@@ -36,45 +42,56 @@ import com.android.intentresolver.emptystate.EmptyStateProvider;
*/
public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
- private final UserHandle mPersonalProfileUserHandle;
+ private final ProfileHelper mProfileHelper;
private final EmptyState mNoWorkToPersonalEmptyState;
private final EmptyState mNoPersonalToWorkEmptyState;
private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
- private final UserHandle mTabOwnerUserHandleForLaunch;
- public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
+ public NoCrossProfileEmptyStateProvider(
+ ProfileHelper profileHelper,
EmptyState noWorkToPersonalEmptyState,
EmptyState noPersonalToWorkEmptyState,
- CrossProfileIntentsChecker crossProfileIntentsChecker,
- UserHandle tabOwnerUserHandleForLaunch) {
- mPersonalProfileUserHandle = personalUserHandle;
+ CrossProfileIntentsChecker crossProfileIntentsChecker) {
+ mProfileHelper = profileHelper;
mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
mCrossProfileIntentsChecker = crossProfileIntentsChecker;
- mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch;
+ }
+
+ private boolean anyCrossProfileAllowedIntents(ResolverListAdapter selected, UserHandle source) {
+ List<Intent> intents = selected.getIntents();
+ UserHandle target = selected.getUserHandle();
+ return mCrossProfileIntentsChecker.hasCrossProfileIntents(intents,
+ source.getIdentifier(), target.getIdentifier());
}
@Nullable
@Override
- public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
- boolean shouldShowBlocker =
- !mTabOwnerUserHandleForLaunch.equals(resolverListAdapter.getUserHandle())
- && !mCrossProfileIntentsChecker
- .hasCrossProfileIntents(resolverListAdapter.getIntents(),
- mTabOwnerUserHandleForLaunch.getIdentifier(),
- resolverListAdapter.getUserHandle().getIdentifier());
-
- if (!shouldShowBlocker) {
+ public EmptyState getEmptyState(ResolverListAdapter adapter) {
+ Profile launchedAsProfile = mProfileHelper.getLaunchedAsProfile();
+ User launchedAs = mProfileHelper.getLaunchedAsProfile().getPrimary();
+ UserHandle tabOwnerHandle = adapter.getUserHandle();
+ boolean launchedAsSameUser = launchedAs.getHandle().equals(tabOwnerHandle);
+ Profile.Type tabOwnerType = mProfileHelper.findProfileType(tabOwnerHandle);
+
+ // Not applicable for private profile.
+ if (launchedAsProfile.getType() == Profile.Type.PRIVATE
+ || tabOwnerType == Profile.Type.PRIVATE) {
return null;
}
- if (resolverListAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
- return mNoWorkToPersonalEmptyState;
- } else {
- return mNoPersonalToWorkEmptyState;
+ // Allow access to the tab when launched by the same user as the tab owner
+ // or when there is at least one target which is permitted for cross-profile.
+ if (launchedAsSameUser || anyCrossProfileAllowedIntents(adapter, tabOwnerHandle)) {
+ return null;
}
- }
+ switch (launchedAsProfile.getType()) {
+ case WORK: return mNoWorkToPersonalEmptyState;
+ case PERSONAL: return mNoPersonalToWorkEmptyState;
+ }
+ return null;
+ }
/**
* Empty state that gets strings from the device policy manager and tracks events into
diff --git a/java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java
index 43785db3..5d7cf26e 100644
--- a/java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/v2/profiles/MultiProfilePagerAdapter.java
@@ -15,7 +15,6 @@
*/
package com.android.intentresolver.v2.profiles;
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Trace;
import android.os.UserHandle;
@@ -32,6 +31,7 @@ import androidx.viewpager.widget.ViewPager;
import com.android.intentresolver.ResolverListAdapter;
import com.android.intentresolver.emptystate.EmptyState;
import com.android.intentresolver.emptystate.EmptyStateProvider;
+import com.android.intentresolver.v2.shared.model.Profile;
import com.android.internal.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -61,10 +61,11 @@ public class MultiProfilePagerAdapter<
SinglePageAdapterT,
ListAdapterT extends ResolverListAdapter> extends PagerAdapter {
- public static final int PROFILE_PERSONAL = 0;
- public static final int PROFILE_WORK = 1;
+ public static final int PROFILE_PERSONAL = Profile.Type.PERSONAL.ordinal();
+ public static final int PROFILE_WORK = Profile.Type.WORK.ordinal();
- @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
+ // Removed, must be constants. This is only used for linting anyway.
+ // @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
public @interface ProfileType {}
private final Function<SinglePageAdapterT, ListAdapterT> mListAdapterExtractor;
diff --git a/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java b/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
index d13677e1..47d9c8c2 100644
--- a/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
+++ b/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
@@ -53,7 +53,7 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
private UsageStatsManager mUsm;
@Override
- public ChooserListAdapter createChooserListAdapter(
+ public final ChooserListAdapter createChooserListAdapter(
Context context,
List<Intent> payloadIntents,
Intent[] initialIntents,
@@ -140,7 +140,7 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- protected ChooserListController createListController(UserHandle userHandle) {
+ public final ChooserListController createListController(UserHandle userHandle) {
if (userHandle == UserHandle.SYSTEM) {
return sOverrides.resolverListController;
}
diff --git a/tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt b/tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt
index b4df058c..2022d967 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt
+++ b/tests/unit/src/com/android/intentresolver/v2/ProfileAvailabilityTest.kt
@@ -16,7 +16,6 @@
package com.android.intentresolver.v2
-import android.util.Log
import com.android.intentresolver.v2.data.repository.FakeUserRepository
import com.android.intentresolver.v2.domain.interactor.UserInteractor
import com.android.intentresolver.v2.shared.model.Profile
@@ -27,8 +26,6 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
-private const val TAG = "ProfileAvailabilityTest"
-
@OptIn(ExperimentalCoroutinesApi::class)
class ProfileAvailabilityTest {
private val personalUser = User(0, User.Role.PERSONAL)
@@ -42,7 +39,7 @@ class ProfileAvailabilityTest {
@Test
fun testProfileAvailable() = runTest {
- val availability = ProfileAvailability(backgroundScope, interactor)
+ val availability = ProfileAvailability(backgroundScope, interactor, mapOf())
runCurrent()
assertThat(availability.isAvailable(personalProfile)).isTrue()
@@ -61,7 +58,7 @@ class ProfileAvailabilityTest {
@Test
fun waitingToEnableProfile() = runTest {
- val availability = ProfileAvailability(backgroundScope, interactor)
+ val availability = ProfileAvailability(backgroundScope, interactor, mapOf())
runCurrent()
availability.requestQuietModeState(workProfile, true)
@@ -75,4 +72,4 @@ class ProfileAvailabilityTest {
assertThat(availability.waitingToEnableProfile).isFalse()
}
-} \ No newline at end of file
+}