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