• 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 android.app.WallpaperManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.SharedPreferences;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.ResolveInfo;
29 import android.content.res.Resources;
30 import android.graphics.Bitmap;
31 import android.graphics.Color;
32 import android.graphics.Matrix;
33 import android.graphics.Paint;
34 import android.graphics.Rect;
35 import android.os.Build;
36 import android.os.Bundle;
37 import android.os.DeadObjectException;
38 import android.os.PowerManager;
39 import android.os.TransactionTooLargeException;
40 import android.text.Spannable;
41 import android.text.SpannableString;
42 import android.text.TextUtils;
43 import android.text.style.TtsSpan;
44 import android.util.DisplayMetrics;
45 import android.util.Log;
46 import android.util.Pair;
47 import android.util.SparseArray;
48 import android.util.TypedValue;
49 import android.view.View;
50 import android.view.accessibility.AccessibilityEvent;
51 import android.view.accessibility.AccessibilityManager;
52 
53 import com.android.launcher3.config.FeatureFlags;
54 
55 import java.io.ByteArrayOutputStream;
56 import java.io.Closeable;
57 import java.io.IOException;
58 import java.lang.reflect.InvocationTargetException;
59 import java.lang.reflect.Method;
60 import java.util.Collection;
61 import java.util.HashSet;
62 import java.util.Locale;
63 import java.util.concurrent.Executor;
64 import java.util.concurrent.LinkedBlockingQueue;
65 import java.util.concurrent.ThreadPoolExecutor;
66 import java.util.concurrent.TimeUnit;
67 import java.util.regex.Matcher;
68 import java.util.regex.Pattern;
69 
70 /**
71  * Various utilities shared amongst the Launcher's classes.
72  */
73 public final class Utilities {
74 
75     private static final String TAG = "Launcher.Utilities";
76 
77     private static final Pattern sTrimPattern =
78             Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
79 
80     private static final int[] sLoc0 = new int[2];
81     private static final int[] sLoc1 = new int[2];
82     private static final float[] sPoint = new float[2];
83     private static final Matrix sMatrix = new Matrix();
84     private static final Matrix sInverseMatrix = new Matrix();
85 
86     public static final boolean ATLEAST_OREO_MR1 =
87             Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1;
88 
89     public static final boolean ATLEAST_OREO =
90             Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
91 
92     public static final boolean ATLEAST_NOUGAT_MR1 =
93             Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
94 
95     public static final boolean ATLEAST_NOUGAT =
96             Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
97 
98     public static final boolean ATLEAST_MARSHMALLOW =
99             Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
100 
101     public static final boolean ATLEAST_LOLLIPOP_MR1 =
102             Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
103 
104     /**
105      * Indicates if the device has a debug build. Should only be used to store additional info or
106      * add extra logging and not for changing the app behavior.
107      */
108     public static final boolean IS_DEBUG_DEVICE = Build.TYPE.toLowerCase().contains("debug");
109 
110     // An intent extra to indicate the horizontal scroll of the wallpaper.
111     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
112 
113     public static final int COLOR_EXTRACTION_JOB_ID = 1;
114     public static final int WALLPAPER_COMPAT_JOB_ID = 2;
115 
116     // These values are same as that in {@link AsyncTask}.
117     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
118     private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
119     private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
120     private static final int KEEP_ALIVE = 1;
121     /**
122      * An {@link Executor} to be used with async task with no limit on the queue size.
123      */
124     public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
125             CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
126             TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
127 
128     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
129 
isPropertyEnabled(String propertyName)130     public static boolean isPropertyEnabled(String propertyName) {
131         return Log.isLoggable(propertyName, Log.VERBOSE);
132     }
133 
isAllowRotationPrefEnabled(Context context)134     public static boolean isAllowRotationPrefEnabled(Context context) {
135         return getPrefs(context).getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
136                 getAllowRotationDefaultValue(context));
137     }
138 
getAllowRotationDefaultValue(Context context)139     public static boolean getAllowRotationDefaultValue(Context context) {
140         if (ATLEAST_NOUGAT) {
141             // If the device was scaled, used the original dimensions to determine if rotation
142             // is allowed of not.
143             Resources res = context.getResources();
144             int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
145                     * res.getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEVICE_STABLE;
146             return originalSmallestWidth >= 600;
147         }
148         return false;
149     }
150 
151     /**
152      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
153      * coordinates.
154      *
155      * @param descendant The descendant to which the passed coordinate is relative.
156      * @param ancestor The root view to make the coordinates relative to.
157      * @param coord The coordinate that we want mapped.
158      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
159      *          sometimes this is relevant as in a child's coordinates within the descendant.
160      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
161      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
162      *         assumption fails, we will need to return a pair of scale factors.
163      */
getDescendantCoordRelativeToAncestor( View descendant, View ancestor, int[] coord, boolean includeRootScroll)164     public static float getDescendantCoordRelativeToAncestor(
165             View descendant, View ancestor, int[] coord, boolean includeRootScroll) {
166         sPoint[0] = coord[0];
167         sPoint[1] = coord[1];
168 
169         float scale = 1.0f;
170         View v = descendant;
171         while(v != ancestor && v != null) {
172             // For TextViews, scroll has a meaning which relates to the text position
173             // which is very strange... ignore the scroll.
174             if (v != descendant || includeRootScroll) {
175                 sPoint[0] -= v.getScrollX();
176                 sPoint[1] -= v.getScrollY();
177             }
178 
179             v.getMatrix().mapPoints(sPoint);
180             sPoint[0] += v.getLeft();
181             sPoint[1] += v.getTop();
182             scale *= v.getScaleX();
183 
184             v = (View) v.getParent();
185         }
186 
187         coord[0] = Math.round(sPoint[0]);
188         coord[1] = Math.round(sPoint[1]);
189         return scale;
190     }
191 
192     /**
193      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}.
194      */
mapCoordInSelfToDescendant(View descendant, View root, int[] coord)195     public static void mapCoordInSelfToDescendant(View descendant, View root, int[] coord) {
196         sMatrix.reset();
197         View v = descendant;
198         while(v != root) {
199             sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
200             sMatrix.postConcat(v.getMatrix());
201             sMatrix.postTranslate(v.getLeft(), v.getTop());
202             v = (View) v.getParent();
203         }
204         sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
205         sMatrix.invert(sInverseMatrix);
206 
207         sPoint[0] = coord[0];
208         sPoint[1] = coord[1];
209         sInverseMatrix.mapPoints(sPoint);
210         coord[0] = Math.round(sPoint[0]);
211         coord[1] = Math.round(sPoint[1]);
212     }
213 
214     /**
215      * Utility method to determine whether the given point, in local coordinates,
216      * is inside the view, where the area of the view is expanded by the slop factor.
217      * This method is called while processing touch-move events to determine if the event
218      * is still within the view.
219      */
pointInView(View v, float localX, float localY, float slop)220     public static boolean pointInView(View v, float localX, float localY, float slop) {
221         return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
222                 localY < (v.getHeight() + slop);
223     }
224 
getCenterDeltaInScreenSpace(View v0, View v1)225     public static int[] getCenterDeltaInScreenSpace(View v0, View v1) {
226         v0.getLocationInWindow(sLoc0);
227         v1.getLocationInWindow(sLoc1);
228 
229         sLoc0[0] += (v0.getMeasuredWidth() * v0.getScaleX()) / 2;
230         sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
231         sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
232         sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
233         return new int[] {sLoc1[0] - sLoc0[0], sLoc1[1] - sLoc0[1]};
234     }
235 
scaleRectAboutCenter(Rect r, float scale)236     public static void scaleRectAboutCenter(Rect r, float scale) {
237         if (scale != 1.0f) {
238             int cx = r.centerX();
239             int cy = r.centerY();
240             r.offset(-cx, -cy);
241 
242             r.left = (int) (r.left * scale + 0.5f);
243             r.top = (int) (r.top * scale + 0.5f);
244             r.right = (int) (r.right * scale + 0.5f);
245             r.bottom = (int) (r.bottom * scale + 0.5f);
246 
247             r.offset(cx, cy);
248         }
249     }
250 
shrinkRect(Rect r, float scaleX, float scaleY)251     public static float shrinkRect(Rect r, float scaleX, float scaleY) {
252         float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
253         if (scale < 1.0f) {
254             int deltaX = (int) (r.width() * (scaleX - scale) * 0.5f);
255             r.left += deltaX;
256             r.right -= deltaX;
257 
258             int deltaY = (int) (r.height() * (scaleY - scale) * 0.5f);
259             r.top += deltaY;
260             r.bottom -= deltaY;
261         }
262         return scale;
263     }
264 
isSystemApp(Context context, Intent intent)265     public static boolean isSystemApp(Context context, Intent intent) {
266         PackageManager pm = context.getPackageManager();
267         ComponentName cn = intent.getComponent();
268         String packageName = null;
269         if (cn == null) {
270             ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
271             if ((info != null) && (info.activityInfo != null)) {
272                 packageName = info.activityInfo.packageName;
273             }
274         } else {
275             packageName = cn.getPackageName();
276         }
277         if (packageName != null) {
278             try {
279                 PackageInfo info = pm.getPackageInfo(packageName, 0);
280                 return (info != null) && (info.applicationInfo != null) &&
281                         ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
282             } catch (NameNotFoundException e) {
283                 return false;
284             }
285         } else {
286             return false;
287         }
288     }
289 
290     /**
291      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
292      * @param bitmap The bitmap to scan
293      * @param samples The approximate max number of samples to use.
294      */
findDominantColorByHue(Bitmap bitmap, int samples)295     public static int findDominantColorByHue(Bitmap bitmap, int samples) {
296         final int height = bitmap.getHeight();
297         final int width = bitmap.getWidth();
298         int sampleStride = (int) Math.sqrt((height * width) / samples);
299         if (sampleStride < 1) {
300             sampleStride = 1;
301         }
302 
303         // This is an out-param, for getting the hsv values for an rgb
304         float[] hsv = new float[3];
305 
306         // First get the best hue, by creating a histogram over 360 hue buckets,
307         // where each pixel contributes a score weighted by saturation, value, and alpha.
308         float[] hueScoreHistogram = new float[360];
309         float highScore = -1;
310         int bestHue = -1;
311 
312         for (int y = 0; y < height; y += sampleStride) {
313             for (int x = 0; x < width; x += sampleStride) {
314                 int argb = bitmap.getPixel(x, y);
315                 int alpha = 0xFF & (argb >> 24);
316                 if (alpha < 0x80) {
317                     // Drop mostly-transparent pixels.
318                     continue;
319                 }
320                 // Remove the alpha channel.
321                 int rgb = argb | 0xFF000000;
322                 Color.colorToHSV(rgb, hsv);
323                 // Bucket colors by the 360 integer hues.
324                 int hue = (int) hsv[0];
325                 if (hue < 0 || hue >= hueScoreHistogram.length) {
326                     // Defensively avoid array bounds violations.
327                     continue;
328                 }
329                 float score = hsv[1] * hsv[2];
330                 hueScoreHistogram[hue] += score;
331                 if (hueScoreHistogram[hue] > highScore) {
332                     highScore = hueScoreHistogram[hue];
333                     bestHue = hue;
334                 }
335             }
336         }
337 
338         SparseArray<Float> rgbScores = new SparseArray<Float>();
339         int bestColor = 0xff000000;
340         highScore = -1;
341         // Go back over the RGB colors that match the winning hue,
342         // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
343         // The highest-scoring RGB color wins.
344         for (int y = 0; y < height; y += sampleStride) {
345             for (int x = 0; x < width; x += sampleStride) {
346                 int rgb = bitmap.getPixel(x, y) | 0xff000000;
347                 Color.colorToHSV(rgb, hsv);
348                 int hue = (int) hsv[0];
349                 if (hue == bestHue) {
350                     float s = hsv[1];
351                     float v = hsv[2];
352                     int bucket = (int) (s * 100) + (int) (v * 10000);
353                     // Score by cumulative saturation * value.
354                     float score = s * v;
355                     Float oldTotal = rgbScores.get(bucket);
356                     float newTotal = oldTotal == null ? score : oldTotal + score;
357                     rgbScores.put(bucket, newTotal);
358                     if (newTotal > highScore) {
359                         highScore = newTotal;
360                         // All the colors in the winning bucket are very similar. Last in wins.
361                         bestColor = rgb;
362                     }
363                 }
364             }
365         }
366         return bestColor;
367     }
368 
369     /*
370      * Finds a system apk which had a broadcast receiver listening to a particular action.
371      * @param action intent action used to find the apk
372      * @return a pair of apk package name and the resources.
373      */
findSystemApk(String action, PackageManager pm)374     static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
375         final Intent intent = new Intent(action);
376         for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
377             if (info.activityInfo != null &&
378                     (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
379                 final String packageName = info.activityInfo.packageName;
380                 try {
381                     final Resources res = pm.getResourcesForApplication(packageName);
382                     return Pair.create(packageName, res);
383                 } catch (NameNotFoundException e) {
384                     Log.w(TAG, "Failed to find resources for " + packageName);
385                 }
386             }
387         }
388         return null;
389     }
390 
391     /**
392      * Compresses the bitmap to a byte array for serialization.
393      */
flattenBitmap(Bitmap bitmap)394     public static byte[] flattenBitmap(Bitmap bitmap) {
395         // Try go guesstimate how much space the icon will take when serialized
396         // to avoid unnecessary allocations/copies during the write.
397         int size = bitmap.getWidth() * bitmap.getHeight() * 4;
398         ByteArrayOutputStream out = new ByteArrayOutputStream(size);
399         try {
400             bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
401             out.flush();
402             out.close();
403             return out.toByteArray();
404         } catch (IOException e) {
405             Log.w(TAG, "Could not write bitmap");
406             return null;
407         }
408     }
409 
410     /**
411      * Trims the string, removing all whitespace at the beginning and end of the string.
412      * Non-breaking whitespaces are also removed.
413      */
trim(CharSequence s)414     public static String trim(CharSequence s) {
415         if (s == null) {
416             return null;
417         }
418 
419         // Just strip any sequence of whitespace or java space characters from the beginning and end
420         Matcher m = sTrimPattern.matcher(s);
421         return m.replaceAll("$1");
422     }
423 
424     /**
425      * Calculates the height of a given string at a specific text size.
426      */
calculateTextHeight(float textSizePx)427     public static int calculateTextHeight(float textSizePx) {
428         Paint p = new Paint();
429         p.setTextSize(textSizePx);
430         Paint.FontMetrics fm = p.getFontMetrics();
431         return (int) Math.ceil(fm.bottom - fm.top);
432     }
433 
434     /**
435      * Convenience println with multiple args.
436      */
println(String key, Object... args)437     public static void println(String key, Object... args) {
438         StringBuilder b = new StringBuilder();
439         b.append(key);
440         b.append(": ");
441         boolean isFirstArgument = true;
442         for (Object arg : args) {
443             if (isFirstArgument) {
444                 isFirstArgument = false;
445             } else {
446                 b.append(", ");
447             }
448             b.append(arg);
449         }
450         System.out.println(b.toString());
451     }
452 
isRtl(Resources res)453     public static boolean isRtl(Resources res) {
454         return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
455     }
456 
457     /**
458      * Returns true if the intent is a valid launch intent for a launcher activity of an app.
459      * This is used to identify shortcuts which are different from the ones exposed by the
460      * applications' manifest file.
461      *
462      * @param launchIntent The intent that will be launched when the shortcut is clicked.
463      */
isLauncherAppTarget(Intent launchIntent)464     public static boolean isLauncherAppTarget(Intent launchIntent) {
465         if (launchIntent != null
466                 && Intent.ACTION_MAIN.equals(launchIntent.getAction())
467                 && launchIntent.getComponent() != null
468                 && launchIntent.getCategories() != null
469                 && launchIntent.getCategories().size() == 1
470                 && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
471                 && TextUtils.isEmpty(launchIntent.getDataString())) {
472             // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
473             Bundle extras = launchIntent.getExtras();
474             return extras == null || extras.keySet().isEmpty();
475         }
476         return false;
477     }
478 
dpiFromPx(int size, DisplayMetrics metrics)479     public static float dpiFromPx(int size, DisplayMetrics metrics){
480         float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
481         return (size / densityRatio);
482     }
pxFromDp(float size, DisplayMetrics metrics)483     public static int pxFromDp(float size, DisplayMetrics metrics) {
484         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
485                 size, metrics));
486     }
pxFromSp(float size, DisplayMetrics metrics)487     public static int pxFromSp(float size, DisplayMetrics metrics) {
488         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
489                 size, metrics));
490     }
491 
createDbSelectionQuery(String columnName, Iterable<?> values)492     public static String createDbSelectionQuery(String columnName, Iterable<?> values) {
493         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values));
494     }
495 
isBootCompleted()496     public static boolean isBootCompleted() {
497         return "1".equals(getSystemProperty("sys.boot_completed", "1"));
498     }
499 
getSystemProperty(String property, String defaultValue)500     public static String getSystemProperty(String property, String defaultValue) {
501         try {
502             Class clazz = Class.forName("android.os.SystemProperties");
503             Method getter = clazz.getDeclaredMethod("get", String.class);
504             String value = (String) getter.invoke(null, property);
505             if (!TextUtils.isEmpty(value)) {
506                 return value;
507             }
508         } catch (Exception e) {
509             Log.d(TAG, "Unable to read system properties");
510         }
511         return defaultValue;
512     }
513 
514     /**
515      * Ensures that a value is within given bounds. Specifically:
516      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
517      * return upperBound; else return value unchanged.
518      */
boundToRange(int value, int lowerBound, int upperBound)519     public static int boundToRange(int value, int lowerBound, int upperBound) {
520         return Math.max(lowerBound, Math.min(value, upperBound));
521     }
522 
523     /**
524      * @see #boundToRange(int, int, int).
525      */
boundToRange(float value, float lowerBound, float upperBound)526     public static float boundToRange(float value, float lowerBound, float upperBound) {
527         return Math.max(lowerBound, Math.min(value, upperBound));
528     }
529 
530     /**
531      * Wraps a message with a TTS span, so that a different message is spoken than
532      * what is getting displayed.
533      * @param msg original message
534      * @param ttsMsg message to be spoken
535      */
wrapForTts(CharSequence msg, String ttsMsg)536     public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) {
537         SpannableString spanned = new SpannableString(msg);
538         spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
539                 0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
540         return spanned;
541     }
542 
543     /**
544      * Replacement for Long.compare() which was added in API level 19.
545      */
longCompare(long lhs, long rhs)546     public static int longCompare(long lhs, long rhs) {
547         return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
548     }
549 
getPrefs(Context context)550     public static SharedPreferences getPrefs(Context context) {
551         return context.getSharedPreferences(
552                 LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
553     }
554 
getDevicePrefs(Context context)555     public static SharedPreferences getDevicePrefs(Context context) {
556         return context.getSharedPreferences(
557                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
558     }
559 
isPowerSaverOn(Context context)560     public static boolean isPowerSaverOn(Context context) {
561         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
562         return powerManager.isPowerSaveMode();
563     }
564 
isWallpaperAllowed(Context context)565     public static boolean isWallpaperAllowed(Context context) {
566         if (ATLEAST_NOUGAT) {
567             try {
568                 WallpaperManager wm = context.getSystemService(WallpaperManager.class);
569                 return (Boolean) wm.getClass().getDeclaredMethod("isSetWallpaperAllowed")
570                         .invoke(wm);
571             } catch (Exception e) { }
572         }
573         return true;
574     }
575 
closeSilently(Closeable c)576     public static void closeSilently(Closeable c) {
577         if (c != null) {
578             try {
579                 c.close();
580             } catch (IOException e) {
581                 if (FeatureFlags.IS_DOGFOOD_BUILD) {
582                     Log.d(TAG, "Error closing", e);
583                 }
584             }
585         }
586     }
587 
588     /**
589      * Returns true if {@param original} contains all entries defined in {@param updates} and
590      * have the same value.
591      * The comparison uses {@link Object#equals(Object)} to compare the values.
592      */
containsAll(Bundle original, Bundle updates)593     public static boolean containsAll(Bundle original, Bundle updates) {
594         for (String key : updates.keySet()) {
595             Object value1 = updates.get(key);
596             Object value2 = original.get(key);
597             if (value1 == null) {
598                 if (value2 != null) {
599                     return false;
600                 }
601             } else if (!value1.equals(value2)) {
602                 return false;
603             }
604         }
605         return true;
606     }
607 
608     /** Returns whether the collection is null or empty. */
isEmpty(Collection c)609     public static boolean isEmpty(Collection c) {
610         return c == null || c.isEmpty();
611     }
612 
sendCustomAccessibilityEvent(View target, int type, String text)613     public static void sendCustomAccessibilityEvent(View target, int type, String text) {
614         AccessibilityManager accessibilityManager = (AccessibilityManager)
615                 target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
616         if (accessibilityManager.isEnabled()) {
617             AccessibilityEvent event = AccessibilityEvent.obtain(type);
618             target.onInitializeAccessibilityEvent(event);
619             event.getText().add(text);
620             accessibilityManager.sendAccessibilityEvent(event);
621         }
622     }
623 
isBinderSizeError(Exception e)624     public static boolean isBinderSizeError(Exception e) {
625         return e.getCause() instanceof TransactionTooLargeException
626                 || e.getCause() instanceof DeadObjectException;
627     }
628 
getOverrideObject(Class<T> clazz, Context context, int resId)629     public static <T> T getOverrideObject(Class<T> clazz, Context context, int resId) {
630         String className = context.getString(resId);
631         if (!TextUtils.isEmpty(className)) {
632             try {
633                 Class<?> cls = Class.forName(className);
634                 return (T) cls.getDeclaredConstructor(Context.class).newInstance(context);
635             } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
636                     | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
637                 Log.e(TAG, "Bad overriden class", e);
638             }
639         }
640 
641         try {
642             return clazz.newInstance();
643         } catch (InstantiationException|IllegalAccessException e) {
644             throw new RuntimeException(e);
645         }
646     }
647 
648     /**
649      * Returns a HashSet with a single element. We use this instead of Collections.singleton()
650      * because HashSet ensures all operations, such as remove, are supported.
651      */
singletonHashSet(T elem)652     public static <T> HashSet<T> singletonHashSet(T elem) {
653         HashSet<T> hashSet = new HashSet<>(1);
654         hashSet.add(elem);
655         return hashSet;
656     }
657 
658 }
659