diff options
10 files changed, 337 insertions, 185 deletions
diff --git a/java/src/com/android/intentresolver/v2/ActivityLogic.kt b/java/src/com/android/intentresolver/v2/ActivityLogic.kt index 0613882e..e5b89dfa 100644 --- a/java/src/com/android/intentresolver/v2/ActivityLogic.kt +++ b/java/src/com/android/intentresolver/v2/ActivityLogic.kt @@ -1,10 +1,18 @@ package com.android.intentresolver.v2 import android.app.Activity +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL +import android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK import android.content.Context import android.content.Intent import android.net.Uri +import android.os.UserHandle +import android.os.UserManager +import android.util.Log import androidx.activity.ComponentActivity +import com.android.intentresolver.AnnotatedUserHandles +import com.android.intentresolver.R import com.android.intentresolver.icons.TargetDataLoader /** @@ -30,11 +38,20 @@ interface ActivityLogic : CommonActivityLogic { val supportsAlwaysUseOption: Boolean /** Fetches display info for processed candidates. */ val targetDataLoader: TargetDataLoader + /** The theme to use. */ + val themeResId: Int + /** + * Message showing that intent is forwarded from managed profile to owner or other way around. + */ + val profileSwitchMessage: String? /** * Called after Activity superclass creation, but before any other onCreate logic is performed. */ fun preInitialization() + + /** Sets [profileSwitchMessage] to null */ + fun clearProfileSwitchMessage() } /** @@ -42,10 +59,21 @@ interface ActivityLogic : CommonActivityLogic { * activities (including test activities), should live here. */ interface CommonActivityLogic { + /** The tag to use when logging. */ + val tag: String /** A reference to the activity owning, and used by, this logic. */ val activity: ComponentActivity /** The name of the referring package. */ val referrerPackageName: String? + /** User manager system service. */ + val userManager: UserManager + /** Device policy manager system service. */ + val devicePolicyManager: DevicePolicyManager + /** Current [UserHandle]s retrievable by type. */ + val annotatedUserHandles: AnnotatedUserHandles? + + /** Returns display message indicating intent forwarding or null if not intent forwarding. */ + fun forwardMessageFor(intent: Intent): String? // TODO: For some reason the IDE complains about getting Activity fields from a // ComponentActivity. These are a band-aid until the bug is fixed and should be removed when @@ -60,7 +88,10 @@ interface CommonActivityLogic { * [ActivityLogic] implementations. Test implementations of [ActivityLogic] may need to create their * own [CommonActivityLogic] implementation. */ -class CommonActivityLogicImpl(activityProvider: () -> ComponentActivity) : CommonActivityLogic { +class CommonActivityLogicImpl( + override val tag: String, + activityProvider: () -> ComponentActivity, +) : CommonActivityLogic { override val activity: ComponentActivity by lazy { activityProvider() } @@ -74,6 +105,52 @@ class CommonActivityLogicImpl(activityProvider: () -> ComponentActivity) : Commo } } + override val userManager: UserManager by lazy { + activity.context.getSystemService(Context.USER_SERVICE) as UserManager + } + + override val devicePolicyManager: DevicePolicyManager by lazy { + activity.context.getSystemService(DevicePolicyManager::class.java)!! + } + + override val annotatedUserHandles: AnnotatedUserHandles? by lazy { + try { + AnnotatedUserHandles.forShareActivity(activity) + } catch (e: SecurityException) { + Log.e(tag, "Request from UID without necessary permissions", e) + null + } + } + + private val forwardToPersonalMessage: String? by lazy { + devicePolicyManager.resources.getString(FORWARD_INTENT_TO_PERSONAL) { + activity.context.getString(R.string.forward_intent_to_owner) + } + } + + private val forwardToWorkMessage: String? by lazy { + devicePolicyManager.resources.getString(FORWARD_INTENT_TO_WORK) { + activity.context.getString(R.string.forward_intent_to_work) + } + } + + override fun forwardMessageFor(intent: Intent): String? { + val contentUserHint = intent.contentUserHint + if ( + contentUserHint != UserHandle.USER_CURRENT && contentUserHint != UserHandle.myUserId() + ) { + val originUserInfo = userManager.getUserInfo(contentUserHint) + val originIsManaged = originUserInfo?.isManagedProfile ?: false + val targetIsManaged = userManager.isManagedProfile + return when { + originIsManaged && !targetIsManaged -> forwardToPersonalMessage + !originIsManaged && targetIsManaged -> forwardToWorkMessage + else -> null + } + } + return null + } + companion object { private const val ANDROID_APP_URI_SCHEME = "android-app" } diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java index d2dabfb3..36e0cad1 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActivity.java +++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java @@ -76,6 +76,7 @@ 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; @@ -244,43 +245,21 @@ public class ChooserActivity extends Hilt_ChooserActivity implements */ private boolean mFinishWhenStopped = false; - @Override - protected void onCreate(Bundle savedInstanceState) { - Tracer.INSTANCE.markLaunched(); - AtomicLong intentReceivedTime = new AtomicLong(-1); + private final AtomicLong mIntentReceivedTime = new AtomicLong(-1); + + ChooserActivity() { + super(); mLogic = new ChooserActivityLogic( TAG, () -> this, () -> mTargetDataLoader, - () -> { - intentReceivedTime.set(System.currentTimeMillis()); - mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET); - - mPinnedSharedPrefs = getPinnedSharedPrefs(this); - mMaxTargetsPerRow = - getResources().getInteger(R.integer.config_chooser_max_targets_per_row); - mShouldDisplayLandscape = - shouldDisplayLandscape(getResources().getConfiguration().orientation); - - - ChooserRequestParameters chooserRequest = - ((ChooserActivityLogic) mLogic).getChooserRequestParameters(); - if (chooserRequest == null) { - return Unit.INSTANCE; - } - setRetainInOnStop(chooserRequest.shouldRetainInOnStop()); - - createProfileRecords( - new AppPredictorFactory( - this, - chooserRequest.getSharedText(), - chooserRequest.getTargetIntentFilter() - ), - chooserRequest.getTargetIntentFilter() - ); - return Unit.INSTANCE; - } + this::onPreinitialization ); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Tracer.INSTANCE.markLaunched(); super.onCreate(savedInstanceState); if (getChooserRequest() == null) { finish(); @@ -291,6 +270,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements // Skip initializing any additional resources. return; } + setTheme(mLogic.getThemeResId()); getEventLog().logSharesheetTriggered(); @@ -336,7 +316,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } mChooserShownTime = System.currentTimeMillis(); - final long systemCost = mChooserShownTime - intentReceivedTime.get(); + final long systemCost = mChooserShownTime - mIntentReceivedTime.get(); getEventLog().logChooserActivityShown( isWorkProfile(), chooserRequest.getTargetType(), systemCost); @@ -370,6 +350,34 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mEnterTransitionAnimationDelegate.postponeTransition(); } + protected final Unit onPreinitialization() { + mIntentReceivedTime.set(System.currentTimeMillis()); + mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET); + + mPinnedSharedPrefs = getPinnedSharedPrefs(this); + mMaxTargetsPerRow = + getResources().getInteger(R.integer.config_chooser_max_targets_per_row); + mShouldDisplayLandscape = + shouldDisplayLandscape(getResources().getConfiguration().orientation); + + + ChooserRequestParameters chooserRequest = getChooserRequest(); + if (chooserRequest == null) { + return Unit.INSTANCE; + } + setRetainInOnStop(chooserRequest.shouldRetainInOnStop()); + + createProfileRecords( + new AppPredictorFactory( + this, + chooserRequest.getSharedText(), + chooserRequest.getTargetIntentFilter() + ), + chooserRequest.getTargetIntentFilter() + ); + return Unit.INSTANCE; + } + @Nullable private ChooserRequestParameters getChooserRequest() { return ((ChooserActivityLogic) mLogic).getChooserRequestParameters(); @@ -379,20 +387,19 @@ public class ChooserActivity extends Hilt_ChooserActivity implements return requireNonNull(getChooserRequest()); } - @Override - protected int appliedThemeResId() { - return R.style.Theme_DeviceDefault_Chooser; + private AnnotatedUserHandles requireAnnotatedUserHandles() { + return requireNonNull(mLogic.getAnnotatedUserHandles()); } private void createProfileRecords( AppPredictorFactory factory, IntentFilter targetIntentFilter) { - UserHandle mainUserHandle = getAnnotatedUserHandles().personalProfileUserHandle; + UserHandle mainUserHandle = requireAnnotatedUserHandles().personalProfileUserHandle; ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory); if (record.shortcutLoader == null) { Tracer.INSTANCE.endLaunchToShortcutTrace(); } - UserHandle workUserHandle = getAnnotatedUserHandles().workProfileUserHandle; + UserHandle workUserHandle = requireAnnotatedUserHandles().workProfileUserHandle; if (workUserHandle != null) { createProfileRecord(workUserHandle, targetIntentFilter, factory); } @@ -486,11 +493,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER); return new NoCrossProfileEmptyStateProvider( - getAnnotatedUserHandles().personalProfileUserHandle, + requireAnnotatedUserHandles().personalProfileUserHandle, noWorkToPersonalEmptyState, noPersonalToWorkEmptyState, createCrossProfileIntentsChecker(), - getAnnotatedUserHandles().tabOwnerUserHandleForLaunch); + requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch); } private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile( @@ -504,7 +511,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements initialIntents, rList, filterLastUsed, - /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle, + /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle, targetDataLoader); return new ChooserMultiProfilePagerAdapter( /* context */ this, @@ -512,7 +519,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements createEmptyStateProvider(/* workProfileUserHandle= */ null), /* workProfileQuietModeChecker= */ () -> false, /* workProfileUserHandle= */ null, - getAnnotatedUserHandles().cloneProfileUserHandle, + requireAnnotatedUserHandles().cloneProfileUserHandle, mMaxTargetsPerRow, mFeatureFlags); } @@ -529,7 +536,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements selectedProfile == PROFILE_PERSONAL ? initialIntents : null, rList, filterLastUsed, - /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle, + /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle, targetDataLoader); ChooserGridAdapter workAdapter = createChooserGridAdapter( /* context */ this, @@ -537,17 +544,17 @@ public class ChooserActivity extends Hilt_ChooserActivity implements selectedProfile == PROFILE_WORK ? initialIntents : null, rList, filterLastUsed, - /* userHandle */ getAnnotatedUserHandles().workProfileUserHandle, + /* userHandle */ requireAnnotatedUserHandles().workProfileUserHandle, targetDataLoader); return new ChooserMultiProfilePagerAdapter( /* context */ this, personalAdapter, workAdapter, - createEmptyStateProvider(getAnnotatedUserHandles().workProfileUserHandle), + createEmptyStateProvider(requireAnnotatedUserHandles().workProfileUserHandle), () -> mWorkProfileAvailability.isQuietModeEnabled(), selectedProfile, - getAnnotatedUserHandles().workProfileUserHandle, - getAnnotatedUserHandles().cloneProfileUserHandle, + requireAnnotatedUserHandles().workProfileUserHandle, + requireAnnotatedUserHandles().cloneProfileUserHandle, mMaxTargetsPerRow, mFeatureFlags); } @@ -556,7 +563,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements int selectedProfile = getSelectedProfileExtra(); if (selectedProfile == -1) { selectedProfile = getProfileForUser( - getAnnotatedUserHandles().tabOwnerUserHandleForLaunch); + requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch); } return selectedProfile; } @@ -802,8 +809,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (!chooserRequest.getCallerChooserTargets().isEmpty()) { // Send the caller's chooser targets only to the default profile. UserHandle defaultUser = (findSelectedProfile() == PROFILE_WORK) - ? getAnnotatedUserHandles().workProfileUserHandle - : getAnnotatedUserHandles().personalProfileUserHandle; + ? requireAnnotatedUserHandles().workProfileUserHandle + : requireAnnotatedUserHandles().personalProfileUserHandle; if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle() == defaultUser) { mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( /* origTarget */ null, @@ -1117,7 +1124,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) || (getAnnotatedUserHandles().cloneProfileUserHandle != null)) + return ((record == null) || (requireAnnotatedUserHandles().cloneProfileUserHandle != null)) ? null : record.appPredictor; } @@ -1257,8 +1264,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements int maxTargetsPerRow, TargetDataLoader targetDataLoader) { UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile() - && userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle) - ? getAnnotatedUserHandles().cloneProfileUserHandle : userHandle; + && userHandle.equals(requireAnnotatedUserHandles().personalProfileUserHandle) + ? requireAnnotatedUserHandles().cloneProfileUserHandle : userHandle; return new ChooserListAdapter( context, payloadIntents, @@ -1279,7 +1286,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override protected void onWorkProfileStatusUpdated() { - UserHandle workUser = getAnnotatedUserHandles().workProfileUserHandle; + UserHandle workUser = requireAnnotatedUserHandles().workProfileUserHandle; ProfileRecord record = workUser == null ? null : getProfileRecord(workUser); if (record != null && record.shortcutLoader != null) { record.shortcutLoader.reset(); @@ -1313,7 +1320,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements mPm, getTargetIntent(), mLogic.getReferrerPackageName(), - getAnnotatedUserHandles().userIdOfCallingApp, + requireAnnotatedUserHandles().userIdOfCallingApp, resolverComparator, getQueryIntentsUser(userHandle)); } @@ -1335,7 +1342,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) { safelyStartActivityAsUser( - targetInfo, getAnnotatedUserHandles().personalProfileUserHandle); + targetInfo, + requireAnnotatedUserHandles().personalProfileUserHandle + ); finish(); } @@ -1346,7 +1355,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements ChooserActivity.this, sharedElement, sharedElementName); safelyStartActivityAsUser( targetInfo, - getAnnotatedUserHandles().personalProfileUserHandle, + requireAnnotatedUserHandles().personalProfileUserHandle, options.toBundle()); // Can't finish right away because the shared element transition may not // be ready to start. @@ -1504,7 +1513,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements * Returns {@link #PROFILE_PERSONAL}, otherwise. **/ private int getProfileForUser(UserHandle currentUserHandle) { - if (currentUserHandle.equals(getAnnotatedUserHandles().workProfileUserHandle)) { + if (currentUserHandle.equals(requireAnnotatedUserHandles().workProfileUserHandle)) { return PROFILE_WORK; } // We return personal profile, as it is the default when there is no work profile, personal diff --git a/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt b/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt index 1db3f407..838c39e2 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt +++ b/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt @@ -4,16 +4,26 @@ import android.app.Activity import android.content.Intent import android.util.Log import androidx.activity.ComponentActivity +import androidx.annotation.OpenForTesting import com.android.intentresolver.ChooserRequestParameters +import com.android.intentresolver.R import com.android.intentresolver.icons.TargetDataLoader +import com.android.intentresolver.v2.util.mutableLazy -/** Activity logic for [ChooserActivity]. */ -class ChooserActivityLogic( - private val tag: String, +/** + * Activity logic for [ChooserActivity]. + * + * TODO: Make this class no longer open once [ChooserActivity] no longer needs to cast to access + * [chooserRequestParameters]. For now, this class being open is better than using reflection + * there. + */ +@OpenForTesting +open class ChooserActivityLogic( + tag: String, activityProvider: () -> ComponentActivity, targetDataLoaderProvider: () -> TargetDataLoader, private val onPreInitialization: () -> Unit, -) : ActivityLogic, CommonActivityLogic by CommonActivityLogicImpl(activityProvider) { +) : ActivityLogic, CommonActivityLogic by CommonActivityLogicImpl(tag, activityProvider) { override val targetIntent: Intent by lazy { chooserRequestParameters?.targetIntent ?: Intent() } @@ -37,6 +47,11 @@ class ChooserActivityLogic( override val targetDataLoader: TargetDataLoader by lazy { targetDataLoaderProvider() } + override val themeResId: Int = R.style.Theme_DeviceDefault_Chooser + + private val _profileSwitchMessage = mutableLazy { forwardMessageFor(targetIntent) } + override val profileSwitchMessage: String? by _profileSwitchMessage + val chooserRequestParameters: ChooserRequestParameters? by lazy { try { ChooserRequestParameters( @@ -53,4 +68,8 @@ class ChooserActivityLogic( override fun preInitialization() { onPreInitialization() } + + override fun clearProfileSwitchMessage() { + _profileSwitchMessage.setLazy(null) + } } diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java index 1c9ee99d..b34ce16d 100644 --- a/java/src/com/android/intentresolver/v2/ResolverActivity.java +++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java @@ -17,8 +17,6 @@ package com.android.intentresolver.v2; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; -import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; -import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; @@ -35,6 +33,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; +import static java.util.Objects.requireNonNull; + import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UiThread; @@ -131,7 +131,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.function.Supplier; /** * This is a copy of ResolverActivity to support IntentResolver's ChooserActivity. This code is @@ -143,7 +142,7 @@ import java.util.function.Supplier; public class ResolverActivity extends FragmentActivity implements ResolverListAdapter.ResolverListCommunicator { - protected ActivityLogic mLogic = new ResolverActivityLogic(() -> this); + protected ActivityLogic mLogic = new ResolverActivityLogic(TAG, () -> this); public ResolverActivity() { mIsIntentPicker = getClass().equals(ResolverActivity.class); @@ -157,7 +156,6 @@ public class ResolverActivity extends FragmentActivity implements private Button mOnceButton; protected View mProfileView; private int mLastSelected = AbsListView.INVALID_POSITION; - private String mProfileSwitchMessage; private int mLayoutId; @VisibleForTesting protected final ArrayList<Intent> mIntents = new ArrayList<>(); @@ -224,27 +222,6 @@ public class ResolverActivity extends FragmentActivity implements private UserHandle mHeaderCreatorUser; - // User handle annotations are lazy-initialized to ensure that they're computed exactly once - // (even though they can't be computed prior to activity creation). - // TODO: use a less ad-hoc pattern for lazy initialization (by switching to Dagger or - // introducing a common `LazySingletonSupplier` API, etc), and/or migrate all dependents to a - // new component whose lifecycle is limited to the "created" Activity (so that we can just hold - // the annotations as a `final` ivar, which is a better way to show immutability). - private Supplier<AnnotatedUserHandles> mLazyAnnotatedUserHandles = () -> { - final AnnotatedUserHandles result = computeAnnotatedUserHandles(); - mLazyAnnotatedUserHandles = () -> result; - return result; - }; - - // This method is called exactly once during creation to compute the immutable annotations - // accessible through the lazy supplier {@link mLazyAnnotatedUserHandles}. - // TODO: this is only defined so that tests can provide an override that injects fake - // annotations. Dagger could provide a cleaner model for our testing/injection requirements. - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - protected AnnotatedUserHandles computeAnnotatedUserHandles() { - return AnnotatedUserHandles.forShareActivity(this); - } - @Nullable private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener; @@ -337,6 +314,7 @@ public class ResolverActivity extends FragmentActivity implements // Skip initializing any additional resources. return; } + setTheme(mLogic.getThemeResId()); mLogic.preInitialization(); init( mLogic.getTargetIntent(), @@ -354,15 +332,11 @@ public class ResolverActivity extends FragmentActivity implements Intent[] initialIntents, TargetDataLoader targetDataLoader ) { - setTheme(appliedThemeResId()); - - // Determine whether we should show that intent is forwarded - // from managed profile to owner or other way around. - setProfileSwitchMessage(intent.getContentUserHint()); - - // Force computation of user handle annotations in order to validate the caller ID. (See the - // associated TODO comment to explain why this is structured as a lazy computation.) - AnnotatedUserHandles unusedReferenceToHandles = mLazyAnnotatedUserHandles.get(); + // Calling UID did not have valid permissions + if (mLogic.getAnnotatedUserHandles() == null) { + finish(); + return; + } mWorkProfileAvailability = createWorkProfileAvailabilityManager(); @@ -397,12 +371,20 @@ public class ResolverActivity extends FragmentActivity implements mPersonalPackageMonitor = createPackageMonitor( mMultiProfilePagerAdapter.getPersonalListAdapter()); mPersonalPackageMonitor.register( - this, getMainLooper(), getAnnotatedUserHandles().personalProfileUserHandle, false); + this, + getMainLooper(), + requireAnnotatedUserHandles().personalProfileUserHandle, + false + ); if (shouldShowTabs()) { mWorkPackageMonitor = createPackageMonitor( mMultiProfilePagerAdapter.getWorkListAdapter()); mWorkPackageMonitor.register( - this, getMainLooper(), getAnnotatedUserHandles().workProfileUserHandle, false); + this, + getMainLooper(), + requireAnnotatedUserHandles().workProfileUserHandle, + false + ); } mRegistered = true; @@ -494,15 +476,11 @@ public class ResolverActivity extends FragmentActivity implements ResolverActivity.METRICS_CATEGORY_RESOLVER); return new NoCrossProfileEmptyStateProvider( - getAnnotatedUserHandles().personalProfileUserHandle, + requireAnnotatedUserHandles().personalProfileUserHandle, noWorkToPersonalEmptyState, noPersonalToWorkEmptyState, createCrossProfileIntentsChecker(), - getAnnotatedUserHandles().tabOwnerUserHandleForLaunch); - } - - protected int appliedThemeResId() { - return R.style.Theme_DeviceDefault_Resolver; + requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch); } /** @@ -890,7 +868,7 @@ public class ResolverActivity extends FragmentActivity implements mPm, getTargetIntent(), mLogic.getReferrerPackageName(), - getAnnotatedUserHandles().userIdOfCallingApp, + requireAnnotatedUserHandles().userIdOfCallingApp, resolverComparator, getQueryIntentsUser(userHandle)); } @@ -978,7 +956,8 @@ public class ResolverActivity extends FragmentActivity implements @Override // ResolverListCommunicator public void onHandlePackagesChanged(ResolverListAdapter listAdapter) { if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) { - if (listAdapter.getUserHandle().equals(getAnnotatedUserHandles().workProfileUserHandle) + if (listAdapter.getUserHandle().equals( + requireAnnotatedUserHandles().workProfileUserHandle) && mWorkProfileAvailability.isWaitingToEnableWorkProfile()) { // We have just turned on the work profile and entered the pass code to start it, // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no @@ -1018,13 +997,13 @@ public class ResolverActivity extends FragmentActivity implements protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() { return new WorkProfileAvailabilityManager( getSystemService(UserManager.class), - getAnnotatedUserHandles().workProfileUserHandle, + requireAnnotatedUserHandles().workProfileUserHandle, this::onWorkProfileStatusUpdated); } protected void onWorkProfileStatusUpdated() { if (mMultiProfilePagerAdapter.getCurrentUserHandle().equals( - getAnnotatedUserHandles().workProfileUserHandle)) { + requireAnnotatedUserHandles().workProfileUserHandle)) { mMultiProfilePagerAdapter.rebuildActiveTab(true); } else { mMultiProfilePagerAdapter.clearInactiveProfileCache(); @@ -1042,8 +1021,8 @@ public class ResolverActivity extends FragmentActivity implements UserHandle userHandle, TargetDataLoader targetDataLoader) { UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile() - && userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle) - ? getAnnotatedUserHandles().cloneProfileUserHandle : userHandle; + && userHandle.equals(requireAnnotatedUserHandles().personalProfileUserHandle) + ? requireAnnotatedUserHandles().cloneProfileUserHandle : userHandle; return new ResolverListAdapter( context, payloadIntents, @@ -1092,9 +1071,9 @@ public class ResolverActivity extends FragmentActivity implements final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider( this, workProfileUserHandle, - getAnnotatedUserHandles().personalProfileUserHandle, + requireAnnotatedUserHandles().personalProfileUserHandle, getMetricsCategory(), - getAnnotatedUserHandles().tabOwnerUserHandleForLaunch + requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch ); // Return composite provider, the order matters (the higher, the more priority) @@ -1117,7 +1096,7 @@ public class ResolverActivity extends FragmentActivity implements initialIntents, resolutionList, filterLastUsed, - /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle, + /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle, targetDataLoader); return new ResolverMultiProfilePagerAdapter( /* context */ this, @@ -1125,13 +1104,13 @@ public class ResolverActivity extends FragmentActivity implements createEmptyStateProvider(/* workProfileUserHandle= */ null), /* workProfileQuietModeChecker= */ () -> false, /* workProfileUserHandle= */ null, - getAnnotatedUserHandles().cloneProfileUserHandle); + requireAnnotatedUserHandles().cloneProfileUserHandle); } private UserHandle getIntentUser() { return getIntent().hasExtra(EXTRA_CALLING_USER) ? getIntent().getParcelableExtra(EXTRA_CALLING_USER) - : getAnnotatedUserHandles().tabOwnerUserHandleForLaunch; + : requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch; } private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles( @@ -1144,10 +1123,10 @@ public class ResolverActivity extends FragmentActivity implements // this happens, we check for it here and set the current profile's tab. int selectedProfile = getCurrentProfile(); UserHandle intentUser = getIntentUser(); - if (!getAnnotatedUserHandles().tabOwnerUserHandleForLaunch.equals(intentUser)) { - if (getAnnotatedUserHandles().personalProfileUserHandle.equals(intentUser)) { + if (!requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch.equals(intentUser)) { + if (requireAnnotatedUserHandles().personalProfileUserHandle.equals(intentUser)) { selectedProfile = PROFILE_PERSONAL; - } else if (getAnnotatedUserHandles().workProfileUserHandle.equals(intentUser)) { + } else if (requireAnnotatedUserHandles().workProfileUserHandle.equals(intentUser)) { selectedProfile = PROFILE_WORK; } } else { @@ -1165,10 +1144,10 @@ public class ResolverActivity extends FragmentActivity implements selectedProfile == PROFILE_PERSONAL ? initialIntents : null, resolutionList, (filterLastUsed && UserHandle.myUserId() - == getAnnotatedUserHandles().personalProfileUserHandle.getIdentifier()), - /* userHandle */ getAnnotatedUserHandles().personalProfileUserHandle, + == requireAnnotatedUserHandles().personalProfileUserHandle.getIdentifier()), + /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle, targetDataLoader); - UserHandle workProfileUserHandle = getAnnotatedUserHandles().workProfileUserHandle; + UserHandle workProfileUserHandle = requireAnnotatedUserHandles().workProfileUserHandle; ResolverListAdapter workAdapter = createResolverListAdapter( /* context */ this, /* payloadIntents */ mIntents, @@ -1186,7 +1165,7 @@ public class ResolverActivity extends FragmentActivity implements () -> mWorkProfileAvailability.isQuietModeEnabled(), selectedProfile, workProfileUserHandle, - getAnnotatedUserHandles().cloneProfileUserHandle); + requireAnnotatedUserHandles().cloneProfileUserHandle); } /** @@ -1209,26 +1188,26 @@ public class ResolverActivity extends FragmentActivity implements } protected final @Profile int getCurrentProfile() { - UserHandle launchUser = getAnnotatedUserHandles().tabOwnerUserHandleForLaunch; - UserHandle personalUser = getAnnotatedUserHandles().personalProfileUserHandle; + UserHandle launchUser = requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch; + UserHandle personalUser = requireAnnotatedUserHandles().personalProfileUserHandle; return launchUser.equals(personalUser) ? PROFILE_PERSONAL : PROFILE_WORK; } - protected final AnnotatedUserHandles getAnnotatedUserHandles() { - return mLazyAnnotatedUserHandles.get(); + private AnnotatedUserHandles requireAnnotatedUserHandles() { + return requireNonNull(mLogic.getAnnotatedUserHandles()); } private boolean hasWorkProfile() { - return getAnnotatedUserHandles().workProfileUserHandle != null; + return requireAnnotatedUserHandles().workProfileUserHandle != null; } private boolean hasCloneProfile() { - return getAnnotatedUserHandles().cloneProfileUserHandle != null; + return requireAnnotatedUserHandles().cloneProfileUserHandle != null; } protected final boolean isLaunchedAsCloneProfile() { - UserHandle launchUser = getAnnotatedUserHandles().userHandleSharesheetLaunchedAs; - UserHandle cloneUser = getAnnotatedUserHandles().cloneProfileUserHandle; + UserHandle launchUser = requireAnnotatedUserHandles().userHandleSharesheetLaunchedAs; + UserHandle cloneUser = requireAnnotatedUserHandles().cloneProfileUserHandle; return hasCloneProfile() && launchUser.equals(cloneUser); } @@ -1244,7 +1223,7 @@ public class ResolverActivity extends FragmentActivity implements } // Do not show the profile switch message anymore. - mProfileSwitchMessage = null; + mLogic.clearProfileSwitchMessage(); onTargetSelected(dri, false); finish(); @@ -1273,7 +1252,7 @@ public class ResolverActivity extends FragmentActivity implements .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED) .setBoolean( currentUserHandle.equals( - getAnnotatedUserHandles().personalProfileUserHandle)) + requireAnnotatedUserHandles().personalProfileUserHandle)) .setStrings(getMetricsCategory(), cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target") .write(); @@ -1331,34 +1310,6 @@ public class ResolverActivity extends FragmentActivity implements } } - private void setProfileSwitchMessage(int contentUserHint) { - if ((contentUserHint != UserHandle.USER_CURRENT) - && (contentUserHint != UserHandle.myUserId())) { - UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); - UserInfo originUserInfo = userManager.getUserInfo(contentUserHint); - boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile() - : false; - boolean targetIsManaged = userManager.isManagedProfile(); - if (originIsManaged && !targetIsManaged) { - mProfileSwitchMessage = getForwardToPersonalMsg(); - } else if (!originIsManaged && targetIsManaged) { - mProfileSwitchMessage = getForwardToWorkMsg(); - } - } - } - - private String getForwardToPersonalMsg() { - return getSystemService(DevicePolicyManager.class).getResources().getString( - FORWARD_INTENT_TO_PERSONAL, - () -> getString(R.string.forward_intent_to_owner)); - } - - private String getForwardToWorkMsg() { - return getSystemService(DevicePolicyManager.class).getResources().getString( - FORWARD_INTENT_TO_WORK, - () -> getString(R.string.forward_intent_to_work)); - } - protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) { final ActionTitle title = mLogic.getResolvingHome() ? ActionTitle.HOME @@ -1394,7 +1345,7 @@ public class ResolverActivity extends FragmentActivity implements mPersonalPackageMonitor.register( this, getMainLooper(), - getAnnotatedUserHandles().personalProfileUserHandle, + requireAnnotatedUserHandles().personalProfileUserHandle, false); if (shouldShowTabs()) { if (mWorkPackageMonitor == null) { @@ -1404,7 +1355,7 @@ public class ResolverActivity extends FragmentActivity implements mWorkPackageMonitor.register( this, getMainLooper(), - getAnnotatedUserHandles().workProfileUserHandle, + requireAnnotatedUserHandles().workProfileUserHandle, false); } mRegistered = true; @@ -1612,8 +1563,9 @@ public class ResolverActivity extends FragmentActivity implements } // If needed, show that intent is forwarded // from managed profile to owner or other way around. - if (mProfileSwitchMessage != null) { - Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show(); + String profileSwitchMessage = mLogic.getProfileSwitchMessage(); + if (profileSwitchMessage != null) { + Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show(); } try { if (cti.startAsCaller(this, options, user.getIdentifier())) { @@ -1622,7 +1574,7 @@ public class ResolverActivity extends FragmentActivity implements } } catch (RuntimeException e) { Slog.wtf(TAG, - "Unable to launch as uid " + getAnnotatedUserHandles().userIdOfCallingApp + "Unable to launch as uid " + requireAnnotatedUserHandles().userIdOfCallingApp + " package " + getLaunchedFromPackage() + ", while running in " + ActivityThread.currentProcessName(), e); } @@ -1873,7 +1825,7 @@ public class ResolverActivity extends FragmentActivity implements DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET) .setBoolean(activeListAdapter.getUserHandle() - .equals(getAnnotatedUserHandles().personalProfileUserHandle)) + .equals(requireAnnotatedUserHandles().personalProfileUserHandle)) .setStrings(getMetricsCategory()) .write(); safelyStartActivity(activeProfileTarget); @@ -2157,7 +2109,8 @@ public class ResolverActivity extends FragmentActivity implements // filtered item. We always show the same default app even in the inactive user profile. boolean adapterForCurrentUserHasFilteredItem = mMultiProfilePagerAdapter.getListAdapterForUserHandle( - getAnnotatedUserHandles().tabOwnerUserHandleForLaunch).hasFilteredItem(); + requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch + ).hasFilteredItem(); return mLogic.getSupportsAlwaysUseOption() && adapterForCurrentUserHasFilteredItem; } @@ -2278,7 +2231,7 @@ public class ResolverActivity extends FragmentActivity implements * {@link ResolverListController} configured for the provided {@code userHandle}. */ protected final UserHandle getQueryIntentsUser(UserHandle userHandle) { - return getAnnotatedUserHandles().getQueryIntentsUser(userHandle); + return requireAnnotatedUserHandles().getQueryIntentsUser(userHandle); } /** @@ -2298,9 +2251,9 @@ public class ResolverActivity extends FragmentActivity implements // Add clonedProfileUserHandle to the list only if we are: // a. Building the Personal Tab. // b. CloneProfile exists on the device. - if (userHandle.equals(getAnnotatedUserHandles().personalProfileUserHandle) + if (userHandle.equals(requireAnnotatedUserHandles().personalProfileUserHandle) && hasCloneProfile()) { - userList.add(getAnnotatedUserHandles().cloneProfileUserHandle); + userList.add(requireAnnotatedUserHandles().cloneProfileUserHandle); } return userList; } diff --git a/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt b/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt index 1d02e6c2..1b936159 100644 --- a/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt +++ b/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt @@ -2,13 +2,16 @@ package com.android.intentresolver.v2 import android.content.Intent import androidx.activity.ComponentActivity +import com.android.intentresolver.R import com.android.intentresolver.icons.DefaultTargetDataLoader import com.android.intentresolver.icons.TargetDataLoader +import com.android.intentresolver.v2.util.mutableLazy /** Activity logic for [ResolverActivity]. */ class ResolverActivityLogic( + tag: String, activityProvider: () -> ComponentActivity, -) : ActivityLogic, CommonActivityLogic by CommonActivityLogicImpl(activityProvider) { +) : ActivityLogic, CommonActivityLogic by CommonActivityLogicImpl(tag, activityProvider) { override val targetIntent: Intent by lazy { val intent = Intent(activity.intent) @@ -55,7 +58,16 @@ class ResolverActivityLogic( ) } + override val themeResId: Int = R.style.Theme_DeviceDefault_Resolver + + private val _profileSwitchMessage = mutableLazy { forwardMessageFor(targetIntent) } + override val profileSwitchMessage: String? by _profileSwitchMessage + override fun preInitialization() { // Do nothing } + + override fun clearProfileSwitchMessage() { + _profileSwitchMessage.setLazy(null) + } } diff --git a/java/src/com/android/intentresolver/v2/util/MutableLazy.kt b/java/src/com/android/intentresolver/v2/util/MutableLazy.kt new file mode 100644 index 00000000..4ce9b7fd --- /dev/null +++ b/java/src/com/android/intentresolver/v2/util/MutableLazy.kt @@ -0,0 +1,36 @@ +package com.android.intentresolver.v2.util + +import java.util.concurrent.atomic.AtomicReference +import kotlin.reflect.KProperty + +/** A lazy delegate that can be changed to a new lazy or null at any time. */ +class MutableLazy<T>(initializer: () -> T?) : Lazy<T?> { + + override val value: T? + get() = lazy.get()?.value + + private var lazy: AtomicReference<Lazy<T?>?> = AtomicReference(lazy(initializer)) + + override fun isInitialized(): Boolean = lazy.get()?.isInitialized() != false + + operator fun getValue(thisRef: Any?, property: KProperty<*>): T? = + lazy.get()?.getValue(thisRef, property) + + /** Replace the existing lazy logic with the [newLazy] */ + fun setLazy(newLazy: Lazy<T?>?) { + lazy.set(newLazy) + } + + /** Replace the existing lazy logic with a [Lazy] created from the [newInitializer]. */ + fun setLazy(newInitializer: () -> T?) { + lazy.set(lazy(newInitializer)) + } + + /** Set the lazy logic to null. */ + fun clear() { + lazy.set(null) + } +} + +/** Constructs a [MutableLazy] using the given [initializer] */ +fun <T> mutableLazy(initializer: () -> T?) = MutableLazy(initializer) diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java index 65d33485..6fdba4c2 100644 --- a/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java @@ -33,7 +33,6 @@ import android.os.UserHandle; import androidx.lifecycle.ViewModelProvider; -import com.android.intentresolver.AnnotatedUserHandles; import com.android.intentresolver.ChooserListAdapter; import com.android.intentresolver.ChooserRequestParameters; import com.android.intentresolver.IChooserWrapper; @@ -59,6 +58,17 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW static final ChooserActivityOverrideData sOverrides = ChooserActivityOverrideData.getInstance(); private UsageStatsManager mUsm; + public ChooserWrapperActivity() { + super(); + mLogic = new TestChooserActivityLogic( + "ChooserWrapper", + () -> this, + () -> mTargetDataLoader, + super::onPreinitialization, + sOverrides + ); + } + // ResolverActivity (the base class of ChooserActivity) inspects the launched-from UID at // onCreate and needs to see some non-negative value in the test. @Override @@ -235,11 +245,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW } @Override - protected AnnotatedUserHandles computeAnnotatedUserHandles() { - return sOverrides.annotatedUserHandles; - } - - @Override public UserHandle getCurrentUserHandle() { return mMultiProfilePagerAdapter.getCurrentUserHandle(); } diff --git a/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java index 0fb77457..e5617090 100644 --- a/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java @@ -60,6 +60,11 @@ public class ResolverWrapperActivity extends ResolverActivity { public ResolverWrapperActivity() { super(/* isIntentPicker= */ true); + mLogic = new TestResolverActivityLogic( + "ResolverWrapper", + () -> this, + sOverrides + ); } public CountingIdlingResource getLabelIdlingResource() { @@ -159,11 +164,6 @@ public class ResolverWrapperActivity extends ResolverActivity { } @Override - protected AnnotatedUserHandles computeAnnotatedUserHandles() { - return sOverrides.annotatedUserHandles; - } - - @Override public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { super.startActivityAsUser(intent, options, user); } @@ -179,7 +179,7 @@ public class ResolverWrapperActivity extends ResolverActivity { * <p> * Instead, we use static instances of this object to modify behavior. */ - static class OverrideData { + public static class OverrideData { @SuppressWarnings("Since15") public Function<PackageManager, PackageManager> createPackageManager; public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback; diff --git a/java/tests/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt b/java/tests/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt new file mode 100644 index 00000000..fb1eab6c --- /dev/null +++ b/java/tests/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt @@ -0,0 +1,25 @@ +package com.android.intentresolver.v2 + +import androidx.activity.ComponentActivity +import com.android.intentresolver.AnnotatedUserHandles +import com.android.intentresolver.icons.TargetDataLoader + +/** Activity logic for use when testing [ChooserActivity]. */ +class TestChooserActivityLogic( + tag: String, + activityProvider: () -> ComponentActivity, + targetDataLoaderProvider: () -> TargetDataLoader, + onPreinitialization: () -> Unit, + overrideData: ChooserActivityOverrideData, +) : + ChooserActivityLogic( + tag, + activityProvider, + targetDataLoaderProvider, + onPreinitialization, + ) { + + override val annotatedUserHandles: AnnotatedUserHandles? by lazy { + overrideData.annotatedUserHandles + } +} diff --git a/java/tests/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt b/java/tests/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt new file mode 100644 index 00000000..7f8e6f70 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt @@ -0,0 +1,16 @@ +package com.android.intentresolver.v2 + +import androidx.activity.ComponentActivity +import com.android.intentresolver.AnnotatedUserHandles + +/** Activity logic for use when testing [ResolverActivity]. */ +class TestResolverActivityLogic( + tag: String, + activityProvider: () -> ComponentActivity, + overrideData: ResolverWrapperActivity.OverrideData, +) : ActivityLogic by ResolverActivityLogic(tag, activityProvider) { + + override val annotatedUserHandles: AnnotatedUserHandles? by lazy { + overrideData.annotatedUserHandles + } +} |