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