Util.java revision 482a3a54cfa8ec2cc913efc0e73a01d78453ee6d
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.graphics.Bitmap;
25import android.graphics.BitmapFactory;
26import android.graphics.Canvas;
27import android.graphics.Matrix;
28import android.graphics.Rect;
29import android.media.MediaMetadataRetriever;
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;
36
37import java.io.ByteArrayOutputStream;
38import java.io.Closeable;
39import java.io.FileDescriptor;
40import java.io.IOException;
41
42/**
43 * Collection of utility functions used in this package.
44 */
45public class Util {
46    private static final String TAG = "db.Util";
47
48    private static OnClickListener sNullOnClickListener;
49
50    private Util() {
51    }
52
53    // Rotates the bitmap by the specified degree.
54    // If a new bitmap is created, the original bitmap is recycled.
55    public static Bitmap rotate(Bitmap b, int degrees) {
56        if (degrees != 0 && b != null) {
57            Matrix m = new Matrix();
58            m.setRotate(degrees,
59                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
60            try {
61                Bitmap b2 = Bitmap.createBitmap(
62                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
63                if (b != b2) {
64                    b.recycle();
65                    b = b2;
66                }
67            } catch (OutOfMemoryError ex) {
68                // We have no memory to rotate. Return the original bitmap.
69            }
70        }
71        return b;
72    }
73
74    /*
75     * Compute the sample size as a function of the image size and the target.
76     * Scale the image down so that both the width and height are just above the
77     * target. If this means that one of the dimension goes from above the
78     * target to below the target (e.g. given a width of 480 and an image width
79     * of 600 but sample size of 2 -- i.e. new width 300 -- bump the sample size
80     * down by 1.
81     */
82    public static int computeSampleSize(
83            BitmapFactory.Options options, int target) {
84        int w = options.outWidth;
85        int h = options.outHeight;
86
87        int candidateW = w / target;
88        int candidateH = h / target;
89        int candidate = Math.max(candidateW, candidateH);
90
91        if (candidate == 0) return 1;
92
93        if (candidate > 1) {
94            if ((w > target) && (w / candidate) < target) candidate -= 1;
95        }
96
97        if (candidate > 1) {
98            if ((h > target) && (h / candidate) < target) candidate -= 1;
99        }
100
101        return candidate;
102    }
103
104    public static Bitmap transform(Matrix scaler,
105                                   Bitmap source,
106                                   int targetWidth,
107                                   int targetHeight,
108                                   boolean scaleUp) {
109        int deltaX = source.getWidth() - targetWidth;
110        int deltaY = source.getHeight() - targetHeight;
111        if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
112            /*
113             * In this case the bitmap is smaller, at least in one dimension,
114             * than the target.  Transform it by placing as much of the image
115             * as possible into the target and leaving the top/bottom or
116             * left/right (or both) black.
117             */
118            Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
119                    Bitmap.Config.ARGB_8888);
120            Canvas c = new Canvas(b2);
121
122            int deltaXHalf = Math.max(0, deltaX / 2);
123            int deltaYHalf = Math.max(0, deltaY / 2);
124            Rect src = new Rect(
125                    deltaXHalf,
126                    deltaYHalf,
127                    deltaXHalf + Math.min(targetWidth, source.getWidth()),
128                    deltaYHalf + Math.min(targetHeight, source.getHeight()));
129            int dstX = (targetWidth  - src.width())  / 2;
130            int dstY = (targetHeight - src.height()) / 2;
131            Rect dst = new Rect(
132                    dstX,
133                    dstY,
134                    targetWidth - dstX,
135                    targetHeight - dstY);
136            c.drawBitmap(source, src, dst, null);
137            return b2;
138        }
139        float bitmapWidthF = source.getWidth();
140        float bitmapHeightF = source.getHeight();
141
142        float bitmapAspect = bitmapWidthF / bitmapHeightF;
143        float viewAspect   = (float) targetWidth / targetHeight;
144
145        if (bitmapAspect > viewAspect) {
146            float scale = targetHeight / bitmapHeightF;
147            if (scale < .9F || scale > 1F) {
148                scaler.setScale(scale, scale);
149            } else {
150                scaler = null;
151            }
152        } else {
153            float scale = targetWidth / bitmapWidthF;
154            if (scale < .9F || scale > 1F) {
155                scaler.setScale(scale, scale);
156            } else {
157                scaler = null;
158            }
159        }
160
161        Bitmap b1;
162        if (scaler != null) {
163            // this is used for minithumb and crop, so we want to filter here.
164            b1 = Bitmap.createBitmap(source, 0, 0,
165                    source.getWidth(), source.getHeight(), scaler, true);
166        } else {
167            b1 = source;
168        }
169
170        int dx1 = Math.max(0, b1.getWidth() - targetWidth);
171        int dy1 = Math.max(0, b1.getHeight() - targetHeight);
172
173        Bitmap b2 = Bitmap.createBitmap(
174                b1,
175                dx1 / 2,
176                dy1 / 2,
177                targetWidth,
178                targetHeight);
179
180        if (b1 != source) {
181            b1.recycle();
182        }
183
184        return b2;
185    }
186
187    /**
188     * Creates a centered bitmap of the desired size. Recycles the input.
189     * @param source
190     */
191    public static Bitmap extractMiniThumb(
192            Bitmap source, int width, int height) {
193        return Util.extractMiniThumb(source, width, height, true);
194    }
195
196    public static Bitmap extractMiniThumb(
197            Bitmap source, int width, int height, boolean recycle) {
198        if (source == null) {
199            return null;
200        }
201
202        float scale;
203        if (source.getWidth() < source.getHeight()) {
204            scale = width / (float) source.getWidth();
205        } else {
206            scale = height / (float) source.getHeight();
207        }
208        Matrix matrix = new Matrix();
209        matrix.setScale(scale, scale);
210        Bitmap miniThumbnail = transform(matrix, source, width, height, false);
211
212        if (recycle && miniThumbnail != source) {
213            source.recycle();
214        }
215        return miniThumbnail;
216    }
217
218    /**
219     * Creates a byte[] for a given bitmap of the desired size. Recycles the
220     * input bitmap.
221     */
222    public static byte[] miniThumbData(Bitmap source) {
223        if (source == null) return null;
224
225        Bitmap miniThumbnail = extractMiniThumb(
226                source, IImage.MINI_THUMB_TARGET_SIZE,
227                IImage.MINI_THUMB_TARGET_SIZE);
228
229        ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream();
230        miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);
231        miniThumbnail.recycle();
232
233        try {
234            miniOutStream.close();
235            byte [] data = miniOutStream.toByteArray();
236            return data;
237        } catch (java.io.IOException ex) {
238            Log.e(TAG, "got exception ex " + ex);
239        }
240        return null;
241    }
242
243    /**
244     * Create a video thumbnail for a video. May return null if the video is
245     * corrupt.
246     *
247     * @param filePath
248     */
249    public static Bitmap createVideoThumbnail(String filePath) {
250        Bitmap bitmap = null;
251        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
252        try {
253            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
254            retriever.setDataSource(filePath);
255            bitmap = retriever.captureFrame();
256        } catch (IllegalArgumentException ex) {
257            // Assume this is a corrupt video file
258        } catch (RuntimeException ex) {
259            // Assume this is a corrupt video file.
260        } finally {
261            try {
262                retriever.release();
263            } catch (RuntimeException ex) {
264                // Ignore failures while cleaning up.
265            }
266        }
267        return bitmap;
268    }
269
270    public static <T>  int indexOf(T [] array, T s) {
271        for (int i = 0; i < array.length; i++) {
272            if (array[i].equals(s)) {
273                return i;
274            }
275        }
276        return -1;
277    }
278
279    public static void closeSilently(Closeable c) {
280        if (c == null) return;
281        try {
282            c.close();
283        } catch (Throwable t) {
284            // do nothing
285        }
286    }
287
288    public static void closeSilently(ParcelFileDescriptor c) {
289        if (c == null) return;
290        try {
291            c.close();
292        } catch (Throwable t) {
293            // do nothing
294        }
295    }
296
297    /**
298     * Make a bitmap from a given Uri.
299     *
300     * @param uri
301     */
302    public static Bitmap makeBitmap(int targetWidthOrHeight, Uri uri,
303            ContentResolver cr) {
304        ParcelFileDescriptor input = null;
305        try {
306            input = cr.openFileDescriptor(uri, "r");
307            return makeBitmap(targetWidthOrHeight, uri, cr, input, null);
308        } catch (IOException ex) {
309            return null;
310        } finally {
311            closeSilently(input);
312        }
313    }
314
315    public static Bitmap makeBitmap(int targetWidthHeight, Uri uri,
316            ContentResolver cr, ParcelFileDescriptor pfd,
317            BitmapFactory.Options options) {
318        Bitmap b = null;
319        try {
320            if (pfd == null) pfd = makeInputStream(uri, cr);
321            if (pfd == null) return null;
322            if (options == null) options = new BitmapFactory.Options();
323
324            FileDescriptor fd = pfd.getFileDescriptor();
325            options.inSampleSize = 1;
326            if (targetWidthHeight != -1) {
327                options.inJustDecodeBounds = true;
328                BitmapManager.instance().decodeFileDescriptor(fd, options);
329                if (options.mCancel || options.outWidth == -1
330                        || options.outHeight == -1) {
331                    return null;
332                }
333                options.inSampleSize =
334                        computeSampleSize(options, targetWidthHeight);
335                options.inJustDecodeBounds = false;
336            }
337
338            options.inDither = false;
339            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
340            b = BitmapManager.instance().decodeFileDescriptor(fd, options);
341        } catch (OutOfMemoryError ex) {
342            Log.e(TAG, "Got oom exception ", ex);
343            return null;
344        } finally {
345            closeSilently(pfd);
346        }
347        return b;
348    }
349
350    private static ParcelFileDescriptor makeInputStream(
351            Uri uri, ContentResolver cr) {
352        try {
353            return cr.openFileDescriptor(uri, "r");
354        } catch (IOException ex) {
355            return null;
356        }
357    }
358
359    public static void debugWhere(String tag, String msg) {
360        Log.d(tag, msg + " --- stack trace begins: ");
361        StackTraceElement elements[] = Thread.currentThread().getStackTrace();
362        // skip first 3 element, they are not related to the caller
363        for (int i = 3, n = elements.length; i < n; ++i) {
364            StackTraceElement st = elements[i];
365            String message = String.format("    at %s.%s(%s:%s)",
366                    st.getClassName(), st.getMethodName(), st.getFileName(),
367                    st.getLineNumber());
368            Log.d(tag, message);
369        }
370        Log.d(tag, msg + " --- stack trace ends.");
371    }
372
373    public static <T> void showProgressDialog(final Context context,
374            String title, String message, PriorityTask<T> task) {
375        final ProgressDialog dialog =
376                ProgressDialog.show(context, title, message);
377
378        task.addCallback(new PriorityTask.Callback<T>() {
379
380            public void onCanceled(PriorityTask<T> t) {
381                dialog.dismiss();
382            }
383
384            public void onFail(PriorityTask<T> t, Throwable error) {
385                dialog.dismiss();
386            }
387
388            public void onResultAvailable(PriorityTask<T> t, T result) {
389                dialog.dismiss();
390            }
391        });
392    }
393
394    public static synchronized OnClickListener getNullOnClickListener() {
395        if (sNullOnClickListener == null) {
396            sNullOnClickListener = new OnClickListener() {
397                public void onClick(View v) {
398                }
399            };
400        }
401        return sNullOnClickListener;
402    }
403
404    public static void Assert(boolean cond) {
405        if (!cond) {
406            throw new AssertionError();
407        }
408    }
409
410    public static boolean equals(String a, String b) {
411        // return true if both string are null or the content equals
412        return a == b || a.equals(b);
413    }
414
415    private static class BackgroundJob
416            extends MonitoredActivity.LifeCycleAdapter implements Runnable {
417
418        private final MonitoredActivity mActivity;
419        private final ProgressDialog mDialog;
420        private final Runnable mJob;
421        private final Handler mHandler;
422
423        public BackgroundJob(MonitoredActivity activity, Runnable job,
424                ProgressDialog dialog, Handler handler) {
425            mActivity = activity;
426            mDialog = dialog;
427            mJob = job;
428            mActivity.addLifeCycleListener(this);
429            mHandler = handler;
430        }
431
432        public void run() {
433            try {
434                mJob.run();
435            } finally {
436                mHandler.post(new Runnable() {
437                    public void run() {
438                        mActivity.removeLifeCycleListener(BackgroundJob.this);
439                        mDialog.dismiss();
440                    }
441                });
442            }
443        }
444
445        @Override
446        public void onActivityStopped(MonitoredActivity activity) {
447            mDialog.hide();
448        }
449
450        @Override
451        public void onActivityStarted(MonitoredActivity activity) {
452            mDialog.show();
453        }
454    }
455
456    public static void startBackgroundJob(MonitoredActivity activity,
457            String title, String message, Runnable job, Handler handler) {
458        // Make the progress dialog uncancelable, so that we can gurantee
459        // the thread will be done before the activity getting destroyed.
460        ProgressDialog dialog = ProgressDialog.show(
461                activity, title, message, true, false);
462        new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
463    }
464}
465