• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.intentresolver;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.pm.ActivityInfo;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.content.res.Resources;
26 import android.graphics.Bitmap;
27 import android.graphics.drawable.BitmapDrawable;
28 import android.graphics.drawable.Drawable;
29 import android.os.UserHandle;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 /**
34  * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application icon
35  * and label over any IntentFilter or Activity icon to increase user understanding, with an
36  * exception for applications that hold the right permission. Always attempts to use available
37  * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
38  * Strings to strip creative formatting.
39  *
40  * Use one of the {@link TargetPresentationGetter#Factory} methods to create an instance of the
41  * appropriate concrete type.
42  *
43  * TODO: once this component (and its tests) are merged, it should be possible to refactor and
44  * vastly simplify by precomputing conditional logic at initialization.
45  */
46 public abstract class TargetPresentationGetter {
47     private static final String TAG = "ResolverListAdapter";
48 
49     /** Helper to build appropriate type-specific {@link TargetPresentationGetter} instances. */
50     public static class Factory {
51         private final Context mContext;
52         private final int mIconDpi;
53 
Factory(Context context, int iconDpi)54         public Factory(Context context, int iconDpi) {
55             mContext = context;
56             mIconDpi = iconDpi;
57         }
58 
59         /** Make a {@link TargetPresentationGetter} for an {@link ActivityInfo}. */
makePresentationGetter(ActivityInfo activityInfo)60         public TargetPresentationGetter makePresentationGetter(ActivityInfo activityInfo) {
61             return new ActivityInfoPresentationGetter(mContext, mIconDpi, activityInfo);
62         }
63 
64         /** Make a {@link TargetPresentationGetter} for a {@link ResolveInfo}. */
makePresentationGetter(ResolveInfo resolveInfo)65         public TargetPresentationGetter makePresentationGetter(ResolveInfo resolveInfo) {
66             return new ResolveInfoPresentationGetter(mContext, mIconDpi, resolveInfo);
67         }
68     }
69 
70     @Nullable
getIconSubstituteInternal()71     protected abstract Drawable getIconSubstituteInternal();
72 
73     @Nullable
getAppSubLabelInternal()74     protected abstract String getAppSubLabelInternal();
75 
76     @Nullable
getAppLabelForSubstitutePermission()77     protected abstract String getAppLabelForSubstitutePermission();
78 
79     private Context mContext;
80     private final int mIconDpi;
81     private final boolean mHasSubstitutePermission;
82     private final ApplicationInfo mAppInfo;
83 
84     protected PackageManager mPm;
85 
86     /**
87      * Retrieve the image that should be displayed as the icon when this target is presented to the
88      * specified {@code userHandle}.
89      */
getIcon(UserHandle userHandle)90     public Drawable getIcon(UserHandle userHandle) {
91         return new BitmapDrawable(mContext.getResources(), getIconBitmap(userHandle));
92     }
93 
94     /**
95      * Retrieve the image that should be displayed as the icon when this target is presented to the
96      * specified {@code userHandle}.
97      */
getIconBitmap(@ullable UserHandle userHandle)98     public Bitmap getIconBitmap(@Nullable UserHandle userHandle) {
99         Drawable drawable = null;
100         if (mHasSubstitutePermission) {
101             drawable = getIconSubstituteInternal();
102         }
103 
104         if (drawable == null) {
105             try {
106                 if (mAppInfo.icon != 0) {
107                     drawable = loadIconFromResource(
108                             mPm.getResourcesForApplication(mAppInfo), mAppInfo.icon);
109                 }
110             } catch (PackageManager.NameNotFoundException ignore) { }
111         }
112 
113         // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
114         if (drawable == null) {
115             drawable = mAppInfo.loadIcon(mPm);
116         }
117 
118         SimpleIconFactory iconFactory = SimpleIconFactory.obtain(mContext);
119         Bitmap icon = iconFactory.createUserBadgedIconBitmap(drawable, userHandle);
120         iconFactory.recycle();
121 
122         return icon;
123     }
124 
125     /** Get the label to display for the target. */
getLabel()126     public String getLabel() {
127         String label = null;
128         // Apps with the substitute permission will always show the activity label as the app label
129         // if provided.
130         if (mHasSubstitutePermission) {
131             label = getAppLabelForSubstitutePermission();
132         }
133 
134         if (label == null) {
135             label = (String) mAppInfo.loadLabel(mPm);
136         }
137 
138         return label;
139     }
140 
141     /**
142      * Get the sublabel to display for the target. Clients are responsible for deduping their
143      * presentation if this returns the same value as {@link #getLabel()}.
144      * TODO: this class should take responsibility for that deduping internally so it's an
145      * authoritative record of exactly the content that should be presented.
146      */
getSubLabel()147     public String getSubLabel() {
148         // Apps with the substitute permission will always show the resolve info label as the
149         // sublabel if provided
150         if (mHasSubstitutePermission) {
151             String appSubLabel = getAppSubLabelInternal();
152             // Use the resolve info label as sublabel if it is set
153             if (!TextUtils.isEmpty(appSubLabel) && !TextUtils.equals(appSubLabel, getLabel())) {
154                 return appSubLabel;
155             }
156             return null;
157         }
158         return getAppSubLabelInternal();
159     }
160 
loadLabelFromResource(Resources res, int resId)161     protected String loadLabelFromResource(Resources res, int resId) {
162         return res.getString(resId);
163     }
164 
165     @Nullable
loadIconFromResource(Resources res, int resId)166     protected Drawable loadIconFromResource(Resources res, int resId) {
167         return res.getDrawableForDensity(resId, mIconDpi);
168     }
169 
TargetPresentationGetter(Context context, int iconDpi, ApplicationInfo appInfo)170     private TargetPresentationGetter(Context context, int iconDpi, ApplicationInfo appInfo) {
171         mContext = context;
172         mPm = context.getPackageManager();
173         mAppInfo = appInfo;
174         mIconDpi = iconDpi;
175         mHasSubstitutePermission = (PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
176                 android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
177                 mAppInfo.packageName));
178     }
179 
180     /** Loads the icon and label for the provided ResolveInfo. */
181     private static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
182         private final ResolveInfo mResolveInfo;
183 
ResolveInfoPresentationGetter( Context context, int iconDpi, ResolveInfo resolveInfo)184         ResolveInfoPresentationGetter(
185                 Context context, int iconDpi, ResolveInfo resolveInfo) {
186             super(context, iconDpi, resolveInfo.activityInfo);
187             mResolveInfo = resolveInfo;
188         }
189 
190         @Override
getIconSubstituteInternal()191         protected Drawable getIconSubstituteInternal() {
192             Drawable drawable = null;
193             try {
194                 // Do not use ResolveInfo#getIconResource() as it defaults to the app
195                 if (mResolveInfo.resolvePackageName != null && mResolveInfo.icon != 0) {
196                     drawable = loadIconFromResource(
197                             mPm.getResourcesForApplication(mResolveInfo.resolvePackageName),
198                             mResolveInfo.icon);
199                 }
200             } catch (PackageManager.NameNotFoundException e) {
201                 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
202                         + "couldn't find resources for package", e);
203             }
204 
205             // Fall back to ActivityInfo if no icon is found via ResolveInfo
206             if (drawable == null) {
207                 drawable = super.getIconSubstituteInternal();
208             }
209 
210             return drawable;
211         }
212 
213         @Override
getAppSubLabelInternal()214         protected String getAppSubLabelInternal() {
215             // Will default to app name if no intent filter or activity label set, make sure to
216             // check if subLabel matches label before final display
217             return mResolveInfo.loadLabel(mPm).toString();
218         }
219 
220         @Override
getAppLabelForSubstitutePermission()221         protected String getAppLabelForSubstitutePermission() {
222             // Will default to app name if no activity label set
223             return mResolveInfo.getComponentInfo().loadLabel(mPm).toString();
224         }
225     }
226 
227     /** Loads the icon and label for the provided {@link ActivityInfo}. */
228     private static class ActivityInfoPresentationGetter extends TargetPresentationGetter {
229         private final ActivityInfo mActivityInfo;
230 
ActivityInfoPresentationGetter( Context context, int iconDpi, ActivityInfo activityInfo)231         ActivityInfoPresentationGetter(
232                 Context context, int iconDpi, ActivityInfo activityInfo) {
233             super(context, iconDpi, activityInfo.applicationInfo);
234             mActivityInfo = activityInfo;
235         }
236 
237         @Override
getIconSubstituteInternal()238         protected Drawable getIconSubstituteInternal() {
239             Drawable drawable = null;
240             try {
241                 // Do not use ActivityInfo#getIconResource() as it defaults to the app
242                 if (mActivityInfo.icon != 0) {
243                     drawable = loadIconFromResource(
244                             mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
245                             mActivityInfo.icon);
246                 }
247             } catch (PackageManager.NameNotFoundException e) {
248                 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
249                         + "couldn't find resources for package", e);
250             }
251 
252             return drawable;
253         }
254 
255         @Override
getAppSubLabelInternal()256         protected String getAppSubLabelInternal() {
257             // Will default to app name if no activity label set, make sure to check if subLabel
258             // matches label before final display
259             return (String) mActivityInfo.loadLabel(mPm);
260         }
261 
262         @Override
getAppLabelForSubstitutePermission()263         protected String getAppLabelForSubstitutePermission() {
264             return getAppSubLabelInternal();
265         }
266     }
267 }
268