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