Util.java revision c1dd72054122befb49aa1ca11ffa589b00186f80
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.content.DialogInterface;
22import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
24import android.graphics.Matrix;
25import android.hardware.Camera;
26import android.util.Log;
27import android.view.Surface;
28import android.view.View;
29import android.view.animation.Animation;
30import android.view.animation.TranslateAnimation;
31
32import com.android.camera.gallery.IImage;
33import com.android.camera.R;
34
35import java.io.Closeable;
36
37/**
38 * Collection of utility functions used in this package.
39 */
40public class Util {
41    private static final String TAG = "Util";
42    public static final int DIRECTION_LEFT = 0;
43    public static final int DIRECTION_RIGHT = 1;
44    public static final int DIRECTION_UP = 2;
45    public static final int DIRECTION_DOWN = 3;
46
47    public static final String REVIEW_ACTION = "com.cooliris.media.action.REVIEW";
48
49    private Util() {
50    }
51
52    // Rotates the bitmap by the specified degree.
53    // If a new bitmap is created, the original bitmap is recycled.
54    public static Bitmap rotate(Bitmap b, int degrees) {
55        if (degrees != 0 && b != null) {
56            Matrix m = new Matrix();
57            m.setRotate(degrees,
58                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
59            try {
60                Bitmap b2 = Bitmap.createBitmap(
61                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
62                if (b != b2) {
63                    b.recycle();
64                    b = b2;
65                }
66            } catch (OutOfMemoryError ex) {
67                // We have no memory to rotate. Return the original bitmap.
68            }
69        }
70        return b;
71    }
72
73    /*
74     * Compute the sample size as a function of minSideLength
75     * and maxNumOfPixels.
76     * minSideLength is used to specify that minimal width or height of a
77     * bitmap.
78     * maxNumOfPixels is used to specify the maximal size in pixels that is
79     * tolerable in terms of memory usage.
80     *
81     * The function returns a sample size based on the constraints.
82     * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
83     * which indicates no care of the corresponding constraint.
84     * The functions prefers returning a sample size that
85     * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
86     *
87     * Also, the function rounds up the sample size to a power of 2 or multiple
88     * of 8 because BitmapFactory only honors sample size this way.
89     * For example, BitmapFactory downsamples an image by 2 even though the
90     * request is 3. So we round up the sample size to avoid OOM.
91     */
92    public static int computeSampleSize(BitmapFactory.Options options,
93            int minSideLength, int maxNumOfPixels) {
94        int initialSize = computeInitialSampleSize(options, minSideLength,
95                maxNumOfPixels);
96
97        int roundedSize;
98        if (initialSize <= 8) {
99            roundedSize = 1;
100            while (roundedSize < initialSize) {
101                roundedSize <<= 1;
102            }
103        } else {
104            roundedSize = (initialSize + 7) / 8 * 8;
105        }
106
107        return roundedSize;
108    }
109
110    private static int computeInitialSampleSize(BitmapFactory.Options options,
111            int minSideLength, int maxNumOfPixels) {
112        double w = options.outWidth;
113        double h = options.outHeight;
114
115        int lowerBound = (maxNumOfPixels == IImage.UNCONSTRAINED) ? 1 :
116                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
117        int upperBound = (minSideLength == IImage.UNCONSTRAINED) ? 128 :
118                (int) Math.min(Math.floor(w / minSideLength),
119                Math.floor(h / minSideLength));
120
121        if (upperBound < lowerBound) {
122            // return the larger one when there is no overlapping zone.
123            return lowerBound;
124        }
125
126        if ((maxNumOfPixels == IImage.UNCONSTRAINED) &&
127                (minSideLength == IImage.UNCONSTRAINED)) {
128            return 1;
129        } else if (minSideLength == IImage.UNCONSTRAINED) {
130            return lowerBound;
131        } else {
132            return upperBound;
133        }
134    }
135
136    public static <T>  int indexOf(T [] array, T s) {
137        for (int i = 0; i < array.length; i++) {
138            if (array[i].equals(s)) {
139                return i;
140            }
141        }
142        return -1;
143    }
144
145    public static void closeSilently(Closeable c) {
146        if (c == null) return;
147        try {
148            c.close();
149        } catch (Throwable t) {
150            // do nothing
151        }
152    }
153
154    public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
155        try {
156            BitmapFactory.Options options = new BitmapFactory.Options();
157            options.inJustDecodeBounds = true;
158            BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
159                    options);
160            if (options.mCancel || options.outWidth == -1
161                    || options.outHeight == -1) {
162                return null;
163            }
164            options.inSampleSize = computeSampleSize(
165                    options, IImage.UNCONSTRAINED, maxNumOfPixels);
166            options.inJustDecodeBounds = false;
167
168            options.inDither = false;
169            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
170            return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
171                    options);
172        } catch (OutOfMemoryError ex) {
173            Log.e(TAG, "Got oom exception ", ex);
174            return null;
175        }
176    }
177
178    public static void Assert(boolean cond) {
179        if (!cond) {
180            throw new AssertionError();
181        }
182    }
183
184    public static void showFatalErrorAndFinish(
185            final Activity activity, String title, String message) {
186        DialogInterface.OnClickListener buttonListener =
187                new DialogInterface.OnClickListener() {
188            public void onClick(DialogInterface dialog, int which) {
189                activity.finish();
190            }
191        };
192        new AlertDialog.Builder(activity)
193                .setCancelable(false)
194                .setIcon(android.R.drawable.ic_dialog_alert)
195                .setTitle(title)
196                .setMessage(message)
197                .setNeutralButton(R.string.details_ok, buttonListener)
198                .show();
199    }
200
201    public static Animation slideOut(View view, int to) {
202        view.setVisibility(View.INVISIBLE);
203        Animation anim;
204        switch (to) {
205            case DIRECTION_LEFT:
206                anim = new TranslateAnimation(0, -view.getWidth(), 0, 0);
207                break;
208            case DIRECTION_RIGHT:
209                anim = new TranslateAnimation(0, view.getWidth(), 0, 0);
210                break;
211            case DIRECTION_UP:
212                anim = new TranslateAnimation(0, 0, 0, -view.getHeight());
213                break;
214            case DIRECTION_DOWN:
215                anim = new TranslateAnimation(0, 0, 0, view.getHeight());
216                break;
217            default:
218                throw new IllegalArgumentException(Integer.toString(to));
219        }
220        anim.setDuration(500);
221        view.startAnimation(anim);
222        return anim;
223    }
224
225    public static Animation slideIn(View view, int from) {
226        view.setVisibility(View.VISIBLE);
227        Animation anim;
228        switch (from) {
229            case DIRECTION_LEFT:
230                anim = new TranslateAnimation(-view.getWidth(), 0, 0, 0);
231                break;
232            case DIRECTION_RIGHT:
233                anim = new TranslateAnimation(view.getWidth(), 0, 0, 0);
234                break;
235            case DIRECTION_UP:
236                anim = new TranslateAnimation(0, 0, -view.getHeight(), 0);
237                break;
238            case DIRECTION_DOWN:
239                anim = new TranslateAnimation(0, 0, view.getHeight(), 0);
240                break;
241            default:
242                throw new IllegalArgumentException(Integer.toString(from));
243        }
244        anim.setDuration(500);
245        view.startAnimation(anim);
246        return anim;
247    }
248
249    public static <T> T checkNotNull(T object) {
250        if (object == null) throw new NullPointerException();
251        return object;
252    }
253
254    public static boolean equals(Object a, Object b) {
255        return (a == b) || (a == null ? false : a.equals(b));
256    }
257
258    public static boolean isPowerOf2(int n) {
259        return (n & -n) == n;
260    }
261
262    public static int nextPowerOf2(int n) {
263        n -= 1;
264        n |= n >>> 16;
265        n |= n >>> 8;
266        n |= n >>> 4;
267        n |= n >>> 2;
268        n |= n >>> 1;
269        return n + 1;
270    }
271
272    public static float distance(float x, float y, float sx, float sy) {
273        float dx = x - sx;
274        float dy = y - sy;
275        return (float) Math.sqrt(dx * dx + dy * dy);
276    }
277
278    public static int clamp(int x, int min, int max) {
279        if (x > max) return max;
280        if (x < min) return min;
281        return x;
282    }
283
284    public static int getDisplayRotation(Activity activity) {
285        int rotation = activity.getWindowManager().getDefaultDisplay()
286                .getRotation();
287        switch (rotation) {
288            case Surface.ROTATION_0: return 0;
289            case Surface.ROTATION_90: return 90;
290            case Surface.ROTATION_180: return 180;
291            case Surface.ROTATION_270: return 270;
292        }
293        return 0;
294    }
295
296    public static void setCameraDisplayOrientation(Activity activity,
297            int cameraId, Camera camera) {
298        // See android.hardware.Camera.setCameraDisplayOrientation for
299        // documentation.
300        Camera.CameraInfo info = new Camera.CameraInfo();
301        Camera.getCameraInfo(cameraId, info);
302        int degrees = getDisplayRotation(activity);
303        int result;
304        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
305            result = (info.orientation + degrees) % 360;
306            result = (360 - result) % 360;  // compensate the mirror
307        } else {  // back-facing
308            result = (info.orientation - degrees + 360) % 360;
309        }
310        camera.setDisplayOrientation(result);
311    }
312}
313