summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2022-12-16 17:36:27 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-12-16 17:36:27 +0000
commit4ebf21f578b78ed83de87f829c4a126c9bb40759 (patch)
treef78dbe0381fcd8cf202afdfaa1ffef83885546d1 /java
parent9ebd81953ce60236381a068a0b7c52c8164bbfea (diff)
parent6e627be666fe43a9f13559856e158196d8ed7f4e (diff)
Merge "Extract TargetPresentationGetter hierarchy" into tm-qpr-dev
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java4
-rw-r--r--java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java10
-rw-r--r--java/src/com/android/intentresolver/ResolverListAdapter.java219
-rw-r--r--java/src/com/android/intentresolver/TargetPresentationGetter.java267
-rw-r--r--java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java28
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java3
-rw-r--r--java/tests/src/com/android/intentresolver/IChooserWrapper.java3
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverActivityTest.java56
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverDataProvider.java12
-rw-r--r--java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt204
10 files changed, 513 insertions, 293 deletions
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 12a054b9..699190f9 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -224,7 +224,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
ri.icon = 0;
}
DisplayResolveInfo displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo(
- ii, ri, ii, makePresentationGetter(ri));
+ ii, ri, ii, mPresentationFactory.makePresentationGetter(ri));
mCallerTargets.add(displayResolveInfo);
if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
}
@@ -715,7 +715,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
// Now fetch app icon and raster with no badging even in work profile
- Bitmap appIcon = makePresentationGetter(info).getIconBitmap(null);
+ Bitmap appIcon = mPresentationFactory.makePresentationGetter(info).getIconBitmap(null);
// Raster target drawable with appIcon as a badge
SimpleIconFactory sif = SimpleIconFactory.obtain(context);
diff --git a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
index f4d4a6d1..0aa32505 100644
--- a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
+++ b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java
@@ -19,8 +19,6 @@ package com.android.intentresolver;
import static android.content.Context.ACTIVITY_SERVICE;
-import static com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
-
import static java.util.stream.Collectors.toList;
import android.annotation.NonNull;
@@ -136,7 +134,7 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment
ImageView icon = v.findViewById(com.android.internal.R.id.icon);
RecyclerView rv = v.findViewById(com.android.internal.R.id.listContainer);
- final ResolveInfoPresentationGetter pg = getProvidingAppPresentationGetter();
+ final TargetPresentationGetter pg = getProvidingAppPresentationGetter();
title.setText(isShortcutTarget() ? mShortcutTitle : pg.getLabel());
icon.setImageDrawable(pg.getIcon(mUserHandle));
rv.setAdapter(new VHAdapter(items));
@@ -270,14 +268,14 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment
return getPinIcon(isPinned(dri));
}
- private ResolveInfoPresentationGetter getProvidingAppPresentationGetter() {
+ private TargetPresentationGetter getProvidingAppPresentationGetter() {
final ActivityManager am = (ActivityManager) getContext()
.getSystemService(ACTIVITY_SERVICE);
final int iconDpi = am.getLauncherLargeIconDensity();
// Use the matching application icon and label for the title, any TargetInfo will do
- return new ResolveInfoPresentationGetter(getContext(), iconDpi,
- mTargetInfos.get(0).getResolveInfo());
+ return new TargetPresentationGetter.Factory(getContext(), iconDpi)
+ .makePresentationGetter(mTargetInfos.get(0).getResolveInfo());
}
private boolean isPinned(DisplayResolveInfo dri) {
diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java
index 5513eda2..eecb914c 100644
--- a/java/src/com/android/intentresolver/ResolverListAdapter.java
+++ b/java/src/com/android/intentresolver/ResolverListAdapter.java
@@ -26,15 +26,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.RemoteException;
@@ -74,6 +70,7 @@ public class ResolverListAdapter extends BaseAdapter {
protected final LayoutInflater mInflater;
protected final ResolverListCommunicator mResolverListCommunicator;
protected final ResolverListController mResolverListController;
+ protected final TargetPresentationGetter.Factory mPresentationFactory;
private final List<Intent> mIntents;
private final Intent[] mInitialIntents;
@@ -126,6 +123,7 @@ public class ResolverListAdapter extends BaseAdapter {
mIsAudioCaptureDevice = isAudioCaptureDevice;
final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE);
mIconDpi = am.getLauncherLargeIconDensity();
+ mPresentationFactory = new TargetPresentationGetter.Factory(mContext, mIconDpi);
}
public final DisplayResolveInfo getFirstDisplayResolveInfo() {
@@ -479,7 +477,7 @@ public class ResolverListAdapter extends BaseAdapter {
ri.loadLabel(mPm),
null,
ii,
- makePresentationGetter(ri)));
+ mPresentationFactory.makePresentationGetter(ri)));
}
}
@@ -532,7 +530,7 @@ public class ResolverListAdapter extends BaseAdapter {
intent,
add,
(replaceIntent != null) ? replaceIntent : defaultIntent,
- makePresentationGetter(add));
+ mPresentationFactory.makePresentationGetter(add));
dri.setPinned(rci.isPinned());
if (rci.isPinned()) {
Log.i(TAG, "Pinned item: " + rci.name);
@@ -765,17 +763,9 @@ public class ResolverListAdapter extends BaseAdapter {
return sSuspendedMatrixColorFilter;
}
- ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) {
- return new ActivityInfoPresentationGetter(mContext, mIconDpi, ai);
- }
-
- ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) {
- return new ResolveInfoPresentationGetter(mContext, mIconDpi, ri);
- }
-
Drawable loadIconForResolveInfo(ResolveInfo ri) {
// Load icons based on the current process. If in work profile icons should be badged.
- return makePresentationGetter(ri).getIcon(getUserHandle());
+ return mPresentationFactory.makePresentationGetter(ri).getIcon(getUserHandle());
}
protected final Drawable loadIconPlaceholder() {
@@ -875,8 +865,9 @@ public class ResolverListAdapter extends BaseAdapter {
Intent replacementIntent = resolverListCommunicator.getReplacementIntent(
resolveInfo.activityInfo, targetIntent);
- ResolveInfoPresentationGetter presentationGetter =
- new ResolveInfoPresentationGetter(context, iconDpi, resolveInfo);
+ TargetPresentationGetter presentationGetter =
+ new TargetPresentationGetter.Factory(context, iconDpi)
+ .makePresentationGetter(resolveInfo);
return DisplayResolveInfo.newDisplayResolveInfo(
resolvedComponentInfo.getIntentAt(0),
@@ -979,8 +970,8 @@ public class ResolverListAdapter extends BaseAdapter {
@Override
protected CharSequence[] doInBackground(Void... voids) {
- ResolveInfoPresentationGetter pg =
- makePresentationGetter(mDisplayResolveInfo.getResolveInfo());
+ TargetPresentationGetter pg = mPresentationFactory.makePresentationGetter(
+ mDisplayResolveInfo.getResolveInfo());
if (mIsAudioCaptureDevice) {
// This is an audio capture device, so check record permissions
@@ -1051,194 +1042,4 @@ public class ResolverListAdapter extends BaseAdapter {
}
}
}
-
- /**
- * Loads the icon and label for the provided ResolveInfo.
- */
- @VisibleForTesting
- public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
- private final ResolveInfo mRi;
- public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) {
- super(ctx, iconDpi, ri.activityInfo);
- mRi = ri;
- }
-
- @Override
- Drawable getIconSubstituteInternal() {
- Drawable dr = null;
- try {
- // Do not use ResolveInfo#getIconResource() as it defaults to the app
- if (mRi.resolvePackageName != null && mRi.icon != 0) {
- dr = loadIconFromResource(
- mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon);
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
- + "couldn't find resources for package", e);
- }
-
- // Fall back to ActivityInfo if no icon is found via ResolveInfo
- if (dr == null) dr = super.getIconSubstituteInternal();
-
- return dr;
- }
-
- @Override
- String getAppSubLabelInternal() {
- // Will default to app name if no intent filter or activity label set, make sure to
- // check if subLabel matches label before final display
- return mRi.loadLabel(mPm).toString();
- }
-
- @Override
- String getAppLabelForSubstitutePermission() {
- // Will default to app name if no activity label set
- return mRi.getComponentInfo().loadLabel(mPm).toString();
- }
- }
-
- /**
- * Loads the icon and label for the provided ActivityInfo.
- */
- @VisibleForTesting
- public static class ActivityInfoPresentationGetter extends
- TargetPresentationGetter {
- private final ActivityInfo mActivityInfo;
- public ActivityInfoPresentationGetter(Context ctx, int iconDpi,
- ActivityInfo activityInfo) {
- super(ctx, iconDpi, activityInfo.applicationInfo);
- mActivityInfo = activityInfo;
- }
-
- @Override
- Drawable getIconSubstituteInternal() {
- Drawable dr = null;
- try {
- // Do not use ActivityInfo#getIconResource() as it defaults to the app
- if (mActivityInfo.icon != 0) {
- dr = loadIconFromResource(
- mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
- mActivityInfo.icon);
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
- + "couldn't find resources for package", e);
- }
-
- return dr;
- }
-
- @Override
- String getAppSubLabelInternal() {
- // Will default to app name if no activity label set, make sure to check if subLabel
- // matches label before final display
- return (String) mActivityInfo.loadLabel(mPm);
- }
-
- @Override
- String getAppLabelForSubstitutePermission() {
- return getAppSubLabelInternal();
- }
- }
-
- /**
- * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application
- * icon and label over any IntentFilter or Activity icon to increase user understanding, with an
- * exception for applications that hold the right permission. Always attempts to use available
- * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
- * Strings to strip creative formatting.
- */
- private abstract static class TargetPresentationGetter {
- @Nullable abstract Drawable getIconSubstituteInternal();
- @Nullable abstract String getAppSubLabelInternal();
- @Nullable abstract String getAppLabelForSubstitutePermission();
-
- private Context mCtx;
- private final int mIconDpi;
- private final boolean mHasSubstitutePermission;
- private final ApplicationInfo mAi;
-
- protected PackageManager mPm;
-
- TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) {
- mCtx = ctx;
- mPm = ctx.getPackageManager();
- mAi = ai;
- mIconDpi = iconDpi;
- mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
- android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
- mAi.packageName);
- }
-
- public Drawable getIcon(UserHandle userHandle) {
- return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle));
- }
-
- public Bitmap getIconBitmap(@Nullable UserHandle userHandle) {
- Drawable dr = null;
- if (mHasSubstitutePermission) {
- dr = getIconSubstituteInternal();
- }
-
- if (dr == null) {
- try {
- if (mAi.icon != 0) {
- dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
- }
- } catch (PackageManager.NameNotFoundException ignore) {
- }
- }
-
- // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
- if (dr == null) {
- dr = mAi.loadIcon(mPm);
- }
-
- SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx);
- Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle);
- sif.recycle();
-
- return icon;
- }
-
- public String getLabel() {
- String label = null;
- // Apps with the substitute permission will always show the activity label as the
- // app label if provided
- if (mHasSubstitutePermission) {
- label = getAppLabelForSubstitutePermission();
- }
-
- if (label == null) {
- label = (String) mAi.loadLabel(mPm);
- }
-
- return label;
- }
-
- public String getSubLabel() {
- // Apps with the substitute permission will always show the resolve info label as the
- // sublabel if provided
- if (mHasSubstitutePermission){
- String appSubLabel = getAppSubLabelInternal();
- // Use the resolve info label as sublabel if it is set
- if(!TextUtils.isEmpty(appSubLabel)
- && !TextUtils.equals(appSubLabel, getLabel())){
- return appSubLabel;
- }
- return null;
- }
- return getAppSubLabelInternal();
- }
-
- protected String loadLabelFromResource(Resources res, int resId) {
- return res.getString(resId);
- }
-
- @Nullable
- protected Drawable loadIconFromResource(Resources res, int resId) {
- return res.getDrawableForDensity(resId, mIconDpi);
- }
-
- }
}
diff --git a/java/src/com/android/intentresolver/TargetPresentationGetter.java b/java/src/com/android/intentresolver/TargetPresentationGetter.java
new file mode 100644
index 00000000..f8b36566
--- /dev/null
+++ b/java/src/com/android/intentresolver/TargetPresentationGetter.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2022 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;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application icon
+ * and label over any IntentFilter or Activity icon to increase user understanding, with an
+ * exception for applications that hold the right permission. Always attempts to use available
+ * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
+ * Strings to strip creative formatting.
+ *
+ * Use one of the {@link TargetPresentationGetter#Factory} methods to create an instance of the
+ * appropriate concrete type.
+ *
+ * TODO: once this component (and its tests) are merged, it should be possible to refactor and
+ * vastly simplify by precomputing conditional logic at initialization.
+ */
+public abstract class TargetPresentationGetter {
+ private static final String TAG = "ResolverListAdapter";
+
+ /** Helper to build appropriate type-specific {@link TargetPresentationGetter} instances. */
+ public static class Factory {
+ private final Context mContext;
+ private final int mIconDpi;
+
+ public Factory(Context context, int iconDpi) {
+ mContext = context;
+ mIconDpi = iconDpi;
+ }
+
+ /** Make a {@link TargetPresentationGetter} for an {@link ActivityInfo}. */
+ public TargetPresentationGetter makePresentationGetter(ActivityInfo activityInfo) {
+ return new ActivityInfoPresentationGetter(mContext, mIconDpi, activityInfo);
+ }
+
+ /** Make a {@link TargetPresentationGetter} for a {@link ResolveInfo}. */
+ public TargetPresentationGetter makePresentationGetter(ResolveInfo resolveInfo) {
+ return new ResolveInfoPresentationGetter(mContext, mIconDpi, resolveInfo);
+ }
+ }
+
+ @Nullable
+ protected abstract Drawable getIconSubstituteInternal();
+
+ @Nullable
+ protected abstract String getAppSubLabelInternal();
+
+ @Nullable
+ protected abstract String getAppLabelForSubstitutePermission();
+
+ private Context mContext;
+ private final int mIconDpi;
+ private final boolean mHasSubstitutePermission;
+ private final ApplicationInfo mAppInfo;
+
+ protected PackageManager mPm;
+
+ /**
+ * Retrieve the image that should be displayed as the icon when this target is presented to the
+ * specified {@code userHandle}.
+ */
+ public Drawable getIcon(UserHandle userHandle) {
+ return new BitmapDrawable(mContext.getResources(), getIconBitmap(userHandle));
+ }
+
+ /**
+ * Retrieve the image that should be displayed as the icon when this target is presented to the
+ * specified {@code userHandle}.
+ */
+ public Bitmap getIconBitmap(@Nullable UserHandle userHandle) {
+ Drawable drawable = null;
+ if (mHasSubstitutePermission) {
+ drawable = getIconSubstituteInternal();
+ }
+
+ if (drawable == null) {
+ try {
+ if (mAppInfo.icon != 0) {
+ drawable = loadIconFromResource(
+ mPm.getResourcesForApplication(mAppInfo), mAppInfo.icon);
+ }
+ } catch (PackageManager.NameNotFoundException ignore) { }
+ }
+
+ // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
+ if (drawable == null) {
+ drawable = mAppInfo.loadIcon(mPm);
+ }
+
+ SimpleIconFactory iconFactory = SimpleIconFactory.obtain(mContext);
+ Bitmap icon = iconFactory.createUserBadgedIconBitmap(drawable, userHandle);
+ iconFactory.recycle();
+
+ return icon;
+ }
+
+ /** Get the label to display for the target. */
+ public String getLabel() {
+ String label = null;
+ // Apps with the substitute permission will always show the activity label as the app label
+ // if provided.
+ if (mHasSubstitutePermission) {
+ label = getAppLabelForSubstitutePermission();
+ }
+
+ if (label == null) {
+ label = (String) mAppInfo.loadLabel(mPm);
+ }
+
+ return label;
+ }
+
+ /**
+ * Get the sublabel to display for the target. Clients are responsible for deduping their
+ * presentation if this returns the same value as {@link #getLabel()}.
+ * TODO: this class should take responsibility for that deduping internally so it's an
+ * authoritative record of exactly the content that should be presented.
+ */
+ public String getSubLabel() {
+ // Apps with the substitute permission will always show the resolve info label as the
+ // sublabel if provided
+ if (mHasSubstitutePermission) {
+ String appSubLabel = getAppSubLabelInternal();
+ // Use the resolve info label as sublabel if it is set
+ if (!TextUtils.isEmpty(appSubLabel) && !TextUtils.equals(appSubLabel, getLabel())) {
+ return appSubLabel;
+ }
+ return null;
+ }
+ return getAppSubLabelInternal();
+ }
+
+ protected String loadLabelFromResource(Resources res, int resId) {
+ return res.getString(resId);
+ }
+
+ @Nullable
+ protected Drawable loadIconFromResource(Resources res, int resId) {
+ return res.getDrawableForDensity(resId, mIconDpi);
+ }
+
+ private TargetPresentationGetter(Context context, int iconDpi, ApplicationInfo appInfo) {
+ mContext = context;
+ mPm = context.getPackageManager();
+ mAppInfo = appInfo;
+ mIconDpi = iconDpi;
+ mHasSubstitutePermission = (PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
+ android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
+ mAppInfo.packageName));
+ }
+
+ /** Loads the icon and label for the provided ResolveInfo. */
+ private static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
+ private final ResolveInfo mResolveInfo;
+
+ ResolveInfoPresentationGetter(
+ Context context, int iconDpi, ResolveInfo resolveInfo) {
+ super(context, iconDpi, resolveInfo.activityInfo);
+ mResolveInfo = resolveInfo;
+ }
+
+ @Override
+ protected Drawable getIconSubstituteInternal() {
+ Drawable drawable = null;
+ try {
+ // Do not use ResolveInfo#getIconResource() as it defaults to the app
+ if (mResolveInfo.resolvePackageName != null && mResolveInfo.icon != 0) {
+ drawable = loadIconFromResource(
+ mPm.getResourcesForApplication(mResolveInfo.resolvePackageName),
+ mResolveInfo.icon);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+ + "couldn't find resources for package", e);
+ }
+
+ // Fall back to ActivityInfo if no icon is found via ResolveInfo
+ if (drawable == null) {
+ drawable = super.getIconSubstituteInternal();
+ }
+
+ return drawable;
+ }
+
+ @Override
+ protected String getAppSubLabelInternal() {
+ // Will default to app name if no intent filter or activity label set, make sure to
+ // check if subLabel matches label before final display
+ return mResolveInfo.loadLabel(mPm).toString();
+ }
+
+ @Override
+ protected String getAppLabelForSubstitutePermission() {
+ // Will default to app name if no activity label set
+ return mResolveInfo.getComponentInfo().loadLabel(mPm).toString();
+ }
+ }
+
+ /** Loads the icon and label for the provided {@link ActivityInfo}. */
+ private static class ActivityInfoPresentationGetter extends TargetPresentationGetter {
+ private final ActivityInfo mActivityInfo;
+
+ ActivityInfoPresentationGetter(
+ Context context, int iconDpi, ActivityInfo activityInfo) {
+ super(context, iconDpi, activityInfo.applicationInfo);
+ mActivityInfo = activityInfo;
+ }
+
+ @Override
+ protected Drawable getIconSubstituteInternal() {
+ Drawable drawable = null;
+ try {
+ // Do not use ActivityInfo#getIconResource() as it defaults to the app
+ if (mActivityInfo.icon != 0) {
+ drawable = loadIconFromResource(
+ mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
+ mActivityInfo.icon);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+ + "couldn't find resources for package", e);
+ }
+
+ return drawable;
+ }
+
+ @Override
+ protected String getAppSubLabelInternal() {
+ // Will default to app name if no activity label set, make sure to check if subLabel
+ // matches label before final display
+ return (String) mActivityInfo.loadLabel(mPm);
+ }
+
+ @Override
+ protected String getAppLabelForSubstitutePermission() {
+ return getAppSubLabelInternal();
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
index c1b007af..db5ae0b4 100644
--- a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
@@ -28,7 +28,7 @@ import android.os.Bundle;
import android.os.UserHandle;
import com.android.intentresolver.ResolverActivity;
-import com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
+import com.android.intentresolver.TargetPresentationGetter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -45,7 +45,7 @@ public class DisplayResolveInfo implements TargetInfo {
private final Intent mResolvedIntent;
private final List<Intent> mSourceIntents = new ArrayList<>();
private final boolean mIsSuspended;
- private ResolveInfoPresentationGetter mResolveInfoPresentationGetter;
+ private TargetPresentationGetter mPresentationGetter;
private boolean mPinned = false;
private final IconHolder mDisplayIconHolder = new SettableIconHolder();
@@ -54,7 +54,7 @@ public class DisplayResolveInfo implements TargetInfo {
Intent originalIntent,
ResolveInfo resolveInfo,
@NonNull Intent resolvedIntent,
- @Nullable ResolveInfoPresentationGetter presentationGetter) {
+ @Nullable TargetPresentationGetter presentationGetter) {
return newDisplayResolveInfo(
originalIntent,
resolveInfo,
@@ -71,14 +71,14 @@ public class DisplayResolveInfo implements TargetInfo {
CharSequence displayLabel,
CharSequence extendedInfo,
@NonNull Intent resolvedIntent,
- @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ @Nullable TargetPresentationGetter presentationGetter) {
return new DisplayResolveInfo(
originalIntent,
resolveInfo,
displayLabel,
extendedInfo,
resolvedIntent,
- resolveInfoPresentationGetter);
+ presentationGetter);
}
private DisplayResolveInfo(
@@ -87,12 +87,12 @@ public class DisplayResolveInfo implements TargetInfo {
CharSequence displayLabel,
CharSequence extendedInfo,
@NonNull Intent resolvedIntent,
- @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ @Nullable TargetPresentationGetter presentationGetter) {
mSourceIntents.add(originalIntent);
mResolveInfo = resolveInfo;
mDisplayLabel = displayLabel;
mExtendedInfo = extendedInfo;
- mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
+ mPresentationGetter = presentationGetter;
final ActivityInfo ai = mResolveInfo.activityInfo;
mIsSuspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
@@ -108,7 +108,7 @@ public class DisplayResolveInfo implements TargetInfo {
DisplayResolveInfo other,
Intent fillInIntent,
int flags,
- ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ TargetPresentationGetter presentationGetter) {
mSourceIntents.addAll(other.getAllSourceIntents());
mResolveInfo = other.mResolveInfo;
mIsSuspended = other.mIsSuspended;
@@ -116,7 +116,7 @@ public class DisplayResolveInfo implements TargetInfo {
mExtendedInfo = other.mExtendedInfo;
mResolvedIntent = new Intent(other.mResolvedIntent);
mResolvedIntent.fillIn(fillInIntent, flags);
- mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
+ mPresentationGetter = presentationGetter;
mDisplayIconHolder.setDisplayIcon(other.mDisplayIconHolder.getDisplayIcon());
}
@@ -128,7 +128,7 @@ public class DisplayResolveInfo implements TargetInfo {
mDisplayLabel = other.mDisplayLabel;
mExtendedInfo = other.mExtendedInfo;
mResolvedIntent = other.mResolvedIntent;
- mResolveInfoPresentationGetter = other.mResolveInfoPresentationGetter;
+ mPresentationGetter = other.mPresentationGetter;
mDisplayIconHolder.setDisplayIcon(other.mDisplayIconHolder.getDisplayIcon());
}
@@ -143,9 +143,9 @@ public class DisplayResolveInfo implements TargetInfo {
}
public CharSequence getDisplayLabel() {
- if (mDisplayLabel == null && mResolveInfoPresentationGetter != null) {
- mDisplayLabel = mResolveInfoPresentationGetter.getLabel();
- mExtendedInfo = mResolveInfoPresentationGetter.getSubLabel();
+ if (mDisplayLabel == null && mPresentationGetter != null) {
+ mDisplayLabel = mPresentationGetter.getLabel();
+ mExtendedInfo = mPresentationGetter.getSubLabel();
}
return mDisplayLabel;
}
@@ -169,7 +169,7 @@ public class DisplayResolveInfo implements TargetInfo {
@Override
public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
- return new DisplayResolveInfo(this, fillInIntent, flags, mResolveInfoPresentationGetter);
+ return new DisplayResolveInfo(this, fillInIntent, flags, mPresentationGetter);
}
@Override
diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
index 9f1dab77..97de97f5 100644
--- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
+++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
@@ -38,7 +38,6 @@ import android.util.Size;
import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
-import com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.NotSelectableTargetInfo;
import com.android.intentresolver.chooser.TargetInfo;
@@ -250,7 +249,7 @@ public class ChooserWrapperActivity
@Override
public DisplayResolveInfo createTestDisplayResolveInfo(Intent originalIntent, ResolveInfo pri,
CharSequence pLabel, CharSequence pInfo, Intent replacementIntent,
- @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ @Nullable TargetPresentationGetter resolveInfoPresentationGetter) {
return DisplayResolveInfo.newDisplayResolveInfo(
originalIntent,
pri,
diff --git a/java/tests/src/com/android/intentresolver/IChooserWrapper.java b/java/tests/src/com/android/intentresolver/IChooserWrapper.java
index 0d44e147..af897a47 100644
--- a/java/tests/src/com/android/intentresolver/IChooserWrapper.java
+++ b/java/tests/src/com/android/intentresolver/IChooserWrapper.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
-import com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import java.util.concurrent.Executor;
@@ -40,7 +39,7 @@ public interface IChooserWrapper {
UsageStatsManager getUsageStatsManager();
DisplayResolveInfo createTestDisplayResolveInfo(Intent originalIntent, ResolveInfo pri,
CharSequence pLabel, CharSequence pInfo, Intent replacementIntent,
- @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter);
+ @Nullable TargetPresentationGetter resolveInfoPresentationGetter);
UserHandle getCurrentUserHandle();
ChooserActivityLogger getChooserActivityLogger();
Executor getMainExecutor();
diff --git a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
index 07cbd6a4..62c16ff5 100644
--- a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
+++ b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
@@ -27,7 +27,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.android.intentresolver.MatcherUtils.first;
-import static com.android.intentresolver.ResolverDataProvider.createPackageManagerMockedInfo;
import static com.android.intentresolver.ResolverWrapperActivity.sOverrides;
import static org.hamcrest.CoreMatchers.allOf;
@@ -39,34 +38,25 @@ import static org.mockito.Mockito.when;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.fail;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
-import androidx.test.core.app.ActivityScenario;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.NoMatchingViewException;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
import com.android.intentresolver.ResolverActivity.ResolvedComponentInfo;
-import com.android.intentresolver.ResolverDataProvider.PackageManagerMockedInfo;
-import com.android.intentresolver.ResolverListAdapter.ActivityInfoPresentationGetter;
-import com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.intentresolver.widget.ResolverDrawerLayout;
+import com.android.internal.R;
import org.junit.Before;
import org.junit.Ignore;
@@ -364,50 +354,6 @@ public class ResolverActivityTest {
}
@Test
- public void getActivityLabelAndSubLabel() throws Exception {
- ActivityInfoPresentationGetter pg;
- PackageManagerMockedInfo info;
-
- info = createPackageManagerMockedInfo(false);
- pg = new ActivityInfoPresentationGetter(
- info.ctx, 0, info.activityInfo);
- assertThat("Label should match app label", pg.getLabel().equals(
- info.setAppLabel));
- assertThat("Sublabel should match activity label if set",
- pg.getSubLabel().equals(info.setActivityLabel));
-
- info = createPackageManagerMockedInfo(true);
- pg = new ActivityInfoPresentationGetter(
- info.ctx, 0, info.activityInfo);
- assertThat("With override permission label should match activity label if set",
- pg.getLabel().equals(info.setActivityLabel));
- assertThat("With override permission sublabel should be empty",
- TextUtils.isEmpty(pg.getSubLabel()));
- }
-
- @Test
- public void getResolveInfoLabelAndSubLabel() throws Exception {
- ResolveInfoPresentationGetter pg;
- PackageManagerMockedInfo info;
-
- info = createPackageManagerMockedInfo(false);
- pg = new ResolveInfoPresentationGetter(
- info.ctx, 0, info.resolveInfo);
- assertThat("Label should match app label", pg.getLabel().equals(
- info.setAppLabel));
- assertThat("Sublabel should match resolve info label if set",
- pg.getSubLabel().equals(info.setResolveInfoLabel));
-
- info = createPackageManagerMockedInfo(true);
- pg = new ResolveInfoPresentationGetter(
- info.ctx, 0, info.resolveInfo);
- assertThat("With override permission label should match activity label if set",
- pg.getLabel().equals(info.setActivityLabel));
- assertThat("With override permission the sublabel should be the resolve info label",
- pg.getSubLabel().equals(info.setResolveInfoLabel));
- }
-
- @Test
public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
Intent sendIntent = createSendImageIntent();
markWorkProfileUserAvailable();
diff --git a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
index 01d07639..fb928e09 100644
--- a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
+++ b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
@@ -93,11 +93,17 @@ public class ResolverDataProvider {
public String setResolveInfoLabel;
}
+ /** Create a {@link PackageManagerMockedInfo} with all distinct labels. */
static PackageManagerMockedInfo createPackageManagerMockedInfo(boolean hasOverridePermission) {
- final String appLabel = "app_label";
- final String activityLabel = "activity_label";
- final String resolveInfoLabel = "resolve_info_label";
+ return createPackageManagerMockedInfo(
+ hasOverridePermission, "app_label", "activity_label", "resolve_info_label");
+ }
+ static PackageManagerMockedInfo createPackageManagerMockedInfo(
+ boolean hasOverridePermission,
+ String appLabel,
+ String activityLabel,
+ String resolveInfoLabel) {
MockContext ctx = new MockContext() {
@Override
public PackageManager getPackageManager() {
diff --git a/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt b/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt
new file mode 100644
index 00000000..e62672a3
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 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
+
+import com.android.intentresolver.ResolverDataProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+/**
+ * Unit tests for the various implementations of {@link TargetPresentationGetter}.
+ * TODO: consider expanding to cover icon logic (not just labels/sublabels).
+ * TODO: these are conceptually "acceptance tests" that provide comprehensive coverage of the
+ * apparent variations in the legacy implementation. The tests probably don't have to be so
+ * exhaustive if we're able to impose a simpler design on the implementation.
+ */
+class TargetPresentationGetterTest {
+ fun makeResolveInfoPresentationGetter(
+ withSubstitutePermission: Boolean,
+ appLabel: String,
+ activityLabel: String,
+ resolveInfoLabel: String): TargetPresentationGetter {
+ val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo(
+ withSubstitutePermission, appLabel, activityLabel, resolveInfoLabel)
+ val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100)
+ return factory.makePresentationGetter(testPackageInfo.resolveInfo)
+ }
+
+ fun makeActivityInfoPresentationGetter(
+ withSubstitutePermission: Boolean,
+ appLabel: String?,
+ activityLabel: String?): TargetPresentationGetter {
+ val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo(
+ withSubstitutePermission, appLabel, activityLabel, "")
+ val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100)
+ return factory.makePresentationGetter(testPackageInfo.activityInfo)
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ false, "app_label", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ false, "app_label", "app_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, there's no logic to dedupe the labels.
+ // TODO: this matches our observations in the legacy code, but is it the right behavior? It
+ // seems like {@link ResolverListAdapter.ViewHolder#bindLabel()} has some logic to dedupe in
+ // the UI at least, but maybe that logic should be pulled back to the "presentation"?
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("app_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_nullRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(false, null, "activity_label")
+ assertThat(presentationGetter.getLabel()).isNull()
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_emptyRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(false, "", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(false, "app_label", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, empty sublabels are passed through as-is.
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("")
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ true, "app_label", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, the same ("activity") label is requested as both the label
+ // and sublabel, even though the other value ("app_label") was distinct. Thus this behaves the
+ // same as a dupe.
+ assertThat(presentationGetter.getSubLabel()).isEqualTo(null)
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ true, "app_label", "app_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // With the substitute permission, duped sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_nullRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(true, "app_label", null)
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // With the substitute permission, null inputs are a special case that produces null outputs
+ // (i.e., they're not simply passed-through from the inputs).
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_emptyRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(true, "app_label", "")
+ // Empty "labels" are taken as-is and (unlike nulls) don't prompt a fallback to the sublabel.
+ // Thus (as in the previous case with substitute permission & "distinct" labels), this is
+ // treated as a dupe.
+ assertThat(presentationGetter.getLabel()).isEqualTo("")
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(true, "", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, empty sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testResolveInfoLabels_noSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ false, "app_label", "activity_label", "resolve_info_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label")
+ }
+
+ @Test
+ fun testResolveInfoLabels_noSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ false, "app_label", "activity_label", "app_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, there's no logic to dedupe the labels.
+ // TODO: this matches our observations in the legacy code, but is it the right behavior? It
+ // seems like {@link ResolverListAdapter.ViewHolder#bindLabel()} has some logic to dedupe in
+ // the UI at least, but maybe that logic should be pulled back to the "presentation"?
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("app_label")
+ }
+
+ @Test
+ fun testResolveInfoLabels_noSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ false, "app_label", "activity_label", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, empty sublabels are passed through as-is.
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("")
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "activity_label", "resolve_info_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label")
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "activity_label", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, duped sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "activity_label", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, empty sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_emptyRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("")
+ // With the substitute permission, empty sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+}