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