summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ajinkya Chalke <achalke@google.com> 2024-08-29 09:54:12 +0000
committer Ajinkya Chalke <achalke@google.com> 2024-09-05 13:30:14 +0000
commita0d49f65830c51ad879b746f25880c8d6223c97b (patch)
treed883d4dce14b71c21bd2b2d9308946ce3d8e1199
parent61614716dc98b4096c9b136113a8d0ca9a781b72 (diff)
Handle cross profile error in backlinks
- While sending screenshots of cross profile apps to note-taking apps is fine, sending deeplinks doesn't work as the note-taking app will not be able to open the backlink in the other profile. So, don't backlink if the screenshotted app is not of same user and show an error msg to user. - Also, updated the ViewModel to inject application context to retrieve PackageManager from appropriate user's context instead of injecting and using current user's PackageManager. Bug: 356513124 Test: atest AppClipsActivityTest AppClipsViewModelTest Flag: com.android.systemui.app_clips_backlinks Change-Id: Ifad8d58212c82b0dae13ca881ff187f449a085aa
-rw-r--r--packages/SystemUI/res/layout/app_clips_screenshot.xml17
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java83
7 files changed, 323 insertions, 89 deletions
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index d7b94ec015ac..7b7c96cb0322 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -82,6 +82,23 @@
app:layout_constraintStart_toEndOf="@id/backlinks_include_data"
app:layout_constraintTop_toTopOf="parent" />
+ <TextView
+ android:id="@+id/backlinks_cross_profile_error"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginStart="8dp"
+ android:drawablePadding="4dp"
+ android:drawableStart="@drawable/ic_info_outline"
+ android:drawableTint="?androidprv:attr/materialColorOnBackground"
+ android:gravity="center"
+ android:paddingHorizontal="8dp"
+ android:text="@string/backlinks_cross_profile_error"
+ android:textColor="?androidprv:attr/materialColorOnBackground"
+ android:visibility="gone"
+ app:layout_constraintBottom_toTopOf="@id/preview"
+ app:layout_constraintStart_toEndOf="@id/backlinks_data"
+ app:layout_constraintTop_toTopOf="parent" />
+
<ImageView
android:id="@+id/preview"
android:layout_width="0px"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fdba2e67664f..b8e2ec170dd9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -269,8 +269,12 @@
<string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string>
<!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
<string name="app_clips_save_add_to_note">Add to note</string>
+ <!-- A check box used in App Clips flow to return the captured backlink of the screenshotted app to notes app. [CHAR LIMIT=NONE] -->
<string name="backlinks_include_link">Include link</string>
+ <!-- A label for backlinks app that is used if there are multiple backlinks with same app name. [CHAR LIMIT=NONE] -->
<string name="backlinks_duplicate_label_format"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> <xliff:g id="frequencyCount" example="(1)">(%2$d)</xliff:g></string>
+ <!-- An error message to inform user that capturing backlink from cross profile apps is not possible. [CHAR LIMIT=NONE] -->
+ <string name="backlinks_cross_profile_error">Links can\'t be added from other profiles</string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_title">Screen Recorder</string>
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index ad5e772934c8..15a1d1d61626 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -68,6 +68,8 @@ import com.android.settingslib.Utils;
import com.android.systemui.Flags;
import com.android.systemui.log.DebugLogger;
import com.android.systemui.res.R;
+import com.android.systemui.screenshot.appclips.InternalBacklinksData.BacklinksData;
+import com.android.systemui.screenshot.appclips.InternalBacklinksData.CrossProfileError;
import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;
@@ -100,6 +102,7 @@ public class AppClipsActivity extends ComponentActivity {
private static final String TAG = AppClipsActivity.class.getSimpleName();
private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
private static final int DRAWABLE_END = 2;
+ private static final float DISABLE_ALPHA = 0.5f;
private final AppClipsViewModel.Factory mViewModelFactory;
private final PackageManager mPackageManager;
@@ -116,6 +119,7 @@ public class AppClipsActivity extends ComponentActivity {
private Button mCancel;
private CheckBox mBacklinksIncludeDataCheckBox;
private TextView mBacklinksDataTextView;
+ private TextView mBacklinksCrossProfileError;
private AppClipsViewModel mViewModel;
private ResultReceiver mResultReceiver;
@@ -192,8 +196,8 @@ public class AppClipsActivity extends ComponentActivity {
mBacklinksDataTextView = mLayout.findViewById(R.id.backlinks_data);
mBacklinksIncludeDataCheckBox = mLayout.findViewById(R.id.backlinks_include_data);
mBacklinksIncludeDataCheckBox.setOnCheckedChangeListener(
- (buttonView, isChecked) ->
- mBacklinksDataTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE));
+ this::backlinksIncludeDataCheckBoxCheckedChangeListener);
+ mBacklinksCrossProfileError = mLayout.findViewById(R.id.backlinks_cross_profile_error);
mViewModel = new ViewModelProvider(this, mViewModelFactory).get(AppClipsViewModel.class);
mViewModel.getScreenshot().observe(this, this::setScreenshot);
@@ -312,10 +316,11 @@ public class AppClipsActivity extends ComponentActivity {
Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
data.putParcelable(EXTRA_SCREENSHOT_URI, uri);
+ InternalBacklinksData selectedBacklink = mViewModel.mSelectedBacklinksLiveData.getValue();
if (mBacklinksIncludeDataCheckBox.getVisibility() == View.VISIBLE
&& mBacklinksIncludeDataCheckBox.isChecked()
- && mViewModel.mSelectedBacklinksLiveData.getValue() != null) {
- ClipData backlinksData = mViewModel.mSelectedBacklinksLiveData.getValue().getClipData();
+ && selectedBacklink instanceof BacklinksData) {
+ ClipData backlinksData = ((BacklinksData) selectedBacklink).getClipData();
data.putParcelable(EXTRA_CLIP_DATA, backlinksData);
DebugLogger.INSTANCE.logcatMessage(this,
@@ -459,6 +464,38 @@ public class AppClipsActivity extends ComponentActivity {
mBacklinksDataTextView.setCompoundDrawablesRelative(/* start= */ appIcon, /* top= */
null, /* end= */ dropDownIcon, /* bottom= */ null);
+
+ updateViewsToShowOrHideBacklinkError(backlinksData);
+ }
+
+ /** Updates views to show or hide error with backlink. */
+ private void updateViewsToShowOrHideBacklinkError(InternalBacklinksData backlinksData) {
+ // Remove the check box change listener before updating it to avoid updating backlink text
+ // view visibility.
+ mBacklinksIncludeDataCheckBox.setOnCheckedChangeListener(null);
+ if (backlinksData instanceof CrossProfileError) {
+ // There's error with the backlink, unselect the checkbox and disable it.
+ mBacklinksIncludeDataCheckBox.setEnabled(false);
+ mBacklinksIncludeDataCheckBox.setChecked(false);
+ mBacklinksIncludeDataCheckBox.setAlpha(DISABLE_ALPHA);
+
+ mBacklinksCrossProfileError.setVisibility(View.VISIBLE);
+ } else {
+ // When there is no error, ensure the check box is enabled and checked.
+ mBacklinksIncludeDataCheckBox.setEnabled(true);
+ mBacklinksIncludeDataCheckBox.setChecked(true);
+ mBacklinksIncludeDataCheckBox.setAlpha(1.0f);
+
+ mBacklinksCrossProfileError.setVisibility(View.GONE);
+ }
+
+ // (Re)Set the check box change listener as we're done making changes to the check box.
+ mBacklinksIncludeDataCheckBox.setOnCheckedChangeListener(
+ this::backlinksIncludeDataCheckBoxCheckedChangeListener);
+ }
+
+ private void backlinksIncludeDataCheckBoxCheckedChangeListener(View unused, boolean isChecked) {
+ mBacklinksDataTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
private Rect createBacklinksTextViewDrawableBounds() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index 3530b3ff0dfd..9a38358a2768 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -23,13 +23,13 @@ import static android.content.Intent.CATEGORY_LAUNCHER;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
-import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.app.assist.AssistContent;
import android.content.ClipData;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -51,11 +51,14 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.log.DebugLogger;
import com.android.systemui.screenshot.AssistContentRequester;
import com.android.systemui.screenshot.ImageExporter;
+import com.android.systemui.screenshot.appclips.InternalBacklinksData.BacklinksData;
+import com.android.systemui.screenshot.appclips.InternalBacklinksData.CrossProfileError;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
@@ -82,7 +85,7 @@ final class AppClipsViewModel extends ViewModel {
private final ImageExporter mImageExporter;
private final IActivityTaskManager mAtmService;
private final AssistContentRequester mAssistContentRequester;
- private final PackageManager mPackageManager;
+ @Application private final Context mContext;
@Main
private final Executor mMainExecutor;
@@ -97,13 +100,13 @@ final class AppClipsViewModel extends ViewModel {
private AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
ImageExporter imageExporter, IActivityTaskManager atmService,
- AssistContentRequester assistContentRequester, PackageManager packageManager,
+ AssistContentRequester assistContentRequester, @Application Context context,
@Main Executor mainExecutor, @Background Executor bgExecutor) {
mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
mImageExporter = imageExporter;
mAtmService = atmService;
mAssistContentRequester = assistContentRequester;
- mPackageManager = packageManager;
+ mContext = context;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
@@ -243,6 +246,10 @@ final class AppClipsViewModel extends ViewModel {
allTasksOnDisplay
.stream()
.filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore))
+ .map(taskInfo -> new InternalTaskInfo(taskInfo.topActivityInfo,
+ taskInfo.taskId, taskInfo.userId,
+ getPackageManagerForUser(taskInfo.userId)))
+ .filter(this::canAppStartThroughLauncher)
.map(this::getBacklinksDataForTaskInfo)
.toList(),
mBgExecutor);
@@ -284,33 +291,34 @@ final class AppClipsViewModel extends ViewModel {
/**
* Returns whether the app represented by the provided {@link TaskInfo} should be included for
* querying for {@link AssistContent}.
+ *
+ * <p>This does not check whether the task has a launcher icon.
*/
private boolean shouldIncludeTask(TaskInfo taskInfo, Set<Integer> taskIdsToIgnore) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("shouldIncludeTask taskId %d; topActivity %s", taskInfo.taskId,
taskInfo.topActivity));
- // Only consider tasks that shouldn't be ignored, are visible, running, and have a launcher
- // icon. Furthermore, types such as launcher/home/dock/assistant are ignored.
+ // Only consider tasks that shouldn't be ignored, are visible, and running. Furthermore,
+ // types such as launcher/home/dock/assistant are ignored.
return !taskIdsToIgnore.contains(taskInfo.taskId)
&& taskInfo.isVisible
&& taskInfo.isRunning
&& taskInfo.numActivities > 0
&& taskInfo.topActivity != null
&& taskInfo.topActivityInfo != null
- && taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_STANDARD
- && canAppStartThroughLauncher(taskInfo.topActivity.getPackageName());
+ && taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_STANDARD;
}
/**
- * Returns whether the app represented by the provided {@code packageName} can be launched
- * through the all apps tray by a user.
+ * Returns whether the app represented by the {@link InternalTaskInfo} can be launched through
+ * the all apps tray by a user.
*/
- private boolean canAppStartThroughLauncher(String packageName) {
+ private boolean canAppStartThroughLauncher(InternalTaskInfo internalTaskInfo) {
// Use Intent.resolveActivity API to check if the intent resolves as that is what Android
// uses internally when apps use Context.startActivity.
- return getMainLauncherIntentForPackage(packageName).resolveActivity(mPackageManager)
- != null;
+ return getMainLauncherIntentForTask(internalTaskInfo)
+ .resolveActivity(internalTaskInfo.getPackageManager()) != null;
}
/**
@@ -318,18 +326,36 @@ final class AppClipsViewModel extends ViewModel {
* is captured by querying the system using {@link TaskInfo#taskId}.
*/
private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskInfo(
- TaskInfo taskInfo) {
+ InternalTaskInfo internalTaskInfo) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("getBacklinksDataForTaskId for taskId %d; topActivity %s",
- taskInfo.taskId, taskInfo.topActivity));
+ internalTaskInfo.getTaskId(),
+ internalTaskInfo.getTopActivityNameForDebugLogging()));
+
+ // Unlike other SysUI components, App Clips is started by the notes app so it runs as the
+ // same user as the notes app. That is, if the notes app was running as work profile user
+ // then App Clips also runs as work profile user. This is why while checking for user of the
+ // screenshotted app the check is performed using UserHandle.myUserId instead of using the
+ // more complex UserTracker.
+ if (internalTaskInfo.getUserId() != UserHandle.myUserId()) {
+ return getCrossProfileErrorBacklinkForTask(internalTaskInfo);
+ }
SettableFuture<InternalBacklinksData> backlinksData = SettableFuture.create();
- int taskId = taskInfo.taskId;
- mAssistContentRequester.requestAssistContent(taskId, assistContent ->
- backlinksData.set(getBacklinksDataFromAssistContent(taskInfo, assistContent)));
+ int taskId = internalTaskInfo.getTaskId();
+ mAssistContentRequester.requestAssistContent(taskId, assistContent -> backlinksData.set(
+ getBacklinksDataFromAssistContent(internalTaskInfo, assistContent)));
return withTimeout(backlinksData);
}
+ private ListenableFuture<InternalBacklinksData> getCrossProfileErrorBacklinkForTask(
+ InternalTaskInfo internalTaskInfo) {
+ String appName = internalTaskInfo.getTopActivityAppName();
+ Drawable appIcon = internalTaskInfo.getTopActivityAppIcon();
+ InternalBacklinksData errorData = new CrossProfileError(appIcon, appName);
+ return Futures.immediateFuture(errorData);
+ }
+
/** Returns the same {@link ListenableFuture} but with a 5 {@link TimeUnit#SECONDS} timeout. */
private static <V> ListenableFuture<V> withTimeout(ListenableFuture<V> future) {
return Futures.withTimeout(future, 5L, TimeUnit.SECONDS,
@@ -351,22 +377,24 @@ final class AppClipsViewModel extends ViewModel {
* {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER}.
* </ul>
*
- * @param taskInfo {@link RootTaskInfo} of the task which provided the {@link AssistContent}.
+ * @param internalTaskInfo {@link InternalTaskInfo} of the task which provided the
+ * {@link AssistContent}.
* @param content the {@link AssistContent} to map into Backlinks {@link ClipData}.
* @return {@link InternalBacklinksData} that represents the Backlinks data along with app icon.
*/
- private InternalBacklinksData getBacklinksDataFromAssistContent(TaskInfo taskInfo,
+ private InternalBacklinksData getBacklinksDataFromAssistContent(
+ InternalTaskInfo internalTaskInfo,
@Nullable AssistContent content) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("getBacklinksDataFromAssistContent taskId %d; topActivity %s",
- taskInfo.taskId, taskInfo.topActivity));
+ internalTaskInfo.getTaskId(),
+ internalTaskInfo.getTopActivityNameForDebugLogging()));
- String appName = getAppNameOfTask(taskInfo);
- String packageName = taskInfo.topActivity.getPackageName();
- Drawable appIcon = taskInfo.topActivityInfo.loadIcon(mPackageManager);
+ String appName = internalTaskInfo.getTopActivityAppName();
+ Drawable appIcon = internalTaskInfo.getTopActivityAppIcon();
ClipData mainLauncherIntent = ClipData.newIntent(appName,
- getMainLauncherIntentForPackage(packageName));
- InternalBacklinksData fallback = new InternalBacklinksData(mainLauncherIntent, appIcon);
+ getMainLauncherIntentForTask(internalTaskInfo));
+ InternalBacklinksData fallback = new BacklinksData(mainLauncherIntent, appIcon);
if (content == null) {
return fallback;
}
@@ -378,10 +406,10 @@ final class AppClipsViewModel extends ViewModel {
Uri uri = content.getWebUri();
Intent backlinksIntent = new Intent(ACTION_VIEW).setData(uri);
- if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
+ if (doesIntentResolveToSameTask(backlinksIntent, internalTaskInfo)) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> "getBacklinksDataFromAssistContent: using app provided uri");
- return new InternalBacklinksData(ClipData.newRawUri(appName, uri), appIcon);
+ return new BacklinksData(ClipData.newRawUri(appName, uri), appIcon);
}
}
@@ -391,11 +419,10 @@ final class AppClipsViewModel extends ViewModel {
() -> "getBacklinksDataFromAssistContent: app has provided an intent");
Intent backlinksIntent = content.getIntent();
- if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
+ if (doesIntentResolveToSameTask(backlinksIntent, internalTaskInfo)) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> "getBacklinksDataFromAssistContent: using app provided intent");
- return new InternalBacklinksData(ClipData.newIntent(appName, backlinksIntent),
- appIcon);
+ return new BacklinksData(ClipData.newIntent(appName, backlinksIntent), appIcon);
}
}
@@ -404,28 +431,28 @@ final class AppClipsViewModel extends ViewModel {
return fallback;
}
- private boolean doesIntentResolveToSamePackage(Intent intentToResolve,
- String requiredPackageName) {
- ComponentName resolvedComponent = intentToResolve.resolveActivity(mPackageManager);
+ private boolean doesIntentResolveToSameTask(Intent intentToResolve,
+ InternalTaskInfo requiredTaskInfo) {
+ PackageManager packageManager = requiredTaskInfo.getPackageManager();
+ ComponentName resolvedComponent = intentToResolve.resolveActivity(packageManager);
if (resolvedComponent == null) {
return false;
}
+ String requiredPackageName = requiredTaskInfo.getTopActivityPackageName();
return resolvedComponent.getPackageName().equals(requiredPackageName);
}
- private String getAppNameOfTask(TaskInfo taskInfo) {
- return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString();
- }
-
- private Intent getMainLauncherIntentForPackage(String pkgName) {
+ private Intent getMainLauncherIntentForTask(InternalTaskInfo internalTaskInfo) {
+ String pkgName = internalTaskInfo.getTopActivityPackageName();
Intent intent = new Intent(ACTION_MAIN).addCategory(CATEGORY_LAUNCHER).setPackage(pkgName);
// Not all apps use DEFAULT_CATEGORY for their main launcher activity so the exact component
// needs to be queried and set on the Intent in order for note-taking apps to be able to
// start this intent. When starting an activity with an implicit intent, Android adds the
// DEFAULT_CATEGORY flag otherwise it fails to resolve the intent.
- ResolveInfo resolvedActivity = mPackageManager.resolveActivity(intent, /* flags= */ 0);
+ PackageManager packageManager = internalTaskInfo.getPackageManager();
+ ResolveInfo resolvedActivity = packageManager.resolveActivity(intent, /* flags= */ 0);
if (resolvedActivity != null) {
intent.setComponent(resolvedActivity.getComponentInfo().getComponentName());
}
@@ -433,6 +460,17 @@ final class AppClipsViewModel extends ViewModel {
return intent;
}
+ private PackageManager getPackageManagerForUser(int userId) {
+ // If app clips was launched as the same user, then reuse the available PM from mContext.
+ if (mContext.getUserId() == userId) {
+ return mContext.getPackageManager();
+ }
+
+ // PackageManager required for a different user, create its context and return its PM.
+ UserHandle userHandle = UserHandle.of(userId);
+ return mContext.createContextAsUser(userHandle, /* flags= */ 0).getPackageManager();
+ }
+
/** Helper factory to help with injecting {@link AppClipsViewModel}. */
static final class Factory implements ViewModelProvider.Factory {
@@ -440,7 +478,7 @@ final class AppClipsViewModel extends ViewModel {
private final ImageExporter mImageExporter;
private final IActivityTaskManager mAtmService;
private final AssistContentRequester mAssistContentRequester;
- private final PackageManager mPackageManager;
+ @Application private final Context mContext;
@Main
private final Executor mMainExecutor;
@Background
@@ -449,13 +487,13 @@ final class AppClipsViewModel extends ViewModel {
@Inject
Factory(AppClipsCrossProcessHelper appClipsCrossProcessHelper, ImageExporter imageExporter,
IActivityTaskManager atmService, AssistContentRequester assistContentRequester,
- PackageManager packageManager, @Main Executor mainExecutor,
+ @Application Context context, @Main Executor mainExecutor,
@Background Executor bgExecutor) {
mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
mImageExporter = imageExporter;
mAtmService = atmService;
mAssistContentRequester = assistContentRequester;
- mPackageManager = packageManager;
+ mContext = context;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
}
@@ -469,7 +507,7 @@ final class AppClipsViewModel extends ViewModel {
//noinspection unchecked
return (T) new AppClipsViewModel(mAppClipsCrossProcessHelper, mImageExporter,
- mAtmService, mAssistContentRequester, mPackageManager, mMainExecutor,
+ mAtmService, mAssistContentRequester, mContext, mMainExecutor,
mBgExecutor);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
index 30c33c5224ee..234692ea2fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
@@ -16,10 +16,49 @@
package com.android.systemui.screenshot.appclips
+import android.app.TaskInfo
import android.content.ClipData
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-/** A class to hold the [ClipData] for backlinks and the corresponding app's [Drawable] icon. */
-internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable) {
- var displayLabel: String = clipData.description.label.toString()
+/**
+ * A class to hold the [ClipData] for backlinks, the corresponding app's [Drawable] icon, and
+ * represent error when necessary.
+ */
+internal sealed class InternalBacklinksData(
+ open val appIcon: Drawable,
+ open var displayLabel: String
+) {
+ data class BacklinksData(val clipData: ClipData, override val appIcon: Drawable) :
+ InternalBacklinksData(appIcon, clipData.description.label.toString())
+
+ data class CrossProfileError(
+ override val appIcon: Drawable,
+ override var displayLabel: String
+ ) : InternalBacklinksData(appIcon, displayLabel)
+}
+
+/**
+ * A class to hold important members of [TaskInfo] and its associated user's [PackageManager] for
+ * ease of querying.
+ *
+ * @note A task can have a different app running on top. For example, an app "A" can use camera app
+ * to capture an image. In this case the top app will be the camera app even though the task
+ * belongs to app A. This is expected behaviour because user will be taking a screenshot of the
+ * content rendered by the camera (top) app.
+ */
+internal data class InternalTaskInfo(
+ private val topActivityInfo: ActivityInfo,
+ val taskId: Int,
+ val userId: Int,
+ val packageManager: PackageManager
+) {
+ fun getTopActivityNameForDebugLogging(): String = topActivityInfo.name
+
+ fun getTopActivityPackageName(): String = topActivityInfo.packageName
+
+ fun getTopActivityAppName(): String = topActivityInfo.loadLabel(packageManager).toString()
+
+ fun getTopActivityAppIcon(): Drawable = topActivityInfo.loadIcon(packageManager)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index eb1a04d8e4ff..e1cd5e420f66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -30,12 +30,15 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.IActivityTaskManager;
import android.app.assist.AssistContent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -51,6 +54,7 @@ import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
@@ -120,6 +124,8 @@ public final class AppClipsActivityTest extends SysuiTestCase {
@Mock
private AssistContentRequester mAssistContentRequester;
@Mock
+ private Context mMockedContext;
+ @Mock
private PackageManager mPackageManager;
@Mock
private UserTracker mUserTracker;
@@ -127,6 +133,9 @@ public final class AppClipsActivityTest extends SysuiTestCase {
private UiEventLogger mUiEventLogger;
private AppClipsActivity mActivity;
+ private TextView mBacklinksDataTextView;
+ private CheckBox mBacklinksIncludeDataCheckBox;
+ private TextView mBacklinksCrossProfileErrorTextView;
// Using the deprecated ActivityTestRule and SingleActivityFactory to help with injecting mocks.
private final SingleActivityFactory<AppClipsActivityTestable> mFactory =
@@ -136,7 +145,7 @@ public final class AppClipsActivityTest extends SysuiTestCase {
return new AppClipsActivityTestable(
new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper,
mImageExporter, mAtmService, mAssistContentRequester,
- mPackageManager, getContext().getMainExecutor(),
+ mMockedContext, getContext().getMainExecutor(),
directExecutor()),
mPackageManager, mUserTracker, mUiEventLogger);
}
@@ -162,6 +171,9 @@ public final class AppClipsActivityTest extends SysuiTestCase {
when(mImageExporter.export(any(Executor.class), any(UUID.class), any(Bitmap.class),
eq(Process.myUserHandle()), eq(Display.DEFAULT_DISPLAY)))
.thenReturn(Futures.immediateFuture(result));
+
+ when(mMockedContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mMockedContext.createContextAsUser(any(), anyInt())).thenReturn(mMockedContext);
}
@After
@@ -175,10 +187,9 @@ public final class AppClipsActivityTest extends SysuiTestCase {
launchActivity();
assertThat(((ImageView) mActivity.findViewById(R.id.preview)).getDrawable()).isNotNull();
- assertThat(mActivity.findViewById(R.id.backlinks_data).getVisibility())
- .isEqualTo(View.GONE);
- assertThat(mActivity.findViewById(R.id.backlinks_include_data).getVisibility())
- .isEqualTo(View.GONE);
+ assertThat(mBacklinksDataTextView.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mBacklinksIncludeDataCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mBacklinksCrossProfileErrorTextView.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -228,20 +239,23 @@ public final class AppClipsActivityTest extends SysuiTestCase {
waitForIdleSync();
assertThat(mDisplayIdCaptor.getValue()).isEqualTo(mActivity.getDisplayId());
- TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
- assertThat(backlinksData.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
- assertThat(backlinksData.getCompoundDrawablesRelative()[0]).isEqualTo(FAKE_DRAWABLE);
+ assertThat(mBacklinksDataTextView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mBacklinksDataTextView.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(mBacklinksDataTextView.getCompoundDrawablesRelative()[0])
+ .isEqualTo(FAKE_DRAWABLE);
// Verify dropdown icon is not shown and there are no click listeners on text view.
- assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNull();
- assertThat(backlinksData.hasOnClickListeners()).isFalse();
+ assertThat(mBacklinksDataTextView.getCompoundDrawablesRelative()[2]).isNull();
+ assertThat(mBacklinksDataTextView.hasOnClickListeners()).isFalse();
- CheckBox backlinksIncludeData = mActivity.findViewById(R.id.backlinks_include_data);
- assertThat(backlinksIncludeData.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(backlinksIncludeData.getText().toString())
+ assertThat(mBacklinksIncludeDataCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mBacklinksIncludeDataCheckBox.getText().toString())
.isEqualTo(mActivity.getString(R.string.backlinks_include_link));
- assertThat(backlinksIncludeData.isChecked()).isTrue();
+ assertThat(mBacklinksIncludeDataCheckBox.isChecked()).isTrue();
+
+ assertThat(mBacklinksIncludeDataCheckBox.getAlpha()).isEqualTo(1.0f);
+ assertThat(mBacklinksIncludeDataCheckBox.isEnabled()).isTrue();
+ assertThat(mBacklinksCrossProfileErrorTextView.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -258,8 +272,7 @@ public final class AppClipsActivityTest extends SysuiTestCase {
assertThat(backlinksIncludeData.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(backlinksIncludeData.isChecked()).isFalse();
- TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
- assertThat(backlinksData.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mBacklinksDataTextView.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -300,12 +313,11 @@ public final class AppClipsActivityTest extends SysuiTestCase {
waitForIdleSync();
// Verify default backlink shown to user and text view has on click listener.
- TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
- assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
- assertThat(backlinksData.hasOnClickListeners()).isTrue();
+ assertThat(mBacklinksDataTextView.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(mBacklinksDataTextView.hasOnClickListeners()).isTrue();
// Verify dropdown icon is not null.
- assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNotNull();
+ assertThat(mBacklinksDataTextView.getCompoundDrawablesRelative()[2]).isNotNull();
}
@Test
@@ -336,12 +348,35 @@ public final class AppClipsActivityTest extends SysuiTestCase {
waitForIdleSync();
// Verify default backlink shown to user has the numerical suffix.
- TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
- assertThat(backlinksData.getText().toString()).isEqualTo(
- mContext.getString(R.string.backlinks_duplicate_label_format,
+ assertThat(mBacklinksDataTextView.getText().toString()).isEqualTo(
+ getContext().getString(R.string.backlinks_duplicate_label_format,
BACKLINKS_TASK_APP_NAME, 1));
}
+ @Test
+ @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
+ public void appClipsLaunched_backlinks_singleBacklink_crossProfileError()
+ throws RemoteException {
+ // Set up mocking for cross profile backlink.
+ setUpMocksForBacklinks();
+ ActivityManager.RunningTaskInfo crossProfileTaskInfo = createTaskInfoForBacklinksTask();
+ crossProfileTaskInfo.userId = UserHandle.myUserId() + 1;
+ reset(mAtmService);
+ when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
+ mDisplayIdCaptor.capture())).thenReturn(List.of(crossProfileTaskInfo));
+
+ // Trigger backlinks.
+ launchActivity();
+ waitForIdleSync();
+
+ // Verify views for cross profile backlinks error.
+ assertThat(mBacklinksIncludeDataCheckBox.getAlpha()).isLessThan(1.0f);
+ assertThat(mBacklinksIncludeDataCheckBox.isEnabled()).isFalse();
+ assertThat(mBacklinksIncludeDataCheckBox.isChecked()).isFalse();
+
+ assertThat(mBacklinksCrossProfileErrorTextView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
private void setUpMocksForBacklinks() throws RemoteException {
when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
mDisplayIdCaptor.capture()))
@@ -373,11 +408,15 @@ public final class AppClipsActivityTest extends SysuiTestCase {
mActivity = mActivityRule.launchActivity(intent);
waitForIdleSync();
+ mBacklinksDataTextView = mActivity.findViewById(R.id.backlinks_data);
+ mBacklinksIncludeDataCheckBox = mActivity.findViewById(R.id.backlinks_include_data);
+ mBacklinksCrossProfileErrorTextView = mActivity.findViewById(
+ R.id.backlinks_cross_profile_error);
}
private ResultReceiver createResultReceiver(
BiConsumer<Integer, Bundle> resultReceiverConsumer) {
- ResultReceiver testReceiver = new ResultReceiver(mContext.getMainThreadHandler()) {
+ ResultReceiver testReceiver = new ResultReceiver(getContext().getMainThreadHandler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
resultReceiverConsumer.accept(resultCode, resultData);
@@ -394,7 +433,7 @@ public final class AppClipsActivityTest extends SysuiTestCase {
}
private void runOnMainThread(Runnable runnable) {
- mContext.getMainExecutor().execute(runnable);
+ getContext().getMainExecutor().execute(runnable);
}
private static ResolveInfo createBacklinksTaskResolveInfo() {
@@ -418,6 +457,7 @@ public final class AppClipsActivityTest extends SysuiTestCase {
taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ taskInfo.userId = UserHandle.myUserId();
return taskInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index 178547e4ca3a..5d71c054244a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -43,6 +44,7 @@ import android.app.assist.AssistContent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -62,6 +64,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.screenshot.AssistContentRequester;
import com.android.systemui.screenshot.ImageExporter;
+import com.android.systemui.screenshot.appclips.InternalBacklinksData.BacklinksData;
+import com.android.systemui.screenshot.appclips.InternalBacklinksData.CrossProfileError;
import com.google.common.util.concurrent.Futures;
@@ -92,10 +96,16 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName";
private static final AssistContent EMPTY_ASSIST_CONTENT = new AssistContent();
- @Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
- @Mock private ImageExporter mImageExporter;
- @Mock private IActivityTaskManager mAtmService;
- @Mock private AssistContentRequester mAssistContentRequester;
+ @Mock
+ private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+ @Mock
+ private ImageExporter mImageExporter;
+ @Mock
+ private IActivityTaskManager mAtmService;
+ @Mock
+ private AssistContentRequester mAssistContentRequester;
+ @Mock
+ Context mMockedContext;
@Mock
private PackageManager mPackageManager;
private ArgumentCaptor<Intent> mPackageManagerIntentCaptor;
@@ -112,10 +122,14 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
when(mPackageManager.resolveActivity(mPackageManagerIntentCaptor.capture(), anyInt()))
.thenReturn(createBacklinksTaskResolveInfo());
when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+ when(mMockedContext.getPackageManager()).thenReturn(mPackageManager);
mViewModel = new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, mImageExporter,
- mAtmService, mAssistContentRequester, mPackageManager,
+ mAtmService, mAssistContentRequester, mMockedContext,
getContext().getMainExecutor(), directExecutor()).create(AppClipsViewModel.class);
+
+ when(mMockedContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mMockedContext.createContextAsUser(any(), anyInt())).thenReturn(mMockedContext);
}
@Test
@@ -199,7 +213,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
assertThat(queriedIntent.getData()).isEqualTo(expectedUri);
assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW);
- InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
+ BacklinksData result = (BacklinksData) mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
ClipDescription resultDescription = clipData.getDescription();
@@ -238,7 +252,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
Intent queriedIntent = mPackageManagerIntentCaptor.getValue();
assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage());
- InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
+ BacklinksData result = (BacklinksData) mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
ClipDescription resultDescription = clipData.getDescription();
@@ -307,6 +321,8 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
@Test
public void triggerBacklinks_taskIdsToIgnoreConsidered_noBacklinkAvailable() {
+ mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
+
mViewModel.triggerBacklinks(Set.of(BACKLINKS_TASK_ID), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -362,16 +378,58 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
waitForIdleSync();
// Verify two backlinks are received and the first backlink is set as default selected.
- assertThat(mViewModel.mSelectedBacklinksLiveData.getValue().getClipData().getItemAt(
- 0).getUri()).isEqualTo(expectedUri);
+ assertThat(
+ ((BacklinksData) mViewModel.mSelectedBacklinksLiveData.getValue())
+ .getClipData().getItemAt(0).getUri())
+ .isEqualTo(expectedUri);
List<InternalBacklinksData> actualBacklinks = mViewModel.getBacklinksLiveData().getValue();
assertThat(actualBacklinks).hasSize(2);
- assertThat(actualBacklinks.get(0).getClipData().getItemAt(0).getUri())
+ assertThat(((BacklinksData) actualBacklinks.get(0)).getClipData().getItemAt(0).getUri())
.isEqualTo(expectedUri);
- assertThat(actualBacklinks.get(1).getClipData().getItemAt(0).getIntent())
+ assertThat(((BacklinksData) actualBacklinks.get(1)).getClipData().getItemAt(0).getIntent())
.isEqualTo(expectedIntent);
}
+ @Test
+ public void triggerBacklinks_singleCrossProfileApp_shouldIndicateError()
+ throws RemoteException {
+ reset(mAtmService);
+ RunningTaskInfo taskInfo = createTaskInfoForBacklinksTask();
+ taskInfo.userId = UserHandle.myUserId() + 1;
+ when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
+ .thenReturn(List.of(taskInfo));
+
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue())
+ .isInstanceOf(CrossProfileError.class);
+ }
+
+ @Test
+ public void triggerBacklinks_multipleBacklinks_includesCrossProfileError()
+ throws RemoteException {
+ // Set up mocking for multiple backlinks.
+ mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
+ reset(mAtmService);
+ RunningTaskInfo runningTaskInfo1 = createTaskInfoForBacklinksTask();
+ RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
+ runningTaskInfo2.userId = UserHandle.myUserId() + 1;
+
+ when(mAtmService.getTasks(anyInt(), anyBoolean(), anyBoolean(), anyInt()))
+ .thenReturn(List.of(runningTaskInfo1, runningTaskInfo2));
+
+ // Set up complete, trigger the backlinks action.
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ // Verify two backlinks are received and only second has error.
+ List<InternalBacklinksData> actualBacklinks = mViewModel.getBacklinksLiveData().getValue();
+ assertThat(actualBacklinks).hasSize(2);
+ assertThat(actualBacklinks.get(0)).isInstanceOf(BacklinksData.class);
+ assertThat(actualBacklinks.get(1)).isInstanceOf(CrossProfileError.class);
+ }
+
private void resetPackageManagerMockingForUsingFallbackBacklinks() {
ResolveInfo backlinksTaskResolveInfo = createBacklinksTaskResolveInfo();
reset(mPackageManager);
@@ -389,7 +447,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
}
private void verifyMainLauncherBacklinksIntent() {
- InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
+ BacklinksData result = (BacklinksData) mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
@@ -436,6 +494,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ taskInfo.userId = UserHandle.myUserId();
return taskInfo;
}
}