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