summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--java/src/com/android/intentresolver/v2/ActivityLogic.kt36
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java58
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt21
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverActivity.java114
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt30
-rw-r--r--java/src/com/android/intentresolver/v2/ui/model/ActivityLaunch.kt7
-rw-r--r--java/src/com/android/intentresolver/v2/ui/model/ActivityLaunchModule.kt5
-rw-r--r--java/src/com/android/intentresolver/v2/ui/model/ChooserRequest.kt38
-rw-r--r--java/src/com/android/intentresolver/v2/ui/model/ResolverRequest.kt68
-rw-r--r--java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt7
-rw-r--r--java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestReader.kt59
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java4
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt3
-rw-r--r--tests/activity/src/com/android/intentresolver/v2/ui/model/TestActivityLaunchModule.kt2
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityLaunchTest.kt44
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt87
-rw-r--r--tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestTest.kt120
17 files changed, 478 insertions, 225 deletions
diff --git a/java/src/com/android/intentresolver/v2/ActivityLogic.kt b/java/src/com/android/intentresolver/v2/ActivityLogic.kt
index b9686418..62ace0da 100644
--- a/java/src/com/android/intentresolver/v2/ActivityLogic.kt
+++ b/java/src/com/android/intentresolver/v2/ActivityLogic.kt
@@ -15,7 +15,6 @@
*/
package com.android.intentresolver.v2
-import android.content.Intent
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
@@ -30,18 +29,7 @@ import com.android.intentresolver.WorkProfileAvailabilityManager
* 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 additional targets, if any. */
- val targetIntent: 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>?
- /** The intents for potential actual targets. [targetIntent] must be first. */
- val payloadIntents: List<Intent>
-}
+interface ActivityLogic : CommonActivityLogic
/**
* Logic that is common to all IntentResolver activities. Anything that is the same across
@@ -50,14 +38,13 @@ interface ActivityLogic : CommonActivityLogic {
interface CommonActivityLogic {
/** The tag to use when logging. */
val tag: String
+
/** A reference to the activity owning, and used by, this logic. */
val activity: ComponentActivity
- /** The name of the referring package. */
- val referrerPackageName: String?
- /** User manager system service. */
- val userManager: UserManager
+
/** Current [UserHandle]s retrievable by type. */
val annotatedUserHandles: AnnotatedUserHandles?
+
/** Monitors for changes to work profile availability. */
val workProfileAvailabilityManager: WorkProfileAvailabilityManager
}
@@ -73,16 +60,7 @@ class CommonActivityLogicImpl(
onWorkProfileStatusUpdated: () -> Unit,
) : CommonActivityLogic {
- override val referrerPackageName: String? =
- activity.referrer.let {
- if (ANDROID_APP_URI_SCHEME == it?.scheme) {
- it.host
- } else {
- null
- }
- }
-
- override val userManager: UserManager = activity.getSystemService()!!
+ private val userManager: UserManager = activity.getSystemService()!!
override val annotatedUserHandles: AnnotatedUserHandles? =
try {
@@ -98,8 +76,4 @@ class CommonActivityLogicImpl(
annotatedUserHandles?.workProfileUserHandle,
onWorkProfileStatusUpdated,
)
-
- companion object {
- private const val ANDROID_APP_URI_SCHEME = "android-app"
- }
}
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java
index c1184a80..29a792f6 100644
--- a/java/src/com/android/intentresolver/v2/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java
@@ -322,12 +322,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
private ChooserViewModel mViewModel;
@VisibleForTesting
- protected ChooserActivityLogic createActivityLogic(ChooserRequest chooserRequest) {
+ protected ChooserActivityLogic createActivityLogic() {
return new ChooserActivityLogic(
TAG,
/* activity = */ this,
- this::onWorkProfileStatusUpdated,
- chooserRequest);
+ this::onWorkProfileStatusUpdated);
}
@NonNull
@@ -355,7 +354,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
finish();
return;
}
- mLogic = createActivityLogic(mViewModel.getChooserRequest());
+ mLogic = createActivityLogic();
init();
}
@@ -381,14 +380,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
chooserRequest.getShareTargetFilter()
);
- Intent intent = mLogic.getTargetIntent();
- List<Intent> initialIntents = mLogic.getInitialIntents();
-
- // Calling UID did not have valid permissions
- if (mLogic.getAnnotatedUserHandles() == null) {
- finish();
- return;
- }
+ Intent intent = mViewModel.getChooserRequest().getTargetIntent();
+ List<Intent> initialIntents = mViewModel.getChooserRequest().getInitialIntents();
mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
requireNonNullElse(initialIntents, emptyList()).toArray(new Intent[0]),
@@ -509,7 +502,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
Log.d(TAG, "System Time Cost is " + systemCost);
}
getEventLog().logShareStarted(
- mLogic.getReferrerPackageName(),
+ chooserRequest.getReferrerPackage(),
chooserRequest.getTargetType(),
chooserRequest.getCallerChooserTargets().size(),
chooserRequest.getInitialIntents().size(),
@@ -714,9 +707,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
}
- CharSequence title = mLogic.getTitle() != null
- ? mLogic.getTitle()
- : getTitleForAction(mLogic.getTargetIntent(), mLogic.getDefaultTitleResId());
+ CharSequence title = mViewModel.getChooserRequest().getTitle() != null
+ ? mViewModel.getChooserRequest().getTitle()
+ : getTitleForAction(mViewModel.getChooserRequest().getTargetIntent(),
+ mViewModel.getChooserRequest().getDefaultTitleResource());
if (!TextUtils.isEmpty(title)) {
final TextView titleView = findViewById(com.android.internal.R.id.title);
@@ -815,7 +809,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
// If needed, show that intent is forwarded
// from managed profile to owner or other way around.
- String profileSwitchMessage = mIntentForwarding.forwardMessageFor(mLogic.getTargetIntent());
+ String profileSwitchMessage = mIntentForwarding.forwardMessageFor(
+ mViewModel.getChooserRequest().getTargetIntent());
if (profileSwitchMessage != null) {
Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show();
}
@@ -1283,7 +1278,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
boolean filterLastUsed) {
ChooserGridAdapter adapter = createChooserGridAdapter(
/* context */ this,
- mLogic.getPayloadIntents(),
+ mViewModel.getChooserRequest().getPayloadIntents(),
initialIntents,
rList,
filterLastUsed,
@@ -1314,7 +1309,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
int selectedProfile = findSelectedProfile();
ChooserGridAdapter personalAdapter = createChooserGridAdapter(
/* context */ this,
- mLogic.getPayloadIntents(),
+ mViewModel.getChooserRequest().getPayloadIntents(),
selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
rList,
filterLastUsed,
@@ -1322,7 +1317,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
);
ChooserGridAdapter workAdapter = createChooserGridAdapter(
/* context */ this,
- mLogic.getPayloadIntents(),
+ mViewModel.getChooserRequest().getPayloadIntents(),
selectedProfile == PROFILE_WORK ? initialIntents : null,
rList,
filterLastUsed,
@@ -1823,7 +1818,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (info != null) {
sendClickToAppPredictor(info);
final ResolveInfo ri = info.getResolveInfo();
- Intent targetIntent = mLogic.getTargetIntent();
+ Intent targetIntent = mViewModel.getChooserRequest().getTargetIntent();
if (ri != null && ri.activityInfo != null && targetIntent != null) {
ChooserListAdapter currentListAdapter =
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
@@ -1959,7 +1954,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
}
- @VisibleForTesting
public ChooserGridAdapter createChooserGridAdapter(
Context context,
List<Intent> payloadIntents,
@@ -1967,7 +1961,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
List<ResolveInfo> rList,
boolean filterLastUsed,
UserHandle userHandle) {
- ChooserRequest parameters = mViewModel.getChooserRequest();
+ ChooserRequest request = mViewModel.getChooserRequest();
ChooserListAdapter chooserListAdapter = createChooserListAdapter(
context,
payloadIntents,
@@ -1976,8 +1970,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
filterLastUsed,
createListController(userHandle),
userHandle,
- mLogic.getTargetIntent(),
- parameters.getReferrerFillInIntent(),
+ request.getTargetIntent(),
+ request.getReferrerFillInIntent(),
mMaxTargetsPerRow
);
@@ -2081,8 +2075,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (appPredictor != null) {
resolverComparator = new AppPredictionServiceResolverComparator(
this,
- mLogic.getTargetIntent(),
- mLogic.getReferrerPackageName(),
+ mViewModel.getChooserRequest().getTargetIntent(),
+ mViewModel.getChooserRequest().getLaunchedFromPackage(),
appPredictor,
userHandle,
getEventLog(),
@@ -2092,8 +2086,8 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
resolverComparator =
new ResolverRankerServiceResolverComparator(
this,
- mLogic.getTargetIntent(),
- mLogic.getReferrerPackageName(),
+ mViewModel.getChooserRequest().getTargetIntent(),
+ mViewModel.getChooserRequest().getReferrerPackage(),
null,
getEventLog(),
getResolverRankerServiceUserHandleList(userHandle),
@@ -2103,9 +2097,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return new ChooserListController(
this,
mPackageManager,
- mLogic.getTargetIntent(),
- mLogic.getReferrerPackageName(),
- mActivityLaunch.getFromUid(),
+ mViewModel.getChooserRequest().getTargetIntent(),
+ mViewModel.getChooserRequest().getReferrerPackage(),
+ requireAnnotatedUserHandles().userIdOfCallingApp,
resolverComparator,
getQueryIntentsUser(userHandle));
}
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt b/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt
index f6054885..84b7d9a9 100644
--- a/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt
+++ b/java/src/com/android/intentresolver/v2/ChooserActivityLogic.kt
@@ -1,11 +1,7 @@
package com.android.intentresolver.v2
-import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.annotation.OpenForTesting
-import com.android.intentresolver.v2.ui.model.ChooserRequest
-
-private const val TAG = "ChooserActivityLogic"
/**
* Activity logic for [ChooserActivity].
@@ -18,25 +14,10 @@ open class ChooserActivityLogic(
tag: String,
activity: ComponentActivity,
onWorkProfileStatusUpdated: () -> Unit,
- private val chooserRequest: ChooserRequest? = null,
) :
ActivityLogic,
CommonActivityLogic by CommonActivityLogicImpl(
tag,
activity,
onWorkProfileStatusUpdated,
- ) {
-
- override val targetIntent: Intent = chooserRequest?.targetIntent ?: Intent()
-
- override val title: CharSequence? = chooserRequest?.title
-
- override val defaultTitleResId: Int = chooserRequest?.defaultTitleResource ?: 0
-
- override val initialIntents: List<Intent>? = chooserRequest?.initialIntents?.toList()
-
- override val payloadIntents: List<Intent> = buildList {
- add(targetIntent)
- chooserRequest?.additionalTargets?.let { addAll(it) }
- }
-}
+ )
diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java
index b8638ba4..77d1dbf5 100644
--- a/java/src/com/android/intentresolver/v2/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java
@@ -25,11 +25,10 @@ import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_S
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
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.Collections.emptyList;
import static java.util.Objects.requireNonNull;
-import static java.util.Objects.requireNonNullElse;
import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
@@ -105,13 +104,15 @@ import com.android.intentresolver.v2.MultiProfilePagerAdapter.OnSwitchOnWorkSele
import com.android.intentresolver.v2.MultiProfilePagerAdapter.ProfileType;
import com.android.intentresolver.v2.MultiProfilePagerAdapter.TabConfig;
import com.android.intentresolver.v2.data.repository.DevicePolicyResources;
+import com.android.intentresolver.v2.domain.model.Profile;
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.WorkProfilePausedEmptyStateProvider;
-import com.android.intentresolver.v2.ext.IntentExtKt;
import com.android.intentresolver.v2.ui.ActionTitle;
import com.android.intentresolver.v2.ui.model.ActivityLaunch;
+import com.android.intentresolver.v2.ui.model.ResolverRequest;
+import com.android.intentresolver.v2.validation.ValidationResult;
import com.android.intentresolver.widget.ResolverDrawerLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
@@ -144,10 +145,11 @@ import javax.inject.Inject;
public class ResolverActivity extends Hilt_ResolverActivity implements
ResolverListAdapter.ResolverListCommunicator {
+ @Inject public PackageManager mPackageManager;
@Inject public ActivityLaunch mActivityLaunch;
@Inject public DevicePolicyResources mDevicePolicyResources;
@Inject public IntentForwarding mIntentForwarding;
- @Inject public PackageManager mPackageManager;
+ private ResolverRequest mResolverRequest;
protected ActivityLogic mLogic;
protected TargetDataLoader mTargetDataLoader;
@@ -185,32 +187,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
protected ResolverMultiProfilePagerAdapter mMultiProfilePagerAdapter;
-
- // Intent extra for connected audio devices
- public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
-
- /**
- * Integer extra to indicate which profile should be automatically selected.
- * <p>Can only be used if there is a work profile.
- * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
- */
- protected static final String EXTRA_SELECTED_PROFILE =
- "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
-
- /**
- * {@link UserHandle} extra to indicate the user of the user that the starting intent
- * originated from.
- * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()},
- * as there are edge cases when the intent resolver is launched in the other profile.
- * For example, when we have 0 resolved apps in current profile and multiple resolved
- * apps in the other profile, opening a link from the current profile launches the intent
- * resolver in the other one. b/148536209 for more info.
- */
- static final String EXTRA_CALLING_USER =
- "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER";
-
- protected static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL;
- protected static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK;
+ public static final int PROFILE_PERSONAL = MultiProfilePagerAdapter.PROFILE_PERSONAL;
+ public static final int PROFILE_WORK = MultiProfilePagerAdapter.PROFILE_WORK;
private UserHandle mHeaderCreatorUser;
@@ -234,7 +212,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
}
@VisibleForTesting
- protected ResolverActivityLogic createActivityLogic() {
+ protected ActivityLogic createActivityLogic() {
return new ResolverActivityLogic(
TAG,
/* activity = */ this,
@@ -261,22 +239,24 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
finish();
}
+ ValidationResult<ResolverRequest> result = readResolverRequest(mActivityLaunch);
+ if (!result.isSuccess()) {
+ result.reportToLogcat(TAG);
+ finish();
+ }
+ mResolverRequest = result.getOrThrow();
mLogic = createActivityLogic();
- mResolvingHome = IntentExtKt.isHomeIntent(getIntent());
+ mResolvingHome = mResolverRequest.isResolvingHome();
mTargetDataLoader = new DefaultTargetDataLoader(
this,
getLifecycle(),
- getIntent().getBooleanExtra(
- ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE,
- /* defaultValue = */ false)
- );
+ mResolverRequest.isAudioCaptureDevice());
init();
restore(savedInstanceState);
}
private void init() {
- Intent intent = mLogic.getTargetIntent();
- List<Intent> initialIntents = mLogic.getInitialIntents();
+ Intent intent = mResolverRequest.getIntent();
// 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
@@ -289,8 +269,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
boolean filterLastUsed = !isVoiceInteraction()
&& !hasWorkProfile() && !hasCloneProfile();
mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(
- requireNonNullElse(initialIntents, emptyList()).toArray(new Intent[0]),
- /* resolutionList = */ null,
+ new Intent[0],
+ /* resolutionList = */ mResolverRequest.getResolutionList(),
filterLastUsed
);
if (configureContentView(mTargetDataLoader)) {
@@ -764,8 +744,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
ResolverRankerServiceResolverComparator resolverComparator =
new ResolverRankerServiceResolverComparator(
this,
- mLogic.getTargetIntent(),
- mLogic.getReferrerPackageName(),
+ mResolverRequest.getIntent(),
+ mActivityLaunch.getReferrerPackage(),
null,
null,
getResolverRankerServiceUserHandleList(userHandle),
@@ -773,8 +753,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
return new ResolverListController(
this,
mPackageManager,
- mLogic.getTargetIntent(),
- mLogic.getReferrerPackageName(),
+ mActivityLaunch.getIntent(),
+ mActivityLaunch.getReferrerPackage(),
mActivityLaunch.getFromUid(),
resolverComparator,
getQueryIntentsUser(userHandle));
@@ -920,7 +900,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
filterLastUsed,
createListController(userHandle),
userHandle,
- mLogic.getTargetIntent(),
+ mResolverRequest.getIntent(),
this,
initialIntentsUserSpace,
mTargetDataLoader);
@@ -964,7 +944,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
boolean filterLastUsed) {
ResolverListAdapter personalAdapter = createResolverListAdapter(
/* context */ this,
- mLogic.getPayloadIntents(),
+ mResolverRequest.getPayloadIntents(),
initialIntents,
resolutionList,
filterLastUsed,
@@ -987,9 +967,8 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
}
private UserHandle getIntentUser() {
- return getIntent().hasExtra(EXTRA_CALLING_USER)
- ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
- : requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch;
+ return Objects.requireNonNullElse(mResolverRequest.getCallingUser(),
+ requireAnnotatedUserHandles().tabOwnerUserHandleForLaunch);
}
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
@@ -1018,7 +997,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
// resolver list. So filterLastUsed should be false for the other profile.
ResolverListAdapter personalAdapter = createResolverListAdapter(
/* context */ this,
- mLogic.getPayloadIntents(),
+ mResolverRequest.getPayloadIntents(),
selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
resolutionList,
(filterLastUsed && UserHandle.myUserId()
@@ -1028,7 +1007,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
UserHandle workProfileUserHandle = requireAnnotatedUserHandles().workProfileUserHandle;
ResolverListAdapter workAdapter = createResolverListAdapter(
/* context */ this,
- mLogic.getPayloadIntents(),
+ mResolverRequest.getPayloadIntents(),
selectedProfile == PROFILE_WORK ? initialIntents : null,
resolutionList,
(filterLastUsed && UserHandle.myUserId()
@@ -1060,20 +1039,17 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
/**
* Returns {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} if the {@link
* #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied.
- * @throws IllegalArgumentException if the value passed to the {@link #EXTRA_SELECTED_PROFILE}
- * extra is not {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}
*/
final int getSelectedProfileExtra() {
- int selectedProfile = -1;
- if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
- selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
- if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) {
- throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value "
- + selectedProfile + ". Must be either ResolverActivity.PROFILE_PERSONAL or "
- + "ResolverActivity.PROFILE_WORK.");
- }
+ Profile.Type selected = mResolverRequest.getSelectedProfile();
+ if (selected == null) {
+ return -1;
+ }
+ switch (selected) {
+ case PERSONAL: return PROFILE_PERSONAL;
+ case WORK: return PROFILE_WORK;
+ default: return -1;
}
- return selectedProfile;
}
protected final @ProfileType int getCurrentProfile() {
@@ -1302,9 +1278,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
if (!hasRecordPermission) {
// OK, we know the record permission, is this a capture device
- boolean hasAudioCapture =
- getIntent().getBooleanExtra(
- ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
+ boolean hasAudioCapture = mResolverRequest.isAudioCaptureDevice();
enabled = !hasAudioCapture;
}
}
@@ -1491,7 +1465,8 @@ 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(mLogic.getTargetIntent());
+ String profileSwitchMessage =
+ mIntentForwarding.forwardMessageFor(mResolverRequest.getIntent());
if (profileSwitchMessage != null) {
Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show();
}
@@ -1771,10 +1746,9 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
}
}
-
- CharSequence title = mLogic.getTitle() != null
- ? mLogic.getTitle()
- : getTitleForAction(mLogic.getTargetIntent(), mLogic.getDefaultTitleResId());
+ CharSequence title = mResolverRequest.getTitle() != null
+ ? mResolverRequest.getTitle()
+ : getTitleForAction(mResolverRequest.getIntent(), 0);
if (!TextUtils.isEmpty(title)) {
final TextView titleView = findViewById(com.android.internal.R.id.title);
diff --git a/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt b/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt
index 13353041..7eb63ab3 100644
--- a/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt
+++ b/java/src/com/android/intentresolver/v2/ResolverActivityLogic.kt
@@ -1,6 +1,5 @@
package com.android.intentresolver.v2
-import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.annotation.OpenForTesting
@@ -16,31 +15,4 @@ open class ResolverActivityLogic(
tag,
activity,
onWorkProfileStatusUpdated,
- ) {
-
- final override val targetIntent: Intent = let {
- 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 title: CharSequence? = null
-
- override val defaultTitleResId: Int = 0
-
- override val initialIntents: List<Intent>? = null
-
- override val payloadIntents: List<Intent> = listOf(targetIntent)
-}
+ )
diff --git a/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunch.kt b/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunch.kt
index fd25ea42..e5f342d9 100644
--- a/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunch.kt
+++ b/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunch.kt
@@ -29,7 +29,7 @@ data class ActivityLaunch(
/** The identifier for the sending app and user */
val fromUid: Int,
/** The package of the sending app */
- val fromPackage: String?,
+ val fromPackage: String,
/** The referrer as supplied to the activity. */
val referrer: Uri?
) : Parcelable {
@@ -38,10 +38,13 @@ data class ActivityLaunch(
) : this(
intent = source.requireParcelable(),
fromUid = source.readInt(),
- fromPackage = source.readString(),
+ fromPackage = requireNotNull(source.readString()),
referrer = source.readParcelable()
)
+ /** A package name from referrer, if it is an android-app URI */
+ val referrerPackage = referrer?.takeIf { it.scheme == ANDROID_APP_SCHEME }?.authority
+
override fun describeContents() = 0 /* flags */
override fun writeToParcel(dest: Parcel, flags: Int) {
diff --git a/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunchModule.kt b/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunchModule.kt
index 3311467e..bb8f3a54 100644
--- a/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunchModule.kt
+++ b/java/src/com/android/intentresolver/v2/ui/model/ActivityLaunchModule.kt
@@ -33,7 +33,10 @@ object ActivityLaunchModule {
return ActivityLaunch(
activity.intent,
activity.launchedFromUid,
- activity.launchedFromPackage,
+ requireNotNull(activity.launchedFromPackage) {
+ "activity.launchedFromPackage was null. This is expected to be non-null for " +
+ "any system-signed application!"
+ },
activity.referrer
)
}
diff --git a/java/src/com/android/intentresolver/v2/ui/model/ChooserRequest.kt b/java/src/com/android/intentresolver/v2/ui/model/ChooserRequest.kt
index 2fbf94a2..d41d0874 100644
--- a/java/src/com/android/intentresolver/v2/ui/model/ChooserRequest.kt
+++ b/java/src/com/android/intentresolver/v2/ui/model/ChooserRequest.kt
@@ -19,16 +19,17 @@ import android.content.ComponentName
import android.content.Intent
import android.content.Intent.ACTION_SEND
import android.content.Intent.ACTION_SEND_MULTIPLE
+import android.content.Intent.EXTRA_REFERRER
import android.content.IntentFilter
import android.content.IntentSender
+import android.net.Uri
import android.os.Bundle
import android.service.chooser.ChooserAction
import android.service.chooser.ChooserTarget
import androidx.annotation.StringRes
import com.android.intentresolver.v2.ext.hasAction
-const val MAX_CHOOSER_ACTIONS = 5
-const val MAX_INITIAL_INTENTS = 2
+const val ANDROID_APP_SCHEME = "android-app"
/** All of the things that are consumed from an incoming share Intent (+Extras). */
data class ChooserRequest(
@@ -58,10 +59,10 @@ data class ChooserRequest(
@get:StringRes val defaultTitleResource: Int = 0,
/**
- * An empty intent which carries an extra of [Intent.EXTRA_REFERRER]. To be merged with outgoing
- * intents. This provides the original referrer value to the target.
+ * The referrer value as received by the caller. It may have been supplied via [EXTRA_REFERRER]
+ * or synthesized from callerPackageName. This value is merged into outgoing intents.
*/
- val referrerFillInIntent: Intent,
+ val referrer: Uri?,
/**
* Choices to exclude from results.
@@ -163,18 +164,29 @@ data class ChooserRequest(
*/
val shareTargetFilter: IntentFilter? = null
) {
+ val referrerPackage = referrer?.takeIf { it.scheme == ANDROID_APP_SCHEME }?.authority
+
+ fun getReferrerFillInIntent(): Intent {
+ return Intent().apply {
+ referrerPackage?.also { pkg ->
+ putExtra(EXTRA_REFERRER, Uri.parse("$ANDROID_APP_SCHEME://$pkg"))
+ }
+ }
+ }
+
+ val payloadIntents = listOf(targetIntent) + additionalTargets
/** Constructs an instance from only the required values. */
constructor(
targetIntent: Intent,
- referrerPackageName: String
+ launchedFromPackage: String,
+ referrer: Uri?
) : this(
- targetIntent,
- targetIntent.action,
- targetIntent.hasAction(ACTION_SEND, ACTION_SEND_MULTIPLE),
- targetIntent.type,
- referrerPackageName,
- referrerFillInIntent =
- Intent().apply { putExtra(Intent.EXTRA_REFERRER, referrerPackageName) }
+ targetIntent = targetIntent,
+ targetAction = targetIntent.action,
+ isSendActionTarget = targetIntent.hasAction(ACTION_SEND, ACTION_SEND_MULTIPLE),
+ targetType = targetIntent.type,
+ launchedFromPackage = launchedFromPackage,
+ referrer = referrer
)
}
diff --git a/java/src/com/android/intentresolver/v2/ui/model/ResolverRequest.kt b/java/src/com/android/intentresolver/v2/ui/model/ResolverRequest.kt
new file mode 100644
index 00000000..5abfb602
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/ui/model/ResolverRequest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.model
+
+import android.content.Intent
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
+import com.android.intentresolver.v2.domain.model.Profile
+import com.android.intentresolver.v2.ext.isHomeIntent
+
+/** All of the things that are consumed from an incoming Intent Resolution request (+Extras). */
+data class ResolverRequest(
+ /** The intent to be resolved to a target. */
+ val intent: Intent,
+
+ /**
+ * Supplied by the system to indicate which profile should be selected by default. This is
+ * required since ResolverActivity may be launched as either the originating OR target user when
+ * resolving a cross profile intent.
+ *
+ * Valid values are: [PERSONAL][Profile.Type.PERSONAL] and [WORK][Profile.Type.WORK] and null
+ * when the intent is not a forwarded cross-profile intent.
+ */
+ val selectedProfile: Profile.Type?,
+
+ /**
+ * When handing a cross profile forwarded intent, this is the user which started the original
+ * intent. This is required to allow ResolverActivity to be launched as the target user under
+ * some conditions.
+ */
+ val callingUser: UserHandle?,
+
+ /**
+ * Indicates if resolving actions for a connected device which has audio capture capability
+ * (e.g. is a USB Microphone).
+ *
+ * When used to handle a connected device, ResolverActivity uses this signal to present a
+ * warning when a resolved application does not hold the RECORD_AUDIO permission. (If selected
+ * the app would be able to capture audio directly via the device, bypassing audio API
+ * permissions.)
+ */
+ val isAudioCaptureDevice: Boolean = false,
+
+ /** A list of a resolved activity targets. This list overrides normal intent resolution. */
+ val resolutionList: List<ResolveInfo>? = null,
+
+ /** A customized title for the resolver interface. */
+ val title: String? = null,
+) {
+ val isResolvingHome = intent.isHomeIntent()
+
+ /** For compatibility with existing code shared between chooser/resolver. */
+ val payloadIntents: List<Intent> = listOf(intent)
+}
diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt
index 33868aaf..45e2ea64 100644
--- a/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt
+++ b/java/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestReader.kt
@@ -46,14 +46,15 @@ import com.android.intentresolver.v2.ext.hasAction
import com.android.intentresolver.v2.ext.ifMatch
import com.android.intentresolver.v2.ui.model.ActivityLaunch
import com.android.intentresolver.v2.ui.model.ChooserRequest
-import com.android.intentresolver.v2.ui.model.MAX_CHOOSER_ACTIONS
-import com.android.intentresolver.v2.ui.model.MAX_INITIAL_INTENTS
import com.android.intentresolver.v2.validation.ValidationResult
import com.android.intentresolver.v2.validation.types.IntentOrUri
import com.android.intentresolver.v2.validation.types.array
import com.android.intentresolver.v2.validation.types.value
import com.android.intentresolver.v2.validation.validateFrom
+private const val MAX_CHOOSER_ACTIONS = 5
+private const val MAX_INITIAL_INTENTS = 2
+
private fun Intent.hasSendAction() = hasAction(ACTION_SEND, ACTION_SEND_MULTIPLE)
internal fun Intent.maybeAddSendActionFlags() =
@@ -134,7 +135,7 @@ fun readChooserRequest(launch: ActivityLaunch): ValidationResult<ChooserRequest>
},
title = customTitle,
defaultTitleResource = defaultTitleResource,
- referrerFillInIntent = referrerFillIn,
+ referrer = launch.referrer,
filteredComponentNames = filteredComponents,
callerChooserTargets = callerChooserTargets,
chooserActions = chooserActions,
diff --git a/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestReader.kt b/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestReader.kt
new file mode 100644
index 00000000..fc9f1e01
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestReader.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.os.Bundle
+import android.os.UserHandle
+import com.android.intentresolver.v2.ResolverActivity.PROFILE_PERSONAL
+import com.android.intentresolver.v2.ResolverActivity.PROFILE_WORK
+import com.android.intentresolver.v2.domain.model.Profile
+import com.android.intentresolver.v2.ui.model.ActivityLaunch
+import com.android.intentresolver.v2.ui.model.ResolverRequest
+import com.android.intentresolver.v2.validation.Validation
+import com.android.intentresolver.v2.validation.ValidationResult
+import com.android.intentresolver.v2.validation.types.value
+import com.android.intentresolver.v2.validation.validateFrom
+
+const val EXTRA_CALLING_USER = "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"
+const val EXTRA_SELECTED_PROFILE =
+ "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE"
+const val EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device"
+
+fun readResolverRequest(launch: ActivityLaunch): ValidationResult<ResolverRequest> {
+ @Suppress("DEPRECATION")
+ return validateFrom((launch.intent.extras ?: Bundle())::get) {
+ val callingUser = optional(value<UserHandle>(EXTRA_CALLING_USER))
+ val selectedProfile = checkSelectedProfile()
+ val audioDevice = optional(value<Boolean>(EXTRA_IS_AUDIO_CAPTURE_DEVICE)) ?: false
+ ResolverRequest(launch.intent, selectedProfile, callingUser, audioDevice)
+ }
+}
+
+private fun Validation.checkSelectedProfile(): Profile.Type? {
+ return when (val selected = optional(value<Int>(EXTRA_SELECTED_PROFILE))) {
+ null -> null
+ PROFILE_PERSONAL -> Profile.Type.PERSONAL
+ PROFILE_WORK -> Profile.Type.WORK
+ else ->
+ error(
+ EXTRA_SELECTED_PROFILE +
+ " has invalid value ($selected)." +
+ " Must be either ResolverActivity.PROFILE_PERSONAL ($PROFILE_PERSONAL)" +
+ " or ResolverActivity.PROFILE_WORK ($PROFILE_WORK)."
+ )
+ }
+}
diff --git a/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java b/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
index 64c8e49d..07e6e7b4 100644
--- a/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
+++ b/tests/activity/src/com/android/intentresolver/v2/ChooserWrapperActivity.java
@@ -40,7 +40,6 @@ import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
import com.android.intentresolver.shortcuts.ShortcutLoader;
-import com.android.intentresolver.v2.ui.model.ChooserRequest;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.List;
@@ -55,12 +54,11 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
private UsageStatsManager mUsm;
@Override
- protected final ChooserActivityLogic createActivityLogic(ChooserRequest chooserRequest) {
+ protected final ChooserActivityLogic createActivityLogic() {
return new TestChooserActivityLogic(
"ChooserWrapper",
/* activity = */ this,
this::onWorkProfileStatusUpdated,
- chooserRequest,
sOverrides.annotatedUserHandles,
sOverrides.mWorkProfileAvailability);
}
diff --git a/tests/activity/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt b/tests/activity/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt
index 3c22254a..fe649819 100644
--- a/tests/activity/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt
+++ b/tests/activity/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt
@@ -3,14 +3,12 @@ package com.android.intentresolver.v2
import androidx.activity.ComponentActivity
import com.android.intentresolver.AnnotatedUserHandles
import com.android.intentresolver.WorkProfileAvailabilityManager
-import com.android.intentresolver.v2.ui.model.ChooserRequest
/** Activity logic for use when testing [ChooserActivity]. */
class TestChooserActivityLogic(
tag: String,
activity: ComponentActivity,
onWorkProfileStatusUpdated: () -> Unit,
- chooserRequest: ChooserRequest? = null,
private val annotatedUserHandlesOverride: AnnotatedUserHandles?,
private val workProfileAvailabilityOverride: WorkProfileAvailabilityManager?,
) :
@@ -18,7 +16,6 @@ class TestChooserActivityLogic(
tag,
activity,
onWorkProfileStatusUpdated,
- chooserRequest,
) {
override val annotatedUserHandles: AnnotatedUserHandles?
get() = annotatedUserHandlesOverride ?: super.annotatedUserHandles
diff --git a/tests/activity/src/com/android/intentresolver/v2/ui/model/TestActivityLaunchModule.kt b/tests/activity/src/com/android/intentresolver/v2/ui/model/TestActivityLaunchModule.kt
index d674bbc2..7dd15dbe 100644
--- a/tests/activity/src/com/android/intentresolver/v2/ui/model/TestActivityLaunchModule.kt
+++ b/tests/activity/src/com/android/intentresolver/v2/ui/model/TestActivityLaunchModule.kt
@@ -36,6 +36,6 @@ class TestActivityLaunchModule {
companion object {
const val LAUNCHED_FROM_PACKAGE = "example.com"
const val LAUNCHED_FROM_UID = 1234
- val REFERRER: Uri = Uri.parse("android-app://$LAUNCHED_FROM_PACKAGE")
+ val REFERRER: Uri = Uri.fromParts(ANDROID_APP_SCHEME, LAUNCHED_FROM_PACKAGE, "")
}
}
diff --git a/tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityLaunchTest.kt b/tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityLaunchTest.kt
index 3e9f43da..25eac220 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityLaunchTest.kt
+++ b/tests/unit/src/com/android/intentresolver/v2/ui/model/ActivityLaunchTest.kt
@@ -21,6 +21,7 @@ import android.content.Intent.ACTION_CHOOSER
import android.content.Intent.EXTRA_TEXT
import android.net.Uri
import com.android.intentresolver.v2.ext.toParcelAndBack
+import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
@@ -28,7 +29,7 @@ class ActivityLaunchTest {
@Test
fun testDefaultValues() {
- val input = ActivityLaunch(Intent(ACTION_CHOOSER), 0, null, null)
+ val input = ActivityLaunch(Intent(ACTION_CHOOSER), 0, "example.com", null)
val output = input.toParcelAndBack()
@@ -46,7 +47,46 @@ class ActivityLaunchTest {
assertEquals(input, output)
}
- fun assertEquals(expected: ActivityLaunch, actual: ActivityLaunch) {
+ @Test
+ fun testReferrerPackage_withAppReferrer_usesReferrer() {
+ val launch1 =
+ ActivityLaunch(
+ intent = Intent(),
+ fromUid = 1000,
+ fromPackage = "other.example.com",
+ referrer = Uri.parse("android-app://app.example.com")
+ )
+
+ assertThat(launch1.referrerPackage).isEqualTo("app.example.com")
+ }
+
+ @Test
+ fun testReferrerPackage_httpReferrer_isNull() {
+ val launch =
+ ActivityLaunch(
+ intent = Intent(),
+ fromUid = 1000,
+ fromPackage = "example.com",
+ referrer = Uri.parse("http://some.other.value")
+ )
+
+ assertThat(launch.referrerPackage).isNull()
+ }
+
+ @Test
+ fun testReferrerPackage_nullReferrer_isNull() {
+ val launch =
+ ActivityLaunch(
+ intent = Intent(),
+ fromUid = 1000,
+ fromPackage = "example.com",
+ referrer = null
+ )
+
+ assertThat(launch.referrerPackage).isNull()
+ }
+
+ private fun assertEquals(expected: ActivityLaunch, actual: ActivityLaunch) {
// Test fields separately: Intent does not override equals()
assertWithMessage("%s.filterEquals(%s)", actual.intent, expected.intent)
.that(actual.intent.filterEquals(expected.intent))
diff --git a/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt b/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt
index 29bb5cbd..3174c5f6 100644
--- a/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt
+++ b/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ChooserRequestTest.kt
@@ -18,7 +18,11 @@ package com.android.intentresolver.v2.ui.viewmodel
import android.content.Intent
import android.content.Intent.ACTION_CHOOSER
import android.content.Intent.ACTION_SEND
+import android.content.Intent.ACTION_SEND_MULTIPLE
+import android.content.Intent.EXTRA_ALTERNATE_INTENTS
import android.content.Intent.EXTRA_INTENT
+import android.content.Intent.EXTRA_REFERRER
+import android.net.Uri
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import com.android.intentresolver.v2.ui.model.ActivityLaunch
@@ -28,21 +32,27 @@ import com.android.intentresolver.v2.validation.ValidationResultSubject.Companio
import com.google.common.truth.Truth.assertThat
import org.junit.Test
-@Suppress("DEPRECATION")
-class ChooserRequestTest {
+private fun createLaunch(
+ targetIntent: Intent?,
+ referrer: Uri? = null,
+ additionalIntents: List<Intent>? = null
+) =
+ ActivityLaunch(
+ Intent(ACTION_CHOOSER).apply {
+ targetIntent?.also { putExtra(EXTRA_INTENT, it) }
+ additionalIntents?.also { putExtra(EXTRA_ALTERNATE_INTENTS, it.toTypedArray()) }
+ },
+ fromUid = 10000,
+ fromPackage = "com.android.example",
+ referrer = referrer ?: "android-app://com.android.example".toUri()
+ )
- val intent = Intent(ACTION_CHOOSER)
- private val mActivityLaunch =
- ActivityLaunch(
- intent,
- fromUid = 10000,
- fromPackage = "com.android.example",
- referrer = "android-app://com.android.example".toUri()
- )
+class ChooserRequestTest {
@Test
fun missingIntent() {
- val result = readChooserRequest(mActivityLaunch)
+ val launch = createLaunch(targetIntent = null)
+ val result = readChooserRequest(launch)
assertThat(result).value().isNull()
assertThat(result)
@@ -51,14 +61,61 @@ class ChooserRequestTest {
}
@Test
- fun minimal() {
- intent.putExtras(bundleOf(EXTRA_INTENT to Intent(ACTION_SEND)))
+ fun referrerFillIn() {
+ val referrer = Uri.parse("android-app://example.com")
+ val launch = createLaunch(targetIntent = Intent(ACTION_SEND), referrer)
+ launch.intent.putExtras(bundleOf(EXTRA_REFERRER to referrer))
+
+ val result = readChooserRequest(launch)
+
+ val fillIn = result.value?.getReferrerFillInIntent()
+ assertThat(fillIn?.hasExtra(EXTRA_REFERRER)).isTrue()
+ assertThat(fillIn?.getParcelableExtra(EXTRA_REFERRER, Uri::class.java)).isEqualTo(referrer)
+ }
+
+ @Test
+ fun referrerPackage_isNullWithNonAppReferrer() {
+ val referrer = Uri.parse("http://example.com")
+ val intent = Intent().putExtras(bundleOf(EXTRA_INTENT to Intent(ACTION_SEND)))
+
+ val launch = createLaunch(targetIntent = intent, referrer = referrer)
+
+ val result = readChooserRequest(launch)
+
+ assertThat(result.value?.referrerPackage).isNull()
+ }
+
+ @Test
+ fun referrerPackage_fromAppReferrer() {
+ val referrer = Uri.parse("android-app://example.com")
+ val launch = createLaunch(targetIntent = Intent(ACTION_SEND), referrer)
+
+ launch.intent.putExtras(bundleOf(EXTRA_REFERRER to referrer))
+
+ val result = readChooserRequest(launch)
+
+ assertThat(result.value?.referrerPackage).isEqualTo(referrer.authority)
+ }
- val result = readChooserRequest(mActivityLaunch)
+ @Test
+ fun payloadIntents_includesTargetThenAdditional() {
+ val intent1 = Intent(ACTION_SEND)
+ val intent2 = Intent(ACTION_SEND_MULTIPLE)
+ val launch = createLaunch(targetIntent = intent1, additionalIntents = listOf(intent2))
+ val result = readChooserRequest(launch)
+
+ assertThat(result.value?.payloadIntents).containsExactly(intent1, intent2)
+ }
+
+ @Test
+ fun testRequest_withOnlyRequiredValues() {
+ val intent = Intent().putExtras(bundleOf(EXTRA_INTENT to Intent(ACTION_SEND)))
+ val launch = createLaunch(targetIntent = intent)
+ val result = readChooserRequest(launch)
assertThat(result).value().isNotNull()
val value: ChooserRequest = result.getOrThrow()
- assertThat(value.launchedFromPackage).isEqualTo(mActivityLaunch.fromPackage)
+ assertThat(value.launchedFromPackage).isEqualTo(launch.fromPackage)
assertThat(result).findings().isEmpty()
}
}
diff --git a/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestTest.kt b/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestTest.kt
new file mode 100644
index 00000000..a5acb0d3
--- /dev/null
+++ b/tests/unit/src/com/android/intentresolver/v2/ui/viewmodel/ResolverRequestTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.content.Intent
+import android.content.Intent.ACTION_VIEW
+import android.net.Uri
+import android.os.UserHandle
+import androidx.core.net.toUri
+import androidx.core.os.bundleOf
+import com.android.intentresolver.v2.ResolverActivity.PROFILE_WORK
+import com.android.intentresolver.v2.domain.model.Profile.Type.WORK
+import com.android.intentresolver.v2.ui.model.ActivityLaunch
+import com.android.intentresolver.v2.ui.model.ResolverRequest
+import com.android.intentresolver.v2.validation.UncaughtException
+import com.android.intentresolver.v2.validation.ValidationResultSubject.Companion.assertThat
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+
+private val targetUri = Uri.parse("content://example.com/123")
+
+private fun createLaunch(
+ targetIntent: Intent,
+ referrer: Uri? = null,
+) =
+ ActivityLaunch(
+ intent = targetIntent,
+ fromUid = 10000,
+ fromPackage = "com.android.example",
+ referrer = referrer ?: "android-app://com.android.example".toUri()
+ )
+
+class ResolverRequestTest {
+ @Test
+ fun testDefaults() {
+ val intent = Intent(ACTION_VIEW).apply { data = targetUri }
+ val launch = createLaunch(intent)
+
+ val result = readResolverRequest(launch)
+ assertThat(result).isSuccess()
+ assertThat(result).findings().isEmpty()
+ val value: ResolverRequest = result.getOrThrow()
+
+ assertThat(value.intent.filterEquals(launch.intent)).isTrue()
+ assertThat(value.callingUser).isNull()
+ assertThat(value.selectedProfile).isNull()
+ }
+
+ @Test
+ fun testInvalidSelectedProfile() {
+ val intent =
+ Intent(ACTION_VIEW).apply {
+ data = targetUri
+ putExtra(EXTRA_SELECTED_PROFILE, -1000)
+ }
+
+ val launch = createLaunch(intent)
+
+ val result = readResolverRequest(launch)
+
+ assertThat(result).isFailure()
+ assertWithMessage("the first finding")
+ .that(result.findings.firstOrNull())
+ .isInstanceOf(UncaughtException::class.java)
+ }
+
+ @Test
+ fun payloadIntents_includesOnlyTarget() {
+ val intent2 = Intent(Intent.ACTION_SEND_MULTIPLE)
+ val intent1 =
+ Intent(Intent.ACTION_SEND).apply {
+ putParcelableArrayListExtra(Intent.EXTRA_ALTERNATE_INTENTS, arrayListOf(intent2))
+ }
+ val launch = createLaunch(targetIntent = intent1)
+
+ val result = readResolverRequest(launch)
+
+ // Assert that payloadIntents does NOT include EXTRA_ALTERNATE_INTENTS
+ // that is only supported for Chooser and should be not be added here.
+ assertThat(result.value?.payloadIntents).containsExactly(intent1)
+ }
+
+ @Test
+ fun testAllValues() {
+ val intent = Intent(ACTION_VIEW).apply { data = Uri.parse("content://example.com/123") }
+ val launch = createLaunch(targetIntent = intent)
+
+ launch.intent.putExtras(
+ bundleOf(
+ EXTRA_CALLING_USER to UserHandle.of(123),
+ EXTRA_SELECTED_PROFILE to PROFILE_WORK,
+ EXTRA_IS_AUDIO_CAPTURE_DEVICE to true,
+ )
+ )
+
+ val result = readResolverRequest(launch)
+
+ assertThat(result).value().isNotNull()
+ val value: ResolverRequest = result.getOrThrow()
+
+ assertThat(value.intent.filterEquals(launch.intent)).isTrue()
+ assertThat(value.isAudioCaptureDevice).isTrue()
+ assertThat(value.callingUser).isEqualTo(UserHandle.of(123))
+ assertThat(value.selectedProfile).isEqualTo(WORK)
+ }
+}