• 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.icons.BitmapInfo.FLAG_THEMED;
20 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ICON_BADGED;
21 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
22 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
23 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
24 
25 import android.annotation.TargetApi;
26 import android.app.ActivityManager;
27 import android.app.Person;
28 import android.app.WallpaperManager;
29 import android.content.Context;
30 import android.content.pm.LauncherActivityInfo;
31 import android.content.pm.LauncherApps;
32 import android.content.pm.ShortcutInfo;
33 import android.content.res.Configuration;
34 import android.content.res.Resources;
35 import android.graphics.Color;
36 import android.graphics.ColorFilter;
37 import android.graphics.LightingColorFilter;
38 import android.graphics.Matrix;
39 import android.graphics.Paint;
40 import android.graphics.Point;
41 import android.graphics.PointF;
42 import android.graphics.Rect;
43 import android.graphics.RectF;
44 import android.graphics.drawable.AdaptiveIconDrawable;
45 import android.graphics.drawable.ColorDrawable;
46 import android.graphics.drawable.Drawable;
47 import android.os.Build;
48 import android.os.Build.VERSION_CODES;
49 import android.os.DeadObjectException;
50 import android.os.Handler;
51 import android.os.Message;
52 import android.os.Process;
53 import android.os.TransactionTooLargeException;
54 import android.provider.Settings;
55 import android.text.Spannable;
56 import android.text.SpannableString;
57 import android.text.TextUtils;
58 import android.text.style.TtsSpan;
59 import android.util.DisplayMetrics;
60 import android.util.Log;
61 import android.util.TypedValue;
62 import android.view.MotionEvent;
63 import android.view.View;
64 import android.view.ViewConfiguration;
65 import android.view.animation.Interpolator;
66 
67 import androidx.annotation.ChecksSdkIntAtLeast;
68 import androidx.annotation.IntDef;
69 import androidx.annotation.NonNull;
70 import androidx.core.graphics.ColorUtils;
71 
72 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
73 import com.android.launcher3.graphics.TintedDrawableSpan;
74 import com.android.launcher3.icons.ShortcutCachingLogic;
75 import com.android.launcher3.icons.ThemedIconDrawable;
76 import com.android.launcher3.model.data.ItemInfo;
77 import com.android.launcher3.model.data.ItemInfoWithIcon;
78 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
79 import com.android.launcher3.shortcuts.ShortcutKey;
80 import com.android.launcher3.shortcuts.ShortcutRequest;
81 import com.android.launcher3.testing.shared.ResourceUtils;
82 import com.android.launcher3.util.IntArray;
83 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
84 import com.android.launcher3.util.Themes;
85 import com.android.launcher3.views.ActivityContext;
86 import com.android.launcher3.views.BaseDragLayer;
87 import com.android.launcher3.widget.PendingAddShortcutInfo;
88 
89 import java.lang.reflect.Method;
90 import java.util.Collections;
91 import java.util.List;
92 import java.util.Locale;
93 import java.util.regex.Matcher;
94 import java.util.regex.Pattern;
95 
96 /**
97  * Various utilities shared amongst the Launcher's classes.
98  */
99 public final class Utilities {
100 
101     private static final String TAG = "Launcher.Utilities";
102 
103     private static final Pattern sTrimPattern =
104             Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
105 
106     private static final Matrix sMatrix = new Matrix();
107     private static final Matrix sInverseMatrix = new Matrix();
108 
109     public static final String[] EMPTY_STRING_ARRAY = new String[0];
110     public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
111 
112     @ChecksSdkIntAtLeast(api = VERSION_CODES.P)
113     public static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
114 
115     @ChecksSdkIntAtLeast(api = VERSION_CODES.Q)
116     public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
117 
118     @ChecksSdkIntAtLeast(api = VERSION_CODES.R)
119     public static final boolean ATLEAST_R = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
120 
121     @ChecksSdkIntAtLeast(api = VERSION_CODES.S)
122     public static final boolean ATLEAST_S = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
123 
124     @ChecksSdkIntAtLeast(api = VERSION_CODES.TIRAMISU, codename = "T")
125     public static final boolean ATLEAST_T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
126 
127     /**
128      * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
129      */
130     public static final int EDGE_NAV_BAR = 1 << 8;
131 
132     /**
133      * Indicates if the device has a debug build. Should only be used to store additional info or
134      * add extra logging and not for changing the app behavior.
135      * @deprecated Use {@link BuildConfig#IS_DEBUG_DEVICE} directly
136      */
137     @Deprecated
138     public static final boolean IS_DEBUG_DEVICE = BuildConfig.IS_DEBUG_DEVICE;
139 
140     public static final int TRANSLATE_UP = 0;
141     public static final int TRANSLATE_DOWN = 1;
142     public static final int TRANSLATE_LEFT = 2;
143     public static final int TRANSLATE_RIGHT = 3;
144 
145     @IntDef({TRANSLATE_UP, TRANSLATE_DOWN, TRANSLATE_LEFT, TRANSLATE_RIGHT})
146     public @interface AdjustmentDirection{}
147 
148     /**
149      * Returns true if theme is dark.
150      */
isDarkTheme(Context context)151     public static boolean isDarkTheme(Context context) {
152         Configuration configuration = context.getResources().getConfiguration();
153         int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
154         return nightMode == Configuration.UI_MODE_NIGHT_YES;
155     }
156 
isDevelopersOptionsEnabled(Context context)157     public static boolean isDevelopersOptionsEnabled(Context context) {
158         return Settings.Global.getInt(context.getApplicationContext().getContentResolver(),
159                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
160     }
161 
162     private static boolean sIsRunningInTestHarness = ActivityManager.isRunningInTestHarness();
163 
isRunningInTestHarness()164     public static boolean isRunningInTestHarness() {
165         return sIsRunningInTestHarness;
166     }
167 
enableRunningInTestHarnessForTests()168     public static void enableRunningInTestHarnessForTests() {
169         sIsRunningInTestHarness = true;
170     }
171 
isPropertyEnabled(String propertyName)172     public static boolean isPropertyEnabled(String propertyName) {
173         return Log.isLoggable(propertyName, Log.VERBOSE);
174     }
175 
176     /**
177      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
178      * coordinates.
179      *
180      * @param descendant The descendant to which the passed coordinate is relative.
181      * @param ancestor The root view to make the coordinates relative to.
182      * @param coord The coordinate that we want mapped.
183      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
184      *          sometimes this is relevant as in a child's coordinates within the descendant.
185      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
186      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
187      *         assumption fails, we will need to return a pair of scale factors.
188      */
getDescendantCoordRelativeToAncestor( View descendant, View ancestor, float[] coord, boolean includeRootScroll)189     public static float getDescendantCoordRelativeToAncestor(
190             View descendant, View ancestor, float[] coord, boolean includeRootScroll) {
191         return getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, includeRootScroll,
192                 false);
193     }
194 
195     /**
196      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
197      * coordinates.
198      *
199      * @param descendant The descendant to which the passed coordinate is relative.
200      * @param ancestor The root view to make the coordinates relative to.
201      * @param coord The coordinate that we want mapped.
202      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
203      *          sometimes this is relevant as in a child's coordinates within the descendant.
204      * @param ignoreTransform If true, view transform is ignored
205      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
206      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
207      *         assumption fails, we will need to return a pair of scale factors.
208      */
getDescendantCoordRelativeToAncestor(View descendant, View ancestor, float[] coord, boolean includeRootScroll, boolean ignoreTransform)209     public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor,
210             float[] coord, boolean includeRootScroll, boolean ignoreTransform) {
211         float scale = 1.0f;
212         View v = descendant;
213         while(v != ancestor && v != null) {
214             // For TextViews, scroll has a meaning which relates to the text position
215             // which is very strange... ignore the scroll.
216             if (v != descendant || includeRootScroll) {
217                 offsetPoints(coord, -v.getScrollX(), -v.getScrollY());
218             }
219 
220             if (!ignoreTransform) {
221                 v.getMatrix().mapPoints(coord);
222             }
223             offsetPoints(coord, v.getLeft(), v.getTop());
224             scale *= v.getScaleX();
225 
226             v = v.getParent() instanceof View ? (View) v.getParent() : null;
227         }
228         return scale;
229     }
230 
231     /**
232      * Returns bounds for a child view of DragLayer, in drag layer coordinates.
233      *
234      * see {@link com.android.launcher3.dragndrop.DragLayer}.
235      *
236      * @param viewBounds Bounds of the view wanted in drag layer coordinates, relative to the view
237      *                   itself. eg. (0, 0, view.getWidth, view.getHeight)
238      * @param ignoreTransform If true, view transform is ignored
239      * @param outRect The out rect where we return the bounds of {@param view} in drag layer coords.
240      */
getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view, Rect viewBounds, boolean ignoreTransform, float[] recycle, RectF outRect)241     public static void getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view,
242             Rect viewBounds, boolean ignoreTransform, float[] recycle, RectF outRect) {
243         float[] points = recycle == null ? new float[4] : recycle;
244         points[0] = viewBounds.left;
245         points[1] = viewBounds.top;
246         points[2] = viewBounds.right;
247         points[3] = viewBounds.bottom;
248 
249         Utilities.getDescendantCoordRelativeToAncestor(view, dragLayer, points,
250                 false, ignoreTransform);
251         outRect.set(
252                 Math.min(points[0], points[2]),
253                 Math.min(points[1], points[3]),
254                 Math.max(points[0], points[2]),
255                 Math.max(points[1], points[3]));
256     }
257 
258     /**
259      * Similar to {@link #mapCoordInSelfToDescendant(View descendant, View root, float[] coord)}
260      * but accepts a Rect instead of float[].
261      */
mapRectInSelfToDescendant(View descendant, View root, Rect rect)262     public static void mapRectInSelfToDescendant(View descendant, View root, Rect rect) {
263         float[] coords = new float[]{rect.left, rect.top, rect.right, rect.bottom};
264         mapCoordInSelfToDescendant(descendant, root, coords);
265         rect.set((int) coords[0], (int) coords[1], (int) coords[2], (int) coords[3]);
266     }
267 
268     /**
269      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
270      */
mapCoordInSelfToDescendant(View descendant, View root, float[] coord)271     public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
272         sMatrix.reset();
273         View v = descendant;
274         while(v != root) {
275             sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
276             sMatrix.postConcat(v.getMatrix());
277             sMatrix.postTranslate(v.getLeft(), v.getTop());
278             v = (View) v.getParent();
279         }
280         sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
281         sMatrix.invert(sInverseMatrix);
282         sInverseMatrix.mapPoints(coord);
283     }
284 
285     /**
286      * Sets {@param out} to be same as {@param in} by rounding individual values
287      */
roundArray(float[] in, int[] out)288     public static void roundArray(float[] in, int[] out) {
289         for (int i = 0; i < in.length; i++) {
290             out[i] = Math.round(in[i]);
291         }
292     }
293 
offsetPoints(float[] points, float offsetX, float offsetY)294     public static void offsetPoints(float[] points, float offsetX, float offsetY) {
295         for (int i = 0; i < points.length; i += 2) {
296             points[i] += offsetX;
297             points[i + 1] += offsetY;
298         }
299     }
300 
301     /**
302      * Utility method to determine whether the given point, in local coordinates,
303      * is inside the view, where the area of the view is expanded by the slop factor.
304      * This method is called while processing touch-move events to determine if the event
305      * is still within the view.
306      */
pointInView(View v, float localX, float localY, float slop)307     public static boolean pointInView(View v, float localX, float localY, float slop) {
308         return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
309                 localY < (v.getHeight() + slop);
310     }
311 
scaleRectFAboutCenter(RectF r, float scale)312     public static void scaleRectFAboutCenter(RectF r, float scale) {
313         scaleRectFAboutCenter(r, scale, scale);
314     }
315 
316     /**
317      * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows different scales
318      * for X and Y
319      */
scaleRectFAboutCenter(RectF r, float scaleX, float scaleY)320     public static void scaleRectFAboutCenter(RectF r, float scaleX, float scaleY) {
321         float px = r.centerX();
322         float py = r.centerY();
323         r.offset(-px, -py);
324         r.left = r.left * scaleX;
325         r.top = r.top * scaleY;
326         r.right = r.right * scaleX;
327         r.bottom = r.bottom * scaleY;
328         r.offset(px, py);
329     }
330 
scaleRectAboutCenter(Rect r, float scale)331     public static void scaleRectAboutCenter(Rect r, float scale) {
332         if (scale != 1.0f) {
333             int cx = r.centerX();
334             int cy = r.centerY();
335             r.offset(-cx, -cy);
336             r.left = (int) (r.left * scale + 0.5f);
337             r.top = (int) (r.top * scale + 0.5f);
338             r.right = (int) (r.right * scale + 0.5f);
339             r.bottom = (int) (r.bottom * scale + 0.5f);
340             r.offset(cx, cy);
341         }
342     }
343 
shrinkRect(Rect r, float scaleX, float scaleY)344     public static float shrinkRect(Rect r, float scaleX, float scaleY) {
345         float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
346         if (scale < 1.0f) {
347             int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
348             r.left += deltaX;
349             r.right -= deltaX;
350 
351             int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
352             r.top += deltaY;
353             r.bottom -= deltaY;
354         }
355         return scale;
356     }
357 
358     /**
359      * Sets the x and y pivots for scaling from one Rect to another.
360      *
361      * @param src the source rectangle to scale from.
362      * @param dst the destination rectangle to scale to.
363      * @param outPivot the pivots set for scaling from src to dst.
364      */
getPivotsForScalingRectToRect(Rect src, Rect dst, PointF outPivot)365     public static void getPivotsForScalingRectToRect(Rect src, Rect dst, PointF outPivot) {
366         float pivotXPct = ((float) src.left - dst.left) / ((float) dst.width() - src.width());
367         outPivot.x = dst.left + dst.width() * pivotXPct;
368 
369         float pivotYPct = ((float) src.top - dst.top) / ((float) dst.height() - src.height());
370         outPivot.y = dst.top + dst.height() * pivotYPct;
371     }
372 
373     /**
374      * Maps t from one range to another range.
375      * @param t The value to map.
376      * @param fromMin The lower bound of the range that t is being mapped from.
377      * @param fromMax The upper bound of the range that t is being mapped from.
378      * @param toMin The lower bound of the range that t is being mapped to.
379      * @param toMax The upper bound of the range that t is being mapped to.
380      * @return The mapped value of t.
381      */
mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax, Interpolator interpolator)382     public static float mapToRange(float t, float fromMin, float fromMax, float toMin, float toMax,
383             Interpolator interpolator) {
384         if (fromMin == fromMax || toMin == toMax) {
385             Log.e(TAG, "mapToRange: range has 0 length");
386             return toMin;
387         }
388         float progress = getProgress(t, fromMin, fromMax);
389         return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
390     }
391 
392     /** 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)393     public static float mapBoundToRange(float t, float lowerBound, float upperBound,
394             float toMin, float toMax, Interpolator interpolator) {
395         return mapToRange(boundToRange(t, lowerBound, upperBound), lowerBound, upperBound,
396                 toMin, toMax, interpolator);
397     }
398 
getProgress(float current, float min, float max)399     public static float getProgress(float current, float min, float max) {
400         return Math.abs(current - min) / Math.abs(max - min);
401     }
402 
mapRange(float value, float min, float max)403     public static float mapRange(float value, float min, float max) {
404         return min + (value * (max - min));
405     }
406 
407     /**
408      * Trims the string, removing all whitespace at the beginning and end of the string.
409      * Non-breaking whitespaces are also removed.
410      */
411     @NonNull
trim(CharSequence s)412     public static String trim(CharSequence s) {
413         if (s == null) {
414             return "";
415         }
416 
417         // Just strip any sequence of whitespace or java space characters from the beginning and end
418         Matcher m = sTrimPattern.matcher(s);
419         return m.replaceAll("$1");
420     }
421 
422     /**
423      * Calculates the height of a given string at a specific text size.
424      */
calculateTextHeight(float textSizePx)425     public static int calculateTextHeight(float textSizePx) {
426         Paint p = new Paint();
427         p.setTextSize(textSizePx);
428         Paint.FontMetrics fm = p.getFontMetrics();
429         return (int) Math.ceil(fm.bottom - fm.top);
430     }
431 
isRtl(Resources res)432     public static boolean isRtl(Resources res) {
433         return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
434     }
435 
436     /** Converts a pixel value (px) to scale pixel value (SP) for the current device. */
pxToSp(float size)437     public static float pxToSp(float size) {
438         return size / Resources.getSystem().getDisplayMetrics().scaledDensity;
439     }
440 
dpiFromPx(float size, int densityDpi)441     public static float dpiFromPx(float size, int densityDpi) {
442         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
443         return (size / densityRatio);
444     }
445 
446     /** Converts a dp value to pixels for the current device. */
dpToPx(float dp)447     public static int dpToPx(float dp) {
448         return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
449     }
450 
451     /** Converts a dp value to pixels for a certain density. */
dpToPx(float dp, int densityDpi)452     public static int dpToPx(float dp, int densityDpi) {
453         float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
454         return (int) (dp * densityRatio);
455     }
456 
pxFromSp(float size, DisplayMetrics metrics)457     public static int pxFromSp(float size, DisplayMetrics metrics) {
458         return pxFromSp(size, metrics, 1f);
459     }
460 
pxFromSp(float size, DisplayMetrics metrics, float scale)461     public static int pxFromSp(float size, DisplayMetrics metrics, float scale) {
462         float value = scale * TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, metrics);
463         return ResourceUtils.roundPxValueFromFloat(value);
464     }
465 
createDbSelectionQuery(String columnName, IntArray values)466     public static String createDbSelectionQuery(String columnName, IntArray values) {
467         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, values.toConcatString());
468     }
469 
isBootCompleted()470     public static boolean isBootCompleted() {
471         return "1".equals(getSystemProperty("sys.boot_completed", "1"));
472     }
473 
getSystemProperty(String property, String defaultValue)474     public static String getSystemProperty(String property, String defaultValue) {
475         try {
476             Class clazz = Class.forName("android.os.SystemProperties");
477             Method getter = clazz.getDeclaredMethod("get", String.class);
478             String value = (String) getter.invoke(null, property);
479             if (!TextUtils.isEmpty(value)) {
480                 return value;
481             }
482         } catch (Exception e) {
483             Log.d(TAG, "Unable to read system properties");
484         }
485         return defaultValue;
486     }
487 
488     /**
489      * Ensures that a value is within given bounds. Specifically:
490      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
491      * return upperBound; else return value unchanged.
492      */
boundToRange(int value, int lowerBound, int upperBound)493     public static int boundToRange(int value, int lowerBound, int upperBound) {
494         return Math.max(lowerBound, Math.min(value, upperBound));
495     }
496 
497     /**
498      * @see #boundToRange(int, int, int).
499      */
boundToRange(float value, float lowerBound, float upperBound)500     public static float boundToRange(float value, float lowerBound, float upperBound) {
501         return Math.max(lowerBound, Math.min(value, upperBound));
502     }
503 
504     /**
505      * @see #boundToRange(int, int, int).
506      */
boundToRange(long value, long lowerBound, long upperBound)507     public static long boundToRange(long value, long lowerBound, long upperBound) {
508         return Math.max(lowerBound, Math.min(value, upperBound));
509     }
510 
511     /**
512      * Wraps a message with a TTS span, so that a different message is spoken than
513      * what is getting displayed.
514      * @param msg original message
515      * @param ttsMsg message to be spoken
516      */
wrapForTts(CharSequence msg, String ttsMsg)517     public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) {
518         SpannableString spanned = new SpannableString(msg);
519         spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
520                 0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
521         return spanned;
522     }
523 
524     /**
525      * Prefixes a text with the provided icon
526      */
prefixTextWithIcon(Context context, int iconRes, CharSequence msg)527     public static CharSequence prefixTextWithIcon(Context context, int iconRes, CharSequence msg) {
528         // Update the hint to contain the icon.
529         // Prefix the original hint with two spaces. The first space gets replaced by the icon
530         // using span. The second space is used for a singe space character between the hint
531         // and the icon.
532         SpannableString spanned = new SpannableString("  " + msg);
533         spanned.setSpan(new TintedDrawableSpan(context, iconRes),
534                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
535         return spanned;
536     }
537 
isWallpaperSupported(Context context)538     public static boolean isWallpaperSupported(Context context) {
539         return context.getSystemService(WallpaperManager.class).isWallpaperSupported();
540     }
541 
isWallpaperAllowed(Context context)542     public static boolean isWallpaperAllowed(Context context) {
543         return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
544     }
545 
isBinderSizeError(Exception e)546     public static boolean isBinderSizeError(Exception e) {
547         return e.getCause() instanceof TransactionTooLargeException
548                 || e.getCause() instanceof DeadObjectException;
549     }
550 
551     /**
552      * Utility method to post a runnable on the handler, skipping the synchronization barriers.
553      */
postAsyncCallback(Handler handler, Runnable callback)554     public static void postAsyncCallback(Handler handler, Runnable callback) {
555         Message msg = Message.obtain(handler, callback);
556         msg.setAsynchronous(true);
557         handler.sendMessage(msg);
558     }
559 
560     /**
561      * Returns the full drawable for info without any flattening or pre-processing.
562      *
563      * @param shouldThemeIcon If true, will theme icons when applicable
564      * @param outObj this is set to the internal data associated with {@code info},
565      *               eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
566      */
567     @TargetApi(Build.VERSION_CODES.TIRAMISU)
getFullDrawable(Context context, ItemInfo info, int width, int height, boolean shouldThemeIcon, Object[] outObj)568     public static Drawable getFullDrawable(Context context, ItemInfo info, int width, int height,
569             boolean shouldThemeIcon, Object[] outObj) {
570         Drawable icon = loadFullDrawableWithoutTheme(context, info, width, height, outObj);
571         if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && shouldThemeIcon) {
572             AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon.mutate();
573             Drawable mono = aid.getMonochrome();
574             if (mono != null && Themes.isThemedIconEnabled(context)) {
575                 int[] colors = ThemedIconDrawable.getColors(context);
576                 mono = mono.mutate();
577                 mono.setTint(colors[1]);
578                 return new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
579             }
580         }
581         return icon;
582     }
583 
loadFullDrawableWithoutTheme(Context context, ItemInfo info, int width, int height, Object[] outObj)584     private static Drawable loadFullDrawableWithoutTheme(Context context, ItemInfo info,
585             int width, int height, Object[] outObj) {
586         ActivityContext activity = ActivityContext.lookupContext(context);
587         LauncherAppState appState = LauncherAppState.getInstance(context);
588         if (info instanceof PendingAddShortcutInfo) {
589             ShortcutConfigActivityInfo activityInfo =
590                     ((PendingAddShortcutInfo) info).getActivityInfo(context);
591             outObj[0] = activityInfo;
592             return activityInfo.getFullResIcon(appState.getIconCache());
593         }
594         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
595             LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
596                     .resolveActivity(info.getIntent(), info.user);
597             outObj[0] = activityInfo;
598             return activityInfo == null ? null : LauncherAppState.getInstance(context)
599                     .getIconProvider().getIcon(
600                             activityInfo, activity.getDeviceProfile().inv.fillResIconDpi);
601         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
602             List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
603                     .buildRequest(context)
604                     .query(ShortcutRequest.ALL);
605             if (si.isEmpty()) {
606                 return null;
607             } else {
608                 outObj[0] = si.get(0);
609                 return ShortcutCachingLogic.getIcon(context, si.get(0),
610                         appState.getInvariantDeviceProfile().fillResIconDpi);
611             }
612         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
613             FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
614                     activity, info.id, new Point(width, height));
615             if (icon == null) {
616                 return null;
617             }
618             outObj[0] = icon;
619             return icon;
620         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION
621                 && info instanceof ItemInfoWithIcon) {
622             return ((ItemInfoWithIcon) info).bitmap.newIcon(context);
623         } else {
624             return null;
625         }
626     }
627 
628     /**
629      * For apps icons and shortcut icons that have badges, this method creates a drawable that can
630      * later on be rendered on top of the layers for the badges. For app icons, work profile badges
631      * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no
632      * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
633      **/
634     @TargetApi(Build.VERSION_CODES.O)
getBadge(Context context, ItemInfo info, Object obj)635     public static Drawable getBadge(Context context, ItemInfo info, Object obj) {
636         LauncherAppState appState = LauncherAppState.getInstance(context);
637         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
638             boolean iconBadged = (info instanceof ItemInfoWithIcon)
639                     && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
640             if ((info.id == ItemInfo.NO_ID && !iconBadged)
641                     || !(obj instanceof ShortcutInfo)) {
642                 // The item is not yet added on home screen.
643                 return new ColorDrawable(Color.TRANSPARENT);
644             }
645             ShortcutInfo si = (ShortcutInfo) obj;
646             return LauncherAppState.getInstance(appState.getContext())
647                     .getIconCache().getShortcutInfoBadge(si).newIcon(context, FLAG_THEMED);
648         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
649             return ((FolderAdaptiveIcon) obj).getBadge();
650         } else {
651             return Process.myUserHandle().equals(info.user)
652                     ? new ColorDrawable(Color.TRANSPARENT)
653                     : context.getDrawable(R.drawable.ic_work_app_badge);
654         }
655     }
656 
squaredHypot(float x, float y)657     public static float squaredHypot(float x, float y) {
658         return x * x + y * y;
659     }
660 
squaredTouchSlop(Context context)661     public static float squaredTouchSlop(Context context) {
662         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
663         return slop * slop;
664     }
665 
666     /**
667      * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
668      * sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
669      * the final bounds.
670      */
rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight, int delta)671     public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
672             int delta) {
673         int rdelta = ((delta % 4) + 4) % 4;
674         int origLeft = inOutBounds.left;
675         switch (rdelta) {
676             case 0:
677                 return;
678             case 1:
679                 inOutBounds.left = inOutBounds.top;
680                 inOutBounds.top = parentWidth - inOutBounds.right;
681                 inOutBounds.right = inOutBounds.bottom;
682                 inOutBounds.bottom = parentWidth - origLeft;
683                 return;
684             case 2:
685                 inOutBounds.left = parentWidth - inOutBounds.right;
686                 inOutBounds.right = parentWidth - origLeft;
687                 return;
688             case 3:
689                 inOutBounds.left = parentHeight - inOutBounds.bottom;
690                 inOutBounds.bottom = inOutBounds.right;
691                 inOutBounds.right = parentHeight - inOutBounds.top;
692                 inOutBounds.top = origLeft;
693                 return;
694         }
695     }
696 
697     /**
698      * Make a color filter that blends a color into the destination based on a scalable amout.
699      *
700      * @param color to blend in.
701      * @param tintAmount [0-1] 0 no tinting, 1 full color.
702      * @return ColorFilter for tinting, or {@code null} if no filter is needed.
703      */
makeColorTintingColorFilter(int color, float tintAmount)704     public static ColorFilter makeColorTintingColorFilter(int color, float tintAmount) {
705         if (tintAmount == 0f) {
706             return null;
707         }
708         return new LightingColorFilter(
709                 // This isn't blending in white, its making a multiplication mask for the base color
710                 ColorUtils.blendARGB(Color.WHITE, 0, tintAmount),
711                 ColorUtils.blendARGB(0, color, tintAmount));
712     }
713 
getViewBounds(@onNull View v)714     public static Rect getViewBounds(@NonNull View v) {
715         int[] pos = new int[2];
716         v.getLocationOnScreen(pos);
717         return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight());
718     }
719 
720     /**
721      * Returns a list of screen-splitting options depending on the device orientation (split top for
722      * portrait, split right for landscape)
723      */
getSplitPositionOptions( DeviceProfile dp)724     public static List<SplitPositionOption> getSplitPositionOptions(
725             DeviceProfile dp) {
726         return Collections.singletonList(new SplitPositionOption(
727                 dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical,
728                 R.string.recent_task_option_split_screen,
729                 dp.isLandscape ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT,
730                 STAGE_TYPE_MAIN
731         ));
732     }
733 
734     /** Logs the Scale and Translate properties of a matrix. Ignores skew and perspective. */
logMatrix(String label, Matrix matrix)735     public static void logMatrix(String label, Matrix matrix) {
736         float[] matrixValues = new float[9];
737         matrix.getValues(matrixValues);
738         Log.d(label, String.format("%s: %s\nscale (x,y) = (%f, %f)\ntranslate (x,y) = (%f, %f)",
739                 label, matrix, matrixValues[Matrix.MSCALE_X], matrixValues[Matrix.MSCALE_Y],
740                 matrixValues[Matrix.MTRANS_X], matrixValues[Matrix.MTRANS_Y]
741         ));
742     }
743 
744     /**
745      * Translates the {@code targetView} so that it overlaps with {@code exclusionBounds} as little
746      * as possible, while remaining within {@code inclusionBounds}.
747      * <p>
748      * {@code inclusionBounds} will always take precedence over {@code exclusionBounds}, so if
749      * {@code targetView} needs to be translated outside of {@code inclusionBounds} to fully fix an
750      * overlap with {@code exclusionBounds}, then {@code targetView} will only be translated up to
751      * the border of {@code inclusionBounds}.
752      * <p>
753      * Note: {@code targetViewBounds}, {@code inclusionBounds} and {@code exclusionBounds} must all
754      * be in relation to the same reference point on screen.
755      * <p>
756      * @param targetView the view being translated
757      * @param targetViewBounds the bounds of the {@code targetView}
758      * @param inclusionBounds the bounds the {@code targetView} absolutely must stay within
759      * @param exclusionBounds the bounds to try to move the {@code targetView} away from
760      * @param adjustmentDirection the translation direction that should be attempted to fix an
761      *                            overlap
762      */
translateOverlappingView( @onNull View targetView, @NonNull Rect targetViewBounds, @NonNull Rect inclusionBounds, @NonNull Rect exclusionBounds, @AdjustmentDirection int adjustmentDirection)763     public static void translateOverlappingView(
764             @NonNull View targetView,
765             @NonNull Rect targetViewBounds,
766             @NonNull Rect inclusionBounds,
767             @NonNull Rect exclusionBounds,
768             @AdjustmentDirection int adjustmentDirection) {
769         switch (adjustmentDirection) {
770             case TRANSLATE_RIGHT:
771                 targetView.setTranslationX(Math.min(
772                         // Translate to the right if the view is overlapping on the left.
773                         Math.max(0, exclusionBounds.right - targetViewBounds.left),
774                         // Do not translate beyond the inclusion bounds.
775                         inclusionBounds.right - targetViewBounds.right));
776                 break;
777             case TRANSLATE_LEFT:
778                 targetView.setTranslationX(Math.max(
779                         // Translate to the left if the view is overlapping on the right.
780                         Math.min(0, exclusionBounds.left - targetViewBounds.right),
781                         // Do not translate beyond the inclusion bounds.
782                         inclusionBounds.left - targetViewBounds.left));
783                 break;
784             case TRANSLATE_DOWN:
785                 targetView.setTranslationY(Math.min(
786                         // Translate downwards if the view is overlapping on the top.
787                         Math.max(0, exclusionBounds.bottom - targetViewBounds.top),
788                         // Do not translate beyond the inclusion bounds.
789                         inclusionBounds.bottom - targetViewBounds.bottom));
790                 break;
791             case TRANSLATE_UP:
792                 targetView.setTranslationY(Math.max(
793                         // Translate upwards if the view is overlapping on the bottom.
794                         Math.min(0, exclusionBounds.top - targetViewBounds.bottom),
795                         // Do not translate beyond the inclusion bounds.
796                         inclusionBounds.top - targetViewBounds.top));
797                 break;
798             default:
799                 // No-Op
800         }
801     }
802 }
803