diff options
5 files changed, 332 insertions, 189 deletions
diff --git a/java/src/com/android/intentresolver/v2/ActivityLogic.kt b/java/src/com/android/intentresolver/v2/ActivityLogic.kt new file mode 100644 index 00000000..0613882e --- /dev/null +++ b/java/src/com/android/intentresolver/v2/ActivityLogic.kt @@ -0,0 +1,90 @@ +package com.android.intentresolver.v2 + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.activity.ComponentActivity +import com.android.intentresolver.icons.TargetDataLoader + +/** + * 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 { + /** The intent for the target. This will always come before [additionalTargets], if any. */ + val targetIntent: Intent + /** Whether the intent is for home. */ + val resolvingHome: Boolean + /** Intents for additional targets. These will always come after [targetIntent]. */ + val additionalTargets: List<Intent>? + /** Custom title to display. */ + val title: CharSequence? + /** Resource ID for the title to display when there is no custom title. */ + val defaultTitleResId: Int + /** Intents received to be processed. */ + val initialIntents: List<Intent>? + /** Whether or not this activity supports choosing a default handler for the intent. */ + val supportsAlwaysUseOption: Boolean + /** Fetches display info for processed candidates. */ + val targetDataLoader: TargetDataLoader + + /** + * Called after Activity superclass creation, but before any other onCreate logic is performed. + */ + fun preInitialization() +} + +/** + * Logic that is common to all IntentResolver activities. Anything that is the same across + * activities (including test activities), should live here. + */ +interface CommonActivityLogic { + /** A reference to the activity owning, and used by, this logic. */ + val activity: ComponentActivity + /** The name of the referring package. */ + val referrerPackageName: 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 + // possible. + val ComponentActivity.context: Context + val ComponentActivity.intent: Intent + val ComponentActivity.referrer: Uri? +} + +/** + * 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(activityProvider: () -> ComponentActivity) : CommonActivityLogic { + + override val activity: ComponentActivity by lazy { activityProvider() } + + override val referrerPackageName: String? by lazy { + activity.referrer.let { + if (ANDROID_APP_URI_SCHEME == it?.scheme) { + it.host + } else { + null + } + } + } + + companion object { + private const val ANDROID_APP_URI_SCHEME = "android-app" + } + + // 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 + // possible. + override val ComponentActivity.context: Context + get() = (this as Activity) + override val ComponentActivity.intent: Intent + get() = (this as Activity).intent + override val ComponentActivity.referrer: Uri? + get() = (this as Activity).referrer +} diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java index c1d73e69..d2dabfb3 100644 --- a/java/src/com/android/intentresolver/v2/ChooserActivity.java +++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java @@ -28,6 +28,8 @@ import static androidx.lifecycle.LifecycleKt.getCoroutineScope; import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET; +import static java.util.Objects.requireNonNull; + import android.annotation.IntDef; import android.annotation.Nullable; import android.app.Activity; @@ -116,6 +118,8 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import dagger.hilt.android.AndroidEntryPoint; +import kotlin.Unit; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.Collator; @@ -129,6 +133,7 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import javax.inject.Inject; @@ -195,15 +200,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Inject @NearbyShare public Optional<ComponentName> mNearbyShare; @Inject public TargetDataLoader mTargetDataLoader; - /* TODO: this is `nullable` because we have to defer the assignment til onCreate(). We make the - * only assignment there, and expect it to be ready by the time we ever use it -- - * someday if we move all the usage to a component with a narrower lifecycle (something that - * matches our Activity's create/destroy lifecycle, not its Java object lifecycle) then we - * should be able to make this assignment as "final." - */ - @Nullable - private ChooserRequestParameters mChooserRequest; - private ChooserRefinementManager mRefinementManager; private ChooserContentPreviewUi mChooserContentPreviewUi; @@ -251,44 +247,50 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override protected void onCreate(Bundle savedInstanceState) { Tracer.INSTANCE.markLaunched(); + AtomicLong intentReceivedTime = new AtomicLong(-1); + 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; + } + ); super.onCreate(savedInstanceState); - - final long intentReceivedTime = System.currentTimeMillis(); - mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET); - - try { - mChooserRequest = new ChooserRequestParameters( - getIntent(), - getReferrerPackageName(), - getReferrer()); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Caller provided invalid Chooser request parameters", e); + if (getChooserRequest() == null) { finish(); return; } - mPinnedSharedPrefs = getPinnedSharedPrefs(this); - mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); - mShouldDisplayLandscape = - shouldDisplayLandscape(getResources().getConfiguration().orientation); - setRetainInOnStop(mChooserRequest.shouldRetainInOnStop()); - - createProfileRecords( - new AppPredictorFactory( - this, - mChooserRequest.getSharedText(), - mChooserRequest.getTargetIntentFilter()), - mChooserRequest.getTargetIntentFilter()); - - init( - mChooserRequest.getTargetIntent(), - mChooserRequest.getAdditionalTargets(), - mChooserRequest.getTitle(), - mChooserRequest.getDefaultTitleResource(), - mChooserRequest.getInitialIntents(), - /* resolutionList= */ null, - /* supportsAlwaysUseOption= */ false, - mTargetDataLoader, - /* safeForwardingMode= */ true); + if (isFinishing()) { + // Performing a clean exit: + // Skip initializing any additional resources. + return; + } getEventLog().logSharesheetTriggered(); @@ -315,10 +317,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements BasePreviewViewModel previewViewModel = new ViewModelProvider(this, createPreviewViewModelFactory()) .get(BasePreviewViewModel.class); + ChooserRequestParameters chooserRequest = requireChooserRequest(); mChooserContentPreviewUi = new ChooserContentPreviewUi( getCoroutineScope(getLifecycle()), - previewViewModel.createOrReuseProvider(mChooserRequest), - mChooserRequest.getTargetIntent(), + previewViewModel.createOrReuseProvider(chooserRequest), + chooserRequest.getTargetIntent(), previewViewModel.createOrReuseImageLoader(), createChooserActionFactory(), mEnterTransitionAnimationDelegate, @@ -333,9 +336,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } mChooserShownTime = System.currentTimeMillis(); - final long systemCost = mChooserShownTime - intentReceivedTime; + final long systemCost = mChooserShownTime - intentReceivedTime.get(); getEventLog().logChooserActivityShown( - isWorkProfile(), mChooserRequest.getTargetType(), systemCost); + isWorkProfile(), chooserRequest.getTargetType(), systemCost); if (mResolverDrawerLayout != null) { mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange); @@ -352,21 +355,30 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } getEventLog().logShareStarted( - getReferrerPackageName(), - mChooserRequest.getTargetType(), - mChooserRequest.getCallerChooserTargets().size(), - (mChooserRequest.getInitialIntents() == null) - ? 0 : mChooserRequest.getInitialIntents().length, + mLogic.getReferrerPackageName(), + chooserRequest.getTargetType(), + chooserRequest.getCallerChooserTargets().size(), + (chooserRequest.getInitialIntents() == null) + ? 0 : chooserRequest.getInitialIntents().length, isWorkProfile(), mChooserContentPreviewUi.getPreferredContentPreview(), - mChooserRequest.getTargetAction(), - mChooserRequest.getChooserActions().size(), - mChooserRequest.getModifyShareAction() != null + chooserRequest.getTargetAction(), + chooserRequest.getChooserActions().size(), + chooserRequest.getModifyShareAction() != null ); mEnterTransitionAnimationDelegate.postponeTransition(); } + @Nullable + private ChooserRequestParameters getChooserRequest() { + return ((ChooserActivityLogic) mLogic).getChooserRequestParameters(); + } + + private ChooserRequestParameters requireChooserRequest() { + return requireNonNull(getChooserRequest()); + } + @Override protected int appliedThemeResId() { return R.style.Theme_DeviceDefault_Chooser; @@ -445,7 +457,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override protected EmptyStateProvider createBlockerEmptyStateProvider() { - final boolean isSendAction = mChooserRequest.isSendActionTarget(); + final boolean isSendAction = requireChooserRequest().isSendActionTarget(); final EmptyState noWorkToPersonalEmptyState = new DevicePolicyBlockerEmptyState( @@ -740,14 +752,15 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override // ResolverListCommunicator public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { - if (mChooserRequest == null) { + ChooserRequestParameters chooserRequest = getChooserRequest(); + if (chooserRequest == null) { return defIntent; } Intent result = defIntent; - if (mChooserRequest.getReplacementExtras() != null) { + if (chooserRequest.getReplacementExtras() != null) { final Bundle replExtras = - mChooserRequest.getReplacementExtras().getBundle(aInfo.packageName); + chooserRequest.getReplacementExtras().getBundle(aInfo.packageName); if (replExtras != null) { result = new Intent(defIntent); result.putExtras(replExtras); @@ -768,12 +781,13 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override public void onActivityStarted(TargetInfo cti) { - if (mChooserRequest.getChosenComponentSender() != null) { + ChooserRequestParameters chooserRequest = requireChooserRequest(); + if (chooserRequest.getChosenComponentSender() != null) { final ComponentName target = cti.getResolvedComponentName(); if (target != null) { final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target); try { - mChooserRequest.getChosenComponentSender().sendIntent( + chooserRequest.getChosenComponentSender().sendIntent( this, Activity.RESULT_OK, fillIn, null, null); } catch (IntentSender.SendIntentException e) { Slog.e(TAG, "Unable to launch supplied IntentSender to report " @@ -784,7 +798,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements } private void addCallerChooserTargets() { - if (!mChooserRequest.getCallerChooserTargets().isEmpty()) { + ChooserRequestParameters chooserRequest = requireChooserRequest(); + if (!chooserRequest.getCallerChooserTargets().isEmpty()) { // Send the caller's chooser targets only to the default profile. UserHandle defaultUser = (findSelectedProfile() == PROFILE_WORK) ? getAnnotatedUserHandles().workProfileUserHandle @@ -792,7 +807,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle() == defaultUser) { mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( /* origTarget */ null, - new ArrayList<>(mChooserRequest.getCallerChooserTargets()), + new ArrayList<>(chooserRequest.getCallerChooserTargets()), TARGET_TYPE_DEFAULT, /* directShareShortcutInfoCache */ Collections.emptyMap(), /* directShareAppTargetCache */ Collections.emptyMap()); @@ -837,7 +852,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements // the logic into `ChooserTargetActionsDialogFragment.show()`. boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned(); IntentFilter intentFilter = targetInfo.isSelectableTargetInfo() - ? mChooserRequest.getTargetIntentFilter() : null; + ? requireChooserRequest().getTargetIntentFilter() : null; String shortcutTitle = targetInfo.isSelectableTargetInfo() ? targetInfo.getDisplayLabel().toString() : null; String shortcutIdKey = targetInfo.getDirectShareShortcutId(); @@ -858,7 +873,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { if (mRefinementManager.maybeHandleSelection( target, - mChooserRequest.getRefinementIntentSender(), + requireChooserRequest().getRefinementIntentSender(), getApplication(), getMainThreadHandler())) { return false; @@ -913,7 +928,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements targetInfo.getResolveInfo().activityInfo.processName, which, /* directTargetAlsoRanked= */ getRankedPosition(targetInfo), - mChooserRequest.getCallerChooserTargets().size(), + requireChooserRequest().getCallerChooserTargets().size(), targetInfo.getHashedTargetIdForMetrics(this), targetInfo.isPinned(), mIsSuccessfullySelected, @@ -1032,7 +1047,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements if (targetIntent == null) { return; } - Intent originalTargetIntent = new Intent(mChooserRequest.getTargetIntent()); + Intent originalTargetIntent = new Intent(requireChooserRequest().getTargetIntent()); // Our TargetInfo implementations add associated component to the intent, let's do the same // for the sake of the comparison below. if (targetIntent.getComponent() != null) { @@ -1152,7 +1167,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements @Override public boolean isComponentFiltered(ComponentName name) { - return mChooserRequest.getFilteredComponentNames().contains(name); + return requireChooserRequest().getFilteredComponentNames().contains(name); } @Override @@ -1179,7 +1194,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements createListController(userHandle), userHandle, getTargetIntent(), - mChooserRequest, + requireChooserRequest(), mMaxTargetsPerRow, targetDataLoader); @@ -1279,14 +1294,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements AbstractResolverComparator resolverComparator; if (appPredictor != null) { resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(), - getReferrerPackageName(), appPredictor, userHandle, getEventLog(), + mLogic.getReferrerPackageName(), appPredictor, userHandle, getEventLog(), mNearbyShare.orElse(null)); } else { resolverComparator = new ResolverRankerServiceResolverComparator( this, getTargetIntent(), - getReferrerPackageName(), + mLogic.getReferrerPackageName(), null, getEventLog(), getResolverRankerServiceUserHandleList(userHandle), @@ -1297,7 +1312,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements this, mPm, getTargetIntent(), - getReferrerPackageName(), + mLogic.getReferrerPackageName(), getAnnotatedUserHandles().userIdOfCallingApp, resolverComparator, getQueryIntentsUser(userHandle)); @@ -1311,7 +1326,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements private ChooserActionFactory createChooserActionFactory() { return new ChooserActionFactory( this, - mChooserRequest, + requireChooserRequest(), mImageEditor, getEventLog(), (isExcluded) -> mExcludeSharedText = isExcluded, @@ -1681,7 +1696,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements * @return true if we want to show the content preview area */ protected boolean shouldShowContentPreview() { - return (mChooserRequest != null) && mChooserRequest.isSendActionTarget(); + ChooserRequestParameters chooserRequest = getChooserRequest(); + return (chooserRequest != null) && chooserRequest.isSendActionTarget(); } private void updateStickyContentPreview() { diff --git a/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt b/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt new file mode 100644 index 00000000..1db3f407 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt @@ -0,0 +1,56 @@ +package com.android.intentresolver.v2 + +import android.app.Activity +import android.content.Intent +import android.util.Log +import androidx.activity.ComponentActivity +import com.android.intentresolver.ChooserRequestParameters +import com.android.intentresolver.icons.TargetDataLoader + +/** Activity logic for [ChooserActivity]. */ +class ChooserActivityLogic( + private val tag: String, + activityProvider: () -> ComponentActivity, + targetDataLoaderProvider: () -> TargetDataLoader, + private val onPreInitialization: () -> Unit, +) : ActivityLogic, CommonActivityLogic by CommonActivityLogicImpl(activityProvider) { + + override val targetIntent: Intent by lazy { chooserRequestParameters?.targetIntent ?: Intent() } + + override val resolvingHome: Boolean = false + + override val additionalTargets: List<Intent>? by lazy { + chooserRequestParameters?.additionalTargets?.toList() + } + + override val title: CharSequence? by lazy { chooserRequestParameters?.title } + + override val defaultTitleResId: Int by lazy { + chooserRequestParameters?.defaultTitleResource ?: 0 + } + + override val initialIntents: List<Intent>? by lazy { + chooserRequestParameters?.initialIntents?.toList() + } + + override val supportsAlwaysUseOption: Boolean = false + + override val targetDataLoader: TargetDataLoader by lazy { targetDataLoaderProvider() } + + val chooserRequestParameters: ChooserRequestParameters? by lazy { + try { + ChooserRequestParameters( + (activity as Activity).intent, + referrerPackageName, + (activity as Activity).referrer, + ) + } catch (e: IllegalArgumentException) { + Log.e(tag, "Caller provided invalid Chooser request parameters", e) + null + } + } + + override fun preInitialization() { + onPreInitialization() + } +} diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java index a0388456..1c9ee99d 100644 --- a/java/src/com/android/intentresolver/v2/ResolverActivity.java +++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java @@ -27,7 +27,6 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERS import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY; -import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.PermissionChecker.PID_UNKNOWN; import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; @@ -109,7 +108,6 @@ import com.android.intentresolver.emptystate.CompositeEmptyStateProvider; import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; 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.model.ResolverRankerServiceResolverComparator; import com.android.intentresolver.v2.MultiProfilePagerAdapter.MyUserIdProvider; @@ -145,6 +143,8 @@ import java.util.function.Supplier; public class ResolverActivity extends FragmentActivity implements ResolverListAdapter.ResolverListCommunicator { + protected ActivityLogic mLogic = new ResolverActivityLogic(() -> this); + public ResolverActivity() { mIsIntentPicker = getClass().equals(ResolverActivity.class); } @@ -153,33 +153,17 @@ public class ResolverActivity extends FragmentActivity implements mIsIntentPicker = isIntentPicker; } - /** - * Whether to enable a launch mode that is safe to use when forwarding intents received from - * applications and running in system processes. This mode uses Activity.startActivityAsCaller - * instead of the normal Activity.startActivity for launching the activity selected - * by the user. - */ - private boolean mSafeForwardingMode; - private Button mAlwaysButton; private Button mOnceButton; protected View mProfileView; private int mLastSelected = AbsListView.INVALID_POSITION; - private boolean mResolvingHome = false; private String mProfileSwitchMessage; private int mLayoutId; @VisibleForTesting protected final ArrayList<Intent> mIntents = new ArrayList<>(); private PickTargetOptionRequest mPickOptionRequest; - private String mReferrerPackage; - private CharSequence mTitle; - private int mDefaultTitleResId; // Expected to be true if this object is ResolverActivity or is ResolverWrapperActivity. private final boolean mIsIntentPicker; - - // Whether or not this activity supports choosing a default handler for the intent. - @VisibleForTesting - protected boolean mSupportsAlwaysUseOption; protected ResolverDrawerLayout mResolverDrawerLayout; protected PackageManager mPm; @@ -207,8 +191,6 @@ public class ResolverActivity extends FragmentActivity implements private PackageMonitor mPersonalPackageMonitor; private PackageMonitor mWorkPackageMonitor; - private TargetDataLoader mTargetDataLoader; - @VisibleForTesting protected MultiProfilePagerAdapter mMultiProfilePagerAdapter; @@ -355,41 +337,23 @@ public class ResolverActivity extends FragmentActivity implements // Skip initializing any additional resources. return; } - if (mIsIntentPicker) { - // Use a specialized prompt when we're handling the 'Home' app startActivity() - final Intent intent = makeMyIntent(); - final Set<String> categories = intent.getCategories(); - if (Intent.ACTION_MAIN.equals(intent.getAction()) - && categories != null - && categories.size() == 1 - && categories.contains(Intent.CATEGORY_HOME)) { - // Note: this field is not set to true in the compatibility version. - mResolvingHome = true; - } - - init( - intent, - /* additionalTargets= */ null, - /* title= */ null, - /* defaultTitleRes= */ 0, - /* initialIntents= */ null, - /* resolutionList= */ null, - /* supportsAlwaysUseOption= */ true, - createIconLoader(), - /* safeForwardingMode= */ true); - } + mLogic.preInitialization(); + init( + mLogic.getTargetIntent(), + mLogic.getAdditionalTargets() == null + ? null : mLogic.getAdditionalTargets().toArray(new Intent[0]), + mLogic.getInitialIntents() == null + ? null : mLogic.getInitialIntents().toArray(new Intent[0]), + mLogic.getTargetDataLoader() + ); } protected void init( Intent intent, Intent[] additionalTargets, - CharSequence title, - int defaultTitleRes, Intent[] initialIntents, - List<ResolveInfo> resolutionList, - boolean supportsAlwaysUseOption, - TargetDataLoader targetDataLoader, - boolean safeForwardingMode) { + TargetDataLoader targetDataLoader + ) { setTheme(appliedThemeResId()); // Determine whether we should show that intent is forwarded @@ -404,21 +368,12 @@ public class ResolverActivity extends FragmentActivity implements mPm = getPackageManager(); - mReferrerPackage = getReferrerPackageName(); - // The initial intent must come before any other targets that are to be added. mIntents.add(0, new Intent(intent)); if (additionalTargets != null) { Collections.addAll(mIntents, additionalTargets); } - mTitle = title; - mDefaultTitleResId = defaultTitleRes; - - mSupportsAlwaysUseOption = supportsAlwaysUseOption; - mSafeForwardingMode = safeForwardingMode; - mTargetDataLoader = targetDataLoader; - // 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 // turn this off when running under voice interaction, since it results in @@ -427,10 +382,14 @@ public class ResolverActivity extends FragmentActivity implements // We also turn it off when clonedProfile is present on the device, because we might have // different "last chosen" activities in the different profiles, and PackageManager doesn't // provide any more information to help us select between them. - boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction() + boolean filterLastUsed = mLogic.getSupportsAlwaysUseOption() && !isVoiceInteraction() && !shouldShowTabs() && !hasCloneProfile(); mMultiProfilePagerAdapter = createMultiProfilePagerAdapter( - initialIntents, resolutionList, filterLastUsed, targetDataLoader); + initialIntents, + /* resolutionList = */ null, + filterLastUsed, + targetDataLoader + ); if (configureContentView(targetDataLoader)) { return; } @@ -632,7 +591,7 @@ public class ResolverActivity extends FragmentActivity implements } final Intent intent = getIntent(); if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction() - && !mResolvingHome && !mRetainInOnStop) { + && !mLogic.getResolvingHome() && !mRetainInOnStop) { // 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 @@ -677,7 +636,7 @@ public class ResolverActivity extends FragmentActivity implements } ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter() .resolveInfoForPosition(which, hasIndexBeenFiltered); - if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) { + if (mLogic.getResolvingHome() && hasManagedProfile() && !supportsManagedProfiles(ri)) { Toast.makeText(this, getWorkProfileNotSupportedMsg( ri.activityInfo.loadLabel(getPackageManager()).toString()), @@ -691,10 +650,10 @@ public class ResolverActivity extends FragmentActivity implements return; } if (onTargetSelected(target, always)) { - if (always && mSupportsAlwaysUseOption) { + if (always && mLogic.getSupportsAlwaysUseOption()) { MetricsLogger.action( this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS); - } else if (mSupportsAlwaysUseOption) { + } else if (mLogic.getSupportsAlwaysUseOption()) { MetricsLogger.action( this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE); } else { @@ -735,7 +694,7 @@ public class ResolverActivity extends FragmentActivity implements final ResolveInfo ri = target.getResolveInfo(); final Intent intent = target != null ? target.getResolvedIntent() : null; - if (intent != null && (mSupportsAlwaysUseOption + if (intent != null && (mLogic.getSupportsAlwaysUseOption() || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()) && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) { // Build a reasonable intent filter, based on what matched. @@ -921,7 +880,7 @@ public class ResolverActivity extends FragmentActivity implements new ResolverRankerServiceResolverComparator( this, getTargetIntent(), - getReferrerPackageName(), + mLogic.getReferrerPackageName(), null, null, getResolverRankerServiceUserHandleList(userHandle), @@ -930,7 +889,7 @@ public class ResolverActivity extends FragmentActivity implements this, mPm, getTargetIntent(), - getReferrerPackageName(), + mLogic.getReferrerPackageName(), getAnnotatedUserHandles().userIdOfCallingApp, resolverComparator, getQueryIntentsUser(userHandle)); @@ -973,7 +932,7 @@ public class ResolverActivity extends FragmentActivity implements } protected void resetButtonBar() { - if (!mSupportsAlwaysUseOption) { + if (!mLogic.getSupportsAlwaysUseOption()) { return; } final ViewGroup buttonLayout = findViewById(com.android.internal.R.id.button_bar); @@ -1099,13 +1058,6 @@ public class ResolverActivity extends FragmentActivity implements targetDataLoader); } - private TargetDataLoader createIconLoader() { - Intent startIntent = getIntent(); - boolean isAudioCaptureDevice = - startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); - return new DefaultTargetDataLoader(this, getLifecycle(), isAudioCaptureDevice); - } - private LatencyTracker getLatencyTracker() { return LatencyTracker.getInstance(this); } @@ -1153,24 +1105,6 @@ public class ResolverActivity extends FragmentActivity implements ); } - private Intent makeMyIntent() { - Intent intent = new Intent(getIntent()); - intent.setComponent(null); - // The resolver activity is set to be hidden from recent tasks. - // we don't want this attribute to be propagated to the next activity - // being launched. Note that if the original Intent also had this - // flag set, we are now losing it. That should be a very rare case - // and we can live with this. - intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - - // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate - // side, which means we want to open the target app on the same side as ResolverActivity. - if ((intent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { - intent.setFlags(intent.getFlags() & ~FLAG_ACTIVITY_LAUNCH_ADJACENT); - } - return intent; - } - private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile( Intent[] initialIntents, @@ -1377,14 +1311,6 @@ public class ResolverActivity extends FragmentActivity implements return mIntents.isEmpty() ? null : mIntents.get(0); } - protected final String getReferrerPackageName() { - final Uri referrer = getReferrer(); - if (referrer != null && "android-app".equals(referrer.getScheme())) { - return referrer.getHost(); - } - return null; - } - @Override // ResolverListCommunicator public final void updateProfileViewButton() { if (mProfileView == null) { @@ -1434,7 +1360,7 @@ public class ResolverActivity extends FragmentActivity implements } protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) { - final ActionTitle title = mResolvingHome + final ActionTitle title = mLogic.getResolvingHome() ? ActionTitle.HOME : ActionTitle.forAction(intent.getAction()); @@ -1689,13 +1615,6 @@ public class ResolverActivity extends FragmentActivity implements if (mProfileSwitchMessage != null) { Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show(); } - if (!mSafeForwardingMode) { - if (cti.startAsUser(this, options, user)) { - onActivityStarted(cti); - maybeLogCrossProfileTargetLaunch(cti, user); - } - return; - } try { if (cti.startAsCaller(this, options, user.getIdentifier())) { onActivityStarted(cti); @@ -2167,7 +2086,7 @@ public class ResolverActivity extends FragmentActivity implements listView.setOnItemClickListener(listener); listView.setOnItemLongClickListener(listener); - if (mSupportsAlwaysUseOption) { + if (mLogic.getSupportsAlwaysUseOption()) { listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); } } @@ -2188,9 +2107,10 @@ public class ResolverActivity extends FragmentActivity implements } } - CharSequence title = mTitle != null - ? mTitle - : getTitleForAction(getTargetIntent(), mDefaultTitleResId); + + CharSequence title = mLogic.getTitle() != null + ? mLogic.getTitle() + : getTitleForAction(getTargetIntent(), mLogic.getDefaultTitleResId()); if (!TextUtils.isEmpty(title)) { final TextView titleView = findViewById(com.android.internal.R.id.title); @@ -2238,7 +2158,7 @@ public class ResolverActivity extends FragmentActivity implements boolean adapterForCurrentUserHasFilteredItem = mMultiProfilePagerAdapter.getListAdapterForUserHandle( getAnnotatedUserHandles().tabOwnerUserHandleForLaunch).hasFilteredItem(); - return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem; + return mLogic.getSupportsAlwaysUseOption() && adapterForCurrentUserHasFilteredItem; } /** @@ -2387,7 +2307,7 @@ public class ResolverActivity extends FragmentActivity implements private CharSequence getOrLoadDisplayLabel(TargetInfo info) { if (info.isDisplayResolveInfo()) { - mTargetDataLoader.getOrLoadLabel((DisplayResolveInfo) info); + mLogic.getTargetDataLoader().getOrLoadLabel((DisplayResolveInfo) info); } CharSequence displayLabel = info.getDisplayLabel(); return displayLabel == null ? "" : displayLabel; diff --git a/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt b/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt new file mode 100644 index 00000000..1d02e6c2 --- /dev/null +++ b/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt @@ -0,0 +1,61 @@ +package com.android.intentresolver.v2 + +import android.content.Intent +import androidx.activity.ComponentActivity +import com.android.intentresolver.icons.DefaultTargetDataLoader +import com.android.intentresolver.icons.TargetDataLoader + +/** Activity logic for [ResolverActivity]. */ +class ResolverActivityLogic( + activityProvider: () -> ComponentActivity, +) : ActivityLogic, CommonActivityLogic by CommonActivityLogicImpl(activityProvider) { + + override val targetIntent: Intent by lazy { + val intent = Intent(activity.intent) + intent.setComponent(null) + // The resolver activity is set to be hidden from recent tasks. + // we don't want this attribute to be propagated to the next activity + // being launched. Note that if the original Intent also had this + // flag set, we are now losing it. That should be a very rare case + // and we can live with this. + intent.setFlags(intent.flags and Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS.inv()) + + // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate + // side, which means we want to open the target app on the same side as ResolverActivity. + if (intent.flags and Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT != 0) { + intent.setFlags(intent.flags and Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT.inv()) + } + intent + } + + override val resolvingHome: Boolean by lazy { + Intent.ACTION_MAIN == targetIntent.action && + targetIntent.categories?.size == 1 && + targetIntent.categories.contains(Intent.CATEGORY_HOME) + } + + override val additionalTargets: List<Intent>? = null + + override val title: CharSequence? = null + + override val defaultTitleResId: Int = 0 + + override val initialIntents: List<Intent>? = null + + override val supportsAlwaysUseOption: Boolean = true + + override val targetDataLoader: TargetDataLoader by lazy { + DefaultTargetDataLoader( + activity.context, + activity.lifecycle, + activity.intent.getBooleanExtra( + ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, + /* defaultValue = */ false, + ), + ) + } + + override fun preInitialization() { + // Do nothing + } +} |