1 /* 2 * Copyright (C) 2008 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.launcher3; 18 19 import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED; 20 21 import android.animation.ValueAnimator; 22 import android.annotation.TargetApi; 23 import android.app.ActivityManager; 24 import android.app.Person; 25 import android.app.WallpaperManager; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.SharedPreferences; 29 import android.content.pm.LauncherActivityInfo; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.ShortcutInfo; 32 import android.content.res.Resources; 33 import android.graphics.Bitmap; 34 import android.graphics.Color; 35 import android.graphics.Matrix; 36 import android.graphics.Paint; 37 import android.graphics.Point; 38 import android.graphics.Rect; 39 import android.graphics.RectF; 40 import android.graphics.drawable.ColorDrawable; 41 import android.graphics.drawable.Drawable; 42 import android.graphics.drawable.InsetDrawable; 43 import android.os.Build; 44 import android.os.DeadObjectException; 45 import android.os.Handler; 46 import android.os.Message; 47 import android.os.PowerManager; 48 import android.os.TransactionTooLargeException; 49 import android.provider.Settings; 50 import android.text.Spannable; 51 import android.text.SpannableString; 52 import android.text.TextUtils; 53 import android.text.style.TtsSpan; 54 import android.util.DisplayMetrics; 55 import android.util.Log; 56 import android.util.TypedValue; 57 import android.view.MotionEvent; 58 import android.view.View; 59 import android.view.ViewConfiguration; 60 import android.view.animation.Interpolator; 61 62 import com.android.launcher3.compat.LauncherAppsCompat; 63 import com.android.launcher3.compat.ShortcutConfigActivityInfo; 64 import com.android.launcher3.dragndrop.FolderAdaptiveIcon; 65 import com.android.launcher3.graphics.RotationMode; 66 import com.android.launcher3.graphics.TintedDrawableSpan; 67 import com.android.launcher3.icons.LauncherIcons; 68 import com.android.launcher3.shortcuts.DeepShortcutManager; 69 import com.android.launcher3.shortcuts.ShortcutKey; 70 import com.android.launcher3.util.IntArray; 71 import com.android.launcher3.util.PackageManagerHelper; 72 import com.android.launcher3.views.Transposable; 73 import com.android.launcher3.widget.PendingAddShortcutInfo; 74 75 import java.lang.reflect.Method; 76 import java.util.Arrays; 77 import java.util.List; 78 import java.util.Locale; 79 import java.util.regex.Matcher; 80 import java.util.regex.Pattern; 81 82 /** 83 * Various utilities shared amongst the Launcher's classes. 84 */ 85 public final class Utilities { 86 87 private static final String TAG = "Launcher.Utilities"; 88 89 private static final Pattern sTrimPattern = 90 Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$"); 91 92 private static final int[] sLoc0 = new int[2]; 93 private static final int[] sLoc1 = new int[2]; 94 private static final Matrix sMatrix = new Matrix(); 95 private static final Matrix sInverseMatrix = new Matrix(); 96 97 public static final String[] EMPTY_STRING_ARRAY = new String[0]; 98 public static final Person[] EMPTY_PERSON_ARRAY = new Person[0]; 99 100 public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; 101 102 public static final boolean ATLEAST_P = 103 Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; 104 105 public static final boolean ATLEAST_OREO_MR1 = 106 Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1; 107 108 public static final boolean ATLEAST_OREO = 109 Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; 110 111 /** 112 * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}. 113 */ 114 public static final int EDGE_NAV_BAR = 1 << 8; 115 116 /** 117 * Indicates if the device has a debug build. Should only be used to store additional info or 118 * add extra logging and not for changing the app behavior. 119 */ 120 public static final boolean IS_DEBUG_DEVICE = 121 Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") || 122 Build.TYPE.toLowerCase(Locale.ROOT).equals("eng"); 123 isDevelopersOptionsEnabled(Context context)124 public static boolean isDevelopersOptionsEnabled(Context context) { 125 return Settings.Global.getInt(context.getApplicationContext().getContentResolver(), 126 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; 127 } 128 129 // An intent extra to indicate the horizontal scroll of the wallpaper. 130 public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET"; 131 public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR"; 132 133 public static boolean IS_RUNNING_IN_TEST_HARNESS = 134 ActivityManager.isRunningInTestHarness(); 135 enableRunningInTestHarnessForTests()136 public static void enableRunningInTestHarnessForTests() { 137 IS_RUNNING_IN_TEST_HARNESS = true; 138 } 139 isPropertyEnabled(String propertyName)140 public static boolean isPropertyEnabled(String propertyName) { 141 return Log.isLoggable(propertyName, Log.VERBOSE); 142 } 143 existsStyleWallpapers(Context context)144 public static boolean existsStyleWallpapers(Context context) { 145 ResolveInfo ri = context.getPackageManager().resolveActivity( 146 PackageManagerHelper.getStyleWallpapersIntent(context), 0); 147 return ri != null; 148 } 149 150 /** 151 * Given a coordinate relative to the descendant, find the coordinate in a parent view's 152 * coordinates. 153 * 154 * @param descendant The descendant to which the passed coordinate is relative. 155 * @param ancestor The root view to make the coordinates relative to. 156 * @param coord The coordinate that we want mapped. 157 * @param includeRootScroll Whether or not to account for the scroll of the descendant: 158 * sometimes this is relevant as in a child's coordinates within the descendant. 159 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 160 * this scale factor is assumed to be equal in X and Y, and so if at any point this 161 * assumption fails, we will need to return a pair of scale factors. 162 */ getDescendantCoordRelativeToAncestor( View descendant, View ancestor, float[] coord, boolean includeRootScroll)163 public static float getDescendantCoordRelativeToAncestor( 164 View descendant, View ancestor, float[] coord, boolean includeRootScroll) { 165 return getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, includeRootScroll, 166 false, null); 167 } 168 169 /** 170 * Given a coordinate relative to the descendant, find the coordinate in a parent view's 171 * coordinates. 172 * 173 * @param descendant The descendant to which the passed coordinate is relative. 174 * @param ancestor The root view to make the coordinates relative to. 175 * @param coord The coordinate that we want mapped. 176 * @param includeRootScroll Whether or not to account for the scroll of the descendant: 177 * sometimes this is relevant as in a child's coordinates within the descendant. 178 * @param ignoreTransform If true, view transform is ignored 179 * @param outRotation If not null, and {@param ignoreTransform} is true, this is set to the 180 * overall rotation of the view in degrees. 181 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 182 * this scale factor is assumed to be equal in X and Y, and so if at any point this 183 * assumption fails, we will need to return a pair of scale factors. 184 */ getDescendantCoordRelativeToAncestor(View descendant, View ancestor, float[] coord, boolean includeRootScroll, boolean ignoreTransform, float[] outRotation)185 public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor, 186 float[] coord, boolean includeRootScroll, boolean ignoreTransform, 187 float[] outRotation) { 188 float scale = 1.0f; 189 View v = descendant; 190 while(v != ancestor && v != null) { 191 // For TextViews, scroll has a meaning which relates to the text position 192 // which is very strange... ignore the scroll. 193 if (v != descendant || includeRootScroll) { 194 offsetPoints(coord, -v.getScrollX(), -v.getScrollY()); 195 } 196 197 if (ignoreTransform) { 198 if (v instanceof Transposable) { 199 RotationMode m = ((Transposable) v).getRotationMode(); 200 if (m.isTransposed) { 201 sMatrix.setRotate(m.surfaceRotation, v.getPivotX(), v.getPivotY()); 202 sMatrix.mapPoints(coord); 203 204 if (outRotation != null) { 205 outRotation[0] += m.surfaceRotation; 206 } 207 } 208 } 209 } else { 210 v.getMatrix().mapPoints(coord); 211 } 212 offsetPoints(coord, v.getLeft(), v.getTop()); 213 scale *= v.getScaleX(); 214 215 v = (View) v.getParent(); 216 } 217 return scale; 218 } 219 220 /** 221 * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}. 222 */ mapCoordInSelfToDescendant(View descendant, View root, float[] coord)223 public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) { 224 sMatrix.reset(); 225 View v = descendant; 226 while(v != root) { 227 sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY()); 228 sMatrix.postConcat(v.getMatrix()); 229 sMatrix.postTranslate(v.getLeft(), v.getTop()); 230 v = (View) v.getParent(); 231 } 232 sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY()); 233 sMatrix.invert(sInverseMatrix); 234 sInverseMatrix.mapPoints(coord); 235 } 236 237 /** 238 * Sets {@param out} to be same as {@param in} by rounding individual values 239 */ roundArray(float[] in, int[] out)240 public static void roundArray(float[] in, int[] out) { 241 for (int i = 0; i < in.length; i++) { 242 out[i] = Math.round(in[i]); 243 } 244 } 245 offsetPoints(float[] points, float offsetX, float offsetY)246 public static void offsetPoints(float[] points, float offsetX, float offsetY) { 247 for (int i = 0; i < points.length; i += 2) { 248 points[i] += offsetX; 249 points[i + 1] += offsetY; 250 } 251 } 252 253 /** 254 * Utility method to determine whether the given point, in local coordinates, 255 * is inside the view, where the area of the view is expanded by the slop factor. 256 * This method is called while processing touch-move events to determine if the event 257 * is still within the view. 258 */ pointInView(View v, float localX, float localY, float slop)259 public static boolean pointInView(View v, float localX, float localY, float slop) { 260 return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) && 261 localY < (v.getHeight() + slop); 262 } 263 getCenterDeltaInScreenSpace(View v0, View v1)264 public static int[] getCenterDeltaInScreenSpace(View v0, View v1) { 265 v0.getLocationInWindow(sLoc0); 266 v1.getLocationInWindow(sLoc1); 267 268 sLoc0[0] += (v0.getMeasuredWidth() * v0.getScaleX()) / 2; 269 sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2; 270 sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2; 271 sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2; 272 return new int[] {sLoc1[0] - sLoc0[0], sLoc1[1] - sLoc0[1]}; 273 } 274 scaleRectFAboutCenter(RectF r, float scale)275 public static void scaleRectFAboutCenter(RectF r, float scale) { 276 if (scale != 1.0f) { 277 float cx = r.centerX(); 278 float cy = r.centerY(); 279 r.offset(-cx, -cy); 280 r.left = r.left * scale; 281 r.top = r.top * scale ; 282 r.right = r.right * scale; 283 r.bottom = r.bottom * scale; 284 r.offset(cx, cy); 285 } 286 } 287 scaleRectAboutCenter(Rect r, float scale)288 public static void scaleRectAboutCenter(Rect r, float scale) { 289 if (scale != 1.0f) { 290 int cx = r.centerX(); 291 int cy = r.centerY(); 292 r.offset(-cx, -cy); 293 scaleRect(r, scale); 294 r.offset(cx, cy); 295 } 296 } 297 scaleRect(Rect r, float scale)298 public static void scaleRect(Rect r, float scale) { 299 if (scale != 1.0f) { 300 r.left = (int) (r.left * scale + 0.5f); 301 r.top = (int) (r.top * scale + 0.5f); 302 r.right = (int) (r.right * scale + 0.5f); 303 r.bottom = (int) (r.bottom * scale + 0.5f); 304 } 305 } 306 insetRect(Rect r, Rect insets)307 public static void insetRect(Rect r, Rect insets) { 308 r.left = Math.min(r.right, r.left + insets.left); 309 r.top = Math.min(r.bottom, r.top + insets.top); 310 r.right = Math.max(r.left, r.right - insets.right); 311 r.bottom = Math.max(r.top, r.bottom - insets.bottom); 312 } 313 shrinkRect(Rect r, float scaleX, float scaleY)314 public static float shrinkRect(Rect r, float scaleX, float scaleY) { 315 float scale = Math.min(Math.min(scaleX, scaleY), 1.0f); 316 if (scale < 1.0f) { 317 int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f); 318 r.left += deltaX; 319 r.right -= deltaX; 320 321 int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f); 322 r.top += deltaY; 323 r.bottom -= deltaY; 324 } 325 return scale; 326 } 327 328 /** 329 * Maps t from one range to another range. 330 * @param t The value to map. 331 * @param fromMin The lower bound of the range that t is being mapped from. 332 * @param fromMax The upper bound of the range that t is being mapped from. 333 * @param toMin The lower bound of the range that t is being mapped to. 334 * @param toMax The upper bound of the range that t is being mapped to. 335 * @return The mapped value of t. 336 */ mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax, Interpolator interpolator)337 public static float mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax, 338 Interpolator interpolator) { 339 if (fromMin == fromMax || toMin == toMax) { 340 Log.e(TAG, "mapToRange: range has 0 length"); 341 return toMin; 342 } 343 float progress = getProgress(t, fromMin, fromMax); 344 return mapRange(interpolator.getInterpolation(progress), toMin, toMax); 345 } 346 getProgress(float current, float min, float max)347 public static float getProgress(float current, float min, float max) { 348 return Math.abs(current - min) / Math.abs(max - min); 349 } 350 mapRange(float value, float min, float max)351 public static float mapRange(float value, float min, float max) { 352 return min + (value * (max - min)); 353 } 354 355 /** 356 * Trims the string, removing all whitespace at the beginning and end of the string. 357 * Non-breaking whitespaces are also removed. 358 */ trim(CharSequence s)359 public static String trim(CharSequence s) { 360 if (s == null) { 361 return null; 362 } 363 364 // Just strip any sequence of whitespace or java space characters from the beginning and end 365 Matcher m = sTrimPattern.matcher(s); 366 return m.replaceAll("$1"); 367 } 368 369 /** 370 * Calculates the height of a given string at a specific text size. 371 */ calculateTextHeight(float textSizePx)372 public static int calculateTextHeight(float textSizePx) { 373 Paint p = new Paint(); 374 p.setTextSize(textSizePx); 375 Paint.FontMetrics fm = p.getFontMetrics(); 376 return (int) Math.ceil(fm.bottom - fm.top); 377 } 378 isRtl(Resources res)379 public static boolean isRtl(Resources res) { 380 return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 381 } 382 dpiFromPx(int size, DisplayMetrics metrics)383 public static float dpiFromPx(int size, DisplayMetrics metrics){ 384 float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT; 385 return (size / densityRatio); 386 } 387 pxFromSp(float size, DisplayMetrics metrics)388 public static int pxFromSp(float size, DisplayMetrics metrics) { 389 return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 390 size, metrics)); 391 } 392 createDbSelectionQuery(String columnName, IntArray values)393 public static String createDbSelectionQuery(String columnName, IntArray values) { 394 return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, values.toConcatString()); 395 } 396 isBootCompleted()397 public static boolean isBootCompleted() { 398 return "1".equals(getSystemProperty("sys.boot_completed", "1")); 399 } 400 getSystemProperty(String property, String defaultValue)401 public static String getSystemProperty(String property, String defaultValue) { 402 try { 403 Class clazz = Class.forName("android.os.SystemProperties"); 404 Method getter = clazz.getDeclaredMethod("get", String.class); 405 String value = (String) getter.invoke(null, property); 406 if (!TextUtils.isEmpty(value)) { 407 return value; 408 } 409 } catch (Exception e) { 410 Log.d(TAG, "Unable to read system properties"); 411 } 412 return defaultValue; 413 } 414 415 /** 416 * Ensures that a value is within given bounds. Specifically: 417 * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound, 418 * return upperBound; else return value unchanged. 419 */ boundToRange(int value, int lowerBound, int upperBound)420 public static int boundToRange(int value, int lowerBound, int upperBound) { 421 return Math.max(lowerBound, Math.min(value, upperBound)); 422 } 423 424 /** 425 * @see #boundToRange(int, int, int). 426 */ boundToRange(float value, float lowerBound, float upperBound)427 public static float boundToRange(float value, float lowerBound, float upperBound) { 428 return Math.max(lowerBound, Math.min(value, upperBound)); 429 } 430 431 /** 432 * @see #boundToRange(int, int, int). 433 */ boundToRange(long value, long lowerBound, long upperBound)434 public static long boundToRange(long value, long lowerBound, long upperBound) { 435 return Math.max(lowerBound, Math.min(value, upperBound)); 436 } 437 438 /** 439 * Wraps a message with a TTS span, so that a different message is spoken than 440 * what is getting displayed. 441 * @param msg original message 442 * @param ttsMsg message to be spoken 443 */ wrapForTts(CharSequence msg, String ttsMsg)444 public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) { 445 SpannableString spanned = new SpannableString(msg); 446 spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(), 447 0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); 448 return spanned; 449 } 450 451 /** 452 * Prefixes a text with the provided icon 453 */ prefixTextWithIcon(Context context, int iconRes, CharSequence msg)454 public static CharSequence prefixTextWithIcon(Context context, int iconRes, CharSequence msg) { 455 // Update the hint to contain the icon. 456 // Prefix the original hint with two spaces. The first space gets replaced by the icon 457 // using span. The second space is used for a singe space character between the hint 458 // and the icon. 459 SpannableString spanned = new SpannableString(" " + msg); 460 spanned.setSpan(new TintedDrawableSpan(context, iconRes), 461 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 462 return spanned; 463 } 464 getPrefs(Context context)465 public static SharedPreferences getPrefs(Context context) { 466 return context.getSharedPreferences( 467 LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); 468 } 469 getDevicePrefs(Context context)470 public static SharedPreferences getDevicePrefs(Context context) { 471 return context.getSharedPreferences( 472 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE); 473 } 474 areAnimationsEnabled(Context context)475 public static boolean areAnimationsEnabled(Context context) { 476 return ATLEAST_OREO 477 ? ValueAnimator.areAnimatorsEnabled() 478 : !context.getSystemService(PowerManager.class).isPowerSaveMode(); 479 } 480 isWallpaperAllowed(Context context)481 public static boolean isWallpaperAllowed(Context context) { 482 return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed(); 483 } 484 isBinderSizeError(Exception e)485 public static boolean isBinderSizeError(Exception e) { 486 return e.getCause() instanceof TransactionTooLargeException 487 || e.getCause() instanceof DeadObjectException; 488 } 489 490 /** 491 * Utility method to post a runnable on the handler, skipping the synchronization barriers. 492 */ postAsyncCallback(Handler handler, Runnable callback)493 public static void postAsyncCallback(Handler handler, Runnable callback) { 494 Message msg = Message.obtain(handler, callback); 495 msg.setAsynchronous(true); 496 handler.sendMessage(msg); 497 } 498 499 /** 500 * Parses a string encoded using {@link #getPointString(int, int)} 501 */ parsePoint(String point)502 public static Point parsePoint(String point) { 503 String[] split = point.split(","); 504 return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1])); 505 } 506 507 /** 508 * Encodes a point to string to that it can be persisted atomically. 509 */ getPointString(int x, int y)510 public static String getPointString(int x, int y) { 511 return String.format(Locale.ENGLISH, "%d,%d", x, y); 512 } 513 unregisterReceiverSafely(Context context, BroadcastReceiver receiver)514 public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) { 515 try { 516 context.unregisterReceiver(receiver); 517 } catch (IllegalArgumentException e) {} 518 } 519 520 /** 521 * Returns the full drawable for {@param info}. 522 * @param outObj this is set to the internal data associated with {@param info}, 523 * eg {@link LauncherActivityInfo} or {@link ShortcutInfo}. 524 */ getFullDrawable(Launcher launcher, ItemInfo info, int width, int height, boolean flattenDrawable, Object[] outObj)525 public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height, 526 boolean flattenDrawable, Object[] outObj) { 527 LauncherAppState appState = LauncherAppState.getInstance(launcher); 528 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { 529 LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(launcher) 530 .resolveActivity(info.getIntent(), info.user); 531 outObj[0] = activityInfo; 532 return (activityInfo != null) ? appState.getIconCache() 533 .getFullResIcon(activityInfo, flattenDrawable) : null; 534 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 535 if (info instanceof PendingAddShortcutInfo) { 536 ShortcutConfigActivityInfo activityInfo = 537 ((PendingAddShortcutInfo) info).activityInfo; 538 outObj[0] = activityInfo; 539 return activityInfo.getFullResIcon(appState.getIconCache()); 540 } 541 ShortcutKey key = ShortcutKey.fromItemInfo(info); 542 DeepShortcutManager sm = DeepShortcutManager.getInstance(launcher); 543 List<ShortcutInfo> si = sm.queryForFullDetails( 544 key.componentName.getPackageName(), Arrays.asList(key.getId()), key.user); 545 if (si.isEmpty()) { 546 return null; 547 } else { 548 outObj[0] = si.get(0); 549 return sm.getShortcutIconDrawable(si.get(0), 550 appState.getInvariantDeviceProfile().fillResIconDpi); 551 } 552 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { 553 FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon( 554 launcher, info.id, new Point(width, height)); 555 if (icon == null) { 556 return null; 557 } 558 outObj[0] = icon; 559 return icon; 560 } else { 561 return null; 562 } 563 } 564 565 /** 566 * For apps icons and shortcut icons that have badges, this method creates a drawable that can 567 * later on be rendered on top of the layers for the badges. For app icons, work profile badges 568 * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no 569 * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge 570 **/ 571 @TargetApi(Build.VERSION_CODES.O) getBadge(Launcher launcher, ItemInfo info, Object obj)572 public static Drawable getBadge(Launcher launcher, ItemInfo info, Object obj) { 573 LauncherAppState appState = LauncherAppState.getInstance(launcher); 574 int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize; 575 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 576 boolean iconBadged = (info instanceof ItemInfoWithIcon) 577 && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0; 578 if ((info.id == ItemInfo.NO_ID && !iconBadged) 579 || !(obj instanceof ShortcutInfo)) { 580 // The item is not yet added on home screen. 581 return new FixedSizeEmptyDrawable(iconSize); 582 } 583 ShortcutInfo si = (ShortcutInfo) obj; 584 LauncherIcons li = LauncherIcons.obtain(appState.getContext()); 585 Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap; 586 li.recycle(); 587 float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize); 588 float insetFraction = (iconSize - badgeSize) / iconSize; 589 return new InsetDrawable(new FastBitmapDrawable(badge), 590 insetFraction, insetFraction, 0, 0); 591 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { 592 return ((FolderAdaptiveIcon) obj).getBadge(); 593 } else { 594 return launcher.getPackageManager() 595 .getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user); 596 } 597 } 598 squaredHypot(float x, float y)599 public static float squaredHypot(float x, float y) { 600 return x * x + y * y; 601 } 602 squaredTouchSlop(Context context)603 public static float squaredTouchSlop(Context context) { 604 float slop = ViewConfiguration.get(context).getScaledTouchSlop(); 605 return slop * slop; 606 } 607 608 private static class FixedSizeEmptyDrawable extends ColorDrawable { 609 610 private final int mSize; 611 FixedSizeEmptyDrawable(int size)612 public FixedSizeEmptyDrawable(int size) { 613 super(Color.TRANSPARENT); 614 mSize = size; 615 } 616 617 @Override getIntrinsicHeight()618 public int getIntrinsicHeight() { 619 return mSize; 620 } 621 622 @Override getIntrinsicWidth()623 public int getIntrinsicWidth() { 624 return mSize; 625 } 626 } 627 } 628