diff options
author | 2024-03-01 12:37:23 -0500 | |
---|---|---|
committer | 2024-04-04 00:09:05 -0400 | |
commit | 9f4e2ece250f07e214231a89c9aa74ab19d35d30 (patch) | |
tree | 983088328ff213c10daa42710ec3fdc6cfc99dbe | |
parent | a0691fb844a0edef901e00be9acac68f32dd2a88 (diff) |
ResolverActivity Profile integration
* connects ResolverActivity 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
* removes ResolverWorkProfilePausedEmptyStateProvider
* removes ResolverNoCrossProfileEmptyStateProvider
* removes ActivityLogic
* removes ResolverActivityLogic
* removes TestResolverActivityLogic
Bug: 300157408
Bug: 311348033
Test: atest IntentResolver-tests-activity:com.android.intentresolver.v2
Change-Id: Ia4a8bf458ebad12af06b1c6c1f0d8586be452d43
11 files changed, 458 insertions, 719 deletions
diff --git a/java/src/com/android/intentresolver/v2/ActivityLogic.kt b/java/src/com/android/intentresolver/v2/ActivityLogic.kt deleted file mode 100644 index 62ace0da..00000000 --- a/java/src/com/android/intentresolver/v2/ActivityLogic.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.intentresolver.v2 - -import android.os.UserHandle -import android.os.UserManager -import android.util.Log -import androidx.activity.ComponentActivity -import androidx.core.content.getSystemService -import com.android.intentresolver.AnnotatedUserHandles -import com.android.intentresolver.WorkProfileAvailabilityManager - -/** - * Logic for IntentResolver Activities. Anything that is not the same across activities (including - * test activities) should be in this interface. Expect there to be one implementation for each - * activity, including test activities, but all implementations should delegate to a - * CommonActivityLogic implementation. - */ -interface ActivityLogic : CommonActivityLogic - -/** - * Logic that is common to all IntentResolver activities. Anything that is the same across - * 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 - - /** Current [UserHandle]s retrievable by type. */ - val annotatedUserHandles: AnnotatedUserHandles? - - /** Monitors for changes to work profile availability. */ - val workProfileAvailabilityManager: WorkProfileAvailabilityManager -} - -/** - * Concrete implementation of the [CommonActivityLogic] interface meant to be delegated to by - * [ActivityLogic] implementations. Test implementations of [ActivityLogic] may need to create their - * own [CommonActivityLogic] implementation. - */ -class CommonActivityLogicImpl( - override val tag: String, - override val activity: ComponentActivity, - onWorkProfileStatusUpdated: () -> Unit, -) : CommonActivityLogic { - - private val userManager: UserManager = activity.getSystemService()!! - - override val annotatedUserHandles: AnnotatedUserHandles? = - try { - AnnotatedUserHandles.forShareActivity(activity) - } catch (e: SecurityException) { - Log.e(tag, "Request from UID without necessary permissions", e) - null - } - - override val workProfileAvailabilityManager = - WorkProfileAvailabilityManager( - userManager, - annotatedUserHandles?.workProfileUserHandle, - onWorkProfileStatusUpdated, - ) -} diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java index 4e694c3a..86f32864 100644 --- a/java/src/com/android/intentresolver/v2/ResolverActivity.java +++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java @@ -24,8 +24,9 @@ import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_S import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; +import static androidx.lifecycle.LifecycleKt.getCoroutineScope; + import static com.android.intentresolver.v2.ext.CreationExtrasExtKt.addDefaultArgs; -import static com.android.intentresolver.v2.ui.viewmodel.ResolverRequestReaderKt.readResolverRequest; import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; import static java.util.Objects.requireNonNull; @@ -83,14 +84,14 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.viewmodel.CreationExtras; import androidx.viewpager.widget.ViewPager; -import com.android.intentresolver.AnnotatedUserHandles; +import com.android.intentresolver.FeatureFlags; import com.android.intentresolver.R; import com.android.intentresolver.ResolverListAdapter; import com.android.intentresolver.ResolverListController; -import com.android.intentresolver.WorkProfileAvailabilityManager; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.emptystate.CompositeEmptyStateProvider; @@ -99,12 +100,14 @@ import com.android.intentresolver.emptystate.EmptyState; import com.android.intentresolver.emptystate.EmptyStateProvider; import com.android.intentresolver.icons.DefaultTargetDataLoader; import com.android.intentresolver.icons.TargetDataLoader; +import com.android.intentresolver.inject.Background; import com.android.intentresolver.model.ResolverRankerServiceResolverComparator; 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; -import com.android.intentresolver.v2.emptystate.ResolverNoCrossProfileEmptyStateProvider; -import com.android.intentresolver.v2.emptystate.ResolverWorkProfilePausedEmptyStateProvider; +import com.android.intentresolver.v2.emptystate.WorkProfilePausedEmptyStateProvider; import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter; import com.android.intentresolver.v2.profiles.MultiProfilePagerAdapter.ProfileType; import com.android.intentresolver.v2.profiles.OnProfileSelectedListener; @@ -115,11 +118,7 @@ import com.android.intentresolver.v2.shared.model.Profile; import com.android.intentresolver.v2.ui.ActionTitle; import com.android.intentresolver.v2.ui.model.ActivityModel; import com.android.intentresolver.v2.ui.model.ResolverRequest; -import com.android.intentresolver.v2.validation.Finding; -import com.android.intentresolver.v2.validation.FindingsKt; -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.ui.viewmodel.ResolverViewModel; import com.android.intentresolver.widget.ResolverDrawerLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; @@ -131,7 +130,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; @@ -139,10 +137,11 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; import javax.inject.Inject; +import kotlinx.coroutines.CoroutineDispatcher; + /** * This is a copy of ResolverActivity to support IntentResolver's ChooserActivity. This code is * *not* the resolver that is actually triggered by the system right now (you want @@ -153,12 +152,18 @@ import javax.inject.Inject; public class ResolverActivity extends Hilt_ResolverActivity implements ResolverListAdapter.ResolverListCommunicator { + @Inject @Background public CoroutineDispatcher mBackgroundDispatcher; + @Inject public UserInteractor mUserInteractor; + @Inject public ResolverHelper mResolverHelper; @Inject public PackageManager mPackageManager; @Inject public DevicePolicyResources mDevicePolicyResources; @Inject public IntentForwarding mIntentForwarding; - private ResolverRequest mResolverRequest; - private ActivityModel mActivityModel; - protected ActivityLogic mLogic; + @Inject public FeatureFlags mFeatureFlags; + + private ResolverViewModel mViewModel; + private ResolverRequest mRequest; + private ProfileHelper mProfiles; + private ProfileAvailability mProfileAvailability; protected TargetDataLoader mTargetDataLoader; private boolean mResolvingHome; @@ -217,63 +222,133 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } }; } + protected ActivityModel createActivityModel() { return ActivityModel.createFrom(this); } - @VisibleForTesting - protected ActivityLogic createActivityLogic() { - return new ResolverActivityLogic( - TAG, - /* activity = */ this, - this::onWorkProfileStatusUpdated); - } - @NonNull @Override public CreationExtras getDefaultViewModelCreationExtras() { return addDefaultArgs( super.getDefaultViewModelCreationExtras(), - new Pair<>(ActivityModel.ACTIVITY_MODEL_KEY, ActivityModel.createFrom(this))); + new Pair<>(ActivityModel.ACTIVITY_MODEL_KEY, createActivityModel())); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Log.i(TAG, "onCreate"); setTheme(R.style.Theme_DeviceDefault_Resolver); - mActivityModel = createActivityModel(); + mResolverHelper.setInitializer(this::initialize); + } - Log.i(TAG, "onCreate"); - Log.i(TAG, "activityModel=" + mActivityModel.toString()); - int callerUid = mActivityModel.getLaunchedFromUid(); - if (callerUid < 0 || UserHandle.isIsolated(callerUid)) { - Log.e(TAG, "Can't start a resolver from uid " + callerUid); - finish(); + @Override + protected final void onStart() { + super.onStart(); + this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + } + + @Override + protected void onStop() { + super.onStop(); + + final Window window = this.getWindow(); + final WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + window.setAttributes(attrs); + + if (mRegistered) { + mPersonalPackageMonitor.unregister(); + if (mWorkPackageMonitor != null) { + mWorkPackageMonitor.unregister(); + } + mRegistered = false; + } + final Intent intent = getIntent(); + if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction() + && !mResolvingHome) { + // This resolver is in the unusual situation where it has been + // launched at the top of a new task. We don't let it be added + // to the recent tasks shown to the user, and we need to make sure + // that each time we are launched we get the correct launching + // uid (not re-using the same resolver from an old launching uid), + // so we will now finish ourself since being no longer visible, + // the user probably can't get back to us. + if (!isChangingConfigurations()) { + finish(); + } } + } - ValidationResult<ResolverRequest> result = readResolverRequest(mActivityModel); - if (result instanceof Invalid) { - ((Invalid) result).getErrors().forEach(new Consumer<Finding>() { - @Override - public void accept(Finding finding) { - FindingsKt.log(finding, TAG); + @Override + protected final void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); + if (viewPager != null) { + outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem()); + } + } + + @Override + protected final void onRestart() { + super.onRestart(); + if (!mRegistered) { + mPersonalPackageMonitor.register( + this, + getMainLooper(), + mProfiles.getPersonalHandle(), + false); + if (mProfiles.getWorkProfilePresent()) { + if (mWorkPackageMonitor == null) { + mWorkPackageMonitor = createPackageMonitor( + mMultiProfilePagerAdapter.getWorkListAdapter()); } - }); - finish(); + mWorkPackageMonitor.register( + this, + getMainLooper(), + mProfiles.getWorkHandle(), + false); + } + mRegistered = true; + } + mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (!isChangingConfigurations() && mPickOptionRequest != null) { + mPickOptionRequest.cancel(); + } + if (mMultiProfilePagerAdapter != null + && mMultiProfilePagerAdapter.getActiveListAdapter() != null) { + mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy(); } - mResolverRequest = ((Valid<ResolverRequest>) result).getValue(); - mLogic = createActivityLogic(); - mResolvingHome = mResolverRequest.isResolvingHome(); + } + + private void initialize() { + mViewModel = new ViewModelProvider(this).get(ResolverViewModel.class); + mRequest = mViewModel.getRequest().getValue(); + + mProfiles = new ProfileHelper( + mUserInteractor, + getCoroutineScope(getLifecycle()), + mBackgroundDispatcher, + mFeatureFlags); + + mProfileAvailability = new ProfileAvailability( + mUserInteractor, + getCoroutineScope(getLifecycle()), + mBackgroundDispatcher); + + mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated); + + mResolvingHome = mRequest.isResolvingHome(); mTargetDataLoader = new DefaultTargetDataLoader( this, getLifecycle(), - mResolverRequest.isAudioCaptureDevice()); - init(); - restore(savedInstanceState); - } - - private void init() { - Intent intent = mResolverRequest.getIntent(); + mRequest.isAudioCaptureDevice()); // The last argument of createResolverListAdapter is whether to do special handling // of the last used choice to highlight it in the list. We need to always @@ -284,10 +359,10 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // different "last chosen" activities in the different profiles, and PackageManager doesn't // provide any more information to help us select between them. boolean filterLastUsed = !isVoiceInteraction() - && !hasWorkProfile() && !hasCloneProfile(); + && !mProfiles.getWorkProfilePresent() && !mProfiles.getCloneUserPresent(); mMultiProfilePagerAdapter = createMultiProfilePagerAdapter( new Intent[0], - /* resolutionList = */ mResolverRequest.getResolutionList(), + /* resolutionList = */ mRequest.getResolutionList(), filterLastUsed ); if (configureContentView(mTargetDataLoader)) { @@ -299,16 +374,16 @@ public class ResolverActivity extends Hilt_ResolverActivity implements mPersonalPackageMonitor.register( this, getMainLooper(), - requireAnnotatedUserHandles().personalProfileUserHandle, + mProfiles.getPersonalHandle(), false ); - if (hasWorkProfile()) { + if (mProfiles.getWorkProfilePresent()) { mWorkPackageMonitor = createPackageMonitor( mMultiProfilePagerAdapter.getWorkListAdapter()); mWorkPackageMonitor.register( this, getMainLooper(), - requireAnnotatedUserHandles().workProfileUserHandle, + mProfiles.getWorkHandle(), false ); } @@ -337,7 +412,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements mResolverDrawerLayout = rdl; } - + Intent intent = mViewModel.getRequest().getValue().getIntent(); final Set<String> categories = intent.getCategories(); MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem() ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED @@ -364,7 +439,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements List<ResolveInfo> resolutionList, boolean filterLastUsed) { ResolverMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null; - if (hasWorkProfile()) { + if (mProfiles.getWorkProfilePresent()) { resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForTwoProfiles( initialIntents, resolutionList, filterLastUsed); @@ -407,12 +482,11 @@ public class ResolverActivity extends Hilt_ResolverActivity implements /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER); - return new ResolverNoCrossProfileEmptyStateProvider( - requireAnnotatedUserHandles().personalProfileUserHandle, + return new NoCrossProfileEmptyStateProvider( + mProfiles, noWorkToPersonalEmptyState, noPersonalToWorkEmptyState, - createCrossProfileIntentsChecker(), - requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch); + createCrossProfileIntentsChecker()); } /** @@ -432,12 +506,12 @@ public class ResolverActivity extends Hilt_ResolverActivity implements mFooterSpacer = new Space(getApplicationContext()); } else { ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) - .getActiveAdapterView().removeFooterView(mFooterSpacer); + .getActiveAdapterView().removeFooterView(mFooterSpacer); } mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, - mSystemWindowInsets.bottom)); + mSystemWindowInsets.bottom)); ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) - .getActiveAdapterView().addFooterView(mFooterSpacer); + .getActiveAdapterView().addFooterView(mFooterSpacer); } protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { @@ -466,7 +540,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); - if (hasWorkProfile() && !useLayoutWithDefault() + if (mProfiles.getWorkProfilePresent() && !useLayoutWithDefault() && !shouldUseMiniResolver()) { updateIntentPickerPaddings(); } @@ -481,52 +555,6 @@ public class ResolverActivity extends Hilt_ResolverActivity implements return R.layout.resolver_list; } - @Override - protected void onStop() { - super.onStop(); - - final Window window = this.getWindow(); - final WindowManager.LayoutParams attrs = window.getAttributes(); - attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; - window.setAttributes(attrs); - - if (mRegistered) { - mPersonalPackageMonitor.unregister(); - if (mWorkPackageMonitor != null) { - mWorkPackageMonitor.unregister(); - } - mRegistered = false; - } - final Intent intent = getIntent(); - if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction() - && !mResolvingHome) { - // This resolver is in the unusual situation where it has been - // launched at the top of a new task. We don't let it be added - // to the recent tasks shown to the user, and we need to make sure - // that each time we are launched we get the correct launching - // uid (not re-using the same resolver from an old launching uid), - // so we will now finish ourself since being no longer visible, - // the user probably can't get back to us. - if (!isChangingConfigurations()) { - finish(); - } - } - // TODO: should we clean up the work-profile manager before we potentially finish() above? - mLogic.getWorkProfileAvailabilityManager().unregisterWorkProfileStateReceiver(this); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (!isChangingConfigurations() && mPickOptionRequest != null) { - mPickOptionRequest.cancel(); - } - if (mMultiProfilePagerAdapter != null - && mMultiProfilePagerAdapter.getActiveListAdapter() != null) { - mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy(); - } - } - // referenced by layout XML: android:onClick="onButtonClick" public void onButtonClick(View v) { final int id = v.getId(); @@ -582,7 +610,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) { final ItemClickListener listener = new ItemClickListener(); setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener); - if (hasWorkProfile()) { + if (mProfiles.getWorkProfilePresent()) { final ResolverDrawerLayout rdl = findViewById(com.android.internal.R.id.contentPanel); if (rdl != null) { rdl.setMaxCollapsedHeight(getResources() @@ -598,7 +626,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements final Intent intent = target != null ? target.getResolvedIntent() : null; if (intent != null /*&& mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()*/ - && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) { + && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() + != null) { // Build a reasonable intent filter, based on what matched. IntentFilter filter = new IntentFilter(); Intent filterIntent; @@ -761,8 +790,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements ResolverRankerServiceResolverComparator resolverComparator = new ResolverRankerServiceResolverComparator( this, - mResolverRequest.getIntent(), - mActivityModel.getReferrerPackage(), + mRequest.getIntent(), + mViewModel.getActivityModel().getReferrerPackage(), null, null, getResolverRankerServiceUserHandleList(userHandle), @@ -770,17 +799,17 @@ public class ResolverActivity extends Hilt_ResolverActivity implements return new ResolverListController( this, mPackageManager, - mActivityModel.getIntent(), - mActivityModel.getReferrerPackage(), - mActivityModel.getLaunchedFromUid(), + mRequest.getIntent(), + mViewModel.getActivityModel().getReferrerPackage(), + mViewModel.getActivityModel().getLaunchedFromUid(), resolverComparator, - getQueryIntentsUser(userHandle)); + mProfiles.getQueryIntentsHandle(userHandle)); } /** * Finishing procedures to be performed after the list has been rebuilt. * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList. - * @param rebuildCompleted + * * @return <code>true</code> if the activity is finishing and creation should halt. */ protected boolean postRebuildList(boolean rebuildCompleted) { @@ -802,7 +831,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements protected void onProfileTabSelected(int currentPage) { setupViewVisibilities(); maybeLogProfileChange(); - if (hasWorkProfile()) { + if (mProfiles.getWorkProfilePresent()) { // The device policy logger is only concerned with sessions that include a work profile. DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS) @@ -814,6 +843,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements /** * Add a label to signify that the user can pick a different app. + * * @param adapter The adapter used to provide data to item views. */ public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) { @@ -823,7 +853,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements stub.setVisibility(View.VISIBLE); TextView textView = (TextView) LayoutInflater.from(this).inflate( R.layout.resolver_different_item_header, null, false); - if (hasWorkProfile()) { + if (mProfiles.getWorkProfilePresent()) { textView.setGravity(Gravity.CENTER); } stub.addView(textView); @@ -875,7 +905,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements public final void onHandlePackagesChanged(ResolverListAdapter listAdapter) { if (!mMultiProfilePagerAdapter.onHandlePackagesChanged( listAdapter, - mLogic.getWorkProfileAvailabilityManager().isWaitingToEnableWorkProfile())) { + mProfileAvailability.getWaitingToEnableProfile())) { // We no longer have any items... just finish the activity. finish(); } @@ -888,13 +918,12 @@ public class ResolverActivity extends Hilt_ResolverActivity implements return new CrossProfileIntentsChecker(getContentResolver()); } - protected Unit onWorkProfileStatusUpdated() { + private void onWorkProfileStatusUpdated() { if (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_WORK) { mMultiProfilePagerAdapter.rebuildActiveTab(true); } else { mMultiProfilePagerAdapter.clearInactiveProfileCache(); } - return Unit.INSTANCE; } // @NonFinalForTesting @@ -906,9 +935,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements List<ResolveInfo> resolutionList, boolean filterLastUsed, UserHandle userHandle) { - UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile() - && userHandle.equals(requireAnnotatedUserHandles().personalProfileUserHandle) - ? requireAnnotatedUserHandles().cloneProfileUserHandle : userHandle; + UserHandle initialIntentsUserSpace = mProfiles.getQueryIntentsHandle(userHandle); return new ResolverListAdapter( context, payloadIntents, @@ -917,7 +944,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements filterLastUsed, createListController(userHandle), userHandle, - mResolverRequest.getIntent(), + mRequest.getIntent(), this, initialIntentsUserSpace, mTargetDataLoader); @@ -928,8 +955,10 @@ public class ResolverActivity extends Hilt_ResolverActivity implements final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider(); final EmptyStateProvider workProfileOffEmptyStateProvider = - new ResolverWorkProfilePausedEmptyStateProvider(this, workProfileUserHandle, - mLogic.getWorkProfileAvailabilityManager(), + new WorkProfilePausedEmptyStateProvider( + this, + mProfiles, + mProfileAvailability, /* onSwitchOnWorkSelectedListener= */ () -> { if (mOnSwitchOnWorkSelectedListener != null) { @@ -941,9 +970,9 @@ public class ResolverActivity extends Hilt_ResolverActivity 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) @@ -954,18 +983,17 @@ public class ResolverActivity extends Hilt_ResolverActivity implements ); } - private ResolverMultiProfilePagerAdapter - createResolverMultiProfilePagerAdapterForOneProfile( - Intent[] initialIntents, - List<ResolveInfo> resolutionList, - boolean filterLastUsed) { + private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile( + Intent[] initialIntents, + List<ResolveInfo> resolutionList, + boolean filterLastUsed) { ResolverListAdapter personalAdapter = createResolverListAdapter( /* context */ this, - mResolverRequest.getPayloadIntents(), + mRequest.getPayloadIntents(), initialIntents, resolutionList, filterLastUsed, - /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle + /* userHandle */ mProfiles.getPersonalHandle() ); return new ResolverMultiProfilePagerAdapter( /* context */ this, @@ -980,12 +1008,12 @@ public class ResolverActivity extends Hilt_ResolverActivity implements /* workProfileQuietModeChecker= */ () -> false, /* defaultProfile= */ PROFILE_PERSONAL, /* workProfileUserHandle= */ null, - requireAnnotatedUserHandles().cloneProfileUserHandle); + mProfiles.getCloneHandle()); } private UserHandle getIntentUser() { - return Objects.requireNonNullElse(mResolverRequest.getCallingUser(), - requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch); + return Objects.requireNonNullElse(mRequest.getCallingUser(), + mProfiles.getTabOwnerUserHandleForLaunch()); } private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles( @@ -997,10 +1025,10 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // this happens, we check for it here and set the current profile's tab. int selectedProfile = getCurrentProfile(); UserHandle intentUser = getIntentUser(); - if (!requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch.equals(intentUser)) { - if (requireAnnotatedUserHandles().personalProfileUserHandle.equals(intentUser)) { + if (!mProfiles.getTabOwnerUserHandleForLaunch().equals(intentUser)) { + if (mProfiles.getPersonalHandle().equals(intentUser)) { selectedProfile = PROFILE_PERSONAL; - } else if (requireAnnotatedUserHandles().workProfileUserHandle.equals(intentUser)) { + } else if (mProfiles.getWorkHandle().equals(intentUser)) { selectedProfile = PROFILE_WORK; } } else { @@ -1014,17 +1042,17 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // resolver list. So filterLastUsed should be false for the other profile. ResolverListAdapter personalAdapter = createResolverListAdapter( /* context */ this, - mResolverRequest.getPayloadIntents(), + mRequest.getPayloadIntents(), selectedProfile == PROFILE_PERSONAL ? initialIntents : null, resolutionList, (filterLastUsed && UserHandle.myUserId() - == requireAnnotatedUserHandles().personalProfileUserHandle.getIdentifier()), - /* userHandle */ requireAnnotatedUserHandles().personalProfileUserHandle + == mProfiles.getPersonalHandle().getIdentifier()), + /* userHandle */ mProfiles.getPersonalHandle() ); - UserHandle workProfileUserHandle = requireAnnotatedUserHandles().workProfileUserHandle; + UserHandle workProfileUserHandle = mProfiles.getWorkHandle(); ResolverListAdapter workAdapter = createResolverListAdapter( /* context */ this, - mResolverRequest.getPayloadIntents(), + mRequest.getPayloadIntents(), selectedProfile == PROFILE_WORK ? initialIntents : null, resolutionList, (filterLastUsed && UserHandle.myUserId() @@ -1047,10 +1075,13 @@ public class ResolverActivity extends Hilt_ResolverActivity implements TAB_TAG_WORK, workAdapter)), createEmptyStateProvider(workProfileUserHandle), - () -> mLogic.getWorkProfileAvailabilityManager().isQuietModeEnabled(), + /* Supplier<Boolean> (QuietMode enabled) == !(available) */ + () -> !(mProfiles.getWorkProfilePresent() + && mProfileAvailability.isAvailable( + requireNonNull(mProfiles.getWorkProfile()))), selectedProfile, workProfileUserHandle, - requireAnnotatedUserHandles().cloneProfileUserHandle); + mProfiles.getCloneHandle()); } /** @@ -1058,7 +1089,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements * #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied. */ final int getSelectedProfileExtra() { - Profile.Type selected = mResolverRequest.getSelectedProfile(); + Profile.Type selected = mRequest.getSelectedProfile(); if (selected == null) { return -1; } @@ -1070,29 +1101,11 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } protected final @ProfileType int getCurrentProfile() { - UserHandle launchUser = requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch; - UserHandle personalUser = requireAnnotatedUserHandles().personalProfileUserHandle; + UserHandle launchUser = mProfiles.getTabOwnerUserHandleForLaunch(); + UserHandle personalUser = mProfiles.getPersonalHandle(); return launchUser.equals(personalUser) ? PROFILE_PERSONAL : PROFILE_WORK; } - private AnnotatedUserHandles requireAnnotatedUserHandles() { - return requireNonNull(mLogic.getAnnotatedUserHandles()); - } - - private boolean hasWorkProfile() { - return requireAnnotatedUserHandles().workProfileUserHandle != null; - } - - private boolean hasCloneProfile() { - return requireAnnotatedUserHandles().cloneProfileUserHandle != null; - } - - protected final boolean isLaunchedAsCloneProfile() { - UserHandle launchUser = requireAnnotatedUserHandles().userHandleSharesheetLaunchedAs; - UserHandle cloneUser = requireAnnotatedUserHandles().cloneProfileUserHandle; - return hasCloneProfile() && launchUser.equals(cloneUser); - } - private void updateIntentPickerPaddings() { View titleCont = findViewById(com.android.internal.R.id.title_container); titleCont.setPadding( @@ -1109,14 +1122,15 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) { - if (!hasWorkProfile() || currentUserHandle.equals(getUser())) { + // TODO: Test isolation bug, referencing getUser() will break tests with faked profiles + if (!mProfiles.getWorkProfilePresent() || currentUserHandle.equals(getUser())) { return; } DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED) .setBoolean( currentUserHandle.equals( - requireAnnotatedUserHandles().personalProfileUserHandle)) + mProfiles.getPersonalHandle())) .setStrings(getMetricsCategory(), cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target") .write(); @@ -1172,56 +1186,6 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } } - @Override - protected final void onRestart() { - super.onRestart(); - if (!mRegistered) { - mPersonalPackageMonitor.register( - this, - getMainLooper(), - requireAnnotatedUserHandles().personalProfileUserHandle, - false); - if (hasWorkProfile()) { - if (mWorkPackageMonitor == null) { - mWorkPackageMonitor = createPackageMonitor( - mMultiProfilePagerAdapter.getWorkListAdapter()); - } - mWorkPackageMonitor.register( - this, - getMainLooper(), - requireAnnotatedUserHandles().workProfileUserHandle, - false); - } - mRegistered = true; - } - WorkProfileAvailabilityManager workProfileAvailabilityManager = - mLogic.getWorkProfileAvailabilityManager(); - if (hasWorkProfile() && workProfileAvailabilityManager.isWaitingToEnableWorkProfile()) { - if (workProfileAvailabilityManager.isQuietModeEnabled()) { - workProfileAvailabilityManager.markWorkProfileEnabledBroadcastReceived(); - } - } - mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); - } - - @Override - protected final void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager); - if (viewPager != null) { - outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem()); - } - } - - @Override - protected final void onStart() { - super.onStart(); - this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); - if (hasWorkProfile()) { - mLogic.getWorkProfileAvailabilityManager().registerWorkProfileStateReceiver(this); - } - } - private boolean hasManagedProfile() { UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); if (userManager == null) { @@ -1261,7 +1225,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // In case of clonedProfile being active, we do not allow the 'Always' option in the // disambiguation dialog of Personal Profile as the package manager cannot distinguish // between cross-profile preferred activities. - if (hasCloneProfile() + if (mProfiles.getCloneUserPresent() && (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)) { mAlwaysButton.setEnabled(false); return; @@ -1295,7 +1259,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements if (!hasRecordPermission) { // OK, we know the record permission, is this a capture device - boolean hasAudioCapture = mResolverRequest.isAudioCaptureDevice(); + boolean hasAudioCapture = mViewModel.getRequest().getValue().isAudioCaptureDevice(); enabled = !hasAudioCapture; } } @@ -1378,7 +1342,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // We partially rebuild the inactive adapter to determine if we should auto launch // isTabLoaded will be true here if the empty state screen is shown instead of the list. // To date, we really only care about "partially rebuilding" tabs for work and/or personal. - boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildTabs(hasWorkProfile()); + boolean rebuildCompleted = + mMultiProfilePagerAdapter.rebuildTabs(mProfiles.getWorkProfilePresent()); if (shouldUseMiniResolver()) { configureMiniResolverContent(targetDataLoader); @@ -1392,7 +1357,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements mLayoutId = getLayoutResource(); } setContentView(mLayoutId); - mMultiProfilePagerAdapter.setupViewPager(findViewById(com.android.internal.R.id.profile_pager)); + mMultiProfilePagerAdapter.setupViewPager( + findViewById(com.android.internal.R.id.profile_pager)); boolean result = postRebuildList(rebuildCompleted); Trace.endSection(); return result; @@ -1483,7 +1449,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // If needed, show that intent is forwarded // from managed profile to owner or other way around. String profileSwitchMessage = - mIntentForwarding.forwardMessageFor(mResolverRequest.getIntent()); + mIntentForwarding.forwardMessageFor(mRequest.getIntent()); if (profileSwitchMessage != null) { Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show(); } @@ -1493,9 +1459,10 @@ public class ResolverActivity extends Hilt_ResolverActivity implements } } catch (RuntimeException e) { Slog.wtf(TAG, - "Unable to launch as uid " + mActivityModel.getLaunchedFromUid() - + " package " + getLaunchedFromPackage() + ", while running in " - + ActivityThread.currentProcessName(), e); + "Unable to launch as uid " + + mViewModel.getActivityModel().getLaunchedFromUid() + + " package " + mViewModel.getActivityModel().getLaunchedFromPackage() + + ", while running in " + ActivityThread.currentProcessName(), e); } } @@ -1515,7 +1482,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements setupViewVisibilities(); - if (hasWorkProfile()) { + if (mProfiles.getWorkProfilePresent()) { setupProfileTabs(); } @@ -1638,7 +1605,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET) .setBoolean(activeListAdapter.getUserHandle() - .equals(requireAnnotatedUserHandles().personalProfileUserHandle)) + .equals(mProfiles.getPersonalHandle())) .setStrings(getMetricsCategory()) .write(); safelyStartActivity(activeProfileTarget); @@ -1697,7 +1664,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET) .setBoolean(activeListAdapter.getUserHandle() - .equals(requireAnnotatedUserHandles().personalProfileUserHandle)) + .equals(mProfiles.getPersonalHandle())) .setStrings(getMetricsCategory()) .write(); safelyStartActivity(activeProfileTarget); @@ -1755,17 +1722,17 @@ public class ResolverActivity extends Hilt_ResolverActivity implements && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) { return; } - if (!hasWorkProfile() + if (!mProfiles.getWorkProfilePresent() && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) { final TextView titleView = findViewById(com.android.internal.R.id.title); if (titleView != null) { titleView.setVisibility(View.GONE); } } - - CharSequence title = mResolverRequest.getTitle() != null - ? mResolverRequest.getTitle() - : getTitleForAction(mResolverRequest.getIntent(), 0); + ResolverRequest request = mViewModel.getRequest().getValue(); + CharSequence title = mViewModel.getRequest().getValue().getTitle() != null + ? request.getTitle() + : getTitleForAction(request.getIntent(), 0); if (!TextUtils.isEmpty(title)) { final TextView titleView = findViewById(com.android.internal.R.id.title); @@ -1811,7 +1778,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // We only use the default app layout when the profile of the active user has a // filtered item. We always show the same default app even in the inactive user profile. return mMultiProfilePagerAdapter.getListAdapterForUserHandle( - requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch + mProfiles.getTabOwnerUserHandleForLaunch() ).hasFilteredItem(); } @@ -1943,7 +1910,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements * {@link ResolverListController} configured for the provided {@code userHandle}. */ protected final UserHandle getQueryIntentsUser(UserHandle userHandle) { - return requireAnnotatedUserHandles().getQueryIntentsUser(userHandle); + return mProfiles.getQueryIntentsHandle(userHandle); } /** @@ -1963,9 +1930,9 @@ public class ResolverActivity extends Hilt_ResolverActivity implements // 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; } diff --git a/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt b/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt deleted file mode 100644 index 7eb63ab3..00000000 --- a/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.android.intentresolver.v2 - -import androidx.activity.ComponentActivity -import androidx.annotation.OpenForTesting - -/** Activity logic for [ResolverActivity]. */ -@OpenForTesting -open class ResolverActivityLogic( - tag: String, - activity: ComponentActivity, - onWorkProfileStatusUpdated: () -> Unit, -) : - ActivityLogic, - CommonActivityLogic by CommonActivityLogicImpl( - tag, - activity, - onWorkProfileStatusUpdated, - ) diff --git a/java/src/com/android/intentresolver/v2/ResolverHelper.kt b/java/src/com/android/intentresolver/v2/ResolverHelper.kt new file mode 100644 index 00000000..388b30a7 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/ResolverHelper.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.v2 + +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 com.android.intentresolver.inject.Background +import com.android.intentresolver.v2.annotation.JavaInterop +import com.android.intentresolver.v2.domain.interactor.UserInteractor +import com.android.intentresolver.v2.ui.model.ResolverRequest +import com.android.intentresolver.v2.ui.viewmodel.ResolverViewModel +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.CoroutineDispatcher + +private const val TAG: String = "ResolverHelper" + +/** + * __Purpose__ + * + * Cleanup aid. Provides a pathway to cleaner code. + * + * __Incoming References__ + * + * ResolverHelper must not expose any properties or functions directly back to ResolverActivity. If + * a value or operation is required by ResolverActivity, then it must be added to + * ResolverInitializer (or a new interface as appropriate) with ResolverActivity supplying a + * callback to receive it at the appropriate point. This enforces unidirectional control flow. + * + * __Outgoing References__ + * + * _ResolverActivity_ + * + * This class must only reference it's host as Activity/ComponentActivity; no down-cast to + * [ResolverActivity]. Other components should be created here or supplied via Injection, and not + * referenced directly from the activity. 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 [ResolverInitializer]. + * + * _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. + */ +@ActivityScoped +@JavaInterop +class ResolverHelper +@Inject +constructor( + hostActivity: Activity, + private val userInteractor: UserInteractor, + @Background private val background: CoroutineDispatcher, +) : DefaultLifecycleObserver { + // This is guaranteed by Hilt, since only a ComponentActivity is injectable. + private val activity: ComponentActivity = hostActivity as ComponentActivity + private val viewModel by activity.viewModels<ResolverViewModel>() + + private lateinit var activityInitializer: Runnable + + init { + activity.lifecycle.addObserver(this) + } + + /** + * Set the initialization hook for the host activity. + * + * This _must_ be called from [ResolverActivity.onCreate]. + */ + fun setInitializer(initializer: Runnable) { + if (activity.lifecycle.currentState != Lifecycle.State.INITIALIZED) { + error("setInitializer must be called before onCreate returns") + } + activityInitializer = initializer + } + + /** Invoked by Lifecycle, after Activity.onCreate() _returns_. */ + override fun onCreate(owner: LifecycleOwner) { + 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 resolver from uid $callerUid") + activity.finish() + return + } + + when (val request = viewModel.initialRequest) { + is Valid -> initializeActivity(request) + is Invalid -> reportErrorsAndFinish(request) + } + } + + private fun reportErrorsAndFinish(request: Invalid<ResolverRequest>) { + request.errors.forEach { it.log(TAG) } + activity.finish() + } + + private fun initializeActivity(request: Valid<ResolverRequest>) { + Log.d(TAG, "initializeActivity") + request.warnings.forEach { it.log(TAG) } + + activityInitializer.run() + } +} diff --git a/java/src/com/android/intentresolver/v2/emptystate/ResolverNoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/v2/emptystate/ResolverNoCrossProfileEmptyStateProvider.java deleted file mode 100644 index f133c31d..00000000 --- a/java/src/com/android/intentresolver/v2/emptystate/ResolverNoCrossProfileEmptyStateProvider.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.v2.emptystate; - -import android.app.admin.DevicePolicyEventLogger; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.os.UserHandle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -import com.android.intentresolver.ResolverListAdapter; -import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.emptystate.EmptyState; -import com.android.intentresolver.emptystate.EmptyStateProvider; - -/** - * Empty state provider that does not allow cross profile sharing, it will return a blocker - * in case if the profile of the current tab is not the same as the profile of the calling app. - */ -public class ResolverNoCrossProfileEmptyStateProvider implements EmptyStateProvider { - - private final UserHandle mPersonalProfileUserHandle; - private final EmptyState mNoWorkToPersonalEmptyState; - private final EmptyState mNoPersonalToWorkEmptyState; - private final CrossProfileIntentsChecker mCrossProfileIntentsChecker; - private final UserHandle mTabOwnerUserHandleForLaunch; - - public ResolverNoCrossProfileEmptyStateProvider(UserHandle personalUserHandle, - EmptyState noWorkToPersonalEmptyState, - EmptyState noPersonalToWorkEmptyState, - CrossProfileIntentsChecker crossProfileIntentsChecker, - UserHandle tabOwnerUserHandleForLaunch) { - mPersonalProfileUserHandle = personalUserHandle; - mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState; - mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState; - mCrossProfileIntentsChecker = crossProfileIntentsChecker; - mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch; - } - - @Nullable - @Override - public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) { - boolean shouldShowBlocker = - !mTabOwnerUserHandleForLaunch.equals(resolverListAdapter.getUserHandle()) - && !mCrossProfileIntentsChecker - .hasCrossProfileIntents(resolverListAdapter.getIntents(), - mTabOwnerUserHandleForLaunch.getIdentifier(), - resolverListAdapter.getUserHandle().getIdentifier()); - - if (!shouldShowBlocker) { - return null; - } - - if (resolverListAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) { - return mNoWorkToPersonalEmptyState; - } else { - return mNoPersonalToWorkEmptyState; - } - } - - - /** - * Empty state that gets strings from the device policy manager and tracks events into - * event logger of the device policy events. - */ - public static class DevicePolicyBlockerEmptyState implements EmptyState { - - @NonNull - private final Context mContext; - private final String mDevicePolicyStringTitleId; - @StringRes - private final int mDefaultTitleResource; - private final String mDevicePolicyStringSubtitleId; - @StringRes - private final int mDefaultSubtitleResource; - private final int mEventId; - @NonNull - private final String mEventCategory; - - public DevicePolicyBlockerEmptyState(@NonNull Context context, - String devicePolicyStringTitleId, @StringRes int defaultTitleResource, - String devicePolicyStringSubtitleId, @StringRes int defaultSubtitleResource, - int devicePolicyEventId, @NonNull String devicePolicyEventCategory) { - mContext = context; - mDevicePolicyStringTitleId = devicePolicyStringTitleId; - mDefaultTitleResource = defaultTitleResource; - mDevicePolicyStringSubtitleId = devicePolicyStringSubtitleId; - mDefaultSubtitleResource = defaultSubtitleResource; - mEventId = devicePolicyEventId; - mEventCategory = devicePolicyEventCategory; - } - - @Nullable - @Override - public String getTitle() { - return mContext.getSystemService(DevicePolicyManager.class).getResources().getString( - mDevicePolicyStringTitleId, - () -> mContext.getString(mDefaultTitleResource)); - } - - @Nullable - @Override - public String getSubtitle() { - return mContext.getSystemService(DevicePolicyManager.class).getResources().getString( - mDevicePolicyStringSubtitleId, - () -> mContext.getString(mDefaultSubtitleResource)); - } - - @Override - public void onEmptyStateShown() { - DevicePolicyEventLogger.createEvent(mEventId) - .setStrings(mEventCategory) - .write(); - } - - @Override - public boolean shouldSkipDataRebuild() { - return true; - } - } -} diff --git a/java/src/com/android/intentresolver/v2/emptystate/ResolverWorkProfilePausedEmptyStateProvider.java b/java/src/com/android/intentresolver/v2/emptystate/ResolverWorkProfilePausedEmptyStateProvider.java deleted file mode 100644 index eaed35a7..00000000 --- a/java/src/com/android/intentresolver/v2/emptystate/ResolverWorkProfilePausedEmptyStateProvider.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.v2.emptystate; - -import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; - -import android.app.admin.DevicePolicyEventLogger; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.os.UserHandle; -import android.stats.devicepolicy.nano.DevicePolicyEnums; - -import androidx.annotation.NonNull; -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; - -/** - * ResolverActivity empty state provider that returns empty state which is shown when - * work profile is paused and we need to show a button to enable it. - */ -public class ResolverWorkProfilePausedEmptyStateProvider implements EmptyStateProvider { - - private final UserHandle mWorkProfileUserHandle; - private final WorkProfileAvailabilityManager mWorkProfileAvailability; - private final String mMetricsCategory; - private final OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener; - private final Context mContext; - - public ResolverWorkProfilePausedEmptyStateProvider(@NonNull Context context, - @Nullable UserHandle workProfileUserHandle, - @NonNull WorkProfileAvailabilityManager workProfileAvailability, - @Nullable OnSwitchOnWorkSelectedListener onSwitchOnWorkSelectedListener, - @NonNull String metricsCategory) { - mContext = context; - mWorkProfileUserHandle = workProfileUserHandle; - mWorkProfileAvailability = workProfileAvailability; - mMetricsCategory = metricsCategory; - mOnSwitchOnWorkSelectedListener = onSwitchOnWorkSelectedListener; - } - - @Nullable - @Override - public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) { - if (!resolverListAdapter.getUserHandle().equals(mWorkProfileUserHandle) - || !mWorkProfileAvailability.isQuietModeEnabled() - || resolverListAdapter.getCount() == 0) { - return null; - } - - final String title = mContext.getSystemService(DevicePolicyManager.class) - .getResources().getString(RESOLVER_WORK_PAUSED_TITLE, - () -> mContext.getString(R.string.resolver_turn_on_work_apps)); - - return new WorkProfileOffEmptyState(title, (tab) -> { - tab.showSpinner(); - if (mOnSwitchOnWorkSelectedListener != null) { - mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected(); - } - mWorkProfileAvailability.requestQuietModeEnabled(false); - }, mMetricsCategory); - } - - public static class WorkProfileOffEmptyState implements EmptyState { - - private final String mTitle; - private final ClickListener mOnClick; - private final String mMetricsCategory; - - public WorkProfileOffEmptyState(String title, @NonNull ClickListener onClick, - @NonNull String metricsCategory) { - mTitle = title; - mOnClick = onClick; - mMetricsCategory = metricsCategory; - } - - @Nullable - @Override - public String getTitle() { - return mTitle; - } - - @Nullable - @Override - public ClickListener getButtonClickListener() { - return mOnClick; - } - - @Override - public void onEmptyStateShown() { - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED) - .setStrings(mMetricsCategory) - .write(); - } - } -} diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverViewModel.kt b/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverViewModel.kt new file mode 100644 index 00000000..eb6a1b96 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverViewModel.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.v2.ui.viewmodel + +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import com.android.intentresolver.v2.ui.model.ActivityModel +import com.android.intentresolver.v2.ui.model.ActivityModel.Companion.ACTIVITY_MODEL_KEY +import com.android.intentresolver.v2.ui.model.ResolverRequest +import com.android.intentresolver.v2.validation.Invalid +import com.android.intentresolver.v2.validation.Valid +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 = "ResolverViewModel" + +@HiltViewModel +class ResolverViewModel @Inject constructor(args: SavedStateHandle) : ViewModel() { + + /** Parcelable-only references provided from the creating Activity */ + val activityModel: ActivityModel = + requireNotNull(args[ACTIVITY_MODEL_KEY]) { + "ActivityModel missing in SavedStateHandle! ($ACTIVITY_MODEL_KEY)" + } + + /** + * 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 = readResolverRequest(activityModel) + + private lateinit var _request: MutableStateFlow<ResolverRequest> + + /** + * A [StateFlow] of [ResolverRequest]. + * + * Note: Only safe to access after checking if [initialRequest] is [Valid]. + */ + lateinit var request: StateFlow<ResolverRequest> + private set + + init { + when (initialRequest) { + is Valid -> { + _request = MutableStateFlow(initialRequest.value) + request = _request.asStateFlow() + } + is Invalid -> Log.w(TAG, "initialRequest is Invalid, initialization failed") + } + } +} diff --git a/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java b/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java index 05d397a2..81f6f5a6 100644 --- a/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java +++ b/tests/activity/src/com/android/intentresolver/ResolverActivityTest.java @@ -49,9 +49,9 @@ import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; -import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.espresso.Espresso; import androidx.test.espresso.NoMatchingViewException; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; diff --git a/tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java b/tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java index 21fe2904..220a12cc 100644 --- a/tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java +++ b/tests/activity/src/com/android/intentresolver/v2/ResolverActivityTest.java @@ -25,6 +25,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.android.intentresolver.MatcherUtils.first; import static com.android.intentresolver.v2.ResolverWrapperActivity.sOverrides; @@ -55,16 +56,23 @@ import androidx.test.espresso.NoMatchingViewException; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; -import com.android.intentresolver.AnnotatedUserHandles; import com.android.intentresolver.R; import com.android.intentresolver.ResolvedComponentInfo; import com.android.intentresolver.ResolverDataProvider; +import com.android.intentresolver.inject.ApplicationUser; +import com.android.intentresolver.inject.ProfileParent; +import com.android.intentresolver.v2.data.repository.FakeUserRepository; +import com.android.intentresolver.v2.data.repository.UserRepository; +import com.android.intentresolver.v2.data.repository.UserRepositoryModule; +import com.android.intentresolver.v2.shared.model.User; import com.android.intentresolver.widget.ResolverDrawerLayout; import com.google.android.collect.Lists; +import dagger.hilt.android.testing.BindValue; import dagger.hilt.android.testing.HiltAndroidRule; import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.UninstallModules; import org.junit.Before; import org.junit.Ignore; @@ -81,19 +89,15 @@ import java.util.List; */ @RunWith(AndroidJUnit4.class) @HiltAndroidTest +@UninstallModules(UserRepositoryModule.class) public class ResolverActivityTest { - private static final UserHandle PERSONAL_USER_HANDLE = androidx.test.platform.app - .InstrumentationRegistry.getInstrumentation().getTargetContext().getUser(); + private static final UserHandle PERSONAL_USER_HANDLE = + getInstrumentation().getTargetContext().getUser(); private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10); private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11); - - protected Intent getConcreteIntentForLaunch(Intent clientIntent) { - clientIntent.setClass( - androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getTargetContext(), - ResolverWrapperActivity.class); - return clientIntent; - } + private static final User WORK_PROFILE_USER = + new User(WORK_PROFILE_USER_HANDLE.getIdentifier(), User.Role.WORK); @Rule(order = 0) public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this); @@ -106,14 +110,30 @@ public class ResolverActivityTest { public void setup() { // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the // permissions we require (which we'll read from the manifest at runtime). - androidx.test.platform.app.InstrumentationRegistry - .getInstrumentation() + getInstrumentation() .getUiAutomation() .adoptShellPermissionIdentity(); sOverrides.reset(); } + @BindValue + @ApplicationUser + public final UserHandle mApplicationUser = PERSONAL_USER_HANDLE; + + @BindValue + @ProfileParent + public final UserHandle mProfileParent = PERSONAL_USER_HANDLE; + + /** For setup of test state, a mutable reference of mUserRepository */ + private final FakeUserRepository mFakeUserRepo = + new FakeUserRepository(List.of( + new User(PERSONAL_USER_HANDLE.getIdentifier(), User.Role.PERSONAL) + )); + + @BindValue + public final UserRepository mUserRepository = mFakeUserRepo; + @Test public void twoOptionsAndUserSelectsOne() throws InterruptedException { Intent sendIntent = createSendImageIntent(); @@ -404,15 +424,14 @@ public class ResolverActivityTest { @Test public void testWorkTab_workTabUsesExpectedAdapter() { + markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List<ResolvedComponentInfo> personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, WORK_PROFILE_USER_HANDLE); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); waitForIdle(); @@ -424,9 +443,9 @@ public class ResolverActivityTest { @Test public void testWorkTab_personalTabUsesExpectedAdapter() { + markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List<ResolvedComponentInfo> personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, WORK_PROFILE_USER_HANDLE); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); @@ -464,7 +483,8 @@ public class ResolverActivityTest { public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, + createResolvedComponentsForTestWithOtherProfile(3, + /* userId */ WORK_PROFILE_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, WORK_PROFILE_USER_HANDLE); @@ -622,7 +642,7 @@ public class ResolverActivityTest { PERSONAL_USER_HANDLE); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE); - sOverrides.isQuietModeEnabled = true; + mFakeUserRepo.updateState(WORK_PROFILE_USER, false); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); sendIntent.setType("TestType"); @@ -670,7 +690,7 @@ public class ResolverActivityTest { setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); sendIntent.setType("TestType"); - sOverrides.isQuietModeEnabled = true; + mFakeUserRepo.updateState(WORK_PROFILE_USER, false); sOverrides.hasCrossProfileIntents = false; mActivityRule.launchActivity(sendIntent); @@ -740,7 +760,7 @@ public class ResolverActivityTest { setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); sendIntent.setType("TestType"); - sOverrides.isQuietModeEnabled = true; + mFakeUserRepo.updateState(WORK_PROFILE_USER, false); mActivityRule.launchActivity(sendIntent); waitForIdle(); @@ -1068,18 +1088,14 @@ public class ResolverActivityTest { } private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) { - AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder(); - handles - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE) - .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE); if (workAvailable) { - handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE); + mFakeUserRepo.addUser( + new User(WORK_PROFILE_USER_HANDLE.getIdentifier(), User.Role.WORK), true); } if (cloneAvailable) { - handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE); + mFakeUserRepo.addUser( + new User(CLONE_PROFILE_USER_HANDLE.getIdentifier(), User.Role.CLONE), true); } - sOverrides.annotatedUserHandles = handles.build(); } private void setupResolverControllers( @@ -1095,21 +1111,14 @@ public class ResolverActivityTest { Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) + eq(PERSONAL_USER_HANDLE))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when(sOverrides.workResolverListController.getResolversForIntentAsUser( Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), - eq(UserHandle.of(10)))) + eq(WORK_PROFILE_USER_HANDLE))) .thenReturn(new ArrayList<>(workResolvedComponentInfos)); } } diff --git a/tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java b/tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java index 2e29be11..e3d2edbb 100644 --- a/tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java +++ b/tests/activity/src/com/android/intentresolver/v2/ResolverWrapperActivity.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -34,10 +33,8 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.test.espresso.idling.CountingIdlingResource; -import com.android.intentresolver.AnnotatedUserHandles; import com.android.intentresolver.ResolverListAdapter; import com.android.intentresolver.ResolverListController; -import com.android.intentresolver.WorkProfileAvailabilityManager; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.SelectableTargetInfo; import com.android.intentresolver.chooser.TargetInfo; @@ -45,8 +42,6 @@ import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; import com.android.intentresolver.icons.LabelInfo; import com.android.intentresolver.icons.TargetDataLoader; -import kotlin.Unit; - import java.util.List; import java.util.function.Consumer; import java.util.function.Function; @@ -60,19 +55,6 @@ public class ResolverWrapperActivity extends ResolverActivity { private final CountingIdlingResource mLabelIdlingResource = new CountingIdlingResource("LoadLabelTask"); - @Override - protected final ResolverActivityLogic createActivityLogic() { - return new TestResolverActivityLogic( - "ResolverWrapper", - this, - () -> { - onWorkProfileStatusUpdated(); - return Unit.INSTANCE; - }, - sOverrides - ); - } - public CountingIdlingResource getLabelIdlingResource() { return mLabelIdlingResource; } @@ -154,12 +136,6 @@ public class ResolverWrapperActivity extends ResolverActivity { super.startActivityAsUser(intent, options, user); } - @Override - protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle - userHandle) { - return super.getResolverRankerServiceUserHandleListInternal(userHandle); - } - /** * We cannot directly mock the activity created since instrumentation creates it. * <p> @@ -167,58 +143,19 @@ public class ResolverWrapperActivity extends ResolverActivity { */ public static class OverrideData { @SuppressWarnings("Since15") - public Function<PackageManager, PackageManager> createPackageManager; public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback; public ResolverListController resolverListController; public ResolverListController workResolverListController; public Boolean isVoiceInteraction; - public AnnotatedUserHandles annotatedUserHandles; - public Integer myUserId; public boolean hasCrossProfileIntents; - public boolean isQuietModeEnabled; - public WorkProfileAvailabilityManager mWorkProfileAvailability; public CrossProfileIntentsChecker mCrossProfileIntentsChecker; public void reset() { onSafelyStartInternalCallback = null; isVoiceInteraction = null; - createPackageManager = null; resolverListController = mock(ResolverListController.class); workResolverListController = mock(ResolverListController.class); - annotatedUserHandles = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM) - .setPersonalProfileUserHandle(UserHandle.SYSTEM) - .build(); - myUserId = null; hasCrossProfileIntents = true; - isQuietModeEnabled = false; - - mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) { - @Override - public boolean isQuietModeEnabled() { - return isQuietModeEnabled; - } - - @Override - public boolean isWorkProfileUserUnlocked() { - return true; - } - - @Override - public void requestQuietModeEnabled(boolean enabled) { - isQuietModeEnabled = enabled; - } - - @Override - public void markWorkProfileEnabledBroadcastReceived() {} - - @Override - public boolean isWaitingToEnableWorkProfile() { - return false; - } - }; - mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class); when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt())) .thenAnswer(invocation -> hasCrossProfileIntents); diff --git a/tests/activity/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt b/tests/activity/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt deleted file mode 100644 index 6826f23d..00000000 --- a/tests/activity/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.android.intentresolver.v2 - -import androidx.activity.ComponentActivity -import com.android.intentresolver.AnnotatedUserHandles -import com.android.intentresolver.WorkProfileAvailabilityManager - -/** Activity logic for use when testing [ResolverActivity]. */ -class TestResolverActivityLogic( - tag: String, - activity: ComponentActivity, - onWorkProfileStatusUpdated: () -> Unit, - private val overrideData: ResolverWrapperActivity.OverrideData, -) : ResolverActivityLogic(tag, activity, onWorkProfileStatusUpdated) { - - override val annotatedUserHandles: AnnotatedUserHandles? by lazy { - overrideData.annotatedUserHandles - } - - override val workProfileAvailabilityManager: WorkProfileAvailabilityManager by lazy { - overrideData.mWorkProfileAvailability ?: super.workProfileAvailabilityManager - } -} |