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