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.Context; 22 import android.content.Intent; 23 import android.content.res.Resources; 24 import android.graphics.Bitmap; 25 import android.graphics.BlurMaskFilter; 26 import android.graphics.Canvas; 27 import android.graphics.ColorMatrix; 28 import android.graphics.ColorMatrixColorFilter; 29 import android.graphics.Matrix; 30 import android.graphics.Paint; 31 import android.graphics.PaintFlagsDrawFilter; 32 import android.graphics.Rect; 33 import android.graphics.drawable.BitmapDrawable; 34 import android.graphics.drawable.Drawable; 35 import android.graphics.drawable.PaintDrawable; 36 import android.util.DisplayMetrics; 37 import android.util.Log; 38 import android.view.View; 39 import android.widget.Toast; 40 41 import java.util.ArrayList; 42 43 /** 44 * Various utilities shared amongst the Launcher's classes. 45 */ 46 final class Utilities { 47 private static final String TAG = "Launcher.Utilities"; 48 49 private static int sIconWidth = -1; 50 private static int sIconHeight = -1; 51 public static int sIconTextureWidth = -1; 52 public static int sIconTextureHeight = -1; 53 54 private static final Paint sBlurPaint = new Paint(); 55 private static final Paint sGlowColorPressedPaint = new Paint(); 56 private static final Paint sGlowColorFocusedPaint = new Paint(); 57 private static final Paint sDisabledPaint = new Paint(); 58 private static final Rect sOldBounds = new Rect(); 59 private static final Canvas sCanvas = new Canvas(); 60 61 static { 62 sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, 63 Paint.FILTER_BITMAP_FLAG)); 64 } 65 static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff }; 66 static int sColorIndex = 0; 67 68 /** 69 * Returns a FastBitmapDrawable with the icon, accurately sized. 70 */ 71 static Drawable createIconDrawable(Bitmap icon) { 72 FastBitmapDrawable d = new FastBitmapDrawable(icon); 73 d.setFilterBitmap(true); 74 resizeIconDrawable(d); 75 return d; 76 } 77 78 /** 79 * Resizes an icon drawable to the correct icon size. 80 */ 81 static void resizeIconDrawable(Drawable icon) { 82 icon.setBounds(0, 0, sIconTextureWidth, sIconTextureHeight); 83 } 84 85 /** 86 * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS 87 * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size) 88 * to the proper size (48dp) 89 */ 90 static Bitmap createIconBitmap(Bitmap icon, Context context) { 91 int textureWidth = sIconTextureWidth; 92 int textureHeight = sIconTextureHeight; 93 int sourceWidth = icon.getWidth(); 94 int sourceHeight = icon.getHeight(); 95 if (sourceWidth > textureWidth && sourceHeight > textureHeight) { 96 // Icon is bigger than it should be; clip it (solves the GB->ICS migration case) 97 return Bitmap.createBitmap(icon, 98 (sourceWidth - textureWidth) / 2, 99 (sourceHeight - textureHeight) / 2, 100 textureWidth, textureHeight); 101 } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) { 102 // Icon is the right size, no need to change it 103 return icon; 104 } else { 105 // Icon is too small, render to a larger bitmap 106 final Resources resources = context.getResources(); 107 return createIconBitmap(new BitmapDrawable(resources, icon), context); 108 } 109 } 110 111 /** 112 * Returns a bitmap suitable for the all apps view. 113 */ 114 static Bitmap createIconBitmap(Drawable icon, Context context) { 115 synchronized (sCanvas) { // we share the statics :-( 116 if (sIconWidth == -1) { 117 initStatics(context); 118 } 119 120 int width = sIconWidth; 121 int height = sIconHeight; 122 123 if (icon instanceof PaintDrawable) { 124 PaintDrawable painter = (PaintDrawable) icon; 125 painter.setIntrinsicWidth(width); 126 painter.setIntrinsicHeight(height); 127 } else if (icon instanceof BitmapDrawable) { 128 // Ensure the bitmap has a density. 129 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; 130 Bitmap bitmap = bitmapDrawable.getBitmap(); 131 if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { 132 bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics()); 133 } 134 } 135 int sourceWidth = icon.getIntrinsicWidth(); 136 int sourceHeight = icon.getIntrinsicHeight(); 137 if (sourceWidth > 0 && sourceHeight > 0) { 138 // Scale the icon proportionally to the icon dimensions 139 final float ratio = (float) sourceWidth / sourceHeight; 140 if (sourceWidth > sourceHeight) { 141 height = (int) (width / ratio); 142 } else if (sourceHeight > sourceWidth) { 143 width = (int) (height * ratio); 144 } 145 } 146 147 // no intrinsic size --> use default size 148 int textureWidth = sIconTextureWidth; 149 int textureHeight = sIconTextureHeight; 150 151 final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, 152 Bitmap.Config.ARGB_8888); 153 final Canvas canvas = sCanvas; 154 canvas.setBitmap(bitmap); 155 156 final int left = (textureWidth-width) / 2; 157 final int top = (textureHeight-height) / 2; 158 159 @SuppressWarnings("all") // suppress dead code warning 160 final boolean debug = false; 161 if (debug) { 162 // draw a big box for the icon for debugging 163 canvas.drawColor(sColors[sColorIndex]); 164 if (++sColorIndex >= sColors.length) sColorIndex = 0; 165 Paint debugPaint = new Paint(); 166 debugPaint.setColor(0xffcccc00); 167 canvas.drawRect(left, top, left+width, top+height, debugPaint); 168 } 169 170 sOldBounds.set(icon.getBounds()); 171 icon.setBounds(left, top, left+width, top+height); 172 icon.draw(canvas); 173 icon.setBounds(sOldBounds); 174 canvas.setBitmap(null); 175 176 return bitmap; 177 } 178 } 179 180 /** 181 * Returns a Bitmap representing the thumbnail of the specified Bitmap. 182 * 183 * @param bitmap The bitmap to get a thumbnail of. 184 * @param context The application's context. 185 * 186 * @return A thumbnail for the specified bitmap or the bitmap itself if the 187 * thumbnail could not be created. 188 */ 189 static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) { 190 synchronized (sCanvas) { // we share the statics :-( 191 if (sIconWidth == -1) { 192 initStatics(context); 193 } 194 195 if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) { 196 return bitmap; 197 } else { 198 final Resources resources = context.getResources(); 199 return createIconBitmap(new BitmapDrawable(resources, bitmap), context); 200 } 201 } 202 } 203 204 /** 205 * Given a coordinate relative to the descendant, find the coordinate in a parent view's 206 * coordinates. 207 * 208 * @param descendant The descendant to which the passed coordinate is relative. 209 * @param root The root view to make the coordinates relative to. 210 * @param coord The coordinate that we want mapped. 211 * @param includeRootScroll Whether or not to account for the scroll of the descendant: 212 * sometimes this is relevant as in a child's coordinates within the descendant. 213 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 214 * this scale factor is assumed to be equal in X and Y, and so if at any point this 215 * assumption fails, we will need to return a pair of scale factors. 216 */ 217 public static float getDescendantCoordRelativeToParent(View descendant, View root, 218 int[] coord, boolean includeRootScroll) { 219 ArrayList<View> ancestorChain = new ArrayList<View>(); 220 221 float[] pt = {coord[0], coord[1]}; 222 223 View v = descendant; 224 while(v != root && v != null) { 225 ancestorChain.add(v); 226 v = (View) v.getParent(); 227 } 228 ancestorChain.add(root); 229 230 float scale = 1.0f; 231 int count = ancestorChain.size(); 232 for (int i = 0; i < count; i++) { 233 View v0 = ancestorChain.get(i); 234 // For TextViews, scroll has a meaning which relates to the text position 235 // which is very strange... ignore the scroll. 236 if (v0 != descendant || includeRootScroll) { 237 pt[0] -= v0.getScrollX(); 238 pt[1] -= v0.getScrollY(); 239 } 240 241 v0.getMatrix().mapPoints(pt); 242 pt[0] += v0.getLeft(); 243 pt[1] += v0.getTop(); 244 scale *= v0.getScaleX(); 245 } 246 247 coord[0] = (int) Math.round(pt[0]); 248 coord[1] = (int) Math.round(pt[1]); 249 return scale; 250 } 251 252 /** 253 * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. 254 */ 255 public static float mapCoordInSelfToDescendent(View descendant, View root, 256 int[] coord) { 257 ArrayList<View> ancestorChain = new ArrayList<View>(); 258 259 float[] pt = {coord[0], coord[1]}; 260 261 View v = descendant; 262 while(v != root) { 263 ancestorChain.add(v); 264 v = (View) v.getParent(); 265 } 266 ancestorChain.add(root); 267 268 float scale = 1.0f; 269 Matrix inverse = new Matrix(); 270 int count = ancestorChain.size(); 271 for (int i = count - 1; i >= 0; i--) { 272 View ancestor = ancestorChain.get(i); 273 View next = i > 0 ? ancestorChain.get(i-1) : null; 274 275 pt[0] += ancestor.getScrollX(); 276 pt[1] += ancestor.getScrollY(); 277 278 if (next != null) { 279 pt[0] -= next.getLeft(); 280 pt[1] -= next.getTop(); 281 next.getMatrix().invert(inverse); 282 inverse.mapPoints(pt); 283 scale *= next.getScaleX(); 284 } 285 } 286 287 coord[0] = (int) Math.round(pt[0]); 288 coord[1] = (int) Math.round(pt[1]); 289 return scale; 290 } 291 292 private static void initStatics(Context context) { 293 final Resources resources = context.getResources(); 294 final DisplayMetrics metrics = resources.getDisplayMetrics(); 295 final float density = metrics.density; 296 297 sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size); 298 sIconTextureWidth = sIconTextureHeight = sIconWidth; 299 300 sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL)); 301 sGlowColorPressedPaint.setColor(0xffffc300); 302 sGlowColorFocusedPaint.setColor(0xffff8e00); 303 304 ColorMatrix cm = new ColorMatrix(); 305 cm.setSaturation(0.2f); 306 sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm)); 307 sDisabledPaint.setAlpha(0x88); 308 } 309 310 public static void setIconSize(int widthPx) { 311 sIconWidth = sIconHeight = widthPx; 312 sIconTextureWidth = sIconTextureHeight = widthPx; 313 } 314 315 public static void scaleRect(Rect r, float scale) { 316 if (scale != 1.0f) { 317 r.left = (int) (r.left * scale + 0.5f); 318 r.top = (int) (r.top * scale + 0.5f); 319 r.right = (int) (r.right * scale + 0.5f); 320 r.bottom = (int) (r.bottom * scale + 0.5f); 321 } 322 } 323 324 public static void scaleRectAboutCenter(Rect r, float scale) { 325 int cx = r.centerX(); 326 int cy = r.centerY(); 327 r.offset(-cx, -cy); 328 Utilities.scaleRect(r, scale); 329 r.offset(cx, cy); 330 } 331 332 public static void startActivityForResultSafely( 333 Activity activity, Intent intent, int requestCode) { 334 try { 335 activity.startActivityForResult(intent, requestCode); 336 } catch (ActivityNotFoundException e) { 337 Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 338 } catch (SecurityException e) { 339 Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 340 Log.e(TAG, "Launcher does not have the permission to launch " + intent + 341 ". Make sure to create a MAIN intent-filter for the corresponding activity " + 342 "or use the exported attribute for this activity.", e); 343 } 344 } 345 } 346