• 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.content.pm.ActivityInfo;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageManager;
22 import android.content.pm.ResolveInfo;
23 import android.content.res.Resources;
24 import android.graphics.Bitmap;
25 import android.graphics.drawable.Drawable;
26 import android.os.UserHandle;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import androidx.annotation.Nullable;
31 
32 import javax.inject.Provider;
33 
34 /**
35  * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application icon
36  * and label over any IntentFilter or Activity icon to increase user understanding, with an
37  * exception for applications that hold the right permission. Always attempts to use available
38  * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
39  * Strings to strip creative formatting.
40  *
41  * Use one of the {@link TargetPresentationGetter.Factory} methods to create an instance of the
42  * appropriate concrete type.
43  *
44  * TODO: once this component (and its tests) are merged, it should be possible to refactor and
45  * vastly simplify by precomputing conditional logic at initialization.
46  */
47 public abstract class TargetPresentationGetter {
48     private static final String TAG = "ResolverListAdapter";
49 
50     /** Helper to build appropriate type-specific {@link TargetPresentationGetter} instances. */
51     public static class Factory {
52         private final Provider<SimpleIconFactory> mIconFactoryProvider;
53         private final PackageManager mPackageManager;
54         private final int mIconDpi;
55 
Factory( Provider<SimpleIconFactory> iconfactoryProvider, PackageManager packageManager, int iconDpi)56         public Factory(
57                 Provider<SimpleIconFactory> iconfactoryProvider,
58                 PackageManager packageManager,
59                 int iconDpi) {
60             mIconFactoryProvider = iconfactoryProvider;
61             mPackageManager = packageManager;
62             mIconDpi = iconDpi;
63         }
64 
65         /** Make a {@link TargetPresentationGetter} for an {@link ActivityInfo}. */
makePresentationGetter(ActivityInfo activityInfo)66         public TargetPresentationGetter makePresentationGetter(ActivityInfo activityInfo) {
67             return new ActivityInfoPresentationGetter(
68                     mIconFactoryProvider, mPackageManager, mIconDpi, activityInfo);
69         }
70 
71         /** Make a {@link TargetPresentationGetter} for a {@link ResolveInfo}. */
makePresentationGetter(ResolveInfo resolveInfo)72         public TargetPresentationGetter makePresentationGetter(ResolveInfo resolveInfo) {
73             return new ResolveInfoPresentationGetter(
74                     mIconFactoryProvider, mPackageManager, mIconDpi, resolveInfo);
75         }
76     }
77 
78     @Nullable
getIconSubstituteInternal()79     protected abstract Drawable getIconSubstituteInternal();
80 
81     @Nullable
getAppSubLabelInternal()82     protected abstract String getAppSubLabelInternal();
83 
84     @Nullable
getAppLabelForSubstitutePermission()85     protected abstract String getAppLabelForSubstitutePermission();
86 
87     private final Provider<SimpleIconFactory> mIconFactoryProvider;
88     private final int mIconDpi;
89     private final boolean mHasSubstitutePermission;
90     private final ApplicationInfo mAppInfo;
91 
92     protected PackageManager mPm;
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         Bitmap icon;
119         try (SimpleIconFactory iconFactory = mIconFactoryProvider.get()) {
120             icon = iconFactory.createUserBadgedIconBitmap(drawable, userHandle);
121         }
122 
123         return icon;
124     }
125 
126     /** Get the label to display for the target. */
getLabel()127     public String getLabel() {
128         String label = null;
129         // Apps with the substitute permission will always show the activity label as the app label
130         // if provided.
131         if (mHasSubstitutePermission) {
132             label = getAppLabelForSubstitutePermission();
133         }
134 
135         if (label == null) {
136             label = (String) mAppInfo.loadLabel(mPm);
137         }
138 
139         return label;
140     }
141 
142     /**
143      * Get the sublabel to display for the target. Clients are responsible for deduping their
144      * presentation if this returns the same value as {@link #getLabel()}.
145      * TODO: this class should take responsibility for that deduping internally so it's an
146      * authoritative record of exactly the content that should be presented.
147      */
getSubLabel()148     public String getSubLabel() {
149         // Apps with the substitute permission will always show the resolve info label as the
150         // sublabel if provided
151         if (mHasSubstitutePermission) {
152             String appSubLabel = getAppSubLabelInternal();
153             // Use the resolve info label as sublabel if it is set
154             if (!TextUtils.isEmpty(appSubLabel) && !TextUtils.equals(appSubLabel, getLabel())) {
155                 return appSubLabel;
156             }
157             return null;
158         }
159         return getAppSubLabelInternal();
160     }
161 
loadLabelFromResource(Resources res, int resId)162     protected String loadLabelFromResource(Resources res, int resId) {
163         return res.getString(resId);
164     }
165 
166     @Nullable
loadIconFromResource(Resources res, int resId)167     protected Drawable loadIconFromResource(Resources res, int resId) {
168         return res.getDrawableForDensity(resId, mIconDpi);
169     }
170 
TargetPresentationGetter( Provider<SimpleIconFactory> iconfactoryProvider, PackageManager packageManager, int iconDpi, ApplicationInfo appInfo)171     private TargetPresentationGetter(
172             Provider<SimpleIconFactory> iconfactoryProvider,
173             PackageManager packageManager,
174             int iconDpi,
175             ApplicationInfo appInfo) {
176         mIconFactoryProvider = iconfactoryProvider;
177         mPm = packageManager;
178         mAppInfo = appInfo;
179         mIconDpi = iconDpi;
180         mHasSubstitutePermission = (PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
181                 android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
182                 mAppInfo.packageName));
183     }
184 
185     /** Loads the icon and label for the provided ResolveInfo. */
186     private static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
187         private final ResolveInfo mResolveInfo;
188 
ResolveInfoPresentationGetter( Provider<SimpleIconFactory> iconfactoryProvider, PackageManager packageManager, int iconDpi, ResolveInfo resolveInfo)189         ResolveInfoPresentationGetter(
190                 Provider<SimpleIconFactory> iconfactoryProvider,
191                 PackageManager packageManager,
192                 int iconDpi,
193                 ResolveInfo resolveInfo) {
194             super(iconfactoryProvider, packageManager, iconDpi, resolveInfo.activityInfo);
195             mResolveInfo = resolveInfo;
196         }
197 
198         @Override
getIconSubstituteInternal()199         protected Drawable getIconSubstituteInternal() {
200             Drawable drawable = null;
201             try {
202                 // Do not use ResolveInfo#getIconResource() as it defaults to the app
203                 if (mResolveInfo.resolvePackageName != null && mResolveInfo.icon != 0) {
204                     drawable = loadIconFromResource(
205                             mPm.getResourcesForApplication(mResolveInfo.resolvePackageName),
206                             mResolveInfo.icon);
207                 }
208             } catch (PackageManager.NameNotFoundException e) {
209                 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
210                         + "couldn't find resources for package", e);
211             }
212 
213             // Fall back to ActivityInfo if no icon is found via ResolveInfo
214             if (drawable == null) {
215                 drawable = super.getIconSubstituteInternal();
216             }
217 
218             return drawable;
219         }
220 
221         @Override
getAppSubLabelInternal()222         protected String getAppSubLabelInternal() {
223             // Will default to app name if no intent filter or activity label set, make sure to
224             // check if subLabel matches label before final display
225             return mResolveInfo.loadLabel(mPm).toString();
226         }
227 
228         @Override
getAppLabelForSubstitutePermission()229         protected String getAppLabelForSubstitutePermission() {
230             // Will default to app name if no activity label set
231             return mResolveInfo.getComponentInfo().loadLabel(mPm).toString();
232         }
233     }
234 
235     /** Loads the icon and label for the provided {@link ActivityInfo}. */
236     private static class ActivityInfoPresentationGetter extends TargetPresentationGetter {
237         private final ActivityInfo mActivityInfo;
238 
ActivityInfoPresentationGetter( Provider<SimpleIconFactory> iconfactoryProvider, PackageManager packageManager, int iconDpi, ActivityInfo activityInfo)239         ActivityInfoPresentationGetter(
240                 Provider<SimpleIconFactory> iconfactoryProvider,
241                 PackageManager packageManager,
242                 int iconDpi,
243                 ActivityInfo activityInfo) {
244             super(iconfactoryProvider, packageManager, iconDpi, activityInfo.applicationInfo);
245             mActivityInfo = activityInfo;
246         }
247 
248         @Override
getIconSubstituteInternal()249         protected Drawable getIconSubstituteInternal() {
250             Drawable drawable = null;
251             try {
252                 // Do not use ActivityInfo#getIconResource() as it defaults to the app
253                 if (mActivityInfo.icon != 0) {
254                     drawable = loadIconFromResource(
255                             mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
256                             mActivityInfo.icon);
257                 }
258             } catch (PackageManager.NameNotFoundException e) {
259                 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
260                         + "couldn't find resources for package", e);
261             }
262 
263             return drawable;
264         }
265 
266         @Override
getAppSubLabelInternal()267         protected String getAppSubLabelInternal() {
268             // Will default to app name if no activity label set, make sure to check if subLabel
269             // matches label before final display
270             return (String) mActivityInfo.loadLabel(mPm);
271         }
272 
273         @Override
getAppLabelForSubstitutePermission()274         protected String getAppLabelForSubstitutePermission() {
275             return getAppSubLabelInternal();
276         }
277     }
278 }
279