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 TRIMMABLE_CHARACTERS = 47 new UnicodeSet("[[:White_Space:][:Default_Ignorable_Code_Point:][:gc=Cc:]]", 48 /* ignoreWhitespace= */ false).freeze(); 49 50 /** 51 * Create a launchable activity object for a given ResolveInfo and user. 52 * 53 * @param context The context for fetching resources. 54 55 */ LauncherActivityInfo(Context context, LauncherActivityInfoInternal internal)56 LauncherActivityInfo(Context context, LauncherActivityInfoInternal internal) { 57 mPm = context.getPackageManager(); 58 mInternal = internal; 59 } 60 61 /** 62 * Returns the component name of this activity. 63 * 64 * @return ComponentName of the activity 65 */ getComponentName()66 public ComponentName getComponentName() { 67 return mInternal.getComponentName(); 68 } 69 70 /** 71 * Returns the user handle of the user profile that this activity belongs to. In order to 72 * persist the identity of the profile, do not store the UserHandle. Instead retrieve its 73 * serial number from UserManager. You can convert the serial number back to a UserHandle 74 * for later use. 75 * 76 * @see UserManager#getSerialNumberForUser(UserHandle) 77 * @see UserManager#getUserForSerialNumber(long) 78 * 79 * @return The UserHandle of the profile. 80 */ getUser()81 public UserHandle getUser() { 82 return mInternal.getUser(); 83 } 84 85 /** 86 * Retrieves the label for the activity. 87 * 88 * @return The label for the activity. 89 */ getLabel()90 public CharSequence getLabel() { 91 if (!Flags.lightweightInvisibleLabelDetection()) { 92 // TODO: Go through LauncherAppsService 93 return getActivityInfo().loadLabel(mPm); 94 } 95 96 CharSequence label = trim(getActivityInfo().loadLabel(mPm)); 97 // If the trimmed label is empty, use application's label instead 98 if (TextUtils.isEmpty(label)) { 99 label = trim(getApplicationInfo().loadLabel(mPm)); 100 // If the trimmed label is still empty, use package name instead 101 if (TextUtils.isEmpty(label)) { 102 label = getComponentName().getPackageName(); 103 } 104 } 105 // TODO: Go through LauncherAppsService 106 return label; 107 } 108 109 /** 110 * @return Package loading progress, range between [0, 1]. 111 */ getLoadingProgress()112 public @FloatRange(from = 0.0, to = 1.0) float getLoadingProgress() { 113 return mInternal.getIncrementalStatesInfo().getProgress(); 114 } 115 116 /** 117 * Returns the icon for this activity, without any badging for the profile. 118 * @param density The preferred density of the icon, zero for default density. Use 119 * density DPI values from {@link DisplayMetrics}. 120 * @see #getBadgedIcon(int) 121 * @see DisplayMetrics 122 * @return The drawable associated with the activity. 123 */ getIcon(int density)124 public Drawable getIcon(int density) { 125 // TODO: Go through LauncherAppsService 126 final int iconRes = getActivityInfo().getIconResource(); 127 Drawable icon = null; 128 // Get the preferred density icon from the app's resources 129 if (density != 0 && iconRes != 0) { 130 try { 131 final Resources resources = mPm.getResourcesForApplication( 132 getActivityInfo().applicationInfo); 133 icon = resources.getDrawableForDensity(iconRes, density); 134 } catch (NameNotFoundException | Resources.NotFoundException exc) { 135 } 136 } 137 // Get the default density icon 138 if (icon == null) { 139 icon = getActivityInfo().loadIcon(mPm); 140 } 141 return icon; 142 } 143 144 /** 145 * Returns the application flags from the ApplicationInfo of the activity. 146 * 147 * @return Application flags 148 * @hide remove before shipping 149 */ getApplicationFlags()150 public int getApplicationFlags() { 151 return getActivityInfo().flags; 152 } 153 154 /** 155 * Returns the ActivityInfo of the activity. 156 * 157 * @return Activity Info 158 */ 159 @NonNull getActivityInfo()160 public ActivityInfo getActivityInfo() { 161 return mInternal.getActivityInfo(); 162 } 163 164 /** 165 * Returns the application info for the application this activity belongs to. 166 * @return 167 */ getApplicationInfo()168 public ApplicationInfo getApplicationInfo() { 169 return getActivityInfo().applicationInfo; 170 } 171 172 /** 173 * Returns the time at which the package was first installed. 174 * 175 * @return The time of installation of the package, in milliseconds. 176 */ getFirstInstallTime()177 public long getFirstInstallTime() { 178 try { 179 // TODO: Go through LauncherAppsService 180 return mPm.getPackageInfo(getActivityInfo().packageName, 181 PackageManager.MATCH_UNINSTALLED_PACKAGES).firstInstallTime; 182 } catch (NameNotFoundException nnfe) { 183 // Sorry, can't find package 184 return 0; 185 } 186 } 187 188 /** 189 * Returns the name for the activity from android:name in the manifest. 190 * @return the name from android:name for the activity. 191 */ getName()192 public String getName() { 193 return getActivityInfo().name; 194 } 195 196 /** 197 * Returns the activity icon with badging appropriate for the profile. 198 * @param density Optional density for the icon, or 0 to use the default density. Use 199 * {@link DisplayMetrics} for DPI values. 200 * @see DisplayMetrics 201 * @return A badged icon for the activity. 202 */ getBadgedIcon(int density)203 public Drawable getBadgedIcon(int density) { 204 Drawable originalIcon = getIcon(density); 205 206 return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser()); 207 } 208 209 /** 210 * If the {@code ch} is trimmable, return {@code true}. Otherwise, return 211 * {@code false}. If the count of the code points of {@code ch} doesn't 212 * equal 1, return {@code false}. 213 * <p> 214 * There are two types of the trimmable characters. 215 * 1. The character is one of the Default_Ignorable_Code_Point in 216 * <a href=" 217 * https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt"> 218 * DerivedCoreProperties.txt</a>, the White_Space in <a href= 219 * "https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt">PropList.txt 220 * </a> or category Cc. 221 * <p> 222 * 2. The character is not supported in the current system font. 223 * {@link android.graphics.Paint#hasGlyph(String)} 224 * <p> 225 * 226 */ isTrimmable(@onNull Paint paint, @NonNull CharSequence ch)227 private static boolean isTrimmable(@NonNull Paint paint, @NonNull CharSequence ch) { 228 Objects.requireNonNull(paint); 229 Objects.requireNonNull(ch); 230 231 // if ch is empty or it is not a character (i,e, the count of code 232 // point doesn't equal one), return false 233 if (TextUtils.isEmpty(ch) 234 || Character.codePointCount(ch, /* beginIndex= */ 0, ch.length()) != 1) { 235 return false; 236 } 237 238 // Return true for the cases as below: 239 // 1. The character is in the TRIMMABLE_CHARACTERS set 240 // 2. The character is not supported in the system font 241 return TRIMMABLE_CHARACTERS.contains(ch) || !paint.hasGlyph(ch.toString()); 242 } 243 244 /** 245 * If the {@code sequence} has some leading trimmable characters, creates a new copy 246 * and removes the trimmable characters from the copy. Otherwise the given 247 * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)} 248 * to determine whether the character is trimmable or not. 249 * 250 * @return the trimmed string or the original string that has no 251 * leading trimmable characters. 252 * @see #isTrimmable(Paint, CharSequence) 253 * @see #trim(CharSequence) 254 * @see #trimEnd(CharSequence) 255 * 256 * @hide 257 */ 258 @VisibleForTesting 259 @NonNull trimStart(@onNull CharSequence sequence)260 public static CharSequence trimStart(@NonNull CharSequence sequence) { 261 Objects.requireNonNull(sequence); 262 263 if (TextUtils.isEmpty(sequence)) { 264 return sequence; 265 } 266 267 final Paint paint = new Paint(); 268 int trimCount = 0; 269 final int[] codePoints = sequence.codePoints().toArray(); 270 for (int i = 0, length = codePoints.length; i < length; i++) { 271 String ch = new String(new int[]{codePoints[i]}, /* offset= */ 0, /* count= */ 1); 272 if (!isTrimmable(paint, ch)) { 273 break; 274 } 275 trimCount += ch.length(); 276 } 277 if (trimCount == 0) { 278 return sequence; 279 } 280 return sequence.subSequence(trimCount, sequence.length()); 281 } 282 283 /** 284 * If the {@code sequence} has some trailing trimmable characters, creates a new copy 285 * and removes the trimmable characters from the copy. Otherwise the given 286 * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)} 287 * to determine whether the character is trimmable or not. 288 * 289 * @return the trimmed sequence or the original sequence that has no 290 * trailing trimmable characters. 291 * @see #isTrimmable(Paint, CharSequence) 292 * @see #trimStart(CharSequence) 293 * @see #trim(CharSequence) 294 * 295 * @hide 296 */ 297 @VisibleForTesting 298 @NonNull trimEnd(@onNull CharSequence sequence)299 public static CharSequence trimEnd(@NonNull CharSequence sequence) { 300 Objects.requireNonNull(sequence); 301 302 if (TextUtils.isEmpty(sequence)) { 303 return sequence; 304 } 305 306 final Paint paint = new Paint(); 307 int trimCount = 0; 308 final int[] codePoints = sequence.codePoints().toArray(); 309 for (int i = codePoints.length - 1; i >= 0; i--) { 310 String ch = new String(new int[]{codePoints[i]}, /* offset= */ 0, /* count= */ 1); 311 if (!isTrimmable(paint, ch)) { 312 break; 313 } 314 trimCount += ch.length(); 315 } 316 317 if (trimCount == 0) { 318 return sequence; 319 } 320 return sequence.subSequence(0, sequence.length() - trimCount); 321 } 322 323 /** 324 * If the {@code sequence} has some leading or trailing trimmable characters, creates 325 * a new copy and removes the trimmable characters from the copy. Otherwise the given 326 * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)} 327 * to determine whether the character is trimmable or not. 328 * 329 * @return the trimmed sequence or the original sequence that has no leading or 330 * trailing trimmable characters. 331 * @see #isTrimmable(Paint, CharSequence) 332 * @see #trimStart(CharSequence) 333 * @see #trimEnd(CharSequence) 334 * 335 * @hide 336 */ 337 @VisibleForTesting 338 @NonNull trim(@onNull CharSequence sequence)339 public static CharSequence trim(@NonNull CharSequence sequence) { 340 Objects.requireNonNull(sequence); 341 342 if (TextUtils.isEmpty(sequence)) { 343 return sequence; 344 } 345 346 CharSequence result = trimStart(sequence); 347 if (TextUtils.isEmpty(result)) { 348 return result; 349 } 350 351 return trimEnd(result); 352 } 353 } 354