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.Activity;
     20 import android.content.ActivityNotFoundException;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     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.Canvas;
     32 import android.graphics.Color;
     33 import android.graphics.Matrix;
     34 import android.graphics.Paint;
     35 import android.graphics.PaintFlagsDrawFilter;
     36 import android.graphics.Rect;
     37 import android.graphics.drawable.BitmapDrawable;
     38 import android.graphics.drawable.Drawable;
     39 import android.graphics.drawable.PaintDrawable;
     40 import android.os.Build;
     41 import android.util.Log;
     42 import android.util.Pair;
     43 import android.util.SparseArray;
     44 import android.view.View;
     45 import android.widget.Toast;
     46 
     47 import java.util.ArrayList;
     48 
     49 /**
     50  * Various utilities shared amongst the Launcher's classes.
     51  */
     52 public final class Utilities {
     53     private static final String TAG = "Launcher.Utilities";
     54 
     55     private static int sIconWidth = -1;
     56     private static int sIconHeight = -1;
     57     public static int sIconTextureWidth = -1;
     58     public static int sIconTextureHeight = -1;
     59 
     60     private static final Rect sOldBounds = new Rect();
     61     private static final Canvas sCanvas = new Canvas();
     62 
     63     static {
     64         sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
     65                 Paint.FILTER_BITMAP_FLAG));
     66     }
     67     static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
     68     static int sColorIndex = 0;
     69 
     70     static int[] sLoc0 = new int[2];
     71     static int[] sLoc1 = new int[2];
     72 
     73     // To turn on these properties, type
     74     // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
     75     static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
     76     public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
     77 
     78     /**
     79      * Returns a FastBitmapDrawable with the icon, accurately sized.
     80      */
     81     public static FastBitmapDrawable createIconDrawable(Bitmap icon) {
     82         FastBitmapDrawable d = new FastBitmapDrawable(icon);
     83         d.setFilterBitmap(true);
     84         resizeIconDrawable(d);
     85         return d;
     86     }
     87 
     88     /**
     89      * Resizes an icon drawable to the correct icon size.
     90      */
     91     static void resizeIconDrawable(Drawable icon) {
     92         icon.setBounds(0, 0, sIconTextureWidth, sIconTextureHeight);
     93     }
     94 
     95     private static boolean isPropertyEnabled(String propertyName) {
     96         return Log.isLoggable(propertyName, Log.VERBOSE);
     97     }
     98 
     99     public static boolean isRotationEnabled(Context c) {
    100         boolean enableRotation = sForceEnableRotation ||
    101                 c.getResources().getBoolean(R.bool.allow_rotation);
    102         return enableRotation;
    103     }
    104 
    105     /**
    106      * Indicates if the device is running LMP or higher.
    107      */
    108     public static boolean isLmpOrAbove() {
    109         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.L;
    110     }
    111 
    112     /**
    113      * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
    114      * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
    115      * to the proper size (48dp)
    116      */
    117     static Bitmap createIconBitmap(Bitmap icon, Context context) {
    118         int textureWidth = sIconTextureWidth;
    119         int textureHeight = sIconTextureHeight;
    120         int sourceWidth = icon.getWidth();
    121         int sourceHeight = icon.getHeight();
    122         if (sourceWidth > textureWidth && sourceHeight > textureHeight) {
    123             // Icon is bigger than it should be; clip it (solves the GB->ICS migration case)
    124             return Bitmap.createBitmap(icon,
    125                     (sourceWidth - textureWidth) / 2,
    126                     (sourceHeight - textureHeight) / 2,
    127                     textureWidth, textureHeight);
    128         } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) {
    129             // Icon is the right size, no need to change it
    130             return icon;
    131         } else {
    132             // Icon is too small, render to a larger bitmap
    133             final Resources resources = context.getResources();
    134             return createIconBitmap(new BitmapDrawable(resources, icon), context);
    135         }
    136     }
    137 
    138     /**
    139      * Returns a bitmap suitable for the all apps view.
    140      */
    141     public static Bitmap createIconBitmap(Drawable icon, Context context) {
    142         synchronized (sCanvas) { // we share the statics :-(
    143             if (sIconWidth == -1) {
    144                 initStatics(context);
    145             }
    146 
    147             int width = sIconWidth;
    148             int height = sIconHeight;
    149 
    150             if (icon instanceof PaintDrawable) {
    151                 PaintDrawable painter = (PaintDrawable) icon;
    152                 painter.setIntrinsicWidth(width);
    153                 painter.setIntrinsicHeight(height);
    154             } else if (icon instanceof BitmapDrawable) {
    155                 // Ensure the bitmap has a density.
    156                 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
    157                 Bitmap bitmap = bitmapDrawable.getBitmap();
    158                 if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
    159                     bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
    160                 }
    161             }
    162             int sourceWidth = icon.getIntrinsicWidth();
    163             int sourceHeight = icon.getIntrinsicHeight();
    164             if (sourceWidth > 0 && sourceHeight > 0) {
    165                 // Scale the icon proportionally to the icon dimensions
    166                 final float ratio = (float) sourceWidth / sourceHeight;
    167                 if (sourceWidth > sourceHeight) {
    168                     height = (int) (width / ratio);
    169                 } else if (sourceHeight > sourceWidth) {
    170                     width = (int) (height * ratio);
    171                 }
    172             }
    173 
    174             // no intrinsic size --> use default size
    175             int textureWidth = sIconTextureWidth;
    176             int textureHeight = sIconTextureHeight;
    177 
    178             final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
    179                     Bitmap.Config.ARGB_8888);
    180             final Canvas canvas = sCanvas;
    181             canvas.setBitmap(bitmap);
    182 
    183             final int left = (textureWidth-width) / 2;
    184             final int top = (textureHeight-height) / 2;
    185 
    186             @SuppressWarnings("all") // suppress dead code warning
    187             final boolean debug = false;
    188             if (debug) {
    189                 // draw a big box for the icon for debugging
    190                 canvas.drawColor(sColors[sColorIndex]);
    191                 if (++sColorIndex >= sColors.length) sColorIndex = 0;
    192                 Paint debugPaint = new Paint();
    193                 debugPaint.setColor(0xffcccc00);
    194                 canvas.drawRect(left, top, left+width, top+height, debugPaint);
    195             }
    196 
    197             sOldBounds.set(icon.getBounds());
    198             icon.setBounds(left, top, left+width, top+height);
    199             icon.draw(canvas);
    200             icon.setBounds(sOldBounds);
    201             canvas.setBitmap(null);
    202 
    203             return bitmap;
    204         }
    205     }
    206 
    207     /**
    208      * Returns a Bitmap representing the thumbnail of the specified Bitmap.
    209      *
    210      * @param bitmap The bitmap to get a thumbnail of.
    211      * @param context The application's context.
    212      *
    213      * @return A thumbnail for the specified bitmap or the bitmap itself if the
    214      *         thumbnail could not be created.
    215      */
    216     static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) {
    217         synchronized (sCanvas) { // we share the statics :-(
    218             if (sIconWidth == -1) {
    219                 initStatics(context);
    220             }
    221 
    222             if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) {
    223                 return bitmap;
    224             } else {
    225                 final Resources resources = context.getResources();
    226                 return createIconBitmap(new BitmapDrawable(resources, bitmap), context);
    227             }
    228         }
    229     }
    230 
    231     /**
    232      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
    233      * coordinates.
    234      *
    235      * @param descendant The descendant to which the passed coordinate is relative.
    236      * @param root The root view to make the coordinates relative to.
    237      * @param coord The coordinate that we want mapped.
    238      * @param includeRootScroll Whether or not to account for the scroll of the descendant:
    239      *          sometimes this is relevant as in a child's coordinates within the descendant.
    240      * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
    241      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
    242      *         assumption fails, we will need to return a pair of scale factors.
    243      */
    244     public static float getDescendantCoordRelativeToParent(View descendant, View root,
    245                                                            int[] coord, boolean includeRootScroll) {
    246         ArrayList<View> ancestorChain = new ArrayList<View>();
    247 
    248         float[] pt = {coord[0], coord[1]};
    249 
    250         View v = descendant;
    251         while(v != root && v != null) {
    252             ancestorChain.add(v);
    253             v = (View) v.getParent();
    254         }
    255         ancestorChain.add(root);
    256 
    257         float scale = 1.0f;
    258         int count = ancestorChain.size();
    259         for (int i = 0; i < count; i++) {
    260             View v0 = ancestorChain.get(i);
    261             // For TextViews, scroll has a meaning which relates to the text position
    262             // which is very strange... ignore the scroll.
    263             if (v0 != descendant || includeRootScroll) {
    264                 pt[0] -= v0.getScrollX();
    265                 pt[1] -= v0.getScrollY();
    266             }
    267 
    268             v0.getMatrix().mapPoints(pt);
    269             pt[0] += v0.getLeft();
    270             pt[1] += v0.getTop();
    271             scale *= v0.getScaleX();
    272         }
    273 
    274         coord[0] = (int) Math.round(pt[0]);
    275         coord[1] = (int) Math.round(pt[1]);
    276         return scale;
    277     }
    278 
    279     /**
    280      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
    281      */
    282     public static float mapCoordInSelfToDescendent(View descendant, View root,
    283                                                    int[] coord) {
    284         ArrayList<View> ancestorChain = new ArrayList<View>();
    285 
    286         float[] pt = {coord[0], coord[1]};
    287 
    288         View v = descendant;
    289         while(v != root) {
    290             ancestorChain.add(v);
    291             v = (View) v.getParent();
    292         }
    293         ancestorChain.add(root);
    294 
    295         float scale = 1.0f;
    296         Matrix inverse = new Matrix();
    297         int count = ancestorChain.size();
    298         for (int i = count - 1; i >= 0; i--) {
    299             View ancestor = ancestorChain.get(i);
    300             View next = i > 0 ? ancestorChain.get(i-1) : null;
    301 
    302             pt[0] += ancestor.getScrollX();
    303             pt[1] += ancestor.getScrollY();
    304 
    305             if (next != null) {
    306                 pt[0] -= next.getLeft();
    307                 pt[1] -= next.getTop();
    308                 next.getMatrix().invert(inverse);
    309                 inverse.mapPoints(pt);
    310                 scale *= next.getScaleX();
    311             }
    312         }
    313 
    314         coord[0] = (int) Math.round(pt[0]);
    315         coord[1] = (int) Math.round(pt[1]);
    316         return scale;
    317     }
    318 
    319     /**
    320      * Utility method to determine whether the given point, in local coordinates,
    321      * is inside the view, where the area of the view is expanded by the slop factor.
    322      * This method is called while processing touch-move events to determine if the event
    323      * is still within the view.
    324      */
    325     public static boolean pointInView(View v, float localX, float localY, float slop) {
    326         return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
    327                 localY < (v.getHeight() + slop);
    328     }
    329 
    330     private static void initStatics(Context context) {
    331         final Resources resources = context.getResources();
    332         sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size);
    333         sIconTextureWidth = sIconTextureHeight = sIconWidth;
    334     }
    335 
    336     public static void setIconSize(int widthPx) {
    337         sIconWidth = sIconHeight = widthPx;
    338         sIconTextureWidth = sIconTextureHeight = widthPx;
    339     }
    340 
    341     public static void scaleRect(Rect r, float scale) {
    342         if (scale != 1.0f) {
    343             r.left = (int) (r.left * scale + 0.5f);
    344             r.top = (int) (r.top * scale + 0.5f);
    345             r.right = (int) (r.right * scale + 0.5f);
    346             r.bottom = (int) (r.bottom * scale + 0.5f);
    347         }
    348     }
    349 
    350     public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) {
    351         v0.getLocationInWindow(sLoc0);
    352         v1.getLocationInWindow(sLoc1);
    353 
    354         sLoc0[0] += (v0.getMeasuredWidth() * v0.getScaleX()) / 2;
    355         sLoc0[1] += (v0.getMeasuredHeight() * v0.getScaleY()) / 2;
    356         sLoc1[0] += (v1.getMeasuredWidth() * v1.getScaleX()) / 2;
    357         sLoc1[1] += (v1.getMeasuredHeight() * v1.getScaleY()) / 2;
    358 
    359         if (delta == null) {
    360             delta = new int[2];
    361         }
    362 
    363         delta[0] = sLoc1[0] - sLoc0[0];
    364         delta[1] = sLoc1[1] - sLoc0[1];
    365 
    366         return delta;
    367     }
    368 
    369     public static void scaleRectAboutCenter(Rect r, float scale) {
    370         int cx = r.centerX();
    371         int cy = r.centerY();
    372         r.offset(-cx, -cy);
    373         Utilities.scaleRect(r, scale);
    374         r.offset(cx, cy);
    375     }
    376 
    377     public static void startActivityForResultSafely(
    378             Activity activity, Intent intent, int requestCode) {
    379         try {
    380             activity.startActivityForResult(intent, requestCode);
    381         } catch (ActivityNotFoundException e) {
    382             Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
    383         } catch (SecurityException e) {
    384             Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
    385             Log.e(TAG, "Launcher does not have the permission to launch " + intent +
    386                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
    387                     "or use the exported attribute for this activity.", e);
    388         }
    389     }
    390 
    391     static boolean isSystemApp(Context context, Intent intent) {
    392         PackageManager pm = context.getPackageManager();
    393         ComponentName cn = intent.getComponent();
    394         String packageName = null;
    395         if (cn == null) {
    396             ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
    397             if ((info != null) && (info.activityInfo != null)) {
    398                 packageName = info.activityInfo.packageName;
    399             }
    400         } else {
    401             packageName = cn.getPackageName();
    402         }
    403         if (packageName != null) {
    404             try {
    405                 PackageInfo info = pm.getPackageInfo(packageName, 0);
    406                 return (info != null) && (info.applicationInfo != null) &&
    407                         ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
    408             } catch (NameNotFoundException e) {
    409                 return false;
    410             }
    411         } else {
    412             return false;
    413         }
    414     }
    415 
    416     /**
    417      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
    418      * @param bitmap The bitmap to scan
    419      * @param samples The approximate max number of samples to use.
    420      */
    421     static int findDominantColorByHue(Bitmap bitmap, int samples) {
    422         final int height = bitmap.getHeight();
    423         final int width = bitmap.getWidth();
    424         int sampleStride = (int) Math.sqrt((height * width) / samples);
    425         if (sampleStride < 1) {
    426             sampleStride = 1;
    427         }
    428 
    429         // This is an out-param, for getting the hsv values for an rgb
    430         float[] hsv = new float[3];
    431 
    432         // First get the best hue, by creating a histogram over 360 hue buckets,
    433         // where each pixel contributes a score weighted by saturation, value, and alpha.
    434         float[] hueScoreHistogram = new float[360];
    435         float highScore = -1;
    436         int bestHue = -1;
    437 
    438         for (int y = 0; y < height; y += sampleStride) {
    439             for (int x = 0; x < width; x += sampleStride) {
    440                 int argb = bitmap.getPixel(x, y);
    441                 int alpha = 0xFF & (argb >> 24);
    442                 if (alpha < 0x80) {
    443                     // Drop mostly-transparent pixels.
    444                     continue;
    445                 }
    446                 // Remove the alpha channel.
    447                 int rgb = argb | 0xFF000000;
    448                 Color.colorToHSV(rgb, hsv);
    449                 // Bucket colors by the 360 integer hues.
    450                 int hue = (int) hsv[0];
    451                 if (hue < 0 || hue >= hueScoreHistogram.length) {
    452                     // Defensively avoid array bounds violations.
    453                     continue;
    454                 }
    455                 float score = hsv[1] * hsv[2];
    456                 hueScoreHistogram[hue] += score;
    457                 if (hueScoreHistogram[hue] > highScore) {
    458                     highScore = hueScoreHistogram[hue];
    459                     bestHue = hue;
    460                 }
    461             }
    462         }
    463 
    464         SparseArray<Float> rgbScores = new SparseArray<Float>();
    465         int bestColor = 0xff000000;
    466         highScore = -1;
    467         // Go back over the RGB colors that match the winning hue,
    468         // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
    469         // The highest-scoring RGB color wins.
    470         for (int y = 0; y < height; y += sampleStride) {
    471             for (int x = 0; x < width; x += sampleStride) {
    472                 int rgb = bitmap.getPixel(x, y) | 0xff000000;
    473                 Color.colorToHSV(rgb, hsv);
    474                 int hue = (int) hsv[0];
    475                 if (hue == bestHue) {
    476                     float s = hsv[1];
    477                     float v = hsv[2];
    478                     int bucket = (int) (s * 100) + (int) (v * 10000);
    479                     // Score by cumulative saturation * value.
    480                     float score = s * v;
    481                     Float oldTotal = rgbScores.get(bucket);
    482                     float newTotal = oldTotal == null ? score : oldTotal + score;
    483                     rgbScores.put(bucket, newTotal);
    484                     if (newTotal > highScore) {
    485                         highScore = newTotal;
    486                         // All the colors in the winning bucket are very similar. Last in wins.
    487                         bestColor = rgb;
    488                     }
    489                 }
    490             }
    491         }
    492         return bestColor;
    493     }
    494 
    495     /*
    496      * Finds a system apk which had a broadcast receiver listening to a particular action.
    497      * @param action intent action used to find the apk
    498      * @return a pair of apk package name and the resources.
    499      */
    500     static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
    501         final Intent intent = new Intent(action);
    502         for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
    503             if (info.activityInfo != null &&
    504                     (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
    505                 final String packageName = info.activityInfo.packageName;
    506                 try {
    507                     final Resources res = pm.getResourcesForApplication(packageName);
    508                     return Pair.create(packageName, res);
    509                 } catch (NameNotFoundException e) {
    510                     Log.w(TAG, "Failed to find resources for " + packageName);
    511                 }
    512             }
    513         }
    514         return null;
    515     }
    516 }
    517