Util.java revision d5d74642d7c0b43578b43cdd46b70671b64c88e2
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        ParcelFileDescriptor input = null;
306        try {
307            input = cr.openFileDescriptor(uri, "r");
308            return makeBitmap(targetWidthOrHeight, uri, cr, input, null);
309        } catch (IOException ex) {
310            return null;
311        } finally {
312            closeSilently(input);
313        }
314    }
315
316    public static Bitmap makeBitmap(int targetWidthHeight, Uri uri,
317            ContentResolver cr, ParcelFileDescriptor pfd,
318            BitmapFactory.Options options) {
319        Bitmap b = null;
320        try {
321            if (pfd == null) pfd = makeInputStream(uri, cr);
322            if (pfd == null) return null;
323            if (options == null) options = new BitmapFactory.Options();
324
325            FileDescriptor fd = pfd.getFileDescriptor();
326            options.inSampleSize = 1;
327            if (targetWidthHeight != -1) {
328                options.inJustDecodeBounds = true;
329                BitmapManager.instance().decodeFileDescriptor(fd, options);
330                if (options.mCancel || options.outWidth == -1
331                        || options.outHeight == -1) {
332                    return null;
333                }
334                options.inSampleSize =
335                        computeSampleSize(options, targetWidthHeight);
336                options.inJustDecodeBounds = false;
337            }
338
339            options.inDither = false;
340            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
341            b = BitmapManager.instance().decodeFileDescriptor(fd, options);
342        } catch (OutOfMemoryError ex) {
343            Log.e(TAG, "Got oom exception ", ex);
344            return null;
345        } finally {
346            closeSilently(pfd);
347        }
348        return b;
349    }
350
351    private static ParcelFileDescriptor makeInputStream(
352            Uri uri, ContentResolver cr) {
353        try {
354            return cr.openFileDescriptor(uri, "r");
355        } catch (IOException ex) {
356            return null;
357        }
358    }
359
360    public static void debugWhere(String tag, String msg) {
361        Log.d(tag, msg + " --- stack trace begins: ");
362        StackTraceElement elements[] = Thread.currentThread().getStackTrace();
363        // skip first 3 element, they are not related to the caller
364        for (int i = 3, n = elements.length; i < n; ++i) {
365            StackTraceElement st = elements[i];
366            String message = String.format("    at %s.%s(%s:%s)",
367                    st.getClassName(), st.getMethodName(), st.getFileName(),
368                    st.getLineNumber());
369            Log.d(tag, message);
370        }
371        Log.d(tag, msg + " --- stack trace ends.");
372    }
373
374    public static <T> void showProgressDialog(final Context context,
375            String title, String message, PriorityTask<T> task) {
376        final ProgressDialog dialog =
377                ProgressDialog.show(context, title, message);
378
379        task.addCallback(new PriorityTask.Callback<T>() {
380
381            public void onCanceled(PriorityTask<T> t) {
382                dialog.dismiss();
383            }
384
385            public void onFail(PriorityTask<T> t, Throwable error) {
386                dialog.dismiss();
387            }
388
389            public void onResultAvailable(PriorityTask<T> t, T result) {
390                dialog.dismiss();
391            }
392        });
393    }
394
395    public static synchronized OnClickListener getNullOnClickListener() {
396        if (sNullOnClickListener == null) {
397            sNullOnClickListener = new OnClickListener() {
398                public void onClick(View v) {
399                }
400            };
401        }
402        return sNullOnClickListener;
403    }
404
405    public static void Assert(boolean cond) {
406        if (!cond) {
407            throw new AssertionError();
408        }
409    }
410
411    public static boolean equals(String a, String b) {
412        // return true if both string are null or the content equals
413        return a == b || a.equals(b);
414    }
415
416    private static class BackgroundJob
417            extends MonitoredActivity.LifeCycleAdapter implements Runnable {
418
419        private final MonitoredActivity mActivity;
420        private final ProgressDialog mDialog;
421        private final Runnable mJob;
422        private final Handler mHandler;
423
424        public BackgroundJob(MonitoredActivity activity, Runnable job,
425                ProgressDialog dialog, Handler handler) {
426            mActivity = activity;
427            mDialog = dialog;
428            mJob = job;
429            mActivity.addLifeCycleListener(this);
430            mHandler = handler;
431        }
432
433        public void run() {
434            try {
435                mJob.run();
436            } finally {
437                mHandler.post(new Runnable() {
438                    public void run() {
439                        mActivity.removeLifeCycleListener(BackgroundJob.this);
440                        mDialog.dismiss();
441                    }
442                });
443            }
444        }
445
446        @Override
447        public void onActivityStopped(MonitoredActivity activity) {
448            mDialog.hide();
449        }
450
451        @Override
452        public void onActivityStarted(MonitoredActivity activity) {
453            mDialog.show();
454        }
455    }
456
457    public static void startBackgroundJob(MonitoredActivity activity,
458            String title, String message, Runnable job, Handler handler) {
459        // Make the progress dialog uncancelable, so that we can gurantee
460        // the thread will be done before the activity getting destroyed.
461        ProgressDialog dialog = ProgressDialog.show(
462                activity, title, message, true, false);
463        new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
464    }
465
466    // Returns an intent which is used for "set as" menu items.
467    public static Intent createSetAsIntent(IImage image) {
468        Uri u = image.fullSizeImageUri();
469        Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
470        intent.setDataAndType(u, image.getMimeType());
471        intent.putExtra("mimeType", image.getMimeType());
472        return intent;
473    }
474
475    // Returns Options that set the puregeable flag for Bitmap decode.
476    public static BitmapFactory.Options createPurgeableOption() {
477        BitmapFactory.Options options = new BitmapFactory.Options();
478        options.inPurgeable = true;
479        options.inInputShareable = true;
480        return options;
481    }
482}
483