diff options
author | 2024-03-04 15:46:54 -0500 | |
---|---|---|
committer | 2024-03-06 17:06:23 -0500 | |
commit | 35d5f23ff4526ba7410bef545fbbd6ec75d41649 (patch) | |
tree | ad5b76ab9660a8783ff6352ec6f8a69a09d55230 | |
parent | bb0893ed53590a8c116a3ca7ee8b2fbd869fe5c6 (diff) |
ChooserActivity Profile integration [2/2]
* updates 'MultiProfilePagerAdapter' creation
* create tab config list from available profiles
* replace MPPA profile constants with Profile.Type.ordinal
* replace Tab tags with Profile.Type.name()
Bug: 311348033
Test: atest IntentResolver-tests-activity:com.android.intentresolver.v2
Test: atest IntentResolver-tests-unit
Change-Id: Id874721eef89661a5c2dbeb795c2096da544deca
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 +} |