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