blob: f8b36566c614046653911cdffb6306d714afb46f [file] [log] [blame]
/*
* 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();
}
}
}