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