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