• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.TargetApi;
22 import android.app.ActivityManager;
23 import android.app.Person;
24 import android.app.WallpaperManager;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
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.Configuration;
37 import android.content.res.Resources;
38 import android.database.ContentObserver;
39 import android.graphics.Bitmap;
40 import android.graphics.Color;
41 import android.graphics.ColorFilter;
42 import android.graphics.LightingColorFilter;
43 import android.graphics.Matrix;
44 import android.graphics.Paint;
45 import android.graphics.Point;
46 import android.graphics.Rect;
47 import android.graphics.RectF;
48 import android.graphics.drawable.ColorDrawable;
49 import android.graphics.drawable.Drawable;
50 import android.graphics.drawable.InsetDrawable;
51 import android.net.Uri;
52 import android.os.Build;
53 import android.os.DeadObjectException;
54 import android.os.Handler;
55 import android.os.Message;
56 import android.os.TransactionTooLargeException;
57 import android.provider.Settings;
58 import android.text.Spannable;
59 import android.text.SpannableString;
60 import android.text.TextUtils;
61 import android.text.style.TtsSpan;
62 import android.util.DisplayMetrics;
63 import android.util.Log;
64 import android.util.TypedValue;
65 import android.view.MotionEvent;
66 import android.view.View;
67 import android.view.ViewConfiguration;
68 import android.view.animation.Interpolator;
69 import android.widget.LinearLayout;
70 
71 import androidx.core.graphics.ColorUtils;
72 import androidx.core.os.BuildCompat;
73 
74 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
75 import com.android.launcher3.graphics.GridCustomizationsProvider;
76 import com.android.launcher3.graphics.TintedDrawableSpan;
77 import com.android.launcher3.icons.BitmapInfo;
78 import com.android.launcher3.icons.FastBitmapDrawable;
79 import com.android.launcher3.icons.LauncherIcons;
80 import com.android.launcher3.icons.ShortcutCachingLogic;
81 import com.android.launcher3.model.data.ItemInfo;
82 import com.android.launcher3.model.data.ItemInfoWithIcon;
83 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
84 import com.android.launcher3.shortcuts.ShortcutKey;
85 import com.android.launcher3.shortcuts.ShortcutRequest;
86 import com.android.launcher3.util.IntArray;
87 import com.android.launcher3.util.PackageManagerHelper;
88 import com.android.launcher3.views.BaseDragLayer;
89 import com.android.launcher3.widget.PendingAddShortcutInfo;
90 
91 import java.lang.reflect.Method;
92 import java.util.Arrays;
93 import java.util.List;
94 import java.util.Locale;
95 import java.util.function.Consumer;
96 import java.util.regex.Matcher;
97 import java.util.regex.Pattern;
98 
99 /**
100  * Various utilities shared amongst the Launcher's classes.
101  */
102 public final class Utilities {
103 
104     private static final String TAG = "Launcher.Utilities";
105 
106     private static final Pattern sTrimPattern =
107             Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
108 
109     private static final float[] sTmpFloatArray = new float[4];
110 
111     private static final int[] sLoc0 = new int[2];
112     private static final int[] sLoc1 = new int[2];
113     private static final Matrix sMatrix = new Matrix();
114     private static final Matrix sInverseMatrix = new Matrix();
115 
116     public static final String[] EMPTY_STRING_ARRAY = new String[0];
117     public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
118 
119     public static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
120 
121     public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
122 
123     public static final boolean ATLEAST_R = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
124 
125     public static final boolean ATLEAST_S = BuildCompat.isAtLeastS()
126             || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
127 
128     /**
129      * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
130      */
131     public static final int EDGE_NAV_BAR = 1 << 8;
132 
133     /**
134      * Indicates if the device has a debug build. Should only be used to store additional info or
135      * add extra logging and not for changing the app behavior.
136      */
137     public static final boolean IS_DEBUG_DEVICE =
138             Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") ||
139             Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");
140 
141     /**
142      * Returns true if theme is dark.
143      */
isDarkTheme(Context context)144     public static boolean isDarkTheme(Context context) {
145         Configuration configuration = context.getResources().getConfiguration();
146         int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
147         return nightMode == Configuration.UI_MODE_NIGHT_YES;
148     }
149 
isDevelopersOptionsEnabled(Context context)150     public static boolean isDevelopersOptionsEnabled(Context context) {
151         return Settings.Global.getInt(context.getApplicationContext().getContentResolver(),
152                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
153     }
154 
155     // An intent extra to indicate the horizontal scroll of the wallpaper.
156     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
157     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
158 
159     // An intent extra to indicate the launch source by launcher.
160     public static final String EXTRA_WALLPAPER_LAUNCH_SOURCE =
161             "com.android.wallpaper.LAUNCH_SOURCE";
162 
163     public static boolean IS_RUNNING_IN_TEST_HARNESS =
164                     ActivityManager.isRunningInTestHarness();
165 
enableRunningInTestHarnessForTests()166     public static void enableRunningInTestHarnessForTests() {
167         IS_RUNNING_IN_TEST_HARNESS = true;
168     }
169 
isPropertyEnabled(String propertyName)170     public static boolean isPropertyEnabled(String propertyName) {
171         return Log.isLoggable(propertyName, Log.VERBOSE);
172     }
173 
existsStyleWallpapers(Context context)174     public static boolean existsStyleWallpapers(Context context) {
175         ResolveInfo ri = context.getPackageManager().resolveActivity(
176                 PackageManagerHelper.getStyleWallpapersIntent(context), 0);
177         return ri != null;
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      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
190      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
191      *         assumption fails, we will need to return a pair of scale factors.
192      */
getDescendantCoordRelativeToAncestor( View descendant, View ancestor, float[] coord, boolean includeRootScroll)193     public static float getDescendantCoordRelativeToAncestor(
194             View descendant, View ancestor, float[] coord, boolean includeRootScroll) {
195         return getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, includeRootScroll,
196                 false);
197     }
198 
199     /**
200      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
201      * coordinates.
202      *
203      * @param descendant The descendant to which the passed coordinate is relative.
204      * @param ancestor The root view to make the coordinates relative to.
205      * @param coord The coordinate that we want mapped.
206      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
207      *          sometimes this is relevant as in a child's coordinates within the descendant.
208      * @param ignoreTransform If true, view transform is ignored
209      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
210      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
211      *         assumption fails, we will need to return a pair of scale factors.
212      */
getDescendantCoordRelativeToAncestor(View descendant, View ancestor, float[] coord, boolean includeRootScroll, boolean ignoreTransform)213     public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor,
214             float[] coord, boolean includeRootScroll, boolean ignoreTransform) {
215         float scale = 1.0f;
216         View v = descendant;
217         while(v != ancestor && v != null) {
218             // For TextViews, scroll has a meaning which relates to the text position
219             // which is very strange... ignore the scroll.
220             if (v != descendant || includeRootScroll) {
221                 offsetPoints(coord, -v.getScrollX(), -v.getScrollY());
222             }
223 
224             if (!ignoreTransform) {
225                 v.getMatrix().mapPoints(coord);
226             }
227             offsetPoints(coord, v.getLeft(), v.getTop());
228             scale *= v.getScaleX();
229 
230             v = (View) v.getParent();
231         }
232         return scale;
233     }
234 
235     /**
236      * Returns bounds for a child view of DragLayer, in drag layer coordinates.
237      *
238      * see {@link com.android.launcher3.dragndrop.DragLayer}.
239      *
240      * @param viewBounds Bounds of the view wanted in drag layer coordinates, relative to the view
241      *                   itself. eg. (0, 0, view.getWidth, view.getHeight)
242      * @param ignoreTransform If true, view transform is ignored
243      * @param outRect The out rect where we return the bounds of {@param view} in drag layer coords.
244      */
getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view, Rect viewBounds, boolean ignoreTransform, float[] recycle, RectF outRect)245     public static void getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view,
246             Rect viewBounds, boolean ignoreTransform, float[] recycle, RectF outRect) {
247         float[] points = recycle == null ? new float[4] : recycle;
248         points[0] = viewBounds.left;
249         points[1] = viewBounds.top;
250         points[2] = viewBounds.right;
251         points[3] = viewBounds.bottom;
252 
253         Utilities.getDescendantCoordRelativeToAncestor(view, dragLayer, points,
254                 false, ignoreTransform);
255         outRect.set(
256                 Math.min(points[0], points[2]),
257                 Math.min(points[1], points[3]),
258                 Math.max(points[0], points[2]),
259                 Math.max(points[1], points[3]));
260     }
261 
262     /**
263      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
264      */
mapCoordInSelfToDescendant(View descendant, View root, float[] coord)265     public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
266         sMatrix.reset();
267         View v = descendant;
268         while(v != root) {
269             sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
270             sMatrix.postConcat(v.getMatrix());
271             sMatrix.postTranslate(v.getLeft(), v.getTop());
272             v = (View) v.getParent();
273         }
274         sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
275         sMatrix.invert(sInverseMatrix);
276         sInverseMatrix.mapPoints(coord);
277     }
278 
279     /**
280      * Sets {@param out} to be same as {@param in} by rounding individual values
281      */
roundArray(float[] in, int[] out)282     public static void roundArray(float[] in, int[] out) {
283        for (int i = 0; i < in.length; i++) {
284            out[i] = Math.round(in[i]);
285        }
286     }
287 
offsetPoints(float[] points, float offsetX, float offsetY)288     public static void offsetPoints(float[] points, float offsetX, float offsetY) {
289         for (int i = 0; i < points.length; i += 2) {
290             points[i] += offsetX;
291             points[i + 1] += offsetY;
292         }
293     }
294 
295     /**
296      * Utility method to determine whether the given point, in local coordinates,
297      * is inside the view, where the area of the view is expanded by the slop factor.
298      * This method is called while processing touch-move events to determine if the event
299      * is still within the view.
300      */
pointInView(View v, float localX, float localY, float slop)301     public static boolean pointInView(View v, float localX, float localY, float slop) {
302         return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
303                 localY < (v.getHeight() + slop);
304     }
305 
getCenterDeltaInScreenSpace(View v0, View v1)306     public static int[] getCenterDeltaInScreenSpace(View v0, View v1) {
307         v0.getLocationInWindow(sLoc0);
308         v1.getLocationInWindow(sLoc1);
309 
310         sLoc0[0] += (v0.getMeasuredWidth() * v0.getScaleX()) / 2;
311         sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
312         sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
313         sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
314         return new int[] {sLoc1[0] - sLoc0[0], sLoc1[1] - sLoc0[1]};
315     }
316 
317     /**
318      * Helper method to set rectOut with rectFSrc.
319      */
setRect(RectF rectFSrc, Rect rectOut)320     public static void setRect(RectF rectFSrc, Rect rectOut) {
321         rectOut.left = (int) rectFSrc.left;
322         rectOut.top = (int) rectFSrc.top;
323         rectOut.right = (int) rectFSrc.right;
324         rectOut.bottom = (int) rectFSrc.bottom;
325     }
326 
scaleRectFAboutCenter(RectF r, float scale)327     public static void scaleRectFAboutCenter(RectF r, float scale) {
328         scaleRectFAboutPivot(r, scale, r.centerX(), r.centerY());
329     }
330 
scaleRectFAboutPivot(RectF r, float scale, float px, float py)331     public static void scaleRectFAboutPivot(RectF r, float scale, float px, float py) {
332         if (scale != 1.0f) {
333             r.offset(-px, -py);
334             r.left = r.left * scale;
335             r.top = r.top * scale ;
336             r.right = r.right * scale;
337             r.bottom = r.bottom * scale;
338             r.offset(px, py);
339         }
340     }
341 
scaleRectAboutCenter(Rect r, float scale)342     public static void scaleRectAboutCenter(Rect r, float scale) {
343         if (scale != 1.0f) {
344             int cx = r.centerX();
345             int cy = r.centerY();
346             r.offset(-cx, -cy);
347             scaleRect(r, scale);
348             r.offset(cx, cy);
349         }
350     }
351 
scaleRect(Rect r, float scale)352     public static void scaleRect(Rect r, float scale) {
353         if (scale != 1.0f) {
354             r.left = (int) (r.left * scale + 0.5f);
355             r.top = (int) (r.top * scale + 0.5f);
356             r.right = (int) (r.right * scale + 0.5f);
357             r.bottom = (int) (r.bottom * scale + 0.5f);
358         }
359     }
360 
insetRect(Rect r, Rect insets)361     public static void insetRect(Rect r, Rect insets) {
362         r.left = Math.min(r.right, r.left + insets.left);
363         r.top = Math.min(r.bottom, r.top + insets.top);
364         r.right = Math.max(r.left, r.right - insets.right);
365         r.bottom = Math.max(r.top, r.bottom - insets.bottom);
366     }
367 
shrinkRect(Rect r, float scaleX, float scaleY)368     public static float shrinkRect(Rect r, float scaleX, float scaleY) {
369         float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
370         if (scale < 1.0f) {
371             int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
372             r.left += deltaX;
373             r.right -= deltaX;
374 
375             int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
376             r.top += deltaY;
377             r.bottom -= deltaY;
378         }
379         return scale;
380     }
381 
382     /**
383      * Maps t from one range to another range.
384      * @param t The value to map.
385      * @param fromMin The lower bound of the range that t is being mapped from.
386      * @param fromMax The upper bound of the range that t is being mapped from.
387      * @param toMin The lower bound of the range that t is being mapped to.
388      * @param toMax The upper bound of the range that t is being mapped to.
389      * @return The mapped value of t.
390      */
mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax, Interpolator interpolator)391     public static float mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax,
392             Interpolator interpolator) {
393         if (fromMin == fromMax || toMin == toMax) {
394             Log.e(TAG, "mapToRange: range has 0 length");
395             return toMin;
396         }
397         float progress = getProgress(t, fromMin, fromMax);
398         return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
399     }
400 
401     /** Bounds t between a lower and upper bound and maps the result to a range. */
mapBoundToRange(float t, float lowerBound, float upperBound, float toMin, float toMax, Interpolator interpolator)402     public static float mapBoundToRange(float t, float lowerBound, float upperBound,
403             float toMin, float toMax, Interpolator interpolator) {
404         return mapToRange(boundToRange(t, lowerBound, upperBound), lowerBound, upperBound,
405                 toMin, toMax, interpolator);
406     }
407 
getProgress(float current, float min, float max)408     public static float getProgress(float current, float min, float max) {
409         return Math.abs(current - min) / Math.abs(max - min);
410     }
411 
mapRange(float value, float min, float max)412     public static float mapRange(float value, float min, float max) {
413         return min + (value * (max - min));
414     }
415 
416     /**
417      * Bounds parameter to the range [0, 1]
418      */
saturate(float a)419     public static float saturate(float a) {
420         return boundToRange(a, 0, 1.0f);
421     }
422 
423     /**
424      * Returns the compliment (1 - a) of the parameter.
425      */
comp(float a)426     public static float comp(float a) {
427         return 1 - a;
428     }
429 
430     /**
431      * Returns the "probabilistic or" of a and b. (a + b - ab).
432      * Useful beyond probability, can be used to combine two unit progresses for example.
433      */
or(float a, float b)434     public static float or(float a, float b) {
435         float satA = saturate(a);
436         float satB = saturate(b);
437         return satA + satB - (satA * satB);
438     }
439 
440     /**
441      * Trims the string, removing all whitespace at the beginning and end of the string.
442      * Non-breaking whitespaces are also removed.
443      */
trim(CharSequence s)444     public static String trim(CharSequence s) {
445         if (s == null) {
446             return null;
447         }
448 
449         // Just strip any sequence of whitespace or java space characters from the beginning and end
450         Matcher m = sTrimPattern.matcher(s);
451         return m.replaceAll("$1");
452     }
453 
454     /**
455      * Calculates the height of a given string at a specific text size.
456      */
calculateTextHeight(float textSizePx)457     public static int calculateTextHeight(float textSizePx) {
458         Paint p = new Paint();
459         p.setTextSize(textSizePx);
460         Paint.FontMetrics fm = p.getFontMetrics();
461         return (int) Math.ceil(fm.bottom - fm.top);
462     }
463 
isRtl(Resources res)464     public static boolean isRtl(Resources res) {
465         return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
466     }
467 
dpiFromPx(float size, int densityDpi)468     public static float dpiFromPx(float size, int densityDpi) {
469         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
470         return (size / densityRatio);
471     }
472 
473     /** Converts a dp value to pixels for the current device. */
dpToPx(float dp)474     public static int dpToPx(float dp) {
475         return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
476     }
477 
478 
pxFromSp(float size, DisplayMetrics metrics)479     public static int pxFromSp(float size, DisplayMetrics metrics) {
480         return pxFromSp(size, metrics, 1f);
481     }
482 
pxFromSp(float size, DisplayMetrics metrics, float scale)483     public static int pxFromSp(float size, DisplayMetrics metrics, float scale) {
484         return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
485                 size, metrics) * scale);
486     }
487 
createDbSelectionQuery(String columnName, IntArray values)488     public static String createDbSelectionQuery(String columnName, IntArray values) {
489         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, values.toConcatString());
490     }
491 
isBootCompleted()492     public static boolean isBootCompleted() {
493         return "1".equals(getSystemProperty("sys.boot_completed", "1"));
494     }
495 
getSystemProperty(String property, String defaultValue)496     public static String getSystemProperty(String property, String defaultValue) {
497         try {
498             Class clazz = Class.forName("android.os.SystemProperties");
499             Method getter = clazz.getDeclaredMethod("get", String.class);
500             String value = (String) getter.invoke(null, property);
501             if (!TextUtils.isEmpty(value)) {
502                 return value;
503             }
504         } catch (Exception e) {
505             Log.d(TAG, "Unable to read system properties");
506         }
507         return defaultValue;
508     }
509 
510     /**
511      * Ensures that a value is within given bounds. Specifically:
512      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
513      * return upperBound; else return value unchanged.
514      */
boundToRange(int value, int lowerBound, int upperBound)515     public static int boundToRange(int value, int lowerBound, int upperBound) {
516         return Math.max(lowerBound, Math.min(value, upperBound));
517     }
518 
519     /**
520      * @see #boundToRange(int, int, int).
521      */
boundToRange(float value, float lowerBound, float upperBound)522     public static float boundToRange(float value, float lowerBound, float upperBound) {
523         return Math.max(lowerBound, Math.min(value, upperBound));
524     }
525 
526     /**
527      * @see #boundToRange(int, int, int).
528      */
boundToRange(long value, long lowerBound, long upperBound)529     public static long boundToRange(long value, long lowerBound, long upperBound) {
530         return Math.max(lowerBound, Math.min(value, upperBound));
531     }
532 
533     /**
534      * Returns an intent for starting the default home activity
535      */
createHomeIntent()536     public static Intent createHomeIntent() {
537         return new Intent(Intent.ACTION_MAIN)
538                 .addCategory(Intent.CATEGORY_HOME)
539                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
540     }
541 
542     /**
543      * Wraps a message with a TTS span, so that a different message is spoken than
544      * what is getting displayed.
545      * @param msg original message
546      * @param ttsMsg message to be spoken
547      */
wrapForTts(CharSequence msg, String ttsMsg)548     public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) {
549         SpannableString spanned = new SpannableString(msg);
550         spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
551                 0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
552         return spanned;
553     }
554 
555     /**
556      * Prefixes a text with the provided icon
557      */
prefixTextWithIcon(Context context, int iconRes, CharSequence msg)558     public static CharSequence prefixTextWithIcon(Context context, int iconRes, CharSequence msg) {
559         // Update the hint to contain the icon.
560         // Prefix the original hint with two spaces. The first space gets replaced by the icon
561         // using span. The second space is used for a singe space character between the hint
562         // and the icon.
563         SpannableString spanned = new SpannableString("  " + msg);
564         spanned.setSpan(new TintedDrawableSpan(context, iconRes),
565                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
566         return spanned;
567     }
568 
getPrefs(Context context)569     public static SharedPreferences getPrefs(Context context) {
570         // Use application context for shared preferences, so that we use a single cached instance
571         return context.getApplicationContext().getSharedPreferences(
572                 LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
573     }
574 
getDevicePrefs(Context context)575     public static SharedPreferences getDevicePrefs(Context context) {
576         // Use application context for shared preferences, so that we use a single cached instance
577         return context.getApplicationContext().getSharedPreferences(
578                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
579     }
580 
isWallpaperAllowed(Context context)581     public static boolean isWallpaperAllowed(Context context) {
582         return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
583     }
584 
isBinderSizeError(Exception e)585     public static boolean isBinderSizeError(Exception e) {
586         return e.getCause() instanceof TransactionTooLargeException
587                 || e.getCause() instanceof DeadObjectException;
588     }
589 
isGridOptionsEnabled(Context context)590     public static boolean isGridOptionsEnabled(Context context) {
591         return isComponentEnabled(context.getPackageManager(),
592                 context.getPackageName(),
593                 GridCustomizationsProvider.class.getName());
594     }
595 
isComponentEnabled(PackageManager pm, String pkgName, String clsName)596     private static boolean isComponentEnabled(PackageManager pm, String pkgName, String clsName) {
597         ComponentName componentName = new ComponentName(pkgName, clsName);
598         int componentEnabledSetting = pm.getComponentEnabledSetting(componentName);
599 
600         switch (componentEnabledSetting) {
601             case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
602                 return false;
603             case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
604                 return true;
605             case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
606             default:
607                 // We need to get the application info to get the component's default state
608                 try {
609                     PackageInfo packageInfo = pm.getPackageInfo(pkgName,
610                             PackageManager.GET_PROVIDERS | PackageManager.GET_DISABLED_COMPONENTS);
611 
612                     if (packageInfo.providers != null) {
613                         return Arrays.stream(packageInfo.providers).anyMatch(
614                                 pi -> pi.name.equals(clsName) && pi.isEnabled());
615                     }
616 
617                     // the component is not declared in the AndroidManifest
618                     return false;
619                 } catch (PackageManager.NameNotFoundException e) {
620                     // the package isn't installed on the device
621                     return false;
622                 }
623         }
624     }
625 
626     /**
627      * Utility method to post a runnable on the handler, skipping the synchronization barriers.
628      */
postAsyncCallback(Handler handler, Runnable callback)629     public static void postAsyncCallback(Handler handler, Runnable callback) {
630         Message msg = Message.obtain(handler, callback);
631         msg.setAsynchronous(true);
632         handler.sendMessage(msg);
633     }
634 
635     /**
636      * Parses a string encoded using {@link #getPointString(int, int)}
637      */
parsePoint(String point)638     public static Point parsePoint(String point) {
639         String[] split = point.split(",");
640         return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
641     }
642 
643     /**
644      * Encodes a point to string to that it can be persisted atomically.
645      */
getPointString(int x, int y)646     public static String getPointString(int x, int y) {
647         return String.format(Locale.ENGLISH, "%d,%d", x, y);
648     }
649 
unregisterReceiverSafely(Context context, BroadcastReceiver receiver)650     public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) {
651         try {
652             context.unregisterReceiver(receiver);
653         } catch (IllegalArgumentException e) {}
654     }
655 
656     /**
657      * Returns the full drawable for info without any flattening or pre-processing.
658      *
659      * @param outObj this is set to the internal data associated with {@param info},
660      *               eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
661      */
getFullDrawable(Launcher launcher, ItemInfo info, int width, int height, Object[] outObj)662     public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height,
663             Object[] outObj) {
664         Drawable icon = loadFullDrawableWithoutTheme(launcher, info, width, height, outObj);
665         if (icon instanceof BitmapInfo.Extender) {
666             icon = ((BitmapInfo.Extender) icon).getThemedDrawable(launcher);
667         }
668         return icon;
669     }
670 
loadFullDrawableWithoutTheme(Launcher launcher, ItemInfo info, int width, int height, Object[] outObj)671     private static Drawable loadFullDrawableWithoutTheme(Launcher launcher, ItemInfo info,
672             int width, int height, Object[] outObj) {
673         LauncherAppState appState = LauncherAppState.getInstance(launcher);
674         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
675             LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class)
676                     .resolveActivity(info.getIntent(), info.user);
677             outObj[0] = activityInfo;
678             return activityInfo == null ? null : LauncherAppState.getInstance(launcher)
679                     .getIconProvider().getIcon(
680                             activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi);
681         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
682             if (info instanceof PendingAddShortcutInfo) {
683                 ShortcutConfigActivityInfo activityInfo =
684                         ((PendingAddShortcutInfo) info).activityInfo;
685                 outObj[0] = activityInfo;
686                 return activityInfo.getFullResIcon(appState.getIconCache());
687             }
688             List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
689                     .buildRequest(launcher)
690                     .query(ShortcutRequest.ALL);
691             if (si.isEmpty()) {
692                 return null;
693             } else {
694                 outObj[0] = si.get(0);
695                 return ShortcutCachingLogic.getIcon(launcher, si.get(0),
696                         appState.getInvariantDeviceProfile().fillResIconDpi);
697             }
698         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
699             FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
700                     launcher, info.id, new Point(width, height));
701             if (icon == null) {
702                 return null;
703             }
704             outObj[0] = icon;
705             return icon;
706         } else {
707             return null;
708         }
709     }
710 
711     /**
712      * For apps icons and shortcut icons that have badges, this method creates a drawable that can
713      * later on be rendered on top of the layers for the badges. For app icons, work profile badges
714      * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no
715      * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
716      **/
717     @TargetApi(Build.VERSION_CODES.O)
getBadge(Launcher launcher, ItemInfo info, Object obj)718     public static Drawable getBadge(Launcher launcher, ItemInfo info, Object obj) {
719         LauncherAppState appState = LauncherAppState.getInstance(launcher);
720         int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
721         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
722             boolean iconBadged = (info instanceof ItemInfoWithIcon)
723                     && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
724             if ((info.id == ItemInfo.NO_ID && !iconBadged)
725                     || !(obj instanceof ShortcutInfo)) {
726                 // The item is not yet added on home screen.
727                 return new FixedSizeEmptyDrawable(iconSize);
728             }
729             ShortcutInfo si = (ShortcutInfo) obj;
730             Bitmap badge = LauncherAppState.getInstance(appState.getContext())
731                     .getIconCache().getShortcutInfoBadge(si).icon;
732             float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize);
733             float insetFraction = (iconSize - badgeSize) / iconSize;
734             return new InsetDrawable(new FastBitmapDrawable(badge),
735                     insetFraction, insetFraction, 0, 0);
736         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
737             return ((FolderAdaptiveIcon) obj).getBadge();
738         } else {
739             return launcher.getPackageManager()
740                     .getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user);
741         }
742     }
743 
744     /**
745      * @return true is the extra is either null or is of type {@param type}
746      */
isValidExtraType(Intent intent, String key, Class type)747     public static boolean isValidExtraType(Intent intent, String key, Class type) {
748         Object extra = intent.getParcelableExtra(key);
749         return extra == null || type.isInstance(extra);
750     }
751 
squaredHypot(float x, float y)752     public static float squaredHypot(float x, float y) {
753         return x * x + y * y;
754     }
755 
squaredTouchSlop(Context context)756     public static float squaredTouchSlop(Context context) {
757         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
758         return slop * slop;
759     }
760 
761     /**
762      * Helper method to create a content provider
763      */
newContentObserver(Handler handler, Consumer<Uri> command)764     public static ContentObserver newContentObserver(Handler handler, Consumer<Uri> command) {
765         return new ContentObserver(handler) {
766             @Override
767             public void onChange(boolean selfChange, Uri uri) {
768                 command.accept(uri);
769             }
770         };
771     }
772 
773     /**
774      * Compares the ratio of two quantities and returns whether that ratio is greater than the
775      * provided bound. Order of quantities does not matter. Bound should be a decimal representation
776      * of a percentage.
777      */
778     public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
779             float bound) {
780         return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
781     }
782 
783     /**
784      * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
785      * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
786      * the final bounds.
787      */
788     public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
789             int delta) {
790         int rdelta = ((delta % 4) + 4) % 4;
791         int origLeft = inOutBounds.left;
792         switch (rdelta) {
793             case 0:
794                 return;
795             case 1:
796                 inOutBounds.left = inOutBounds.top;
797                 inOutBounds.top = parentWidth - inOutBounds.right;
798                 inOutBounds.right = inOutBounds.bottom;
799                 inOutBounds.bottom = parentWidth - origLeft;
800                 return;
801             case 2:
802                 inOutBounds.left = parentWidth - inOutBounds.right;
803                 inOutBounds.right = parentWidth - origLeft;
804                 return;
805             case 3:
806                 inOutBounds.left = parentHeight - inOutBounds.bottom;
807                 inOutBounds.bottom = inOutBounds.right;
808                 inOutBounds.right = parentHeight - inOutBounds.top;
809                 inOutBounds.top = origLeft;
810                 return;
811         }
812     }
813 
814     /**
815      * Make a color filter that blends a color into the destination based on a scalable amout.
816      *
817      * @param color to blend in.
818      * @param tintAmount [0-1] 0 no tinting, 1 full color.
819      * @return ColorFilter for tinting, or {@code null} if no filter is needed.
820      */
821     public static ColorFilter makeColorTintingColorFilter(int color, float tintAmount) {
822         if (tintAmount == 0f) {
823             return null;
824         }
825         return new LightingColorFilter(
826                 // This isn't blending in white, its making a multiplication mask for the base color
827                 ColorUtils.blendARGB(Color.WHITE, 0, tintAmount),
828                 ColorUtils.blendARGB(0, color, tintAmount));
829     }
830 
831     /**
832      * Sets start margin on the provided {@param view} to be {@param margin}.
833      * Assumes {@param view} is a child of {@link LinearLayout}
834      */
835     public static void setStartMarginForView(View view, int margin) {
836         LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) view.getLayoutParams();
837         lp.setMarginStart(margin);
838         view.setLayoutParams(lp);
839     }
840 
841     private static class FixedSizeEmptyDrawable extends ColorDrawable {
842 
843         private final int mSize;
844 
845         public FixedSizeEmptyDrawable(int size) {
846             super(Color.TRANSPARENT);
847             mSize = size;
848         }
849 
850         @Override
851         public int getIntrinsicHeight() {
852             return mSize;
853         }
854 
855         @Override
856         public int getIntrinsicWidth() {
857             return mSize;
858         }
859     }
860 }
861