Util.java revision 46d402ade45b258f4515c42b6940749364557fb3
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 android.app.Activity;
20import android.app.AlertDialog;
21import android.app.ProgressDialog;
22import android.content.ContentResolver;
23import android.content.DialogInterface;
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.net.Uri;
31import android.os.Handler;
32import android.os.ParcelFileDescriptor;
33import android.util.Log;
34import android.view.View;
35import android.view.animation.Animation;
36import android.view.animation.TranslateAnimation;
37
38import com.android.camera.gallery.IImage;
39
40import java.io.Closeable;
41import java.io.FileDescriptor;
42import java.io.IOException;
43
44/**
45 * Collection of utility functions used in this package.
46 */
47public class Util {
48    private static final String TAG = "Util";
49    public static final int DIRECTION_LEFT = 0;
50    public static final int DIRECTION_RIGHT = 1;
51    public static final int DIRECTION_UP = 2;
52    public static final int DIRECTION_DOWN = 3;
53
54    private Util() {
55    }
56
57    // Rotates the bitmap by the specified degree.
58    // If a new bitmap is created, the original bitmap is recycled.
59    public static Bitmap rotate(Bitmap b, int degrees) {
60        if (degrees != 0 && b != null) {
61            Matrix m = new Matrix();
62            m.setRotate(degrees,
63                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
64            try {
65                Bitmap b2 = Bitmap.createBitmap(
66                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
67                if (b != b2) {
68                    b.recycle();
69                    b = b2;
70                }
71            } catch (OutOfMemoryError ex) {
72                // We have no memory to rotate. Return the original bitmap.
73            }
74        }
75        return b;
76    }
77
78    /*
79     * Compute the sample size as a function of minSideLength
80     * and maxNumOfPixels.
81     * minSideLength is used to specify that minimal width or height of a
82     * bitmap.
83     * maxNumOfPixels is used to specify the maximal size in pixels that is
84     * tolerable in terms of memory usage.
85     *
86     * The function returns a sample size based on the constraints.
87     * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
88     * which indicates no care of the corresponding constraint.
89     * The functions prefers returning a sample size that
90     * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
91     *
92     * Also, the function rounds up the sample size to a power of 2 or multiple
93     * of 8 because BitmapFactory only honors sample size this way.
94     * For example, BitmapFactory downsamples an image by 2 even though the
95     * request is 3. So we round up the sample size to avoid OOM.
96     */
97    public static int computeSampleSize(BitmapFactory.Options options,
98            int minSideLength, int maxNumOfPixels) {
99        int initialSize = computeInitialSampleSize(options, minSideLength,
100                maxNumOfPixels);
101
102        int roundedSize;
103        if (initialSize <= 8) {
104            roundedSize = 1;
105            while (roundedSize < initialSize) {
106                roundedSize <<= 1;
107            }
108        } else {
109            roundedSize = (initialSize + 7) / 8 * 8;
110        }
111
112        return roundedSize;
113    }
114
115    private static int computeInitialSampleSize(BitmapFactory.Options options,
116            int minSideLength, int maxNumOfPixels) {
117        double w = options.outWidth;
118        double h = options.outHeight;
119
120        int lowerBound = (maxNumOfPixels == IImage.UNCONSTRAINED) ? 1 :
121                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
122        int upperBound = (minSideLength == IImage.UNCONSTRAINED) ? 128 :
123                (int) Math.min(Math.floor(w / minSideLength),
124                Math.floor(h / minSideLength));
125
126        if (upperBound < lowerBound) {
127            // return the larger one when there is no overlapping zone.
128            return lowerBound;
129        }
130
131        if ((maxNumOfPixels == IImage.UNCONSTRAINED) &&
132                (minSideLength == IImage.UNCONSTRAINED)) {
133            return 1;
134        } else if (minSideLength == IImage.UNCONSTRAINED) {
135            return lowerBound;
136        } else {
137            return upperBound;
138        }
139    }
140
141    public static <T>  int indexOf(T [] array, T s) {
142        for (int i = 0; i < array.length; i++) {
143            if (array[i].equals(s)) {
144                return i;
145            }
146        }
147        return -1;
148    }
149
150    public static void closeSilently(Closeable c) {
151        if (c == null) return;
152        try {
153            c.close();
154        } catch (Throwable t) {
155            // do nothing
156        }
157    }
158
159    public static void closeSilently(ParcelFileDescriptor c) {
160        if (c == null) return;
161        try {
162            c.close();
163        } catch (Throwable t) {
164            // do nothing
165        }
166    }
167
168    /**
169     * Make a bitmap from a given Uri.
170     *
171     * @param uri
172     */
173    public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
174            Uri uri, ContentResolver cr, boolean useNative) {
175        ParcelFileDescriptor input = null;
176        try {
177            input = cr.openFileDescriptor(uri, "r");
178            BitmapFactory.Options options = null;
179            if (useNative) {
180                options = createNativeAllocOptions();
181            }
182            return makeBitmap(minSideLength, maxNumOfPixels, uri, cr, input,
183                    options);
184        } catch (IOException ex) {
185            return null;
186        } finally {
187            closeSilently(input);
188        }
189    }
190
191    public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
192            ParcelFileDescriptor pfd, boolean useNative) {
193        BitmapFactory.Options options = null;
194        if (useNative) {
195            options = createNativeAllocOptions();
196        }
197        return makeBitmap(minSideLength, maxNumOfPixels, null, null, pfd,
198                options);
199    }
200
201    public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
202            Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
203            BitmapFactory.Options options) {
204        try {
205            if (pfd == null) pfd = makeInputStream(uri, cr);
206            if (pfd == null) return null;
207            if (options == null) options = new BitmapFactory.Options();
208
209            FileDescriptor fd = pfd.getFileDescriptor();
210            options.inJustDecodeBounds = true;
211            BitmapManager.instance().decodeFileDescriptor(fd, options);
212            if (options.mCancel || options.outWidth == -1
213                    || options.outHeight == -1) {
214                return null;
215            }
216            options.inSampleSize = computeSampleSize(
217                    options, minSideLength, maxNumOfPixels);
218            options.inJustDecodeBounds = false;
219
220            options.inDither = false;
221            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
222            return BitmapManager.instance().decodeFileDescriptor(fd, options);
223        } catch (OutOfMemoryError ex) {
224            Log.e(TAG, "Got oom exception ", ex);
225            return null;
226        } finally {
227            closeSilently(pfd);
228        }
229    }
230
231    public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
232        try {
233            BitmapFactory.Options options = new BitmapFactory.Options();
234            options.inJustDecodeBounds = true;
235            BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
236                    options);
237            if (options.mCancel || options.outWidth == -1
238                    || options.outHeight == -1) {
239                return null;
240            }
241            options.inSampleSize = computeSampleSize(
242                    options, IImage.UNCONSTRAINED, maxNumOfPixels);
243            options.inJustDecodeBounds = false;
244
245            options.inDither = false;
246            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
247            return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
248                    options);
249        } catch (OutOfMemoryError ex) {
250            Log.e(TAG, "Got oom exception ", ex);
251            return null;
252        }
253    }
254
255    private static ParcelFileDescriptor makeInputStream(
256            Uri uri, ContentResolver cr) {
257        try {
258            return cr.openFileDescriptor(uri, "r");
259        } catch (IOException ex) {
260            return null;
261        }
262    }
263
264    public static void Assert(boolean cond) {
265        if (!cond) {
266            throw new AssertionError();
267        }
268    }
269
270    public static boolean equals(String a, String b) {
271        // return true if both string are null or the content equals
272        return a == b || a.equals(b);
273    }
274
275    // Returns an intent which is used for "set as" menu items.
276    public static Intent createSetAsIntent(IImage image) {
277        Uri u = image.fullSizeImageUri();
278        Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
279        intent.setDataAndType(u, image.getMimeType());
280        intent.putExtra("mimeType", image.getMimeType());
281        return intent;
282    }
283
284    // Returns Options that set the puregeable flag for Bitmap decode.
285    public static BitmapFactory.Options createNativeAllocOptions() {
286        BitmapFactory.Options options = new BitmapFactory.Options();
287        options.inNativeAlloc = true;
288        return options;
289    }
290
291    public static void showFatalErrorAndFinish(
292            final Activity activity, String title, String message) {
293        DialogInterface.OnClickListener buttonListener =
294                new DialogInterface.OnClickListener() {
295            public void onClick(DialogInterface dialog, int which) {
296                activity.finish();
297            }
298        };
299        new AlertDialog.Builder(activity)
300                .setCancelable(false)
301                .setIcon(android.R.drawable.ic_dialog_alert)
302                .setTitle(title)
303                .setMessage(message)
304                .setNeutralButton(R.string.details_ok, buttonListener)
305                .show();
306    }
307
308    public static Animation slideOut(View view, int to) {
309        view.setVisibility(View.INVISIBLE);
310        Animation anim;
311        switch (to) {
312            case DIRECTION_LEFT:
313                anim = new TranslateAnimation(0, -view.getWidth(), 0, 0);
314                break;
315            case DIRECTION_RIGHT:
316                anim = new TranslateAnimation(0, view.getWidth(), 0, 0);
317                break;
318            case DIRECTION_UP:
319                anim = new TranslateAnimation(0, 0, 0, -view.getHeight());
320                break;
321            case DIRECTION_DOWN:
322                anim = new TranslateAnimation(0, 0, 0, view.getHeight());
323                break;
324            default:
325                throw new IllegalArgumentException(Integer.toString(to));
326        }
327        anim.setDuration(500);
328        view.startAnimation(anim);
329        return anim;
330    }
331
332    public static Animation slideIn(View view, int from) {
333        view.setVisibility(View.VISIBLE);
334        Animation anim;
335        switch (from) {
336            case DIRECTION_LEFT:
337                anim = new TranslateAnimation(-view.getWidth(), 0, 0, 0);
338                break;
339            case DIRECTION_RIGHT:
340                anim = new TranslateAnimation(view.getWidth(), 0, 0, 0);
341                break;
342            case DIRECTION_UP:
343                anim = new TranslateAnimation(0, 0, -view.getHeight(), 0);
344                break;
345            case DIRECTION_DOWN:
346                anim = new TranslateAnimation(0, 0, view.getHeight(), 0);
347                break;
348            default:
349                throw new IllegalArgumentException(Integer.toString(from));
350        }
351        anim.setDuration(500);
352        view.startAnimation(anim);
353        return anim;
354    }
355}
356