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