diff options
| author | 2024-03-01 11:43:26 -0500 | |
|---|---|---|
| committer | 2024-03-06 17:06:21 -0500 | |
| commit | bb0893ed53590a8c116a3ca7ee8b2fbd869fe5c6 (patch) | |
| tree | a8420e6257eac529c36298e93ffb61ffa9d96c2e /java | |
| parent | 720600abecfa30f8ebcddf98bd897572e0bed8b4 (diff) | |
ChooserActivity Profile integration [1/2]
* connects ChooserActivity with UserInteractor
* replaces all existing references as the source of UserHandles
* app continues to explicity use the same profile types as previous
* updates Activity tests to use FakeUserRepository
A following change will modify initialization to incorporate build
the UI based on the list of profiles, instead of explicit references
to individual profile types.
Bug: 300157408
Bug: 311348033
Test: atest IntentResolver-tests-activity:com.android.intentresolver.v2
Change-Id: I6043e57fd7a8aff8c252e2b12171d457612d35dc
Diffstat (limited to 'java')
7 files changed, 320 insertions, 318 deletions
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java index bf651bbf..8fe64da7 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActivity.java +++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java @@ -32,7 +32,6 @@ 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.ui.model.ActivityModel.ACTIVITY_MODEL_KEY; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET; import static java.util.Collections.emptyList; @@ -57,22 +56,18 @@ import android.content.IntentFilter; import android.content.IntentSender; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; -import android.content.pm.UserInfo; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Insets; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.StrictMode; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; -import android.os.UserManager; import android.service.chooser.ChooserTarget; import android.stats.devicepolicy.DevicePolicyEnums; import android.text.TextUtils; @@ -103,7 +98,6 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.ViewPager; -import com.android.intentresolver.AnnotatedUserHandles; import com.android.intentresolver.ChooserGridLayoutManager; import com.android.intentresolver.ChooserListAdapter; import com.android.intentresolver.ChooserRefinementManager; @@ -118,7 +112,6 @@ import com.android.intentresolver.ResolverListAdapter; import com.android.intentresolver.ResolverListController; import com.android.intentresolver.ResolverViewPager; import com.android.intentresolver.StartsSelectedItem; -import com.android.intentresolver.WorkProfileAvailabilityManager; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.MultiDisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; @@ -141,6 +134,7 @@ import com.android.intentresolver.model.ResolverRankerServiceResolverComparator; import com.android.intentresolver.shortcuts.AppPredictorFactory; import com.android.intentresolver.shortcuts.ShortcutLoader; import com.android.intentresolver.v2.data.repository.DevicePolicyResources; +import com.android.intentresolver.v2.domain.interactor.UserInteractor; import com.android.intentresolver.v2.emptystate.NoAppsAvailableEmptyStateProvider; import com.android.intentresolver.v2.emptystate.NoCrossProfileEmptyStateProvider; import com.android.intentresolver.v2.emptystate.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState; @@ -154,6 +148,7 @@ import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.ProfileTy 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.ShareResultSender; import com.android.intentresolver.v2.ui.ShareResultSenderFactory; @@ -173,7 +168,6 @@ import com.google.common.collect.ImmutableList; import dagger.hilt.android.AndroidEntryPoint; import kotlin.Pair; -import kotlin.Unit; import java.util.ArrayList; import java.util.Arrays; @@ -213,7 +207,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements /** * Transition name for the first image preview. * To be used for shared element transition into this activity. - * @hide */ public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image"; @@ -240,7 +233,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private PackageMonitor mWorkPackageMonitor; protected View mProfileView; - protected ActivityLogic mLogic; protected ResolverDrawerLayout mResolverDrawerLayout; protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; protected final LatencyTracker mLatencyTracker = getLatencyTracker(); @@ -274,6 +266,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1; private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2; + @Inject public UserInteractor mUserInteractor; @Inject public ChooserHelper mChooserHelper; @Inject public FeatureFlags mFeatureFlags; @Inject public android.service.chooser.FeatureFlags mChooserServiceFeatureFlags; @@ -287,8 +280,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Inject public ClipboardManager mClipboardManager; @Inject public IntentForwarding mIntentForwarding; @Inject public ShareResultSenderFactory mShareResultSenderFactory; - @Nullable - private ShareResultSender mShareResultSender; + + private ActivityModel mActivityModel; + private ChooserRequest mRequest; + private ProfileHelper mProfiles; + private ProfileAvailability mProfileAvailability; + @Nullable private ShareResultSender mShareResultSender; private ChooserRefinementManager mRefinementManager; @@ -339,15 +336,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private ChooserViewModel mViewModel; - private ActivityModel mActivityModel; - - @VisibleForTesting - protected ChooserActivityLogic createActivityLogic() { - return new ChooserActivityLogic( - TAG, - /* activity = */ this, - this::onWorkProfileStatusUpdated); - } @NonNull @Override @@ -361,42 +349,17 @@ public class ChooserActivity extends Hilt_ChooserActivity implements protected final void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "onCreate"); - mViewModel = new ViewModelProvider(this).get(ChooserViewModel.class); - mActivityModel = mViewModel.getActivityModel(); - - int callerUid = mActivityModel.getLaunchedFromUid(); - if (callerUid < 0 || UserHandle.isIsolated(callerUid)) { - Log.e(TAG, "Can't start a resolver from uid " + callerUid); - finish(); - } setTheme(R.style.Theme_DeviceDefault_Chooser); - Tracer.INSTANCE.markLaunched(); - if (!mViewModel.init()) { - finish(); - return; - } - // The post-create callback is invoked when this function returns, via Lifecycle. - mChooserHelper.setPostCreateCallback(this::init); - - IntentSender chosenComponentSender = - mViewModel.getChooserRequest().getChosenComponentSender(); - if (chosenComponentSender != null) { - mShareResultSender = mShareResultSenderFactory - .create(mActivityModel.getLaunchedFromUid(), chosenComponentSender); - } - mLogic = createActivityLogic(); + // Initializer is invoked when this function returns, via Lifecycle. + mChooserHelper.setInitializer(this::initialize); } @Override protected final void onStart() { super.onStart(); - this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); - if (hasWorkProfile()) { - mLogic.getWorkProfileAvailabilityManager().registerWorkProfileStateReceiver(this); - } } @Override @@ -437,7 +400,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements finish(); } } - mLogic.getWorkProfileAvailabilityManager().unregisterWorkProfileStateReceiver(this); if (mRefinementManager != null) { mRefinementManager.onActivityStop(isChangingConfigurations()); @@ -465,7 +427,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mPersonalPackageMonitor.register( this, getMainLooper(), - requireAnnotatedUserHandles().personalProfileUserHandle, + mProfiles.getPersonalHandle(), false); if (hasWorkProfile()) { if (mWorkPackageMonitor == null) { @@ -475,18 +437,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mWorkPackageMonitor.register( this, getMainLooper(), - requireAnnotatedUserHandles().workProfileUserHandle, + mProfiles.getWorkHandle(), false); } mRegistered = true; } - WorkProfileAvailabilityManager workProfileAvailabilityManager = - mLogic.getWorkProfileAvailabilityManager(); - if (hasWorkProfile() && workProfileAvailabilityManager.isWaitingToEnableWorkProfile()) { - if (workProfileAvailabilityManager.isQuietModeEnabled()) { - workProfileAvailabilityManager.markWorkProfileEnabledBroadcastReceived(); - } - } mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); } @@ -509,31 +464,47 @@ public class ChooserActivity extends Hilt_ChooserActivity implements destroyProfileRecords(); } - private void init() { + /** DO NOT CALL. Only for use from ChooserHelper as a callback. */ + private void initialize(Profile launchedAs, List<Profile> profiles) { + 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); + mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated); + mIntentReceivedTime.set(System.currentTimeMillis()); mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET); mPinnedSharedPrefs = getPinnedSharedPrefs(this); + IntentSender chosenComponentSender = mRequest.getChosenComponentSender(); + if (chosenComponentSender != null) { + mShareResultSender = mShareResultSenderFactory.create( + mViewModel.getActivityModel().getLaunchedFromUid(), chosenComponentSender); + } + mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); mShouldDisplayLandscape = shouldDisplayLandscape(getResources().getConfiguration().orientation); - ChooserRequest chooserRequest = mViewModel.getChooserRequest(); - setRetainInOnStop(chooserRequest.shouldRetainInOnStop()); + setRetainInOnStop(mRequest.shouldRetainInOnStop()); createProfileRecords( new AppPredictorFactory( this, - Objects.toString(chooserRequest.getSharedText(), null), - chooserRequest.getShareTargetFilter(), + Objects.toString(mRequest.getSharedText(), null), + mRequest.getShareTargetFilter(), mAppPredictionAvailable ), - chooserRequest.getShareTargetFilter() + mRequest.getShareTargetFilter() ); - Intent intent = mViewModel.getChooserRequest().getTargetIntent(); - List<Intent> initialIntents = mViewModel.getChooserRequest().getInitialIntents(); + Intent intent = mRequest.getTargetIntent(); + List<Intent> initialIntents = mRequest.getInitialIntents(); + Log.d(TAG, "createMultiProfilePagerAdapter"); mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter( requireNonNullElse(initialIntents, emptyList()).toArray(new Intent[0]), /* resolutionList = */ null, @@ -545,7 +516,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mPersonalPackageMonitor.register( this, getMainLooper(), - requireAnnotatedUserHandles().personalProfileUserHandle, + mProfiles.getPersonalHandle(), false ); if (hasWorkProfile()) { @@ -554,7 +525,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mWorkPackageMonitor.register( this, getMainLooper(), - requireAnnotatedUserHandles().workProfileUserHandle, + mProfiles.getWorkHandle(), false ); } @@ -622,10 +593,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements new ViewModelProvider(this, createPreviewViewModelFactory()) .get(BasePreviewViewModel.class); previewViewModel.init( - chooserRequest.getTargetIntent(), - mActivityModel.getIntent(), - chooserRequest.getAdditionalContentUri(), - chooserRequest.getFocusedItemPosition(), + mRequest.getTargetIntent(), + mViewModel.getActivityModel().getIntent(), + mRequest.getAdditionalContentUri(), + mRequest.getFocusedItemPosition(), mChooserServiceFeatureFlags.chooserPayloadToggling()); ChooserActionFactory chooserActionFactory = createChooserActionFactory(); ChooserContentPreviewUi.ActionFactory actionFactory = chooserActionFactory; @@ -647,13 +618,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mChooserContentPreviewUi = new ChooserContentPreviewUi( getCoroutineScope(getLifecycle()), previewViewModel.getPreviewDataProvider(), - chooserRequest.getTargetIntent(), + mRequest.getTargetIntent(), previewViewModel.getImageLoader(), actionFactory, mEnterTransitionAnimationDelegate, new HeadlineGeneratorImpl(this), - chooserRequest.getContentTypeHint(), - chooserRequest.getMetadataText(), + mRequest.getContentTypeHint(), + mRequest.getMetadataText(), mChooserServiceFeatureFlags.chooserPayloadToggling()); updateStickyContentPreview(); if (shouldShowStickyContentPreview() @@ -665,7 +636,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - mIntentReceivedTime.get(); getEventLog().logChooserActivityShown( - isWorkProfile(), chooserRequest.getTargetType(), systemCost); + isWorkProfile(), mRequest.getTargetType(), systemCost); if (mResolverDrawerLayout != null) { mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange); @@ -679,29 +650,26 @@ public class ChooserActivity extends Hilt_ChooserActivity implements Log.d(TAG, "System Time Cost is " + systemCost); } getEventLog().logShareStarted( - chooserRequest.getReferrerPackage(), - chooserRequest.getTargetType(), - chooserRequest.getCallerChooserTargets().size(), - chooserRequest.getInitialIntents().size(), + mRequest.getReferrerPackage(), + mRequest.getTargetType(), + mRequest.getCallerChooserTargets().size(), + mRequest.getInitialIntents().size(), isWorkProfile(), mChooserContentPreviewUi.getPreferredContentPreview(), - chooserRequest.getTargetAction(), - chooserRequest.getChooserActions().size(), - chooserRequest.getModifyShareAction() != null + mRequest.getTargetAction(), + mRequest.getChooserActions().size(), + mRequest.getModifyShareAction() != null ); mEnterTransitionAnimationDelegate.postponeTransition(); + Tracer.INSTANCE.markLaunched(); } - private void restore(@Nullable Bundle savedInstanceState) { - if (savedInstanceState != null) { - // onRestoreInstanceState - //resetButtonBar(); - ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); - if (viewPager != null) { - viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); - } + @Override + protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { + ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); + if (viewPager != null) { + viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); } - mChooserMultiProfilePagerAdapter.clearInactiveProfileCache(); } @@ -792,7 +760,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET) .setBoolean(activeListAdapter.getUserHandle() - .equals(requireAnnotatedUserHandles().personalProfileUserHandle)) + .equals(mProfiles.getPersonalHandle())) .setStrings(getMetricsCategory()) .write(); safelyStartActivity(activeProfileTarget); @@ -884,10 +852,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } } - CharSequence title = mViewModel.getChooserRequest().getTitle() != null - ? mViewModel.getChooserRequest().getTitle() - : getTitleForAction(mViewModel.getChooserRequest().getTargetIntent(), - mViewModel.getChooserRequest().getDefaultTitleResource()); + CharSequence title = mRequest.getTitle() != null + ? mRequest.getTitle() + : getTitleForAction(mRequest.getTargetIntent(), + mRequest.getDefaultTitleResource()); if (!TextUtils.isEmpty(title)) { final TextView titleView = findViewById(com.android.internal.R.id.title); @@ -942,7 +910,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements // If needed, show that intent is forwarded // from managed profile to owner or other way around. String profileSwitchMessage = mIntentForwarding.forwardMessageFor( - mViewModel.getChooserRequest().getTargetIntent()); + mRequest.getTargetIntent()); if (profileSwitchMessage != null) { Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show(); } @@ -967,14 +935,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED) .setBoolean( currentUserHandle.equals( - requireAnnotatedUserHandles().personalProfileUserHandle)) + mProfiles.getPersonalHandle())) .setStrings(getMetricsCategory(), cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target") .write(); } private boolean hasWorkProfile() { - return requireAnnotatedUserHandles().workProfileUserHandle != null; + return mProfiles.getWorkHandle() != null; } private LatencyTracker getLatencyTracker() { return LatencyTracker.getInstance(this); @@ -999,8 +967,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider(); final EmptyStateProvider workProfileOffEmptyStateProvider = - new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle, - mLogic.getWorkProfileAvailabilityManager(), + new WorkProfilePausedEmptyStateProvider( + this, + mProfiles, + mProfileAvailability, /* onSwitchOnWorkSelectedListener= */ () -> { if (mOnSwitchOnWorkSelectedListener != null) { @@ -1012,9 +982,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider( this, workProfileUserHandle, - requireAnnotatedUserHandles().personalProfileUserHandle, + mProfiles.getPersonalHandle(), getMetricsCategory(), - requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch + mProfiles.getTabOwnerUserHandleForLaunch() ); // Return composite provider, the order matters (the higher, the more priority) @@ -1025,74 +995,24 @@ public class ChooserActivity extends Hilt_ChooserActivity implements ); } - private boolean supportsManagedProfiles(ResolveInfo resolveInfo) { - try { - ApplicationInfo appInfo = mPackageManager.getApplicationInfo( - resolveInfo.activityInfo.packageName, 0 /* default flags */); - return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP; - } catch (PackageManager.NameNotFoundException e) { - return false; - } - } - - private boolean hasManagedProfile() { - UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); - if (userManager == null) { - return false; - } - - try { - List<UserInfo> profiles = userManager.getProfiles(getUserId()); - for (UserInfo userInfo : profiles) { - if (userInfo != null && userInfo.isManagedProfile()) { - return true; - } - } - } catch (SecurityException e) { - return false; - } - return false; - } - - /** - * Returns the {@link UserHandle} to use when querying resolutions for intents in a - * {@link ResolverListController} configured for the provided {@code userHandle}. - */ - protected final UserHandle getQueryIntentsUser(UserHandle userHandle) { - return requireAnnotatedUserHandles().getQueryIntentsUser(userHandle); - } - - protected final boolean isLaunchedAsCloneProfile() { - UserHandle launchUser = requireAnnotatedUserHandles().userHandleSharesheetLaunchedAs; - UserHandle cloneUser = requireAnnotatedUserHandles().cloneProfileUserHandle; - return hasCloneProfile() && launchUser.equals(cloneUser); - } - - private boolean hasCloneProfile() { - return requireAnnotatedUserHandles().cloneProfileUserHandle != null; - } - /** * Returns the {@link List} of {@link UserHandle} to pass on to the * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}. */ - @VisibleForTesting(visibility = PROTECTED) - public final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) { + private List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) { return getResolverRankerServiceUserHandleListInternal(userHandle); } - - @VisibleForTesting - protected List<UserHandle> getResolverRankerServiceUserHandleListInternal( + private List<UserHandle> getResolverRankerServiceUserHandleListInternal( UserHandle userHandle) { List<UserHandle> userList = new ArrayList<>(); userList.add(userHandle); // Add clonedProfileUserHandle to the list only if we are: // a. Building the Personal Tab. // b. CloneProfile exists on the device. - if (userHandle.equals(requireAnnotatedUserHandles().personalProfileUserHandle) - && hasCloneProfile()) { - userList.add(requireAnnotatedUserHandles().cloneProfileUserHandle); + if (userHandle.equals(mProfiles.getPersonalHandle()) + && mProfiles.getCloneUserPresent()) { + userList.add(mProfiles.getCloneHandle()); } return userList; } @@ -1124,7 +1044,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements public final void onHandlePackagesChanged(ResolverListAdapter listAdapter) { if (!mChooserMultiProfilePagerAdapter.onHandlePackagesChanged( (ChooserListAdapter) listAdapter, - mLogic.getWorkProfileAvailabilityManager().isWaitingToEnableWorkProfile())) { + mProfileAvailability.getWaitingToEnableProfile())) { // We no longer have any items... just finish the activity. finish(); } @@ -1284,22 +1204,24 @@ public class ChooserActivity extends Hilt_ChooserActivity implements ////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////// - private AnnotatedUserHandles requireAnnotatedUserHandles() { - return requireNonNull(mLogic.getAnnotatedUserHandles()); - } - private void createProfileRecords( AppPredictorFactory factory, IntentFilter targetIntentFilter) { - UserHandle mainUserHandle = requireAnnotatedUserHandles().personalProfileUserHandle; + UserHandle mainUserHandle = mProfiles.getPersonalHandle(); ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory); if (record.shortcutLoader == null) { Tracer.INSTANCE.endLaunchToShortcutTrace(); } - UserHandle workUserHandle = requireAnnotatedUserHandles().workProfileUserHandle; + UserHandle workUserHandle = mProfiles.getWorkHandle(); if (workUserHandle != null) { createProfileRecord(workUserHandle, targetIntentFilter, factory); } + + UserHandle privateUserHandle = mProfiles.getPrivateHandle(); + if (privateUserHandle != null && mProfileAvailability.isAvailable( + requireNonNull(mProfiles.getPrivateProfile()))) { + createProfileRecord(privateUserHandle, targetIntentFilter, factory); + } } private ProfileRecord createProfileRecord( @@ -1339,7 +1261,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements callback); } - static SharedPreferences getPinnedSharedPrefs(Context context) { + private SharedPreferences getPinnedSharedPrefs(Context context) { return context.getSharedPreferences(PINNED_SHARED_PREFS_NAME, MODE_PRIVATE); } @@ -1358,7 +1280,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } protected EmptyStateProvider createBlockerEmptyStateProvider() { - final boolean isSendAction = mViewModel.getChooserRequest().isSendActionTarget(); + final boolean isSendAction = mRequest.isSendActionTarget(); final EmptyState noWorkToPersonalEmptyState = new DevicePolicyBlockerEmptyState( @@ -1387,11 +1309,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER); return new NoCrossProfileEmptyStateProvider( - requireAnnotatedUserHandles().personalProfileUserHandle, + mProfiles.getPersonalHandle(), noWorkToPersonalEmptyState, noPersonalToWorkEmptyState, createCrossProfileIntentsChecker(), - requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch); + mProfiles.getTabOwnerUserHandleForLaunch()); } private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile( @@ -1400,11 +1322,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements boolean filterLastUsed) { ChooserGridAdapter adapter = createChooserGridAdapter( /* context */ this, - mViewModel.getChooserRequest().getPayloadIntents(), + mRequest.getPayloadIntents(), initialIntents, rList, filterLastUsed, - /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle + /* userHandle */ mProfiles.getPersonalHandle() ); return new ChooserMultiProfilePagerAdapter( /* context */ this, @@ -1419,7 +1341,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements /* workProfileQuietModeChecker= */ () -> false, /* defaultProfile= */ PROFILE_PERSONAL, /* workProfileUserHandle= */ null, - requireAnnotatedUserHandles().cloneProfileUserHandle, + mProfiles.getCloneHandle(), mMaxTargetsPerRow, mFeatureFlags); } @@ -1431,19 +1353,19 @@ public class ChooserActivity extends Hilt_ChooserActivity implements int selectedProfile = findSelectedProfile(); ChooserGridAdapter personalAdapter = createChooserGridAdapter( /* context */ this, - mViewModel.getChooserRequest().getPayloadIntents(), + mRequest.getPayloadIntents(), selectedProfile == PROFILE_PERSONAL ? initialIntents : null, rList, filterLastUsed, - /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle + /* userHandle */ mProfiles.getPersonalHandle() ); ChooserGridAdapter workAdapter = createChooserGridAdapter( /* context */ this, - mViewModel.getChooserRequest().getPayloadIntents(), + mRequest.getPayloadIntents(), selectedProfile == PROFILE_WORK ? initialIntents : null, rList, filterLastUsed, - /* userHandle */ requireAnnotatedUserHandles().workProfileUserHandle + /* userHandle */ mProfiles.getWorkHandle() ); return new ChooserMultiProfilePagerAdapter( /* context */ this, @@ -1460,17 +1382,20 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mDevicePolicyResources.getWorkTabAccessibilityLabel(), TAB_TAG_WORK, workAdapter)), - createEmptyStateProvider(requireAnnotatedUserHandles().workProfileUserHandle), - () -> mLogic.getWorkProfileAvailabilityManager().isQuietModeEnabled(), + createEmptyStateProvider(mProfiles.getWorkHandle()), + /* Supplier<Boolean> (QuietMode enabled) == !(available) */ + () -> !(mProfiles.getWorkProfilePresent() + && mProfileAvailability.isAvailable( + requireNonNull(mProfiles.getWorkProfile()))), selectedProfile, - requireAnnotatedUserHandles().workProfileUserHandle, - requireAnnotatedUserHandles().cloneProfileUserHandle, + mProfiles.getWorkHandle(), + mProfiles.getCloneHandle(), mMaxTargetsPerRow, mFeatureFlags); } private int findSelectedProfile() { - return getProfileForUser(requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch); + return getProfileForUser(mProfiles.getTabOwnerUserHandleForLaunch()); } /** @@ -1478,9 +1403,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements * @return true if it is work profile, false if it is parent profile (or no work profile is * set up) */ - protected boolean isWorkProfile() { - return getSystemService(UserManager.class) - .getUserInfo(UserHandle.myUserId()).isManagedProfile(); + private boolean isWorkProfile() { + return mProfiles.getLaunchedAsProfileType() == Profile.Type.WORK; } //@Override @@ -1618,12 +1542,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override // ResolverListCommunicator public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { - ChooserRequest chooserRequest = mViewModel.getChooserRequest(); - Intent result = defIntent; - if (chooserRequest.getReplacementExtras() != null) { + if (mRequest.getReplacementExtras() != null) { final Bundle replExtras = - chooserRequest.getReplacementExtras().getBundle(aInfo.packageName); + mRequest.getReplacementExtras().getBundle(aInfo.packageName); if (replExtras != null) { result = new Intent(defIntent); result.putExtras(replExtras); @@ -1652,13 +1574,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private void addCallerChooserTargets() { - ChooserRequest chooserRequest = mViewModel.getChooserRequest(); - if (!chooserRequest.getCallerChooserTargets().isEmpty()) { + if (!mRequest.getCallerChooserTargets().isEmpty()) { // Send the caller's chooser targets only to the default profile. if (mChooserMultiProfilePagerAdapter.getActiveProfile() == findSelectedProfile()) { mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( /* origTarget */ null, - new ArrayList<>(chooserRequest.getCallerChooserTargets()), + new ArrayList<>(mRequest.getCallerChooserTargets()), TARGET_TYPE_DEFAULT, /* directShareShortcutInfoCache */ Collections.emptyMap(), /* directShareAppTargetCache */ Collections.emptyMap()); @@ -1676,8 +1597,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return false; } - return mActivityModel.getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, - true); + // TODO: migrate to ChooserRequest + return mViewModel.getActivityModel().getIntent() + .getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true); } private void showTargetDetails(TargetInfo targetInfo) { @@ -1694,7 +1616,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned(); IntentFilter intentFilter; intentFilter = targetInfo.isSelectableTargetInfo() - ? mViewModel.getChooserRequest().getShareTargetFilter() : null; + ? mRequest.getShareTargetFilter() : null; String shortcutTitle = targetInfo.isSelectableTargetInfo() ? targetInfo.getDisplayLabel().toString() : null; String shortcutIdKey = targetInfo.getDirectShareShortcutId(); @@ -1714,7 +1636,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements protected boolean onTargetSelected(TargetInfo target) { if (mRefinementManager.maybeHandleSelection( target, - mViewModel.getChooserRequest().getRefinementIntentSender(), + mRequest.getRefinementIntentSender(), getApplication(), getMainThreadHandler())) { return false; @@ -1788,7 +1710,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements targetInfo.getResolveInfo().activityInfo.processName, which, /* directTargetAlsoRanked= */ getRankedPosition(targetInfo), - mViewModel.getChooserRequest().getCallerChooserTargets().size(), + mRequest.getCallerChooserTargets().size(), targetInfo.getHashedTargetIdForMetrics(this), targetInfo.isPinned(), mIsSuccessfullySelected, @@ -1867,7 +1789,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (info != null) { sendClickToAppPredictor(info); final ResolveInfo ri = info.getResolveInfo(); - Intent targetIntent = mViewModel.getChooserRequest().getTargetIntent(); + Intent targetIntent = mRequest.getTargetIntent(); if (ri != null && ri.activityInfo != null && targetIntent != null) { ChooserListAdapter currentListAdapter = mChooserMultiProfilePagerAdapter.getActiveListAdapter(); @@ -1895,7 +1817,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (targetIntent == null) { return; } - Intent originalTargetIntent = new Intent(mViewModel.getChooserRequest().getTargetIntent()); + Intent originalTargetIntent = new Intent(mRequest.getTargetIntent()); // Our TargetInfo implementations add associated component to the intent, let's do the same // for the sake of the comparison below. if (targetIntent.getComponent() != null) { @@ -1965,7 +1887,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements ProfileRecord record = getProfileRecord(userHandle); // We cannot use APS service when clone profile is present as APS service cannot sort // cross profile targets as of now. - return ((record == null) || (requireAnnotatedUserHandles().cloneProfileUserHandle != null)) + return ((record == null) || (mProfiles.getCloneUserPresent())) ? null : record.appPredictor; } @@ -1981,7 +1903,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements List<ResolveInfo> rList, boolean filterLastUsed, UserHandle userHandle) { - ChooserRequest request = mViewModel.getChooserRequest(); ChooserListAdapter chooserListAdapter = createChooserListAdapter( context, payloadIntents, @@ -1990,8 +1911,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements filterLastUsed, createListController(userHandle), userHandle, - request.getTargetIntent(), - request.getReferrerFillInIntent(), + mRequest.getTargetIntent(), + mRequest.getReferrerFillInIntent(), mMaxTargetsPerRow ); @@ -2045,9 +1966,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements Intent targetIntent, Intent referrerFillInIntent, int maxTargetsPerRow) { - UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile() - && userHandle.equals(requireAnnotatedUserHandles().personalProfileUserHandle) - ? requireAnnotatedUserHandles().cloneProfileUserHandle : userHandle; + UserHandle initialIntentsUserSpace = mProfiles.getQueryIntentsHandle(userHandle); return new ChooserListAdapter( context, payloadIntents, @@ -2073,19 +1992,18 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mFeatureFlags); } - protected Unit onWorkProfileStatusUpdated() { - UserHandle workUser = requireAnnotatedUserHandles().workProfileUserHandle; + private void onWorkProfileStatusUpdated() { + UserHandle workUser = mProfiles.getWorkHandle(); ProfileRecord record = workUser == null ? null : getProfileRecord(workUser); if (record != null && record.shortcutLoader != null) { record.shortcutLoader.reset(); } if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle().equals( - requireAnnotatedUserHandles().workProfileUserHandle)) { + mProfiles.getWorkHandle())) { mChooserMultiProfilePagerAdapter.rebuildActiveTab(true); } else { mChooserMultiProfilePagerAdapter.clearInactiveProfileCache(); } - return Unit.INSTANCE; } @VisibleForTesting @@ -2095,8 +2013,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (appPredictor != null) { resolverComparator = new AppPredictionServiceResolverComparator( this, - mViewModel.getChooserRequest().getTargetIntent(), - mViewModel.getChooserRequest().getLaunchedFromPackage(), + mRequest.getTargetIntent(), + mRequest.getLaunchedFromPackage(), appPredictor, userHandle, getEventLog(), @@ -2106,8 +2024,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements resolverComparator = new ResolverRankerServiceResolverComparator( this, - mViewModel.getChooserRequest().getTargetIntent(), - mViewModel.getChooserRequest().getReferrerPackage(), + mRequest.getTargetIntent(), + mRequest.getReferrerPackage(), null, getEventLog(), getResolverRankerServiceUserHandleList(userHandle), @@ -2117,12 +2035,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return new ChooserListController( this, mPackageManager, - mViewModel.getChooserRequest().getTargetIntent(), - mViewModel.getChooserRequest().getReferrerPackage(), - requireAnnotatedUserHandles().userIdOfCallingApp, + mRequest.getTargetIntent(), + mRequest.getReferrerPackage(), + mViewModel.getActivityModel().getLaunchedFromUid(), resolverComparator, - getQueryIntentsUser(userHandle), - mViewModel.getChooserRequest().getFilteredComponentNames(), + mProfiles.getQueryIntentsHandle(userHandle), + mRequest.getFilteredComponentNames(), mPinnedSharedPrefs); } @@ -2132,13 +2050,12 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private ChooserActionFactory createChooserActionFactory() { - ChooserRequest request = mViewModel.getChooserRequest(); return new ChooserActionFactory( this, - request.getTargetIntent(), - request.getLaunchedFromPackage(), - request.getChooserActions(), - request.getModifyShareAction(), + mRequest.getTargetIntent(), + mRequest.getLaunchedFromPackage(), + mRequest.getChooserActions(), + mRequest.getModifyShareAction(), mImageEditor, getEventLog(), (isExcluded) -> mExcludeSharedText = isExcluded, @@ -2148,7 +2065,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) { safelyStartActivityAsUser( targetInfo, - requireAnnotatedUserHandles().personalProfileUserHandle + mProfiles.getPersonalHandle() ); finish(); } @@ -2160,7 +2077,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements ChooserActivity.this, sharedElement, sharedElementName); safelyStartActivityAsUser( targetInfo, - requireAnnotatedUserHandles().personalProfileUserHandle, + mProfiles.getPersonalHandle(), options.toBundle()); // Can't finish right away because the shared element transition may not // be ready to start. @@ -2317,7 +2234,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements * Returns {@link #PROFILE_PERSONAL}, otherwise. **/ private int getProfileForUser(UserHandle currentUserHandle) { - if (currentUserHandle.equals(requireAnnotatedUserHandles().workProfileUserHandle)) { + if (currentUserHandle.equals(mProfiles.getWorkHandle())) { return PROFILE_WORK; } // We return personal profile, as it is the default when there is no work profile, personal @@ -2503,8 +2420,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements * @return true if we want to show the content preview area */ protected boolean shouldShowContentPreview() { - ChooserRequest chooserRequest = mViewModel.getChooserRequest(); - return (chooserRequest != null) && chooserRequest.isSendActionTarget(); + return mRequest.isSendActionTarget(); } private void updateStickyContentPreview() { diff --git a/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt b/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt deleted file mode 100644 index 84b7d9a9..00000000 --- a/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.android.intentresolver.v2 - -import androidx.activity.ComponentActivity -import androidx.annotation.OpenForTesting - -/** - * Activity logic for [ChooserActivity]. - * - * TODO: Make this class no longer open once [ChooserActivity] no longer needs to cast to access - * [chooserRequest]. For now, this class being open is better than using reflection there. - */ -@OpenForTesting -open class ChooserActivityLogic( - tag: String, - activity: ComponentActivity, - onWorkProfileStatusUpdated: () -> Unit, -) : - ActivityLogic, - CommonActivityLogic by CommonActivityLogicImpl( - tag, - activity, - onWorkProfileStatusUpdated, - ) diff --git a/java/src/com/android/intentresolver/v2/ChooserHelper.kt b/java/src/com/android/intentresolver/v2/ChooserHelper.kt index 17bc2731..5b514614 100644 --- a/java/src/com/android/intentresolver/v2/ChooserHelper.kt +++ b/java/src/com/android/intentresolver/v2/ChooserHelper.kt @@ -17,11 +17,43 @@ package com.android.intentresolver.v2 import android.app.Activity +import android.os.UserHandle +import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.viewModels import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.intentresolver.v2.annotation.JavaInterop +import com.android.intentresolver.v2.domain.interactor.UserInteractor +import com.android.intentresolver.v2.shared.model.Profile +import com.android.intentresolver.v2.ui.model.ChooserRequest +import com.android.intentresolver.v2.ui.viewmodel.ChooserViewModel +import com.android.intentresolver.v2.validation.Invalid +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.flow.first +import kotlinx.coroutines.launch + +private const val TAG: String = "ChooserHelper" + +/** + * Provides initial values to ChooserActivity and completes initialization from onCreate. + * + * This information is collected and provided on behalf of ChooserActivity to eliminate the need for + * suspending functions within remaining synchronous startup code. + */ +@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>) +} /** * __Purpose__ @@ -30,52 +62,108 @@ import javax.inject.Inject * * __Incoming References__ * - * For use by ChooserActivity only; must not be accessed by any code outside of ChooserActivity. - * This prevents circular dependencies and coupling, and maintains unidirectional flow. This is - * important for maintaining a migration path towards healthier architecture. + * ChooserHelper must not expose any properties or functions directly back to ChooserActivity. If a + * value or operation is required by ChooserActivity, then it must be added to ChooserInitializer + * (or a new interface as appropriate) with ChooserActivity supplying a callback to receive it at + * the appropriate point. This enforces unidirectional control flow. * * __Outgoing References__ * * _ChooserActivity_ * * This class must only reference it's host as Activity/ComponentActivity; no down-cast to - * [ChooserActivity]. Other components should be passed in and not pulled from other places. This - * prevents circular dependencies from forming. + * [ChooserActivity]. Other components should be created here or supplied via Injection, and not + * referenced directly within ChooserActivity. This prevents circular dependencies from forming. If + * necessary, during cleanup the dependency can be supplied back to ChooserActivity as described + * above in 'Incoming References', see [ChooserInitializer]. * * _Elsewhere_ * * Where possible, Singleton and ActivityScoped dependencies should be injected here instead of * referenced from an existing location. If not available for injection, the value should be - * constructed here, then provided to where it is needed. If existing objects from ChooserActivity - * are required, supply a factory interface which satisfies the necessary dependencies and use it - * during construction. + * constructed here, then provided to where it is needed. */ - @ActivityScoped -class ChooserHelper @Inject constructor( +@JavaInterop +class ChooserHelper +@Inject +constructor( hostActivity: Activity, + private val userInteractor: UserInteractor, ) : 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 var activityPostCreate: Runnable? = null + private lateinit var activityInitializer: ChooserInitializer init { activity.lifecycle.addObserver(this) } /** - * Provides a optional callback to setup state which is not yet possible to do without circular - * dependencies or by moving more code. + * Set the initialization hook for the host activity. + * + * This _must_ be called from [ChooserActivity.onCreate]. */ - fun setPostCreateCallback(onPostCreate: Runnable) { - activityPostCreate = onPostCreate + fun setInitializer(initializer: ChooserInitializer) { + check(activity.lifecycle.currentState == Lifecycle.State.INITIALIZED) { + "setInitializer must be called before onCreate returns" + } + activityInitializer = initializer } - /** - * Invoked by Lifecycle, after Activity.onCreate() _returns_. - */ + /** Invoked by Lifecycle, after [ChooserActivity.onCreate] _returns_. */ override fun onCreate(owner: LifecycleOwner) { - activityPostCreate?.run() + Log.i(TAG, "CREATE") + Log.i(TAG, "${viewModel.activityModel}") + + val callerUid: Int = viewModel.activityModel.launchedFromUid + if (callerUid < 0 || UserHandle.isIsolated(callerUid)) { + Log.e(TAG, "Can't start a chooser from uid $callerUid") + activity.finish() + return + } + + when (val request = viewModel.initialRequest) { + is Valid -> initializeActivity(request) + is Invalid -> reportErrorsAndFinish(request) + } + } + override fun onStart(owner: LifecycleOwner) { + Log.i(TAG, "START") + } + + override fun onResume(owner: LifecycleOwner) { + Log.i(TAG, "RESUME") + } + + override fun onPause(owner: LifecycleOwner) { + Log.i(TAG, "PAUSE") + } + + override fun onStop(owner: LifecycleOwner) { + Log.i(TAG, "STOP") + } + + override fun onDestroy(owner: LifecycleOwner) { + Log.i(TAG, "DESTROY") + } + + private fun reportErrorsAndFinish(request: Invalid<ChooserRequest>) { + request.errors.forEach { it.log(TAG) } + activity.finish() + } + + 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) + } } -}
\ No newline at end of file +} diff --git a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt b/java/src/com/android/intentresolver/v2/ProfileAvailability.kt index 4d689724..2df25c41 100644 --- a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt +++ b/java/src/com/android/intentresolver/v2/ProfileAvailability.kt @@ -18,18 +18,13 @@ package com.android.intentresolver.v2 import com.android.intentresolver.v2.domain.interactor.UserInteractor import com.android.intentresolver.v2.shared.model.Profile -import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeout /** Provides availability status for profiles */ class ProfileAvailability( @@ -43,6 +38,9 @@ class ProfileAvailability( var waitingToEnableProfile = false private set + /** Set by ChooserActivity to call onWorkProfileStatusUpdated */ + var onProfileStatusChange: Runnable? = null + private var waitJob: Job? = null /** Query current profile availability. An unavailable profile is one which is not active. */ fun isAvailable(profile: Profile) = availability.value[profile] ?: false @@ -61,14 +59,14 @@ class ProfileAvailability( waitingToEnableProfile = true waitJob?.cancel() - val job = scope.launch { - // Wait for the profile to become available - // Wait for the profile to be enabled, then clear this flag - userInteractor.availability.filter { it[profile] == true }.first() - waitingToEnableProfile = false - } + val job = + scope.launch { + // Wait for the profile to become available + userInteractor.availability.filter { it[profile] == true }.first() + } job.invokeOnCompletion { waitingToEnableProfile = false + onProfileStatusChange?.run() } waitJob = job } @@ -76,4 +74,4 @@ class ProfileAvailability( // Apply the change scope.launch { userInteractor.updateState(profile, enableProfile) } } -}
\ No newline at end of file +} diff --git a/java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java b/java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java index a6fee3ec..af13f8fe 100644 --- a/java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/v2/emptystate/WorkProfilePausedEmptyStateProvider.java @@ -18,6 +18,8 @@ package com.android.intentresolver.v2.emptystate; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; +import static java.util.Objects.requireNonNull; + import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.content.Context; @@ -30,9 +32,11 @@ import androidx.annotation.Nullable; import com.android.intentresolver.MultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener; import com.android.intentresolver.R; import com.android.intentresolver.ResolverListAdapter; -import com.android.intentresolver.WorkProfileAvailabilityManager; import com.android.intentresolver.emptystate.EmptyState; import com.android.intentresolver.emptystate.EmptyStateProvider; +import com.android.intentresolver.v2.ProfileAvailability; +import com.android.intentresolver.v2.ProfileHelper; +import com.android.intentresolver.v2.shared.model.Profile; /** * Chooser/ResolverActivity empty state provider that returns empty state which is shown when @@ -40,20 +44,20 @@ import com.android.intentresolver.emptystate.EmptyStateProvider; */ public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider { - private final UserHandle mWorkProfileUserHandle; - private final WorkProfileAvailabilityManager mWorkProfileAvailability; + private final ProfileHelper mProfileHelper; + private final ProfileAvailability mProfileAvailability; private final String mMetricsCategory; private final OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener; private final Context mContext; public WorkProfilePausedEmptyStateProvider(@NonNull Context context, - @Nullable UserHandle workProfileUserHandle, - @NonNull WorkProfileAvailabilityManager workProfileAvailability, + ProfileHelper profileHelper, + ProfileAvailability profileAvailability, @Nullable OnSwitchOnWorkSelectedListener onSwitchOnWorkSelectedListener, @NonNull String metricsCategory) { mContext = context; - mWorkProfileUserHandle = workProfileUserHandle; - mWorkProfileAvailability = workProfileAvailability; + mProfileHelper = profileHelper; + mProfileAvailability = profileAvailability; mMetricsCategory = metricsCategory; mOnSwitchOnWorkSelectedListener = onSwitchOnWorkSelectedListener; } @@ -61,22 +65,33 @@ public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider { @Nullable @Override public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) { - if (!resolverListAdapter.getUserHandle().equals(mWorkProfileUserHandle) - || !mWorkProfileAvailability.isQuietModeEnabled() - || resolverListAdapter.getCount() == 0) { + UserHandle userHandle = resolverListAdapter.getUserHandle(); + if (!mProfileHelper.getWorkProfilePresent()) { + return null; + } + Profile workProfile = requireNonNull(mProfileHelper.getWorkProfile()); + + // Policy: only show the "Work profile paused" state when: + // * provided list adapter is from the work profile + // * the list adapter is not empty + // * work profile quiet mode is _enabled_ (unavailable) + + if (!userHandle.equals(workProfile.getPrimary().getHandle()) + || resolverListAdapter.getCount() == 0 + || mProfileAvailability.isAvailable(workProfile)) { return null; } - final String title = mContext.getSystemService(DevicePolicyManager.class) + 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) -> { + return new WorkProfileOffEmptyState(title, /* EmptyState.ClickListener */ (tab) -> { tab.showSpinner(); if (mOnSwitchOnWorkSelectedListener != null) { mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected(); } - mWorkProfileAvailability.requestQuietModeEnabled(false); + mProfileAvailability.requestQuietModeState(workProfile, false); }, mMetricsCategory); } diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt index 91eed408..7ebf65a9 100644 --- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt +++ b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt @@ -65,10 +65,10 @@ internal fun Intent.maybeAddSendActionFlags() = } fun readChooserRequest( - launch: ActivityModel, + model: ActivityModel, flags: ChooserServiceFlags ): ValidationResult<ChooserRequest> { - val extras = launch.intent.extras ?: Bundle() + val extras = model.intent.extras ?: Bundle() @Suppress("DEPRECATION") return validateFrom(extras::get) { val targetIntent = required(IntentOrUri(EXTRA_INTENT)).maybeAddSendActionFlags() @@ -154,12 +154,12 @@ fun readChooserRequest( isSendActionTarget = isSendAction, targetType = targetIntent.type, launchedFromPackage = - requireNotNull(launch.launchedFromPackage) { + requireNotNull(model.launchedFromPackage) { "launch.fromPackage was null, See Activity.getLaunchedFromPackage()" }, title = customTitle, defaultTitleResource = defaultTitleResource, - referrer = launch.referrer, + referrer = model.referrer, filteredComponentNames = filteredComponents, callerChooserTargets = callerChooserTargets, chooserActions = chooserActions, diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserViewModel.kt b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserViewModel.kt index 8ed2fa29..4d87b2cb 100644 --- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserViewModel.kt +++ b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserViewModel.kt @@ -24,10 +24,11 @@ import com.android.intentresolver.v2.ui.model.ActivityModel.Companion.ACTIVITY_M import com.android.intentresolver.v2.ui.model.ChooserRequest import com.android.intentresolver.v2.validation.Invalid import com.android.intentresolver.v2.validation.Valid -import com.android.intentresolver.v2.validation.ValidationResult -import com.android.intentresolver.v2.validation.log import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow private const val TAG = "ChooserViewModel" @@ -45,23 +46,30 @@ constructor( "ActivityModel missing in SavedStateHandle! ($ACTIVITY_MODEL_KEY)" } - /** The result of reading and validating the inputs provided in savedState. */ - private val status: ValidationResult<ChooserRequest> = readChooserRequest(activityModel, flags) + /** + * Provided only for the express purpose of early exit in the event of an invalid request. + * + * Note: [request] can only be safely accessed after checking if this value is [Valid]. + */ + internal val initialRequest = readChooserRequest(activityModel, flags) - val chooserRequest: ChooserRequest by lazy { - when (status) { - is Valid -> status.value - is Invalid -> error(status.errors) - } - } + private lateinit var _request: MutableStateFlow<ChooserRequest> + + /** + * A [StateFlow] of [ChooserRequest]. + * + * Note: Only safe to access after checking if [initialRequest] is [Valid]. + */ + lateinit var request: StateFlow<ChooserRequest> + private set - fun init(): Boolean { - Log.i(TAG, "viewModel init") - if (status is Invalid) { - status.errors.forEach { finding -> finding.log(TAG) } - return false + init { + when (initialRequest) { + is Valid -> { + _request = MutableStateFlow(initialRequest.value) + request = _request.asStateFlow() + } + is Invalid -> Log.w(TAG, "initialRequest is Invalid, initialization failed") } - Log.i(TAG, "request = $chooserRequest") - return true } } |