• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 android.content.pm;
18 
19 import android.annotation.FloatRange;
20 import android.annotation.NonNull;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.pm.PackageManager.NameNotFoundException;
24 import android.content.res.Resources;
25 import android.graphics.Paint;
26 import android.graphics.drawable.Drawable;
27 import android.icu.text.UnicodeSet;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.text.TextUtils;
31 import android.util.DisplayMetrics;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.Objects;
36 
37 /**
38  * A representation of an activity that can belong to this user or a managed
39  * profile associated with this user. It can be used to query the label, icon
40  * and badged icon for the activity.
41  */
42 public class LauncherActivityInfo {
43     private final PackageManager mPm;
44     private final LauncherActivityInfoInternal mInternal;
45 
46     private static final UnicodeSet INVISIBLE_CHARACTERS =
47             new UnicodeSet("[[:White_Space:][:Default_Ignorable_Code_Point:][:gc=Cc:]]",
48                     /* ignoreWhitespace= */ false).freeze();
49     // Only allow 3 consecutive invisible characters in the prefix of the string.
50     private static final int PREFIX_CONSECUTIVE_INVISIBLE_CHARACTERS_MAXIMUM = 3;
51 
52     /**
53      * Create a launchable activity object for a given ResolveInfo and user.
54      *
55      * @param context The context for fetching resources.
56 
57      */
LauncherActivityInfo(Context context, LauncherActivityInfoInternal internal)58     LauncherActivityInfo(Context context, LauncherActivityInfoInternal internal) {
59         mPm = context.getPackageManager();
60         mInternal = internal;
61     }
62 
63     /**
64      * Returns the component name of this activity.
65      *
66      * @return ComponentName of the activity
67      */
getComponentName()68     public ComponentName getComponentName() {
69         return mInternal.getComponentName();
70     }
71 
72     /**
73      * Returns the user handle of the user profile that this activity belongs to. In order to
74      * persist the identity of the profile, do not store the UserHandle. Instead retrieve its
75      * serial number from UserManager. You can convert the serial number back to a UserHandle
76      * for later use.
77      *
78      * @see UserManager#getSerialNumberForUser(UserHandle)
79      * @see UserManager#getUserForSerialNumber(long)
80      *
81      * @return The UserHandle of the profile.
82      */
getUser()83     public UserHandle getUser() {
84         return mInternal.getUser();
85     }
86 
87     /**
88      * Retrieves the label for the activity.
89      *
90      * @return The label for the activity.
91      */
getLabel()92     public CharSequence getLabel() {
93         if (!Flags.lightweightInvisibleLabelDetection()) {
94             // TODO: Go through LauncherAppsService
95             return getActivityInfo().loadLabel(mPm);
96         }
97 
98         CharSequence label = getActivityInfo().loadLabel(mPm).toString().trim();
99         // If the activity label is visible to the user, return the original activity label
100         if (isVisible(label)) {
101             return label;
102         }
103 
104         // Use application label instead
105         label = getApplicationInfo().loadLabel(mPm).toString().trim();
106         // If the application label is visible to the user, return the original application label
107         if (isVisible(label)) {
108             return label;
109         }
110 
111         // Use package name instead
112         return getComponentName().getPackageName();
113     }
114 
115     /**
116      * @return Package loading progress, range between [0, 1].
117      */
getLoadingProgress()118     public @FloatRange(from = 0.0, to = 1.0) float getLoadingProgress() {
119         return mInternal.getIncrementalStatesInfo().getProgress();
120     }
121 
122     /**
123      * Returns the icon for this activity, without any badging for the profile.
124      * @param density The preferred density of the icon, zero for default density. Use
125      * density DPI values from {@link DisplayMetrics}.
126      * @see #getBadgedIcon(int)
127      * @see DisplayMetrics
128      * @return The drawable associated with the activity.
129      */
getIcon(int density)130     public Drawable getIcon(int density) {
131         // TODO: Go through LauncherAppsService
132         final int iconRes = getActivityInfo().getIconResource();
133         Drawable icon = null;
134         // Get the preferred density icon from the app's resources
135         if (density != 0 && iconRes != 0) {
136             try {
137                 final Resources resources = mPm.getResourcesForApplication(
138                         getActivityInfo().applicationInfo);
139                 icon = resources.getDrawableForDensity(iconRes, density);
140             } catch (NameNotFoundException | Resources.NotFoundException exc) {
141             }
142         }
143         // Get the default density icon
144         if (icon == null) {
145             icon = getActivityInfo().loadIcon(mPm);
146         }
147         return icon;
148     }
149 
150     /**
151      * Returns the application flags from the ApplicationInfo of the activity.
152      *
153      * @return Application flags
154      * @hide remove before shipping
155      */
getApplicationFlags()156     public int getApplicationFlags() {
157         return getActivityInfo().flags;
158     }
159 
160     /**
161      * Returns the ActivityInfo of the activity.
162      *
163      * @return Activity Info
164      */
165     @NonNull
getActivityInfo()166     public ActivityInfo getActivityInfo() {
167         return mInternal.getActivityInfo();
168     }
169 
170     /**
171      * Returns the application info for the application this activity belongs to.
172      * @return
173      */
getApplicationInfo()174     public ApplicationInfo getApplicationInfo() {
175         return getActivityInfo().applicationInfo;
176     }
177 
178     /**
179      * Returns the time at which the package was first installed.
180      *
181      * @return The time of installation of the package, in milliseconds.
182      */
getFirstInstallTime()183     public long getFirstInstallTime() {
184         try {
185             // TODO: Go through LauncherAppsService
186             return mPm.getPackageInfo(getActivityInfo().packageName,
187                     PackageManager.MATCH_UNINSTALLED_PACKAGES).firstInstallTime;
188         } catch (NameNotFoundException nnfe) {
189             // Sorry, can't find package
190             return 0;
191         }
192     }
193 
194     /**
195      * Returns the name for the activity from  android:name in the manifest.
196      * @return the name from android:name for the activity.
197      */
getName()198     public String getName() {
199         return getActivityInfo().name;
200     }
201 
202     /**
203      * Returns the activity icon with badging appropriate for the profile.
204      * @param density Optional density for the icon, or 0 to use the default density. Use
205      * {@link DisplayMetrics} for DPI values.
206      * @see DisplayMetrics
207      * @return A badged icon for the activity.
208      */
getBadgedIcon(int density)209     public Drawable getBadgedIcon(int density) {
210         Drawable originalIcon = getIcon(density);
211 
212         return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser());
213     }
214 
215     /**
216      * Returns whether this activity supports (and can be launched in) multiple instances.
217      * @hide
218      */
supportsMultiInstance()219     public boolean supportsMultiInstance() {
220         return mInternal.supportsMultiInstance();
221     }
222 
223     /**
224      * Check whether the {@code sequence} is visible to the user or not.
225      * <p>
226      * Return {@code false} when one of these conditions are satisfied:
227      * 1. The {@code sequence} starts with at least consecutive three invisible characters.
228      * 2. The sequence is composed of the invisible characters and non-glyph characters.
229      * <p>
230      * Invisible character is one of the Default_Ignorable_Code_Point in
231      * <a href="
232      * https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt">
233      * DerivedCoreProperties.txt</a>, the White_Space in <a href=
234      * "https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt">PropList.txt
235      * </a> or category Cc.
236      * <p>
237      * Non-glyph character means the character is not supported in the current system font.
238      * {@link android.graphics.Paint#hasGlyph(String)}
239      * <p>
240      *
241      * @hide
242      */
243     @VisibleForTesting
isVisible(@onNull CharSequence sequence)244     public static boolean isVisible(@NonNull CharSequence sequence) {
245         Objects.requireNonNull(sequence);
246         if (TextUtils.isEmpty(sequence)) {
247             return false;
248         }
249 
250         final Paint paint = new Paint();
251         int invisibleCharCount = 0;
252         int notSupportedCharCount = 0;
253         final int[] codePoints = sequence.codePoints().toArray();
254         for (int i = 0, length = codePoints.length; i < length; i++) {
255             String ch = new String(new int[]{codePoints[i]}, /* offset= */ 0, /* count= */ 1);
256 
257             // The check steps:
258             // 1. If the character is contained in INVISIBLE_CHARACTERS, invisibleCharCount++.
259             //    1.1 Check whether the invisibleCharCount is larger or equal to
260             //        PREFIX_INVISIBLE_CHARACTERS_MAXIMUM when notSupportedCharCount is zero.
261             //        It means that there are three consecutive invisible characters at the
262             //        start of the string, return false.
263             //    Otherwise, continue.
264             // 2. If the character is not supported on the system:
265             //    notSupportedCharCount++, continue
266             // 3. If it does not continue or return on the above two cases, it means the
267             //    character is visible and supported on the system, break.
268             // After going through the whole string, if the sum of invisibleCharCount
269             // and notSupportedCharCount is smaller than the length of the string, it
270             // means the string has the other visible characters, return true.
271             // Otherwise, return false.
272             if (INVISIBLE_CHARACTERS.contains(ch)) {
273                 invisibleCharCount++;
274                 // If there are three successive invisible characters at the start of the
275                 // string, it is hard to visible to the user.
276                 if (notSupportedCharCount == 0
277                         && invisibleCharCount >= PREFIX_CONSECUTIVE_INVISIBLE_CHARACTERS_MAXIMUM) {
278                     return false;
279                 }
280                 continue;
281             }
282 
283             // The character is not supported on the system, but it may not be an invisible
284             // character. E.g. tofu (a rectangle).
285             if (!paint.hasGlyph(ch)) {
286                 notSupportedCharCount++;
287                 continue;
288             }
289             // The character is visible and supported on the system, break the for loop
290             break;
291         }
292 
293         return (invisibleCharCount + notSupportedCharCount < codePoints.length);
294     }
295 }
296