• 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.BuildConfig.WIDGET_ON_FIRST_SCREEN;
20 import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
21 import static com.android.launcher3.graphics.ShapeDelegate.DEFAULT_PATH_SIZE;
22 import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
23 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
24 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
25 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
26 
27 import android.annotation.SuppressLint;
28 import android.app.ActivityManager;
29 import android.app.ActivityOptions;
30 import android.app.Person;
31 import android.app.WallpaperManager;
32 import android.content.Context;
33 import android.content.pm.LauncherActivityInfo;
34 import android.content.pm.LauncherApps;
35 import android.content.pm.ShortcutInfo;
36 import android.content.res.Configuration;
37 import android.content.res.Resources;
38 import android.graphics.Color;
39 import android.graphics.ColorFilter;
40 import android.graphics.LightingColorFilter;
41 import android.graphics.Matrix;
42 import android.graphics.Paint;
43 import android.graphics.Path;
44 import android.graphics.Point;
45 import android.graphics.PointF;
46 import android.graphics.Rect;
47 import android.graphics.RectF;
48 import android.graphics.drawable.AdaptiveIconDrawable;
49 import android.graphics.drawable.ColorDrawable;
50 import android.graphics.drawable.Drawable;
51 import android.os.Build;
52 import android.os.Build.VERSION_CODES;
53 import android.os.DeadObjectException;
54 import android.os.Handler;
55 import android.os.Message;
56 import android.os.TransactionTooLargeException;
57 import android.text.Spannable;
58 import android.text.SpannableString;
59 import android.text.TextUtils;
60 import android.text.style.TtsSpan;
61 import android.util.DisplayMetrics;
62 import android.util.Log;
63 import android.util.Pair;
64 import android.util.TypedValue;
65 import android.view.MotionEvent;
66 import android.view.View;
67 import android.view.ViewConfiguration;
68 import android.view.ViewGroup;
69 import android.view.animation.Interpolator;
70 
71 import androidx.annotation.ChecksSdkIntAtLeast;
72 import androidx.annotation.IntDef;
73 import androidx.annotation.NonNull;
74 import androidx.annotation.Nullable;
75 import androidx.annotation.WorkerThread;
76 import androidx.core.graphics.ColorUtils;
77 
78 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
79 import com.android.launcher3.graphics.ThemeManager;
80 import com.android.launcher3.graphics.TintedDrawableSpan;
81 import com.android.launcher3.icons.BitmapInfo;
82 import com.android.launcher3.icons.CacheableShortcutInfo;
83 import com.android.launcher3.icons.IconThemeController;
84 import com.android.launcher3.icons.LauncherIcons;
85 import com.android.launcher3.model.data.ItemInfo;
86 import com.android.launcher3.model.data.ItemInfoWithIcon;
87 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
88 import com.android.launcher3.pm.UserCache;
89 import com.android.launcher3.shortcuts.ShortcutKey;
90 import com.android.launcher3.shortcuts.ShortcutRequest;
91 import com.android.launcher3.testing.shared.ResourceUtils;
92 import com.android.launcher3.util.FlagOp;
93 import com.android.launcher3.util.IntArray;
94 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
95 import com.android.launcher3.views.ActivityContext;
96 import com.android.launcher3.views.BaseDragLayer;
97 import com.android.launcher3.widget.PendingAddShortcutInfo;
98 
99 import java.lang.reflect.Method;
100 import java.util.Collections;
101 import java.util.List;
102 import java.util.Locale;
103 import java.util.Objects;
104 import java.util.function.Predicate;
105 
106 /**
107  * Various utilities shared amongst the Launcher's classes.
108  */
109 public final class Utilities {
110 
111     private static final String TAG = "Launcher.Utilities";
112 
113     private static final String TRIM_PATTERN = "(^\\h+|\\h+$)";
114 
115     private static final Matrix sMatrix = new Matrix();
116     private static final Matrix sInverseMatrix = new Matrix();
117 
118     public static final String[] EMPTY_STRING_ARRAY = new String[0];
119     public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
120 
121     @ChecksSdkIntAtLeast(api = VERSION_CODES.TIRAMISU, codename = "T")
122     public static final boolean ATLEAST_T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
123 
124     @ChecksSdkIntAtLeast(api = VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "U")
125     public static final boolean ATLEAST_U = Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE;
126 
127     @ChecksSdkIntAtLeast(api = VERSION_CODES.VANILLA_ICE_CREAM, codename = "V")
128     public static final boolean ATLEAST_V = Build.VERSION.SDK_INT
129             >= VERSION_CODES.VANILLA_ICE_CREAM;
130 
131     /**
132      * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
133      */
134     public static final int EDGE_NAV_BAR = 1 << 8;
135 
136     /**
137      * Indicates if the device has a debug build. Should only be used to store additional info or
138      * add extra logging and not for changing the app behavior.
139      * @deprecated Use {@link BuildConfig#IS_DEBUG_DEVICE} directly
140      */
141     @Deprecated
142     public static final boolean IS_DEBUG_DEVICE = BuildConfig.IS_DEBUG_DEVICE;
143 
144     public static final int TRANSLATE_UP = 0;
145     public static final int TRANSLATE_DOWN = 1;
146     public static final int TRANSLATE_LEFT = 2;
147     public static final int TRANSLATE_RIGHT = 3;
148 
149     public static final boolean SHOULD_SHOW_FIRST_PAGE_WIDGET =
150             enableSmartspaceAsAWidget() && WIDGET_ON_FIRST_SCREEN;
151 
152     @IntDef({TRANSLATE_UP, TRANSLATE_DOWN, TRANSLATE_LEFT, TRANSLATE_RIGHT})
153     public @interface AdjustmentDirection{}
154 
155     /**
156      * Returns true if theme is dark.
157      */
isDarkTheme(Context context)158     public static boolean isDarkTheme(Context context) {
159         Configuration configuration = context.getResources().getConfiguration();
160         int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
161         return nightMode == Configuration.UI_MODE_NIGHT_YES;
162     }
163 
164     private static boolean sIsRunningInTestHarness = ActivityManager.isRunningInTestHarness();
165 
isRunningInTestHarness()166     public static boolean isRunningInTestHarness() {
167         return sIsRunningInTestHarness;
168     }
169 
enableRunningInTestHarnessForTests()170     public static void enableRunningInTestHarnessForTests() {
171         sIsRunningInTestHarness = true;
172     }
173 
174     /** Disables running test in test harness mode */
disableRunningInTestHarnessForTests()175     public static void disableRunningInTestHarnessForTests() {
176         sIsRunningInTestHarness = false;
177     }
178 
isPropertyEnabled(String propertyName)179     public static boolean isPropertyEnabled(String propertyName) {
180         return Log.isLoggable(propertyName, Log.VERBOSE);
181     }
182 
183     /**
184      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
185      * coordinates.
186      *
187      * @param descendant The descendant to which the passed coordinate is relative.
188      * @param ancestor The root view to make the coordinates relative to.
189      * @param coord The coordinate that we want mapped.
190      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
191      *          sometimes this is relevant as in a child's coordinates within the descendant.
192      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
193      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
194      *         assumption fails, we will need to return a pair of scale factors.
195      */
getDescendantCoordRelativeToAncestor( View descendant, View ancestor, float[] coord, boolean includeRootScroll)196     public static float getDescendantCoordRelativeToAncestor(
197             View descendant, View ancestor, float[] coord, boolean includeRootScroll) {
198         return getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, includeRootScroll,
199                 false);
200     }
201 
202     /**
203      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
204      * coordinates.
205      *
206      * @param descendant The descendant to which the passed coordinate is relative.
207      * @param ancestor The root view to make the coordinates relative to.
208      * @param coord The coordinate that we want mapped.
209      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
210      *          sometimes this is relevant as in a child's coordinates within the descendant.
211      * @param ignoreTransform If true, view transform is ignored
212      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
213      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
214      *         assumption fails, we will need to return a pair of scale factors.
215      */
getDescendantCoordRelativeToAncestor(View descendant, View ancestor, float[] coord, boolean includeRootScroll, boolean ignoreTransform)216     public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor,
217             float[] coord, boolean includeRootScroll, boolean ignoreTransform) {
218         float scale = 1.0f;
219         View v = descendant;
220         while(v != ancestor && v != null) {
221             // For TextViews, scroll has a meaning which relates to the text position
222             // which is very strange... ignore the scroll.
223             if (v != descendant || includeRootScroll) {
224                 offsetPoints(coord, -v.getScrollX(), -v.getScrollY());
225             }
226 
227             if (!ignoreTransform) {
228                 v.getMatrix().mapPoints(coord);
229             }
230             offsetPoints(coord, v.getLeft(), v.getTop());
231             scale *= v.getScaleX();
232 
233             v = v.getParent() instanceof View ? (View) v.getParent() : null;
234         }
235         return scale;
236     }
237 
238     /**
239      * Returns bounds for a child view of DragLayer, in drag layer coordinates.
240      *
241      * see {@link com.android.launcher3.dragndrop.DragLayer}.
242      *
243      * @param viewBounds Bounds of the view wanted in drag layer coordinates, relative to the view
244      *                   itself. eg. (0, 0, view.getWidth, view.getHeight)
245      * @param ignoreTransform If true, view transform is ignored
246      * @param outRect The out rect where we return the bounds of {@param view} in drag layer coords.
247      */
getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view, Rect viewBounds, boolean ignoreTransform, float[] recycle, RectF outRect)248     public static void getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view,
249             Rect viewBounds, boolean ignoreTransform, float[] recycle, RectF outRect) {
250         float[] points = recycle == null ? new float[4] : recycle;
251         points[0] = viewBounds.left;
252         points[1] = viewBounds.top;
253         points[2] = viewBounds.right;
254         points[3] = viewBounds.bottom;
255 
256         Utilities.getDescendantCoordRelativeToAncestor(view, dragLayer, points,
257                 false, ignoreTransform);
258         outRect.set(
259                 Math.min(points[0], points[2]),
260                 Math.min(points[1], points[3]),
261                 Math.max(points[0], points[2]),
262                 Math.max(points[1], points[3]));
263     }
264 
265     /**
266      * Similar to {@link #mapCoordInSelfToDescendant(View descendant, View root, float[] coord)}
267      * but accepts a Rect instead of float[].
268      */
mapRectInSelfToDescendant(View descendant, View root, Rect rect)269     public static void mapRectInSelfToDescendant(View descendant, View root, Rect rect) {
270         float[] coords = new float[]{rect.left, rect.top, rect.right, rect.bottom};
271         mapCoordInSelfToDescendant(descendant, root, coords);
272         rect.set((int) coords[0], (int) coords[1], (int) coords[2], (int) coords[3]);
273     }
274 
275     /**
276      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
277      */
mapCoordInSelfToDescendant(View descendant, View root, float[] coord)278     public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
279         sMatrix.reset();
280         //TODO(b/307488755) when implemented this check should be removed
281         if (!Objects.equals(descendant.getWindowId(), root.getWindowId())) {
282             return;
283         }
284         View v = descendant;
285         while (v != root) {
286             sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
287             sMatrix.postConcat(v.getMatrix());
288             sMatrix.postTranslate(v.getLeft(), v.getTop());
289             v = (View) v.getParent();
290         }
291         sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
292         sMatrix.invert(sInverseMatrix);
293         sInverseMatrix.mapPoints(coord);
294     }
295 
296     /**
297      * Sets {@param out} to be same as {@param in} by rounding individual values
298      */
roundArray(float[] in, int[] out)299     public static void roundArray(float[] in, int[] out) {
300         for (int i = 0; i < in.length; i++) {
301             out[i] = Math.round(in[i]);
302         }
303     }
304 
offsetPoints(float[] points, float offsetX, float offsetY)305     public static void offsetPoints(float[] points, float offsetX, float offsetY) {
306         for (int i = 0; i < points.length; i += 2) {
307             points[i] += offsetX;
308             points[i + 1] += offsetY;
309         }
310     }
311 
312     /**
313      * Utility method to determine whether the given point, in local coordinates,
314      * is inside the view, where the area of the view is expanded by the slop factor.
315      * This method is called while processing touch-move events to determine if the event
316      * is still within the view.
317      */
pointInView(View v, float localX, float localY, float slop)318     public static boolean pointInView(View v, float localX, float localY, float slop) {
319         return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
320                 localY < (v.getHeight() + slop);
321     }
322 
scaleRectFAboutCenter(RectF r, float scale)323     public static void scaleRectFAboutCenter(RectF r, float scale) {
324         scaleRectFAboutCenter(r, scale, scale);
325     }
326 
327     /**
328      * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows different scales
329      * for X and Y
330      */
scaleRectFAboutCenter(RectF r, float scaleX, float scaleY)331     public static void scaleRectFAboutCenter(RectF r, float scaleX, float scaleY) {
332         float px = r.centerX();
333         float py = r.centerY();
334         r.offset(-px, -py);
335         r.left = r.left * scaleX;
336         r.top = r.top * scaleY;
337         r.right = r.right * scaleX;
338         r.bottom = r.bottom * scaleY;
339         r.offset(px, py);
340     }
341 
scaleRectAboutCenter(Rect r, float scale)342     public static void scaleRectAboutCenter(Rect r, float scale) {
343         if (scale != 1.0f) {
344             float cx = r.exactCenterX();
345             float cy = r.exactCenterY();
346             r.left = Math.round(cx + (r.left - cx) * scale);
347             r.top = Math.round(cy + (r.top - cy) * scale);
348             r.right = Math.round(cx + (r.right - cx) * scale);
349             r.bottom = Math.round(cy + (r.bottom - cy) * scale);
350         }
351     }
352 
shrinkRect(Rect r, float scaleX, float scaleY)353     public static float shrinkRect(Rect r, float scaleX, float scaleY) {
354         float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
355         if (scale < 1.0f) {
356             int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
357             r.left += deltaX;
358             r.right -= deltaX;
359 
360             int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
361             r.top += deltaY;
362             r.bottom -= deltaY;
363         }
364         return scale;
365     }
366 
367     /**
368      * Sets the x and y pivots for scaling from one Rect to another.
369      *
370      * @param src the source rectangle to scale from.
371      * @param dst the destination rectangle to scale to.
372      * @param outPivot the pivots set for scaling from src to dst.
373      */
getPivotsForScalingRectToRect(Rect src, Rect dst, PointF outPivot)374     public static void getPivotsForScalingRectToRect(Rect src, Rect dst, PointF outPivot) {
375         float pivotXPct = ((float) src.left - dst.left) / ((float) dst.width() - src.width());
376         outPivot.x = dst.left + dst.width() * pivotXPct;
377 
378         float pivotYPct = ((float) src.top - dst.top) / ((float) dst.height() - src.height());
379         outPivot.y = dst.top + dst.height() * pivotYPct;
380     }
381 
382     /**
383      * Scales a {@code RectF} in place about a specified pivot point.
384      *
385      * <p>This method modifies the given {@code RectF} directly to scale it proportionally
386      * by the given {@code scale}, while preserving its center at the specified
387      * {@code (pivotX, pivotY)} coordinates.
388      *
389      * @param rectF the {@code RectF} to scale, modified directly.
390      * @param pivotX the x-coordinate of the pivot point about which to scale.
391      * @param pivotY the y-coordinate of the pivot point about which to scale.
392      * @param scale the factor by which to scale the rectangle. Values less than 1 will
393      *                    shrink the rectangle, while values greater than 1 will enlarge it.
394      */
scaleRectFAboutPivot(RectF rectF, float pivotX, float pivotY, float scale)395     public static void scaleRectFAboutPivot(RectF rectF, float pivotX, float pivotY, float scale) {
396         rectF.offset(-pivotX, -pivotY);
397         rectF.left *= scale;
398         rectF.top *= scale;
399         rectF.right *= scale;
400         rectF.bottom *= scale;
401         rectF.offset(pivotX, pivotY);
402     }
403 
404     /**
405      * Maps t from one range to another range.
406      * @param t The value to map.
407      * @param fromMin The lower bound of the range that t is being mapped from.
408      * @param fromMax The upper bound of the range that t is being mapped from.
409      * @param toMin The lower bound of the range that t is being mapped to.
410      * @param toMax The upper bound of the range that t is being mapped to.
411      * @return The mapped value of t.
412      */
mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax, Interpolator interpolator)413     public static float mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax,
414             Interpolator interpolator) {
415         if (fromMin == fromMax || toMin == toMax) {
416             Log.e(TAG, "mapToRange: range has 0 length");
417             return toMin;
418         }
419         float progress = getProgress(t, fromMin, fromMax);
420         return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
421     }
422 
423     /**
424      * Maps t from one range to another range.
425      * @param t The value to map.
426      * @param fromMin The lower bound of the range that t is being mapped from.
427      * @param fromMax The upper bound of the range that t is being mapped from.
428      * @param toMin The lower bound of the range that t is being mapped to.
429      * @param toMax The upper bound of the range that t is being mapped to.
430      * @return The mapped value of t.
431      */
mapToRange(int t, int fromMin, int fromMax, int toMin, int toMax, Interpolator interpolator)432     public static int mapToRange(int t, int fromMin, int fromMax, int toMin, int toMax,
433             Interpolator interpolator) {
434         if (fromMin == fromMax || toMin == toMax) {
435             Log.e(TAG, "mapToRange: range has 0 length");
436             return toMin;
437         }
438         float progress = getProgress(t, fromMin, fromMax);
439         return (int) mapRange(interpolator.getInterpolation(progress), toMin, toMax);
440     }
441 
442     /** 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)443     public static float mapBoundToRange(float t, float lowerBound, float upperBound,
444             float toMin, float toMax, Interpolator interpolator) {
445         return mapToRange(boundToRange(t, lowerBound, upperBound), lowerBound, upperBound,
446                 toMin, toMax, interpolator);
447     }
448 
getProgress(float current, float min, float max)449     public static float getProgress(float current, float min, float max) {
450         return Math.abs(current - min) / Math.abs(max - min);
451     }
452 
mapRange(float value, float min, float max)453     public static float mapRange(float value, float min, float max) {
454         return min + (value * (max - min));
455     }
456 
457     /**
458      * Trims the string, removing all whitespace at the beginning and end of the string.
459      * Non-breaking whitespaces are also removed.
460      */
461     @NonNull
trim(CharSequence s)462     public static String trim(CharSequence s) {
463         if (s == null) {
464             return "";
465         }
466         return s.toString().replaceAll(TRIM_PATTERN, "").trim();
467     }
468 
469     /**
470      * Calculates the height of a given string at a specific text size.
471      */
calculateTextHeight(float textSizePx)472     public static int calculateTextHeight(float textSizePx) {
473         Paint p = new Paint();
474         p.setTextSize(textSizePx);
475         Paint.FontMetrics fm = p.getFontMetrics();
476         return (int) Math.ceil(fm.bottom - fm.top);
477     }
478 
isRtl(Resources res)479     public static boolean isRtl(Resources res) {
480         return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
481     }
482 
483     /** Converts a pixel value (px) to scale pixel value (SP) for the current device. */
pxToSp(float size)484     public static float pxToSp(float size) {
485         return size / Resources.getSystem().getDisplayMetrics().scaledDensity;
486     }
487 
dpiFromPx(float size, int densityDpi)488     public static float dpiFromPx(float size, int densityDpi) {
489         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
490         return (size / densityRatio);
491     }
492 
493     /** Converts a dp value to pixels for the current device. */
dpToPx(float dp)494     public static int dpToPx(float dp) {
495         return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
496     }
497 
498     /** Converts a dp value to pixels for a certain density. */
dpToPx(float dp, int densityDpi)499     public static int dpToPx(float dp, int densityDpi) {
500         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
501         return (int) (dp * densityRatio);
502     }
503 
pxFromSp(float size, DisplayMetrics metrics)504     public static int pxFromSp(float size, DisplayMetrics metrics) {
505         return pxFromSp(size, metrics, 1f);
506     }
507 
pxFromSp(float size, DisplayMetrics metrics, float scale)508     public static int pxFromSp(float size, DisplayMetrics metrics, float scale) {
509         float value = scale * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, metrics);
510         return ResourceUtils.roundPxValueFromFloat(value);
511     }
512 
createDbSelectionQuery(String columnName, IntArray values)513     public static String createDbSelectionQuery(String columnName, IntArray values) {
514         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, values.toConcatString());
515     }
516 
isBootCompleted()517     public static boolean isBootCompleted() {
518         return "1".equals(getSystemProperty("sys.boot_completed", "1"));
519     }
520 
getSystemProperty(String property, String defaultValue)521     public static String getSystemProperty(String property, String defaultValue) {
522         try {
523             Class clazz = Class.forName("android.os.SystemProperties");
524             Method getter = clazz.getDeclaredMethod("get", String.class);
525             String value = (String) getter.invoke(null, property);
526             if (!TextUtils.isEmpty(value)) {
527                 return value;
528             }
529         } catch (Exception e) {
530             Log.d(TAG, "Unable to read system properties");
531         }
532         return defaultValue;
533     }
534 
535     /**
536      * Ensures that a value is within given bounds. Specifically:
537      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
538      * return upperBound; else return value unchanged.
539      */
boundToRange(int value, int lowerBound, int upperBound)540     public static int boundToRange(int value, int lowerBound, int upperBound) {
541         return Math.max(lowerBound, Math.min(value, upperBound));
542     }
543 
544     /**
545      * @see #boundToRange(int, int, int).
546      */
boundToRange(float value, float lowerBound, float upperBound)547     public static float boundToRange(float value, float lowerBound, float upperBound) {
548         return Math.max(lowerBound, Math.min(value, upperBound));
549     }
550 
551     /**
552      * @see #boundToRange(int, int, int).
553      */
boundToRange(long value, long lowerBound, long upperBound)554     public static long boundToRange(long value, long lowerBound, long upperBound) {
555         return Math.max(lowerBound, Math.min(value, upperBound));
556     }
557 
558     /**
559      * Wraps a message with a TTS span, so that a different message is spoken than
560      * what is getting displayed.
561      * @param msg original message
562      * @param ttsMsg message to be spoken
563      */
wrapForTts(CharSequence msg, String ttsMsg)564     public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) {
565         SpannableString spanned = new SpannableString(msg);
566         spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
567                 0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
568         return spanned;
569     }
570 
571     /**
572      * Prefixes a text with the provided icon
573      */
prefixTextWithIcon(Context context, int iconRes, CharSequence msg)574     public static CharSequence prefixTextWithIcon(Context context, int iconRes, CharSequence msg) {
575         // Update the hint to contain the icon.
576         // Prefix the original hint with two spaces. The first space gets replaced by the icon
577         // using span. The second space is used for a singe space character between the hint
578         // and the icon.
579         SpannableString spanned = new SpannableString("  " + msg);
580         spanned.setSpan(new TintedDrawableSpan(context, iconRes),
581                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
582         return spanned;
583     }
584 
isWallpaperSupported(Context context)585     public static boolean isWallpaperSupported(Context context) {
586         return context.getSystemService(WallpaperManager.class).isWallpaperSupported();
587     }
588 
isWallpaperAllowed(Context context)589     public static boolean isWallpaperAllowed(Context context) {
590         return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
591     }
592 
isBinderSizeError(Exception e)593     public static boolean isBinderSizeError(Exception e) {
594         return e.getCause() instanceof TransactionTooLargeException
595                 || e.getCause() instanceof DeadObjectException;
596     }
597 
598     /**
599      * Utility method to post a runnable on the handler, skipping the synchronization barriers.
600      */
postAsyncCallback(Handler handler, Runnable callback)601     public static void postAsyncCallback(Handler handler, Runnable callback) {
602         Message msg = Message.obtain(handler, callback);
603         msg.setAsynchronous(true);
604         handler.sendMessage(msg);
605     }
606 
607     /**
608      * Utility method to allow background activity launch for the provided activity options
609      */
allowBGLaunch(ActivityOptions options)610     public static ActivityOptions allowBGLaunch(ActivityOptions options) {
611         if (ATLEAST_U) {
612             options.setPendingIntentBackgroundActivityStartMode(
613                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
614         }
615         return options;
616     }
617 
618     /**
619      * Utility method to know if a device's primary language is English.
620      */
isEnglishLanguage(Context context)621     public static boolean isEnglishLanguage(Context context) {
622         return context.getResources().getConfiguration().locale.getLanguage()
623                 .equals(Locale.ENGLISH.getLanguage());
624     }
625 
626     /**
627      * Returns the full drawable for info as multiple layers of AdaptiveIconDrawable. The second
628      * drawable in the Pair is the badge used with the icon.
629      *
630      * @param useTheme If true, will theme icons when applicable
631      */
632     @SuppressLint("UseCompatLoadingForDrawables")
633     @Nullable
634     @WorkerThread
635     public static <T extends Context & ActivityContext> Pair<AdaptiveIconDrawable, Drawable>
getFullDrawable(T context, ItemInfo info, int width, int height, boolean useTheme)636             getFullDrawable(T context, ItemInfo info, int width, int height, boolean useTheme) {
637         LauncherAppState appState = LauncherAppState.getInstance(context);
638         Drawable mainIcon = null;
639 
640         Drawable badge = null;
641         if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.getMatchingLookupFlag().useLowRes()) {
642             badge = iiwi.bitmap.getBadgeDrawable(context, useTheme, getIconShapeOrNull(context));
643         }
644 
645         if (info instanceof PendingAddShortcutInfo) {
646             ShortcutConfigActivityInfo activityInfo =
647                     ((PendingAddShortcutInfo) info).getActivityInfo(context);
648             mainIcon = activityInfo.getFullResIcon(appState.getIconCache());
649         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
650             LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
651                     .resolveActivity(info.getIntent(), info.user);
652             if (activityInfo == null) {
653                 return null;
654             }
655             mainIcon = appState.getIconCache().getFullResIcon(activityInfo.getActivityInfo());
656         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
657             List<ShortcutInfo> siList = ShortcutKey.fromItemInfo(info)
658                     .buildRequest(context)
659                     .query(ShortcutRequest.ALL);
660             if (siList.isEmpty()) {
661                 return null;
662             } else {
663                 ShortcutInfo si = siList.get(0);
664                 mainIcon = CacheableShortcutInfo.getIcon(context, si,
665                         appState.getInvariantDeviceProfile().fillResIconDpi);
666                 // Only fetch badge if the icon is on workspace
667                 if (info.id != ItemInfo.NO_ID && badge == null) {
668                     badge = appState.getIconCache().getShortcutInfoBadge(si).newIcon(
669                             context,
670                             ThemeManager.INSTANCE.get(context).isIconThemeEnabled()
671                                     ? FLAG_THEMED : 0,
672                             getIconShapeOrNull(context)
673                     );
674                 }
675             }
676         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
677             FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
678                     context, info.id, new Point(width, height));
679             if (icon == null) {
680                 return null;
681             }
682             mainIcon =  icon;
683             badge = icon.getBadge();
684         }
685 
686         if (mainIcon == null) {
687             return null;
688         }
689         AdaptiveIconDrawable result;
690         if (mainIcon instanceof AdaptiveIconDrawable aid) {
691             result = aid;
692         } else {
693             // Wrap the main icon in AID
694             try (LauncherIcons li = LauncherIcons.obtain(context)) {
695                 result = li.wrapToAdaptiveIcon(mainIcon);
696             }
697         }
698         if (result == null) {
699             return null;
700         }
701 
702         // Inject theme icon drawable
703         if (ATLEAST_T && useTheme) {
704             IconThemeController themeController =
705                     ThemeManager.INSTANCE.get(context).getThemeController();
706             if (themeController != null) {
707                 AdaptiveIconDrawable themed = themeController.createThemedAdaptiveIcon(
708                         context,
709                         result,
710                         info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null);
711                 if (themed != null) {
712                     result = themed;
713                 }
714             }
715         }
716 
717         if (badge == null) {
718             badge = BitmapInfo.LOW_RES_INFO.withFlags(
719                     UserCache.INSTANCE.get(context)
720                             .getUserInfo(info.user)
721                             .applyBitmapInfoFlags(FlagOp.NO_OP)
722                     )
723                     .getBadgeDrawable(context, useTheme, getIconShapeOrNull(context));
724             if (badge == null) {
725                 badge = new ColorDrawable(Color.TRANSPARENT);
726             }
727         }
728         return Pair.create(result, badge);
729     }
730 
squaredHypot(float x, float y)731     public static float squaredHypot(float x, float y) {
732         return x * x + y * y;
733     }
734 
squaredTouchSlop(Context context)735     public static float squaredTouchSlop(Context context) {
736         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
737         return slop * slop;
738     }
739 
740     /**
741      * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CW. Parent
742      * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
743      * the final bounds.
744      *
745      * As an example if this is the input:
746      * +-------------+
747      * |   +-----+   |
748      * |   |     |   |
749      * |   +-----+   |
750      * |             |
751      * |             |
752      * |             |
753      * +-------------+
754      * This would be case delta % 4 == 0:
755      * +-------------+
756      * |   +-----+   |
757      * |   |     |   |
758      * |   +-----+   |
759      * |             |
760      * |             |
761      * |             |
762      * +-------------+
763      * This would be case delta % 4 == 1:
764      * +----------------+
765      * |          +--+  |
766      * |          |  |  |
767      * |          |  |  |
768      * |          +--+  |
769      * |                |
770      * +----------------+
771      * This would be case delta % 4 == 2: // This is case was reverted to previous behaviour which
772      * doesn't match the illustration due to b/353965234
773      * +-------------+
774      * |             |
775      * |             |
776      * |             |
777      * |   +-----+   |
778      * |   |     |   |
779      * |   +-----+   |
780      * +-------------+
781      * This would be case delta % 4 == 3:
782      * +----------------+
783      * |  +--+          |
784      * |  |  |          |
785      * |  |  |          |
786      * |  +--+          |
787      * |                |
788      * +----------------+
789      */
rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight, int delta)790     public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
791             int delta) {
792         int rdelta = ((delta % 4) + 4) % 4;
793         int origLeft = inOutBounds.left;
794         switch (rdelta) {
795             case 0:
796                 return;
797             case 1:
798                 inOutBounds.left = inOutBounds.top;
799                 inOutBounds.top = parentWidth - inOutBounds.right;
800                 inOutBounds.right = inOutBounds.bottom;
801                 inOutBounds.bottom = parentWidth - origLeft;
802                 return;
803             case 2:
804                 inOutBounds.left = parentWidth - inOutBounds.right;
805                 inOutBounds.right = parentWidth - origLeft;
806                 return;
807             case 3:
808                 inOutBounds.left = parentHeight - inOutBounds.bottom;
809                 inOutBounds.bottom = inOutBounds.right;
810                 inOutBounds.right = parentHeight - inOutBounds.top;
811                 inOutBounds.top = origLeft;
812                 return;
813         }
814     }
815 
816     /**
817      * Make a color filter that blends a color into the destination based on a scalable amout.
818      *
819      * @param color to blend in.
820      * @param tintAmount [0-1] 0 no tinting, 1 full color.
821      * @return ColorFilter for tinting, or {@code null} if no filter is needed.
822      */
makeColorTintingColorFilter(int color, float tintAmount)823     public static ColorFilter makeColorTintingColorFilter(int color, float tintAmount) {
824         if (tintAmount == 0f) {
825             return null;
826         }
827         return new LightingColorFilter(
828                 // This isn't blending in white, its making a multiplication mask for the base color
829                 ColorUtils.blendARGB(Color.WHITE, 0, tintAmount),
830                 ColorUtils.blendARGB(0, color, tintAmount));
831     }
832 
getViewBounds(@onNull View v)833     public static Rect getViewBounds(@NonNull View v) {
834         int[] pos = new int[2];
835         v.getLocationOnScreen(pos);
836         return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
837     }
838 
839     /**
840      * Returns a list of screen-splitting options depending on the device orientation (split top for
841      * portrait, split right for landscape)
842      */
getSplitPositionOptions( DeviceProfile dp)843     public static List<SplitPositionOption> getSplitPositionOptions(
844             DeviceProfile dp) {
845         int splitIconRes = dp.isLeftRightSplit
846                 ? R.drawable.ic_split_horizontal
847                 : R.drawable.ic_split_vertical;
848         int stagePosition = dp.isLeftRightSplit
849                 ? STAGE_POSITION_BOTTOM_OR_RIGHT
850                 : STAGE_POSITION_TOP_OR_LEFT;
851         return Collections.singletonList(new SplitPositionOption(
852                 splitIconRes,
853                 R.string.recent_task_option_split_screen,
854                 stagePosition,
855                 STAGE_TYPE_MAIN
856         ));
857     }
858 
859     /** Logs the Scale and Translate properties of a matrix. Ignores skew and perspective. */
logMatrix(String label, Matrix matrix)860     public static void logMatrix(String label, Matrix matrix) {
861         float[] matrixValues = new float[9];
862         matrix.getValues(matrixValues);
863         Log.d(label, String.format("%s: %s\nscale (x,y) = (%f, %f)\ntranslate (x,y) = (%f, %f)",
864                 label, matrix, matrixValues[Matrix.MSCALE_X], matrixValues[Matrix.MSCALE_Y],
865                 matrixValues[Matrix.MTRANS_X], matrixValues[Matrix.MTRANS_Y]
866         ));
867     }
868 
869     /**
870      * Translates the {@code targetView} so that it overlaps with {@code exclusionBounds} as little
871      * as possible, while remaining within {@code inclusionBounds}.
872      * <p>
873      * {@code inclusionBounds} will always take precedence over {@code exclusionBounds}, so if
874      * {@code targetView} needs to be translated outside of {@code inclusionBounds} to fully fix an
875      * overlap with {@code exclusionBounds}, then {@code targetView} will only be translated up to
876      * the border of {@code inclusionBounds}.
877      * <p>
878      * Note: {@code targetViewBounds}, {@code inclusionBounds} and {@code exclusionBounds} must all
879      * be in relation to the same reference point on screen.
880      * <p>
881      * @param targetView the view being translated
882      * @param targetViewBounds the bounds of the {@code targetView}
883      * @param inclusionBounds the bounds the {@code targetView} absolutely must stay within
884      * @param exclusionBounds the bounds to try to move the {@code targetView} away from
885      * @param adjustmentDirection the translation direction that should be attempted to fix an
886      *                            overlap
887      */
translateOverlappingView( @onNull View targetView, @NonNull Rect targetViewBounds, @NonNull Rect inclusionBounds, @NonNull Rect exclusionBounds, @AdjustmentDirection int adjustmentDirection)888     public static void translateOverlappingView(
889             @NonNull View targetView,
890             @NonNull Rect targetViewBounds,
891             @NonNull Rect inclusionBounds,
892             @NonNull Rect exclusionBounds,
893             @AdjustmentDirection int adjustmentDirection) {
894         if (!Rect.intersects(targetViewBounds, exclusionBounds)) {
895             return;
896         }
897         switch (adjustmentDirection) {
898             case TRANSLATE_RIGHT:
899                 targetView.setTranslationX(Math.min(
900                         // Translate to the right if the view is overlapping on the left.
901                         Math.max(0, exclusionBounds.right - targetViewBounds.left),
902                         // Do not translate beyond the inclusion bounds.
903                         inclusionBounds.right - targetViewBounds.right));
904                 break;
905             case TRANSLATE_LEFT:
906                 targetView.setTranslationX(Math.max(
907                         // Translate to the left if the view is overlapping on the right.
908                         Math.min(0, exclusionBounds.left - targetViewBounds.right),
909                         // Do not translate beyond the inclusion bounds.
910                         inclusionBounds.left - targetViewBounds.left));
911                 break;
912             case TRANSLATE_DOWN:
913                 targetView.setTranslationY(Math.min(
914                         // Translate downwards if the view is overlapping on the top.
915                         Math.max(0, exclusionBounds.bottom - targetViewBounds.top),
916                         // Do not translate beyond the inclusion bounds.
917                         inclusionBounds.bottom - targetViewBounds.bottom));
918                 break;
919             case TRANSLATE_UP:
920                 targetView.setTranslationY(Math.max(
921                         // Translate upwards if the view is overlapping on the bottom.
922                         Math.min(0, exclusionBounds.top - targetViewBounds.bottom),
923                         // Do not translate beyond the inclusion bounds.
924                         inclusionBounds.top - targetViewBounds.top));
925                 break;
926             default:
927                 // No-Op
928         }
929     }
930 
931     /**
932      * Does a depth-first search through the View hierarchy starting at root, to find a view that
933      * matches the predicate. Returns null if no View was found. View has a findViewByPredicate
934      * member function but it is currently a @hide API.
935      */
936     @Nullable
findViewByPredicate(@onNull View root, @NonNull Predicate<View> predicate)937     public static <T extends View> T findViewByPredicate(@NonNull View root,
938             @NonNull Predicate<View> predicate) {
939         if (predicate.test(root)) {
940             return (T) root;
941         }
942         if (root instanceof ViewGroup parent) {
943             int count = parent.getChildCount();
944             for (int i = 0; i < count; i++) {
945                 View view = findViewByPredicate(parent.getChildAt(i), predicate);
946                 if (view != null) {
947                     return (T) view;
948                 }
949             }
950         }
951         return null;
952     }
953 
954     /**
955      * Returns current icon shape to use for badges if flag is on, otherwise null.
956      */
957     @Nullable
getIconShapeOrNull(Context context)958     public static Path getIconShapeOrNull(Context context) {
959         if (Flags.enableLauncherIconShapes()) {
960             return ThemeManager.INSTANCE.get(context)
961                     .getIconShape()
962                     .getPath(DEFAULT_PATH_SIZE);
963         } else {
964             return null;
965         }
966     }
967 
968     /**
969      * Logs with DEBUG priority if the current device is a debug device.
970      *
971      * <p>Debug devices by default include -eng and -userdebug builds, but not -user builds.
972      */
debugLog(String tag, String message)973     public static void debugLog(String tag, String message) {
974         if (BuildConfig.IS_DEBUG_DEVICE) {
975             Log.d(tag, message);
976         }
977     }
978 }
979