summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Govinda Wasserman <gwasserman@google.com> 2023-10-27 13:39:09 -0400
committer Govinda Wasserman <gwasserman@google.com> 2023-10-31 12:49:29 -0400
commit505d41c41d1520f3e84f2cc13e53343d88e9f692 (patch)
tree12f46b4c91dc36d99fe54846cb7458fc25241e4d
parent475d1d0fd9f042dcdc085e2ba095e8164dfd1c15 (diff)
Starts splitting the Chooser and Resolver logic
Creates two new interfaces that house previous activity logic: CommonActivityLogic and ActivityLogic, which extends it. Also creates a concrete instantiation of CommonActivityLogic and two concrete instantiations of ActivityLogic (one for each activity). The concreate ActivityLogic classes delegate to the concrete CommonActivityLogic for that interface surface. The intent is for these classes to act as a scaffolding for separating the Chooser and Resolver activities. Test: atest com.android.intentresolver.v2 BUG:302113519 Change-Id: I82084aad2d7f759763cf6a77c1bebbb3d3f82c5c
-rw-r--r--java/src/com/android/intentresolver/v2/ActivityLogic.kt90
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java164
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt56
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverActivity.java150
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt61
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
+ }
+}