Util.java revision ce033a5871a0b678521411f9a3acaee2ab279178
1/* 2 * Copyright (C) 2009 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 17package com.android.camera; 18 19import com.android.camera.gallery.IImage; 20 21import android.app.ProgressDialog; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.graphics.Bitmap; 26import android.graphics.BitmapFactory; 27import android.graphics.Canvas; 28import android.graphics.Matrix; 29import android.graphics.Rect; 30import android.media.MediaMetadataRetriever; 31import android.net.Uri; 32import android.os.Handler; 33import android.os.ParcelFileDescriptor; 34import android.util.Log; 35import android.view.View; 36import android.view.View.OnClickListener; 37 38import java.io.ByteArrayOutputStream; 39import java.io.Closeable; 40import java.io.FileDescriptor; 41import java.io.IOException; 42 43/** 44 * Collection of utility functions used in this package. 45 */ 46public class Util { 47 private static final String TAG = "db.Util"; 48 49 private static OnClickListener sNullOnClickListener; 50 51 private Util() { 52 } 53 54 // Rotates the bitmap by the specified degree. 55 // If a new bitmap is created, the original bitmap is recycled. 56 public static Bitmap rotate(Bitmap b, int degrees) { 57 if (degrees != 0 && b != null) { 58 Matrix m = new Matrix(); 59 m.setRotate(degrees, 60 (float) b.getWidth() / 2, (float) b.getHeight() / 2); 61 try { 62 Bitmap b2 = Bitmap.createBitmap( 63 b, 0, 0, b.getWidth(), b.getHeight(), m, true); 64 if (b != b2) { 65 b.recycle(); 66 b = b2; 67 } 68 } catch (OutOfMemoryError ex) { 69 // We have no memory to rotate. Return the original bitmap. 70 } 71 } 72 return b; 73 } 74 75 /* 76 * Compute the sample size as a function of minSideLength 77 * and maxNumOfPixels. 78 * minSideLength is used to specify that minimal width or height of a bitmap. 79 * maxNumOfPixels is used to specify the maximal size in pixels that are tolerable 80 * in terms of memory usage. 81 * 82 * The function returns a sample size based on the constraints. 83 * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED, 84 * which indicates no care of the corresponding constraint. 85 * The functions prefers returning a sample size that 86 * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED. 87 */ 88 public static int computeSampleSize(BitmapFactory.Options options, 89 int minSideLength, int maxNumOfPixels) { 90 double w = options.outWidth; 91 double h = options.outHeight; 92 93 int lowerBound = (maxNumOfPixels == IImage.UNCONSTRAINED) ? 1 : 94 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); 95 int upperBound = (minSideLength == IImage.UNCONSTRAINED) ? 128 : 96 (int) Math.min(Math.floor(w / minSideLength), 97 Math.floor(h / minSideLength)); 98 99 if ((maxNumOfPixels == IImage.UNCONSTRAINED) && 100 (minSideLength == IImage.UNCONSTRAINED)) { 101 return 1; 102 } else if (minSideLength == IImage.UNCONSTRAINED) { 103 return lowerBound; 104 } else { 105 return upperBound; 106 } 107 } 108 109 public static Bitmap transform(Matrix scaler, 110 Bitmap source, 111 int targetWidth, 112 int targetHeight, 113 boolean scaleUp) { 114 int deltaX = source.getWidth() - targetWidth; 115 int deltaY = source.getHeight() - targetHeight; 116 if (!scaleUp && (deltaX < 0 || deltaY < 0)) { 117 /* 118 * In this case the bitmap is smaller, at least in one dimension, 119 * than the target. Transform it by placing as much of the image 120 * as possible into the target and leaving the top/bottom or 121 * left/right (or both) black. 122 */ 123 Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight, 124 Bitmap.Config.ARGB_8888); 125 Canvas c = new Canvas(b2); 126 127 int deltaXHalf = Math.max(0, deltaX / 2); 128 int deltaYHalf = Math.max(0, deltaY / 2); 129 Rect src = new Rect( 130 deltaXHalf, 131 deltaYHalf, 132 deltaXHalf + Math.min(targetWidth, source.getWidth()), 133 deltaYHalf + Math.min(targetHeight, source.getHeight())); 134 int dstX = (targetWidth - src.width()) / 2; 135 int dstY = (targetHeight - src.height()) / 2; 136 Rect dst = new Rect( 137 dstX, 138 dstY, 139 targetWidth - dstX, 140 targetHeight - dstY); 141 c.drawBitmap(source, src, dst, null); 142 return b2; 143 } 144 float bitmapWidthF = source.getWidth(); 145 float bitmapHeightF = source.getHeight(); 146 147 float bitmapAspect = bitmapWidthF / bitmapHeightF; 148 float viewAspect = (float) targetWidth / targetHeight; 149 150 if (bitmapAspect > viewAspect) { 151 float scale = targetHeight / bitmapHeightF; 152 if (scale < .9F || scale > 1F) { 153 scaler.setScale(scale, scale); 154 } else { 155 scaler = null; 156 } 157 } else { 158 float scale = targetWidth / bitmapWidthF; 159 if (scale < .9F || scale > 1F) { 160 scaler.setScale(scale, scale); 161 } else { 162 scaler = null; 163 } 164 } 165 166 Bitmap b1; 167 if (scaler != null) { 168 // this is used for minithumb and crop, so we want to filter here. 169 b1 = Bitmap.createBitmap(source, 0, 0, 170 source.getWidth(), source.getHeight(), scaler, true); 171 } else { 172 b1 = source; 173 } 174 175 int dx1 = Math.max(0, b1.getWidth() - targetWidth); 176 int dy1 = Math.max(0, b1.getHeight() - targetHeight); 177 178 Bitmap b2 = Bitmap.createBitmap( 179 b1, 180 dx1 / 2, 181 dy1 / 2, 182 targetWidth, 183 targetHeight); 184 185 if (b1 != source) { 186 b1.recycle(); 187 } 188 189 return b2; 190 } 191 192 /** 193 * Creates a centered bitmap of the desired size. Recycles the input. 194 * @param source 195 */ 196 public static Bitmap extractMiniThumb( 197 Bitmap source, int width, int height) { 198 return Util.extractMiniThumb(source, width, height, true); 199 } 200 201 public static Bitmap extractMiniThumb( 202 Bitmap source, int width, int height, boolean recycle) { 203 if (source == null) { 204 return null; 205 } 206 207 float scale; 208 if (source.getWidth() < source.getHeight()) { 209 scale = width / (float) source.getWidth(); 210 } else { 211 scale = height / (float) source.getHeight(); 212 } 213 Matrix matrix = new Matrix(); 214 matrix.setScale(scale, scale); 215 Bitmap miniThumbnail = transform(matrix, source, width, height, false); 216 217 if (recycle && miniThumbnail != source) { 218 source.recycle(); 219 } 220 return miniThumbnail; 221 } 222 223 /** 224 * Creates a byte[] for a given bitmap of the desired size. Recycles the 225 * input bitmap. 226 */ 227 public static byte[] miniThumbData(Bitmap source) { 228 if (source == null) return null; 229 230 Bitmap miniThumbnail = extractMiniThumb( 231 source, IImage.MINI_THUMB_TARGET_SIZE, 232 IImage.MINI_THUMB_TARGET_SIZE); 233 234 ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream(); 235 miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream); 236 miniThumbnail.recycle(); 237 238 try { 239 miniOutStream.close(); 240 byte [] data = miniOutStream.toByteArray(); 241 return data; 242 } catch (java.io.IOException ex) { 243 Log.e(TAG, "got exception ex " + ex); 244 } 245 return null; 246 } 247 248 /** 249 * Create a video thumbnail for a video. May return null if the video is 250 * corrupt. 251 * 252 * @param filePath 253 */ 254 public static Bitmap createVideoThumbnail(String filePath) { 255 Bitmap bitmap = null; 256 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 257 try { 258 retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); 259 retriever.setDataSource(filePath); 260 bitmap = retriever.captureFrame(); 261 } catch (IllegalArgumentException ex) { 262 // Assume this is a corrupt video file 263 } catch (RuntimeException ex) { 264 // Assume this is a corrupt video file. 265 } finally { 266 try { 267 retriever.release(); 268 } catch (RuntimeException ex) { 269 // Ignore failures while cleaning up. 270 } 271 } 272 return bitmap; 273 } 274 275 public static <T> int indexOf(T [] array, T s) { 276 for (int i = 0; i < array.length; i++) { 277 if (array[i].equals(s)) { 278 return i; 279 } 280 } 281 return -1; 282 } 283 284 public static void closeSilently(Closeable c) { 285 if (c == null) return; 286 try { 287 c.close(); 288 } catch (Throwable t) { 289 // do nothing 290 } 291 } 292 293 public static void closeSilently(ParcelFileDescriptor c) { 294 if (c == null) return; 295 try { 296 c.close(); 297 } catch (Throwable t) { 298 // do nothing 299 } 300 } 301 302 /** 303 * Make a bitmap from a given Uri. 304 * 305 * @param uri 306 */ 307 public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels, 308 Uri uri, ContentResolver cr) { 309 return makeBitmap(minSideLength, maxNumOfPixels, uri, cr, 310 IImage.NO_NATIVE); 311 } 312 313 public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels, 314 Uri uri, ContentResolver cr, boolean useNative) { 315 ParcelFileDescriptor input = null; 316 try { 317 input = cr.openFileDescriptor(uri, "r"); 318 BitmapFactory.Options options = null; 319 if (useNative) { 320 options = createNativeAllocOptions(); 321 } 322 return makeBitmap(minSideLength, maxNumOfPixels, uri, cr, input, 323 options); 324 } catch (IOException ex) { 325 return null; 326 } finally { 327 closeSilently(input); 328 } 329 } 330 331 public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels, 332 ParcelFileDescriptor pfd, boolean useNative) { 333 BitmapFactory.Options options = null; 334 if (useNative) { 335 options = createNativeAllocOptions(); 336 } 337 return makeBitmap(minSideLength, maxNumOfPixels, null, null, pfd, 338 options); 339 } 340 341 public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels, 342 Uri uri, ContentResolver cr, ParcelFileDescriptor pfd, 343 BitmapFactory.Options options) { 344 Bitmap b = null; 345 try { 346 if (pfd == null) pfd = makeInputStream(uri, cr); 347 if (pfd == null) return null; 348 if (options == null) options = new BitmapFactory.Options(); 349 350 FileDescriptor fd = pfd.getFileDescriptor(); 351 options.inSampleSize = 1; 352 options.inJustDecodeBounds = true; 353 BitmapManager.instance().decodeFileDescriptor(fd, options); 354 if (options.mCancel || options.outWidth == -1 355 || options.outHeight == -1) { 356 return null; 357 } 358 options.inSampleSize = computeSampleSize( 359 options, minSideLength, maxNumOfPixels); 360 options.inJustDecodeBounds = false; 361 362 options.inDither = false; 363 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 364 b = BitmapManager.instance().decodeFileDescriptor(fd, options); 365 } catch (OutOfMemoryError ex) { 366 Log.e(TAG, "Got oom exception ", ex); 367 return null; 368 } finally { 369 closeSilently(pfd); 370 } 371 return b; 372 } 373 374 private static ParcelFileDescriptor makeInputStream( 375 Uri uri, ContentResolver cr) { 376 try { 377 return cr.openFileDescriptor(uri, "r"); 378 } catch (IOException ex) { 379 return null; 380 } 381 } 382 383 public static void debugWhere(String tag, String msg) { 384 Log.d(tag, msg + " --- stack trace begins: "); 385 StackTraceElement elements[] = Thread.currentThread().getStackTrace(); 386 // skip first 3 element, they are not related to the caller 387 for (int i = 3, n = elements.length; i < n; ++i) { 388 StackTraceElement st = elements[i]; 389 String message = String.format(" at %s.%s(%s:%s)", 390 st.getClassName(), st.getMethodName(), st.getFileName(), 391 st.getLineNumber()); 392 Log.d(tag, message); 393 } 394 Log.d(tag, msg + " --- stack trace ends."); 395 } 396 397 public static synchronized OnClickListener getNullOnClickListener() { 398 if (sNullOnClickListener == null) { 399 sNullOnClickListener = new OnClickListener() { 400 public void onClick(View v) { 401 } 402 }; 403 } 404 return sNullOnClickListener; 405 } 406 407 public static void Assert(boolean cond) { 408 if (!cond) { 409 throw new AssertionError(); 410 } 411 } 412 413 public static boolean equals(String a, String b) { 414 // return true if both string are null or the content equals 415 return a == b || a.equals(b); 416 } 417 418 private static class BackgroundJob 419 extends MonitoredActivity.LifeCycleAdapter implements Runnable { 420 421 private final MonitoredActivity mActivity; 422 private final ProgressDialog mDialog; 423 private final Runnable mJob; 424 private final Handler mHandler; 425 private final Runnable mCleanupRunner = new Runnable() { 426 public void run() { 427 mActivity.removeLifeCycleListener(BackgroundJob.this); 428 if (mDialog.getWindow() != null) mDialog.dismiss(); 429 } 430 }; 431 432 public BackgroundJob(MonitoredActivity activity, Runnable job, 433 ProgressDialog dialog, Handler handler) { 434 mActivity = activity; 435 mDialog = dialog; 436 mJob = job; 437 mActivity.addLifeCycleListener(this); 438 mHandler = handler; 439 } 440 441 public void run() { 442 try { 443 mJob.run(); 444 } finally { 445 mHandler.post(mCleanupRunner); 446 } 447 } 448 449 450 @Override 451 public void onActivityDestroyed(MonitoredActivity activity) { 452 // We get here only when the onDestroyed being called before 453 // the mCleanupRunner. So, run it now and remove it from the queue 454 mCleanupRunner.run(); 455 mHandler.removeCallbacks(mCleanupRunner); 456 } 457 458 @Override 459 public void onActivityStopped(MonitoredActivity activity) { 460 mDialog.hide(); 461 } 462 463 @Override 464 public void onActivityStarted(MonitoredActivity activity) { 465 mDialog.show(); 466 } 467 } 468 469 public static void startBackgroundJob(MonitoredActivity activity, 470 String title, String message, Runnable job, Handler handler) { 471 // Make the progress dialog uncancelable, so that we can gurantee 472 // the thread will be done before the activity getting destroyed. 473 ProgressDialog dialog = ProgressDialog.show( 474 activity, title, message, true, false); 475 new Thread(new BackgroundJob(activity, job, dialog, handler)).start(); 476 } 477 478 // Returns an intent which is used for "set as" menu items. 479 public static Intent createSetAsIntent(IImage image) { 480 Uri u = image.fullSizeImageUri(); 481 Intent intent = new Intent(Intent.ACTION_ATTACH_DATA); 482 intent.setDataAndType(u, image.getMimeType()); 483 intent.putExtra("mimeType", image.getMimeType()); 484 return intent; 485 } 486 487 // Returns Options that set the puregeable flag for Bitmap decode. 488 public static BitmapFactory.Options createNativeAllocOptions() { 489 BitmapFactory.Options options = new BitmapFactory.Options(); 490 options.inNativeAlloc = true; 491 return options; 492 } 493} 494