• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import android.app.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 {
sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG))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      */
createIconDrawable(Bitmap icon)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      */
resizeIconDrawable(Drawable icon)91     static void resizeIconDrawable(Drawable icon) {
92         icon.setBounds(0, 0, sIconTextureWidth, sIconTextureHeight);
93     }
94 
isPropertyEnabled(String propertyName)95     private static boolean isPropertyEnabled(String propertyName) {
96         return Log.isLoggable(propertyName, Log.VERBOSE);
97     }
98 
isRotationEnabled(Context c)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      */
isLmpOrAbove()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      */
createIconBitmap(Bitmap icon, Context context)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      */
createIconBitmap(Drawable icon, Context context)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      */
resampleIconBitmap(Bitmap bitmap, Context context)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      */
getDescendantCoordRelativeToParent(View descendant, View root, int[] coord, boolean includeRootScroll)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      */
mapCoordInSelfToDescendent(View descendant, View root, int[] coord)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      */
pointInView(View v, float localX, float localY, float slop)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 
initStatics(Context context)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 
setIconSize(int widthPx)336     public static void setIconSize(int widthPx) {
337         sIconWidth = sIconHeight = widthPx;
338         sIconTextureWidth = sIconTextureHeight = widthPx;
339     }
340 
scaleRect(Rect r, float scale)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 
getCenterDeltaInScreenSpace(View v0, View v1, int[] delta)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 
scaleRectAboutCenter(Rect r, float scale)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 
startActivityForResultSafely( Activity activity, Intent intent, int requestCode)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 
isSystemApp(Context context, Intent intent)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      */
findDominantColorByHue(Bitmap bitmap, int samples)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      */
findSystemApk(String action, PackageManager pm)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