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.annotation.TargetApi;
     20 import android.app.Activity;
     21 import android.app.WallpaperManager;
     22 import android.content.ActivityNotFoundException;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.SharedPreferences;
     27 import android.content.pm.ApplicationInfo;
     28 import android.content.pm.PackageInfo;
     29 import android.content.pm.PackageManager;
     30 import android.content.pm.PackageManager.NameNotFoundException;
     31 import android.content.pm.ResolveInfo;
     32 import android.content.res.Resources;
     33 import android.content.res.TypedArray;
     34 import android.database.Cursor;
     35 import android.graphics.Bitmap;
     36 import android.graphics.BitmapFactory;
     37 import android.graphics.Canvas;
     38 import android.graphics.Color;
     39 import android.graphics.Matrix;
     40 import android.graphics.Paint;
     41 import android.graphics.PaintFlagsDrawFilter;
     42 import android.graphics.Rect;
     43 import android.graphics.RectF;
     44 import android.graphics.drawable.BitmapDrawable;
     45 import android.graphics.drawable.Drawable;
     46 import android.graphics.drawable.PaintDrawable;
     47 import android.os.Build;
     48 import android.os.Bundle;
     49 import android.os.PowerManager;
     50 import android.text.Spannable;
     51 import android.text.SpannableString;
     52 import android.text.TextUtils;
     53 import android.text.style.TtsSpan;
     54 import android.util.DisplayMetrics;
     55 import android.util.Log;
     56 import android.util.Pair;
     57 import android.util.SparseArray;
     58 import android.util.TypedValue;
     59 import android.view.MotionEvent;
     60 import android.view.View;
     61 import android.view.accessibility.AccessibilityEvent;
     62 import android.view.accessibility.AccessibilityManager;
     63 import android.widget.Toast;
     64 
     65 import com.android.launcher3.compat.UserHandleCompat;
     66 import com.android.launcher3.config.FeatureFlags;
     67 import com.android.launcher3.config.ProviderConfig;
     68 import com.android.launcher3.graphics.ShadowGenerator;
     69 import com.android.launcher3.util.IconNormalizer;
     70 
     71 import java.io.ByteArrayOutputStream;
     72 import java.io.Closeable;
     73 import java.io.IOException;
     74 import java.lang.reflect.Method;
     75 import java.util.ArrayList;
     76 import java.util.Collection;
     77 import java.util.Locale;
     78 import java.util.Set;
     79 import java.util.concurrent.Executor;
     80 import java.util.concurrent.LinkedBlockingQueue;
     81 import java.util.concurrent.ThreadPoolExecutor;
     82 import java.util.concurrent.TimeUnit;
     83 import java.util.regex.Matcher;
     84 import java.util.regex.Pattern;
     85 
     86 /**
     87  * Various utilities shared amongst the Launcher's classes.
     88  */
     89 public final class Utilities {
     90 
     91     private static final String TAG = "Launcher.Utilities";
     92 
     93     private static final Rect sOldBounds = new Rect();
     94     private static final Canvas sCanvas = new Canvas();
     95 
     96     private static final Pattern sTrimPattern =
     97             Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
     98 
     99     static {
    100         sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
    101                 Paint.FILTER_BITMAP_FLAG));
    102     }
    103     static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
    104     static int sColorIndex = 0;
    105 
    106     private static final int[] sLoc0 = new int[2];
    107     private static final int[] sLoc1 = new int[2];
    108 
    109     public static boolean isNycMR1OrAbove() {
    110         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
    111     }
    112 
    113     public static boolean isNycOrAbove() {
    114         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
    115     }
    116 
    117     public static final boolean ATLEAST_MARSHMALLOW =
    118             Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    119 
    120     public static final boolean ATLEAST_LOLLIPOP_MR1 =
    121             Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
    122 
    123     public static final boolean ATLEAST_LOLLIPOP =
    124             Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    125 
    126     public static final boolean ATLEAST_KITKAT =
    127             Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    128 
    129     public static final boolean ATLEAST_JB_MR1 =
    130             Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
    131 
    132     public static final boolean ATLEAST_JB_MR2 =
    133             Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
    134 
    135     // An intent extra to indicate the horizontal scroll of the wallpaper.
    136     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
    137 
    138     // These values are same as that in {@link AsyncTask}.
    139     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    140     private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    141     private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    142     private static final int KEEP_ALIVE = 1;
    143     /**
    144      * An {@link Executor} to be used with async task with no limit on the queue size.
    145      */
    146     public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
    147             CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
    148             TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    149 
    150     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
    151 
    152     public static boolean isPropertyEnabled(String propertyName) {
    153         return Log.isLoggable(propertyName, Log.VERBOSE);
    154     }
    155 
    156     public static boolean isAllowRotationPrefEnabled(Context context) {
    157         return getPrefs(context).getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
    158                 getAllowRotationDefaultValue(context));
    159     }
    160 
    161     public static boolean getAllowRotationDefaultValue(Context context) {
    162         if (isNycOrAbove()) {
    163             // If the device was scaled, used the original dimensions to determine if rotation
    164             // is allowed of not.
    165             Resources res = context.getResources();
    166             int originalSmallestWidth = res.getConfiguration().smallestScreenWidthDp
    167                     * res.getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEVICE_STABLE;
    168             return originalSmallestWidth >= 600;
    169         }
    170         return false;
    171     }
    172 
    173     public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
    174         byte[] data = c.getBlob(iconIndex);
    175         try {
    176             return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
    177         } catch (Exception e) {
    178             return null;
    179         }
    180     }
    181 
    182     /**
    183      * Returns a bitmap suitable for the all apps view. If the package or the resource do not
    184      * exist, it returns null.
    185      */
    186     public static Bitmap createIconBitmap(String packageName, String resourceName,
    187             Context context) {
    188         PackageManager packageManager = context.getPackageManager();
    189         // the resource
    190         try {
    191             Resources resources = packageManager.getResourcesForApplication(packageName);
    192             if (resources != null) {
    193                 final int id = resources.getIdentifier(resourceName, null, null);
    194                 return createIconBitmap(
    195                         resources.getDrawableForDensity(id, LauncherAppState.getInstance()
    196                                 .getInvariantDeviceProfile().fillResIconDpi), context);
    197             }
    198         } catch (Exception e) {
    199             // Icon not found.
    200         }
    201         return null;
    202     }
    203 
    204     private static int getIconBitmapSize() {
    205         return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
    206     }
    207 
    208     /**
    209      * Returns a bitmap which is of the appropriate size to be displayed as an icon
    210      */
    211     public static Bitmap createIconBitmap(Bitmap icon, Context context) {
    212         final int iconBitmapSize = getIconBitmapSize();
    213         if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
    214             return icon;
    215         }
    216         return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
    217     }
    218 
    219     /**
    220      * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
    221      * The bitmap is also visually normalized with other icons.
    222      */
    223     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    224     public static Bitmap createBadgedIconBitmap(
    225             Drawable icon, UserHandleCompat user, Context context) {
    226         float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
    227                 1 : IconNormalizer.getInstance().getScale(icon, null);
    228         Bitmap bitmap = createIconBitmap(icon, context, scale);
    229         return badgeIconForUser(bitmap, user, context);
    230     }
    231 
    232     /**
    233      * Badges the provided icon with the user badge if required.
    234      */
    235     public static Bitmap badgeIconForUser(Bitmap icon,  UserHandleCompat user, Context context) {
    236         if (Utilities.ATLEAST_LOLLIPOP && user != null
    237                 && !UserHandleCompat.myUserHandle().equals(user)) {
    238             BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
    239             Drawable badged = context.getPackageManager().getUserBadgedIcon(
    240                     drawable, user.getUser());
    241             if (badged instanceof BitmapDrawable) {
    242                 return ((BitmapDrawable) badged).getBitmap();
    243             } else {
    244                 return createIconBitmap(badged, context);
    245             }
    246         } else {
    247             return icon;
    248         }
    249     }
    250 
    251     /**
    252      * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
    253      * normalized with other icons and has enough spacing to add shadow.
    254      */
    255     public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
    256         RectF iconBounds = new RectF();
    257         float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
    258                 1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
    259         scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
    260         return createIconBitmap(icon, context, scale);
    261     }
    262 
    263     /**
    264      * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
    265      * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
    266      */
    267     public static Bitmap addShadowToIcon(Bitmap icon) {
    268         return ShadowGenerator.getInstance().recreateIcon(icon);
    269     }
    270 
    271     /**
    272      * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
    273      */
    274     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    275     public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
    276         int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
    277         synchronized (sCanvas) {
    278             sCanvas.setBitmap(srcTgt);
    279             sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
    280                     new Rect(srcTgt.getWidth() - badgeSize,
    281                             srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
    282                     new Paint(Paint.FILTER_BITMAP_FLAG));
    283             sCanvas.setBitmap(null);
    284         }
    285         return srcTgt;
    286     }
    287 
    288     /**
    289      * Returns a bitmap suitable for the all apps view.
    290      */
    291     public static Bitmap createIconBitmap(Drawable icon, Context context) {
    292         return createIconBitmap(icon, context, 1.0f /* scale */);
    293     }
    294 
    295     /**
    296      * @param scale the scale to apply before drawing {@param icon} on the canvas
    297      */
    298     public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
    299         synchronized (sCanvas) {
    300             final int iconBitmapSize = getIconBitmapSize();
    301 
    302             int width = iconBitmapSize;
    303             int height = iconBitmapSize;
    304 
    305             if (icon instanceof PaintDrawable) {
    306                 PaintDrawable painter = (PaintDrawable) icon;
    307                 painter.setIntrinsicWidth(width);
    308                 painter.setIntrinsicHeight(height);
    309             } else if (icon instanceof BitmapDrawable) {
    310                 // Ensure the bitmap has a density.
    311                 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
    312                 Bitmap bitmap = bitmapDrawable.getBitmap();
    313                 if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
    314                     bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
    315                 }
    316             }
    317             int sourceWidth = icon.getIntrinsicWidth();
    318             int sourceHeight = icon.getIntrinsicHeight();
    319             if (sourceWidth > 0 && sourceHeight > 0) {
    320                 // Scale the icon proportionally to the icon dimensions
    321                 final float ratio = (float) sourceWidth / sourceHeight;
    322                 if (sourceWidth > sourceHeight) {
    323                     height = (int) (width / ratio);
    324                 } else if (sourceHeight > sourceWidth) {
    325                     width = (int) (height * ratio);
    326                 }
    327             }
    328 
    329             // no intrinsic size --> use default size
    330             int textureWidth = iconBitmapSize;
    331             int textureHeight = iconBitmapSize;
    332 
    333             final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
    334                     Bitmap.Config.ARGB_8888);
    335             final Canvas canvas = sCanvas;
    336             canvas.setBitmap(bitmap);
    337 
    338             final int left = (textureWidth-width) / 2;
    339             final int top = (textureHeight-height) / 2;
    340 
    341             @SuppressWarnings("all") // suppress dead code warning
    342             final boolean debug = false;
    343             if (debug) {
    344                 // draw a big box for the icon for debugging
    345                 canvas.drawColor(sColors[sColorIndex]);
    346                 if (++sColorIndex >= sColors.length) sColorIndex = 0;
    347                 Paint debugPaint = new Paint();
    348                 debugPaint.setColor(0xffcccc00);
    349                 canvas.drawRect(left, top, left+width, top+height, debugPaint);
    350             }
    351 
    352             sOldBounds.set(icon.getBounds());
    353             icon.setBounds(left, top, left+width, top+height);
    354             canvas.save(Canvas.MATRIX_SAVE_FLAG);
    355             canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
    356             icon.draw(canvas);
    357             canvas.restore();
    358             icon.setBounds(sOldBounds);
    359             canvas.setBitmap(null);
    360 
    361             return bitmap;
    362         }
    363     }
    364 
    365     /**
    366      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
    367      * coordinates.
    368      *
    369      * @param descendant The descendant to which the passed coordinate is relative.
    370      * @param ancestor The root view to make the coordinates relative to.
    371      * @param coord The coordinate that we want mapped.
    372      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
    373      *          sometimes this is relevant as in a child's coordinates within the descendant.
    374      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
    375      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
    376      *         assumption fails, we will need to return a pair of scale factors.
    377      */
    378     public static float getDescendantCoordRelativeToAncestor(
    379             View descendant, View ancestor, int[] coord, boolean includeRootScroll) {
    380         float[] pt = {coord[0], coord[1]};
    381         float scale = 1.0f;
    382         View v = descendant;
    383         while(v != ancestor && v != null) {
    384             // For TextViews, scroll has a meaning which relates to the text position
    385             // which is very strange... ignore the scroll.
    386             if (v != descendant || includeRootScroll) {
    387                 pt[0] -= v.getScrollX();
    388                 pt[1] -= v.getScrollY();
    389             }
    390 
    391             v.getMatrix().mapPoints(pt);
    392             pt[0] += v.getLeft();
    393             pt[1] += v.getTop();
    394             scale *= v.getScaleX();
    395 
    396             v = (View) v.getParent();
    397         }
    398 
    399         coord[0] = Math.round(pt[0]);
    400         coord[1] = Math.round(pt[1]);
    401         return scale;
    402     }
    403 
    404     /**
    405      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}.
    406      */
    407     public static float mapCoordInSelfToDescendent(View descendant, View root,
    408                                                    int[] coord) {
    409         ArrayList<View> ancestorChain = new ArrayList<View>();
    410 
    411         float[] pt = {coord[0], coord[1]};
    412 
    413         View v = descendant;
    414         while(v != root) {
    415             ancestorChain.add(v);
    416             v = (View) v.getParent();
    417         }
    418         ancestorChain.add(root);
    419 
    420         float scale = 1.0f;
    421         Matrix inverse = new Matrix();
    422         int count = ancestorChain.size();
    423         for (int i = count - 1; i >= 0; i--) {
    424             View ancestor = ancestorChain.get(i);
    425             View next = i > 0 ? ancestorChain.get(i-1) : null;
    426 
    427             pt[0] += ancestor.getScrollX();
    428             pt[1] += ancestor.getScrollY();
    429 
    430             if (next != null) {
    431                 pt[0] -= next.getLeft();
    432                 pt[1] -= next.getTop();
    433                 next.getMatrix().invert(inverse);
    434                 inverse.mapPoints(pt);
    435                 scale *= next.getScaleX();
    436             }
    437         }
    438 
    439         coord[0] = (int) Math.round(pt[0]);
    440         coord[1] = (int) Math.round(pt[1]);
    441         return scale;
    442     }
    443 
    444     /**
    445      * Utility method to determine whether the given point, in local coordinates,
    446      * is inside the view, where the area of the view is expanded by the slop factor.
    447      * This method is called while processing touch-move events to determine if the event
    448      * is still within the view.
    449      */
    450     public static boolean pointInView(View v, float localX, float localY, float slop) {
    451         return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
    452                 localY < (v.getHeight() + slop);
    453     }
    454 
    455     /** Translates MotionEvents from src's coordinate system to dst's. */
    456     public static void translateEventCoordinates(View src, View dst, MotionEvent dstEvent) {
    457         toGlobalMotionEvent(src, dstEvent);
    458         toLocalMotionEvent(dst, dstEvent);
    459     }
    460 
    461     /**
    462      * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
    463      * (scaleX, scaleY, etc).
    464      */
    465     private static void toGlobalMotionEvent(View view, MotionEvent event) {
    466         view.getLocationOnScreen(sLoc0);
    467         event.offsetLocation(sLoc0[0], sLoc0[1]);
    468     }
    469 
    470     /**
    471      * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
    472      * (scaleX, scaleY, etc).
    473      */
    474     private static void toLocalMotionEvent(View view, MotionEvent event) {
    475         view.getLocationOnScreen(sLoc0);
    476         event.offsetLocation(-sLoc0[0], -sLoc0[1]);
    477     }
    478 
    479     public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
    480         v0.getLocationInWindow(sLoc0);
    481         v1.getLocationInWindow(sLoc1);
    482 
    483         sLoc0[0] += (v0.getMeasuredWidth() * v0.getScaleX()) / 2;
    484         sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
    485         sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
    486         sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
    487 
    488         if (delta == null) {
    489             delta = new int[2];
    490         }
    491 
    492         delta[0] = sLoc1[0] - sLoc0[0];
    493         delta[1] = sLoc1[1] - sLoc0[1];
    494 
    495         return delta;
    496     }
    497 
    498     public static void scaleRectAboutCenter(Rect r, float scale) {
    499         if (scale != 1.0f) {
    500             int cx = r.centerX();
    501             int cy = r.centerY();
    502             r.offset(-cx, -cy);
    503 
    504             r.left = (int) (r.left * scale + 0.5f);
    505             r.top = (int) (r.top * scale + 0.5f);
    506             r.right = (int) (r.right * scale + 0.5f);
    507             r.bottom = (int) (r.bottom * scale + 0.5f);
    508 
    509             r.offset(cx, cy);
    510         }
    511     }
    512 
    513     public static void startActivityForResultSafely(
    514             Activity activity, Intent intent, int requestCode) {
    515         try {
    516             activity.startActivityForResult(intent, requestCode);
    517         } catch (ActivityNotFoundException e) {
    518             Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
    519         } catch (SecurityException e) {
    520             Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
    521             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
    522                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
    523                     "or use the exported attribute for this activity.", e);
    524         }
    525     }
    526 
    527     static boolean isSystemApp(Context context, Intent intent) {
    528         PackageManager pm = context.getPackageManager();
    529         ComponentName cn = intent.getComponent();
    530         String packageName = null;
    531         if (cn == null) {
    532             ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
    533             if ((info != null) && (info.activityInfo != null)) {
    534                 packageName = info.activityInfo.packageName;
    535             }
    536         } else {
    537             packageName = cn.getPackageName();
    538         }
    539         if (packageName != null) {
    540             try {
    541                 PackageInfo info = pm.getPackageInfo(packageName, 0);
    542                 return (info != null) && (info.applicationInfo != null) &&
    543                         ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
    544             } catch (NameNotFoundException e) {
    545                 return false;
    546             }
    547         } else {
    548             return false;
    549         }
    550     }
    551 
    552     /**
    553      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
    554      * @param bitmap The bitmap to scan
    555      * @param samples The approximate max number of samples to use.
    556      */
    557     static int findDominantColorByHue(Bitmap bitmap, int samples) {
    558         final int height = bitmap.getHeight();
    559         final int width = bitmap.getWidth();
    560         int sampleStride = (int) Math.sqrt((height * width) / samples);
    561         if (sampleStride < 1) {
    562             sampleStride = 1;
    563         }
    564 
    565         // This is an out-param, for getting the hsv values for an rgb
    566         float[] hsv = new float[3];
    567 
    568         // First get the best hue, by creating a histogram over 360 hue buckets,
    569         // where each pixel contributes a score weighted by saturation, value, and alpha.
    570         float[] hueScoreHistogram = new float[360];
    571         float highScore = -1;
    572         int bestHue = -1;
    573 
    574         for (int y = 0; y < height; y += sampleStride) {
    575             for (int x = 0; x < width; x += sampleStride) {
    576                 int argb = bitmap.getPixel(x, y);
    577                 int alpha = 0xFF & (argb >> 24);
    578                 if (alpha < 0x80) {
    579                     // Drop mostly-transparent pixels.
    580                     continue;
    581                 }
    582                 // Remove the alpha channel.
    583                 int rgb = argb | 0xFF000000;
    584                 Color.colorToHSV(rgb, hsv);
    585                 // Bucket colors by the 360 integer hues.
    586                 int hue = (int) hsv[0];
    587                 if (hue < 0 || hue >= hueScoreHistogram.length) {
    588                     // Defensively avoid array bounds violations.
    589                     continue;
    590                 }
    591                 float score = hsv[1] * hsv[2];
    592                 hueScoreHistogram[hue] += score;
    593                 if (hueScoreHistogram[hue] > highScore) {
    594                     highScore = hueScoreHistogram[hue];
    595                     bestHue = hue;
    596                 }
    597             }
    598         }
    599 
    600         SparseArray<Float> rgbScores = new SparseArray<Float>();
    601         int bestColor = 0xff000000;
    602         highScore = -1;
    603         // Go back over the RGB colors that match the winning hue,
    604         // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
    605         // The highest-scoring RGB color wins.
    606         for (int y = 0; y < height; y += sampleStride) {
    607             for (int x = 0; x < width; x += sampleStride) {
    608                 int rgb = bitmap.getPixel(x, y) | 0xff000000;
    609                 Color.colorToHSV(rgb, hsv);
    610                 int hue = (int) hsv[0];
    611                 if (hue == bestHue) {
    612                     float s = hsv[1];
    613                     float v = hsv[2];
    614                     int bucket = (int) (s * 100) + (int) (v * 10000);
    615                     // Score by cumulative saturation * value.
    616                     float score = s * v;
    617                     Float oldTotal = rgbScores.get(bucket);
    618                     float newTotal = oldTotal == null ? score : oldTotal + score;
    619                     rgbScores.put(bucket, newTotal);
    620                     if (newTotal > highScore) {
    621                         highScore = newTotal;
    622                         // All the colors in the winning bucket are very similar. Last in wins.
    623                         bestColor = rgb;
    624                     }
    625                 }
    626             }
    627         }
    628         return bestColor;
    629     }
    630 
    631     /*
    632      * Finds a system apk which had a broadcast receiver listening to a particular action.
    633      * @param action intent action used to find the apk
    634      * @return a pair of apk package name and the resources.
    635      */
    636     static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
    637         final Intent intent = new Intent(action);
    638         for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
    639             if (info.activityInfo != null &&
    640                     (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
    641                 final String packageName = info.activityInfo.packageName;
    642                 try {
    643                     final Resources res = pm.getResourcesForApplication(packageName);
    644                     return Pair.create(packageName, res);
    645                 } catch (NameNotFoundException e) {
    646                     Log.w(TAG, "Failed to find resources for " + packageName);
    647                 }
    648             }
    649         }
    650         return null;
    651     }
    652 
    653     /**
    654      * Compresses the bitmap to a byte array for serialization.
    655      */
    656     public static byte[] flattenBitmap(Bitmap bitmap) {
    657         // Try go guesstimate how much space the icon will take when serialized
    658         // to avoid unnecessary allocations/copies during the write.
    659         int size = bitmap.getWidth() * bitmap.getHeight() * 4;
    660         ByteArrayOutputStream out = new ByteArrayOutputStream(size);
    661         try {
    662             bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
    663             out.flush();
    664             out.close();
    665             return out.toByteArray();
    666         } catch (IOException e) {
    667             Log.w(TAG, "Could not write bitmap");
    668             return null;
    669         }
    670     }
    671 
    672     /**
    673      * Trims the string, removing all whitespace at the beginning and end of the string.
    674      * Non-breaking whitespaces are also removed.
    675      */
    676     public static String trim(CharSequence s) {
    677         if (s == null) {
    678             return null;
    679         }
    680 
    681         // Just strip any sequence of whitespace or java space characters from the beginning and end
    682         Matcher m = sTrimPattern.matcher(s);
    683         return m.replaceAll("$1");
    684     }
    685 
    686     /**
    687      * Calculates the height of a given string at a specific text size.
    688      */
    689     public static int calculateTextHeight(float textSizePx) {
    690         Paint p = new Paint();
    691         p.setTextSize(textSizePx);
    692         Paint.FontMetrics fm = p.getFontMetrics();
    693         return (int) Math.ceil(fm.bottom - fm.top);
    694     }
    695 
    696     /**
    697      * Convenience println with multiple args.
    698      */
    699     public static void println(String key, Object... args) {
    700         StringBuilder b = new StringBuilder();
    701         b.append(key);
    702         b.append(": ");
    703         boolean isFirstArgument = true;
    704         for (Object arg : args) {
    705             if (isFirstArgument) {
    706                 isFirstArgument = false;
    707             } else {
    708                 b.append(", ");
    709             }
    710             b.append(arg);
    711         }
    712         System.out.println(b.toString());
    713     }
    714 
    715     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    716     public static boolean isRtl(Resources res) {
    717         return ATLEAST_JB_MR1 &&
    718                 (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
    719     }
    720 
    721     /**
    722      * Returns true if the intent is a valid launch intent for a launcher activity of an app.
    723      * This is used to identify shortcuts which are different from the ones exposed by the
    724      * applications' manifest file.
    725      *
    726      * @param launchIntent The intent that will be launched when the shortcut is clicked.
    727      */
    728     public static boolean isLauncherAppTarget(Intent launchIntent) {
    729         if (launchIntent != null
    730                 && Intent.ACTION_MAIN.equals(launchIntent.getAction())
    731                 && launchIntent.getComponent() != null
    732                 && launchIntent.getCategories() != null
    733                 && launchIntent.getCategories().size() == 1
    734                 && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
    735                 && TextUtils.isEmpty(launchIntent.getDataString())) {
    736             // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
    737             Bundle extras = launchIntent.getExtras();
    738             if (extras == null) {
    739                 return true;
    740             } else {
    741                 Set<String> keys = extras.keySet();
    742                 return keys.size() == 1 && keys.contains(ItemInfo.EXTRA_PROFILE);
    743             }
    744         };
    745         return false;
    746     }
    747 
    748     public static float dpiFromPx(int size, DisplayMetrics metrics){
    749         float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
    750         return (size / densityRatio);
    751     }
    752     public static int pxFromDp(float size, DisplayMetrics metrics) {
    753         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    754                 size, metrics));
    755     }
    756     public static int pxFromSp(float size, DisplayMetrics metrics) {
    757         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
    758                 size, metrics));
    759     }
    760 
    761     public static String createDbSelectionQuery(String columnName, Iterable<?> values) {
    762         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values));
    763     }
    764 
    765     public static boolean isBootCompleted() {
    766         return "1".equals(getSystemProperty("sys.boot_completed", "1"));
    767     }
    768 
    769     public static String getSystemProperty(String property, String defaultValue) {
    770         try {
    771             Class clazz = Class.forName("android.os.SystemProperties");
    772             Method getter = clazz.getDeclaredMethod("get", String.class);
    773             String value = (String) getter.invoke(null, property);
    774             if (!TextUtils.isEmpty(value)) {
    775                 return value;
    776             }
    777         } catch (Exception e) {
    778             Log.d(TAG, "Unable to read system properties");
    779         }
    780         return defaultValue;
    781     }
    782 
    783     /**
    784      * Ensures that a value is within given bounds. Specifically:
    785      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
    786      * return upperBound; else return value unchanged.
    787      */
    788     public static int boundToRange(int value, int lowerBound, int upperBound) {
    789         return Math.max(lowerBound, Math.min(value, upperBound));
    790     }
    791 
    792     /**
    793      * @see #boundToRange(int, int, int).
    794      */
    795     public static float boundToRange(float value, float lowerBound, float upperBound) {
    796         return Math.max(lowerBound, Math.min(value, upperBound));
    797     }
    798 
    799     /**
    800      * Wraps a message with a TTS span, so that a different message is spoken than
    801      * what is getting displayed.
    802      * @param msg original message
    803      * @param ttsMsg message to be spoken
    804      */
    805     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    806     public static CharSequence wrapForTts(CharSequence msg, String ttsMsg) {
    807         if (Utilities.ATLEAST_LOLLIPOP) {
    808             SpannableString spanned = new SpannableString(msg);
    809             spanned.setSpan(new TtsSpan.TextBuilder(ttsMsg).build(),
    810                     0, spanned.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
    811             return spanned;
    812         } else {
    813             return msg;
    814         }
    815     }
    816 
    817     /**
    818      * Replacement for Long.compare() which was added in API level 19.
    819      */
    820     public static int longCompare(long lhs, long rhs) {
    821         return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
    822     }
    823 
    824     public static SharedPreferences getPrefs(Context context) {
    825         return context.getSharedPreferences(
    826                 LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
    827     }
    828 
    829     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    830     public static boolean isPowerSaverOn(Context context) {
    831         PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    832         return ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
    833     }
    834 
    835     public static boolean isWallapaperAllowed(Context context) {
    836         if (isNycOrAbove()) {
    837             try {
    838                 WallpaperManager wm = context.getSystemService(WallpaperManager.class);
    839                 return (Boolean) wm.getClass().getDeclaredMethod("isSetWallpaperAllowed")
    840                         .invoke(wm);
    841             } catch (Exception e) { }
    842         }
    843         return true;
    844     }
    845 
    846     public static void closeSilently(Closeable c) {
    847         if (c != null) {
    848             try {
    849                 c.close();
    850             } catch (IOException e) {
    851                 if (ProviderConfig.IS_DOGFOOD_BUILD) {
    852                     Log.d(TAG, "Error closing", e);
    853                 }
    854             }
    855         }
    856     }
    857 
    858     /**
    859      * Returns true if {@param original} contains all entries defined in {@param updates} and
    860      * have the same value.
    861      * The comparison uses {@link Object#equals(Object)} to compare the values.
    862      */
    863     public static boolean containsAll(Bundle original, Bundle updates) {
    864         for (String key : updates.keySet()) {
    865             Object value1 = updates.get(key);
    866             Object value2 = original.get(key);
    867             if (value1 == null) {
    868                 if (value2 != null) {
    869                     return false;
    870                 }
    871             } else if (!value1.equals(value2)) {
    872                 return false;
    873             }
    874         }
    875         return true;
    876     }
    877 
    878     /** Returns whether the collection is null or empty. */
    879     public static boolean isEmpty(Collection c) {
    880         return c == null || c.isEmpty();
    881     }
    882 
    883     /**
    884      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
    885      * This allows the badging to be done based on the action bitmap size rather than
    886      * the scaled bitmap size.
    887      */
    888     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
    889 
    890         public FixedSizeBitmapDrawable(Bitmap bitmap) {
    891             super(null, bitmap);
    892         }
    893 
    894         @Override
    895         public int getIntrinsicHeight() {
    896             return getBitmap().getWidth();
    897         }
    898 
    899         @Override
    900         public int getIntrinsicWidth() {
    901             return getBitmap().getWidth();
    902         }
    903     }
    904 
    905     public static int getColorAccent(Context context) {
    906         TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
    907         int colorAccent = ta.getColor(0, 0);
    908         ta.recycle();
    909         return colorAccent;
    910     }
    911 
    912     public static void sendCustomAccessibilityEvent(View target, int type, String text) {
    913         AccessibilityManager accessibilityManager = (AccessibilityManager)
    914                 target.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
    915         if (accessibilityManager.isEnabled()) {
    916             AccessibilityEvent event = AccessibilityEvent.obtain(type);
    917             target.onInitializeAccessibilityEvent(event);
    918             event.getText().add(text);
    919             accessibilityManager.sendAccessibilityEvent(event);
    920         }
    921     }
    922 }
    923