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