Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0d49f65 authored by Ajinkya Chalke's avatar Ajinkya Chalke
Browse files

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
parent 61614716
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -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"
+4 −0
Original line number Diff line number Diff line
@@ -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>
+41 −4
Original line number Diff line number Diff line
@@ -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() {
+82 −44
Original line number Diff line number Diff line
@@ -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);
        }
    }
+42 −3
Original line number Diff line number Diff line
@@ -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)
}
Loading