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