blob: cbc9ebe342c48eaaad97c718c0f13e71147cfa03 [file] [log] [blame]
Michael Kolb8872c232013-01-29 10:33:22 -08001/*
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
Angus Kongb50b5cb2013-08-09 14:55:20 -070017package com.android.camera.util;
Michael Kolb8872c232013-01-29 10:33:22 -080018
Michael Kolb8872c232013-01-29 10:33:22 -080019import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.admin.DevicePolicyManager;
22import android.content.ActivityNotFoundException;
Sascha Haeberlingfae11a12013-08-15 14:29:49 -070023import android.content.ComponentName;
Michael Kolb8872c232013-01-29 10:33:22 -080024import android.content.ContentResolver;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.graphics.Bitmap;
29import android.graphics.BitmapFactory;
30import android.graphics.Matrix;
31import android.graphics.Point;
32import android.graphics.Rect;
33import android.graphics.RectF;
34import android.hardware.Camera;
35import android.hardware.Camera.CameraInfo;
36import android.hardware.Camera.Parameters;
37import android.hardware.Camera.Size;
38import android.location.Location;
39import android.net.Uri;
Angus Kong4f795b82013-09-16 14:25:35 -070040import android.os.Handler;
Michael Kolb8872c232013-01-29 10:33:22 -080041import android.os.ParcelFileDescriptor;
42import android.telephony.TelephonyManager;
43import android.util.DisplayMetrics;
Michael Kolb8872c232013-01-29 10:33:22 -080044import android.util.Log;
45import android.util.TypedValue;
Doris Liu0ba8eaa2013-10-16 12:51:02 -070046import android.view.Display;
Michael Kolb8872c232013-01-29 10:33:22 -080047import android.view.OrientationEventListener;
48import android.view.Surface;
49import android.view.View;
50import android.view.WindowManager;
51import android.view.animation.AlphaAnimation;
52import android.view.animation.Animation;
Angus Kongb40738a2013-06-03 14:55:34 -070053import android.widget.Toast;
Michael Kolb8872c232013-01-29 10:33:22 -080054
Sascha Haeberlingb7639c62013-09-09 14:42:43 -070055import com.android.camera.CameraActivity;
Angus Kongb50b5cb2013-08-09 14:55:20 -070056import com.android.camera.CameraDisabledException;
Angus Kongb50b5cb2013-08-09 14:55:20 -070057import com.android.camera.CameraHolder;
58import com.android.camera.CameraManager;
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -070059import com.android.camera.util.IntentHelper;
Sascha Haeberling37f36112013-08-06 14:31:52 -070060import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080061
Sascha Haeberling638e6f02013-09-18 14:28:51 -070062import java.io.Closeable;
63import java.io.IOException;
64import java.lang.reflect.Method;
65import java.text.SimpleDateFormat;
66import java.util.Date;
67import java.util.List;
68import java.util.Locale;
69import java.util.StringTokenizer;
70
Michael Kolb8872c232013-01-29 10:33:22 -080071/**
72 * Collection of utility functions used in this package.
73 */
Angus Kongb50b5cb2013-08-09 14:55:20 -070074public class CameraUtil {
Michael Kolb8872c232013-01-29 10:33:22 -080075 private static final String TAG = "Util";
76
ztenghui16a35202013-09-23 11:35:36 -070077 // For calculate the best fps range for still image capture.
78 private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000;
79 private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000;
80
Angus Kongb50b5cb2013-08-09 14:55:20 -070081 // For creating crop intents.
82 public static final String KEY_RETURN_DATA = "return-data";
83 public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
84
Michael Kolb8872c232013-01-29 10:33:22 -080085 // Orientation hysteresis amount used in rounding, in degrees
86 public static final int ORIENTATION_HYSTERESIS = 5;
87
88 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
89 // See android.hardware.Camera.ACTION_NEW_PICTURE.
90 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
91 // See android.hardware.Camera.ACTION_NEW_VIDEO.
92 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
93
Dan Aminzadefca1c5e2013-08-14 17:36:56 -070094 // Broadcast Action: The camera application has become active in picture-taking mode.
95 public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
96 // Broadcast Action: The camera application is no longer in active picture-taking mode.
97 public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
98 // When the camera application is active in picture-taking mode, it listens for this intent,
99 // which upon receipt will trigger the shutter to capture a new picture, as if the user had
100 // pressed the shutter button.
101 public static final String ACTION_CAMERA_SHUTTER_CLICK =
102 "com.android.camera.action.SHUTTER_CLICK";
103
Michael Kolb8872c232013-01-29 10:33:22 -0800104 // Fields from android.hardware.Camera.Parameters
105 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
106 public static final String RECORDING_HINT = "recording-hint";
107 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
108 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
109 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
110 public static final String SCENE_MODE_HDR = "hdr";
111 public static final String TRUE = "true";
112 public static final String FALSE = "false";
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700113
114 // Fields for the show-on-maps-functionality
115 private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
116 private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
117
118 /** Has to be in sync with the receiving MovieActivity. */
119 public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
Michael Kolb8872c232013-01-29 10:33:22 -0800120
121 public static boolean isSupported(String value, List<String> supported) {
122 return supported == null ? false : supported.indexOf(value) >= 0;
123 }
124
125 public static boolean isAutoExposureLockSupported(Parameters params) {
126 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
127 }
128
129 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
130 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
131 }
132
133 public static boolean isVideoSnapshotSupported(Parameters params) {
134 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
135 }
136
137 public static boolean isCameraHdrSupported(Parameters params) {
138 List<String> supported = params.getSupportedSceneModes();
139 return (supported != null) && supported.contains(SCENE_MODE_HDR);
140 }
141
Michael Kolb8872c232013-01-29 10:33:22 -0800142 public static boolean isMeteringAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700143 return params.getMaxNumMeteringAreas() > 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800144 }
145
Michael Kolb8872c232013-01-29 10:33:22 -0800146 public static boolean isFocusAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700147 return (params.getMaxNumFocusAreas() > 0
148 && isSupported(Parameters.FOCUS_MODE_AUTO,
149 params.getSupportedFocusModes()));
Michael Kolb8872c232013-01-29 10:33:22 -0800150 }
151
152 // Private intent extras. Test only.
153 private static final String EXTRAS_CAMERA_FACING =
154 "android.intent.extras.CAMERA_FACING";
155
156 private static float sPixelDensity = 1;
157 private static ImageFileNamer sImageFileNamer;
158
Angus Kongb50b5cb2013-08-09 14:55:20 -0700159 private CameraUtil() {
Michael Kolb8872c232013-01-29 10:33:22 -0800160 }
161
162 public static void initialize(Context context) {
163 DisplayMetrics metrics = new DisplayMetrics();
164 WindowManager wm = (WindowManager)
165 context.getSystemService(Context.WINDOW_SERVICE);
166 wm.getDefaultDisplay().getMetrics(metrics);
167 sPixelDensity = metrics.density;
168 sImageFileNamer = new ImageFileNamer(
169 context.getString(R.string.image_file_name_format));
170 }
171
172 public static int dpToPixel(int dp) {
173 return Math.round(sPixelDensity * dp);
174 }
175
176 // Rotates the bitmap by the specified degree.
177 // If a new bitmap is created, the original bitmap is recycled.
178 public static Bitmap rotate(Bitmap b, int degrees) {
179 return rotateAndMirror(b, degrees, false);
180 }
181
182 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
183 // original bitmap is recycled.
184 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
185 if ((degrees != 0 || mirror) && b != null) {
186 Matrix m = new Matrix();
187 // Mirror first.
188 // horizontal flip + rotation = -rotation + horizontal flip
189 if (mirror) {
190 m.postScale(-1, 1);
191 degrees = (degrees + 360) % 360;
192 if (degrees == 0 || degrees == 180) {
193 m.postTranslate(b.getWidth(), 0);
194 } else if (degrees == 90 || degrees == 270) {
195 m.postTranslate(b.getHeight(), 0);
196 } else {
197 throw new IllegalArgumentException("Invalid degrees=" + degrees);
198 }
199 }
200 if (degrees != 0) {
201 // clockwise
202 m.postRotate(degrees,
203 (float) b.getWidth() / 2, (float) b.getHeight() / 2);
204 }
205
206 try {
207 Bitmap b2 = Bitmap.createBitmap(
208 b, 0, 0, b.getWidth(), b.getHeight(), m, true);
209 if (b != b2) {
210 b.recycle();
211 b = b2;
212 }
213 } catch (OutOfMemoryError ex) {
214 // We have no memory to rotate. Return the original bitmap.
215 }
216 }
217 return b;
218 }
219
220 /*
221 * Compute the sample size as a function of minSideLength
222 * and maxNumOfPixels.
223 * minSideLength is used to specify that minimal width or height of a
224 * bitmap.
225 * maxNumOfPixels is used to specify the maximal size in pixels that is
226 * tolerable in terms of memory usage.
227 *
228 * The function returns a sample size based on the constraints.
229 * Both size and minSideLength can be passed in as -1
230 * which indicates no care of the corresponding constraint.
231 * The functions prefers returning a sample size that
232 * generates a smaller bitmap, unless minSideLength = -1.
233 *
234 * Also, the function rounds up the sample size to a power of 2 or multiple
235 * of 8 because BitmapFactory only honors sample size this way.
236 * For example, BitmapFactory downsamples an image by 2 even though the
237 * request is 3. So we round up the sample size to avoid OOM.
238 */
239 public static int computeSampleSize(BitmapFactory.Options options,
240 int minSideLength, int maxNumOfPixels) {
241 int initialSize = computeInitialSampleSize(options, minSideLength,
242 maxNumOfPixels);
243
244 int roundedSize;
245 if (initialSize <= 8) {
246 roundedSize = 1;
247 while (roundedSize < initialSize) {
248 roundedSize <<= 1;
249 }
250 } else {
251 roundedSize = (initialSize + 7) / 8 * 8;
252 }
253
254 return roundedSize;
255 }
256
257 private static int computeInitialSampleSize(BitmapFactory.Options options,
258 int minSideLength, int maxNumOfPixels) {
259 double w = options.outWidth;
260 double h = options.outHeight;
261
262 int lowerBound = (maxNumOfPixels < 0) ? 1 :
263 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
264 int upperBound = (minSideLength < 0) ? 128 :
265 (int) Math.min(Math.floor(w / minSideLength),
266 Math.floor(h / minSideLength));
267
268 if (upperBound < lowerBound) {
269 // return the larger one when there is no overlapping zone.
270 return lowerBound;
271 }
272
273 if (maxNumOfPixels < 0 && minSideLength < 0) {
274 return 1;
275 } else if (minSideLength < 0) {
276 return lowerBound;
277 } else {
278 return upperBound;
279 }
280 }
281
282 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
283 try {
284 BitmapFactory.Options options = new BitmapFactory.Options();
285 options.inJustDecodeBounds = true;
286 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
287 options);
288 if (options.mCancel || options.outWidth == -1
289 || options.outHeight == -1) {
290 return null;
291 }
292 options.inSampleSize = computeSampleSize(
293 options, -1, maxNumOfPixels);
294 options.inJustDecodeBounds = false;
295
296 options.inDither = false;
297 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
298 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
299 options);
300 } catch (OutOfMemoryError ex) {
301 Log.e(TAG, "Got oom exception ", ex);
302 return null;
303 }
304 }
305
306 public static void closeSilently(Closeable c) {
307 if (c == null) return;
308 try {
309 c.close();
310 } catch (Throwable t) {
311 // do nothing
312 }
313 }
314
315 public static void Assert(boolean cond) {
316 if (!cond) {
317 throw new AssertionError();
318 }
319 }
320
Michael Kolb8872c232013-01-29 10:33:22 -0800321 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
322 // Check if device policy has disabled the camera.
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700323 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
324 Context.DEVICE_POLICY_SERVICE);
325 if (dpm.getCameraDisabled(null)) {
326 throw new CameraDisabledException();
Michael Kolb8872c232013-01-29 10:33:22 -0800327 }
328 }
329
Angus Kong9ef99252013-07-18 18:04:19 -0700330 public static CameraManager.CameraProxy openCamera(
Angus Kong4f795b82013-09-16 14:25:35 -0700331 Activity activity, final int cameraId,
332 Handler handler, final CameraManager.CameraOpenErrorCallback cb) {
Michael Kolb8872c232013-01-29 10:33:22 -0800333 try {
Angus Kong4f795b82013-09-16 14:25:35 -0700334 throwIfCameraDisabled(activity);
335 return CameraHolder.instance().open(handler, cameraId, cb);
336 } catch (CameraDisabledException ex) {
337 handler.post(new Runnable() {
338 @Override
339 public void run() {
340 cb.onCameraDisabled(cameraId);
341 }
342 });
Michael Kolb8872c232013-01-29 10:33:22 -0800343 }
Angus Kong4f795b82013-09-16 14:25:35 -0700344 return null;
Michael Kolb8872c232013-01-29 10:33:22 -0800345 }
346
347 public static void showErrorAndFinish(final Activity activity, int msgId) {
348 DialogInterface.OnClickListener buttonListener =
349 new DialogInterface.OnClickListener() {
350 @Override
351 public void onClick(DialogInterface dialog, int which) {
352 activity.finish();
353 }
354 };
355 TypedValue out = new TypedValue();
356 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
357 new AlertDialog.Builder(activity)
358 .setCancelable(false)
359 .setTitle(R.string.camera_error_title)
360 .setMessage(msgId)
361 .setNeutralButton(R.string.dialog_ok, buttonListener)
362 .setIcon(out.resourceId)
363 .show();
364 }
365
366 public static <T> T checkNotNull(T object) {
367 if (object == null) throw new NullPointerException();
368 return object;
369 }
370
371 public static boolean equals(Object a, Object b) {
372 return (a == b) || (a == null ? false : a.equals(b));
373 }
374
375 public static int nextPowerOf2(int n) {
376 n -= 1;
377 n |= n >>> 16;
378 n |= n >>> 8;
379 n |= n >>> 4;
380 n |= n >>> 2;
381 n |= n >>> 1;
382 return n + 1;
383 }
384
385 public static float distance(float x, float y, float sx, float sy) {
386 float dx = x - sx;
387 float dy = y - sy;
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700388 return (float) Math.sqrt(dx * dx + dy * dy);
Michael Kolb8872c232013-01-29 10:33:22 -0800389 }
390
391 public static int clamp(int x, int min, int max) {
392 if (x > max) return max;
393 if (x < min) return min;
394 return x;
395 }
396
Igor Murashkin160b0362013-10-14 14:57:30 -0700397 public static float clamp(float x, float min, float max) {
398 if (x > max) return max;
399 if (x < min) return min;
400 return x;
401 }
402
Michael Kolb8872c232013-01-29 10:33:22 -0800403 public static int getDisplayRotation(Activity activity) {
404 int rotation = activity.getWindowManager().getDefaultDisplay()
405 .getRotation();
406 switch (rotation) {
407 case Surface.ROTATION_0: return 0;
408 case Surface.ROTATION_90: return 90;
409 case Surface.ROTATION_180: return 180;
410 case Surface.ROTATION_270: return 270;
411 }
412 return 0;
413 }
414
Doris Liu0ba8eaa2013-10-16 12:51:02 -0700415 /**
416 * Calculate the default orientation of the device based on the width and
417 * height of the display when rotation = 0 (i.e. natural width and height)
418 * @param activity the activity context
419 * @return whether the default orientation of the device is portrait
420 */
421 public static boolean isDefaultToPortrait(Activity activity) {
422 Display currentDisplay = activity.getWindowManager().getDefaultDisplay();
423 Point displaySize = new Point();
424 currentDisplay.getSize(displaySize);
425 int orientation = currentDisplay.getRotation();
426 int naturalWidth, naturalHeight;
427 if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
428 naturalWidth = displaySize.x;
429 naturalHeight = displaySize.y;
430 } else {
431 naturalWidth = displaySize.y;
432 naturalHeight = displaySize.x;
433 }
434 return naturalWidth < naturalHeight;
435 }
436
Michael Kolb8872c232013-01-29 10:33:22 -0800437 public static int getDisplayOrientation(int degrees, int cameraId) {
438 // See android.hardware.Camera.setDisplayOrientation for
439 // documentation.
440 Camera.CameraInfo info = new Camera.CameraInfo();
441 Camera.getCameraInfo(cameraId, info);
442 int result;
443 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
444 result = (info.orientation + degrees) % 360;
445 result = (360 - result) % 360; // compensate the mirror
446 } else { // back-facing
447 result = (info.orientation - degrees + 360) % 360;
448 }
449 return result;
450 }
451
452 public static int getCameraOrientation(int cameraId) {
453 Camera.CameraInfo info = new Camera.CameraInfo();
454 Camera.getCameraInfo(cameraId, info);
455 return info.orientation;
456 }
457
458 public static int roundOrientation(int orientation, int orientationHistory) {
459 boolean changeOrientation = false;
460 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
461 changeOrientation = true;
462 } else {
463 int dist = Math.abs(orientation - orientationHistory);
464 dist = Math.min( dist, 360 - dist );
465 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
466 }
467 if (changeOrientation) {
468 return ((orientation + 45) / 90 * 90) % 360;
469 }
470 return orientationHistory;
471 }
472
Michael Kolb8872c232013-01-29 10:33:22 -0800473 private static Point getDefaultDisplaySize(Activity activity, Point size) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700474 activity.getWindowManager().getDefaultDisplay().getSize(size);
Michael Kolb8872c232013-01-29 10:33:22 -0800475 return size;
476 }
477
478 public static Size getOptimalPreviewSize(Activity currentActivity,
479 List<Size> sizes, double targetRatio) {
Michael Kolb8872c232013-01-29 10:33:22 -0800480
Ruben Brunk714d4d02013-10-09 16:01:16 -0700481 Point[] points = new Point[sizes.size()];
482
483 int index = 0;
484 for (Size s : sizes) {
485 points[index++] = new Point(s.width, s.height);
486 }
487
488 int optimalPickIndex = getOptimalPreviewSize(currentActivity, points, targetRatio);
489 return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex);
490 }
491
492 public static int getOptimalPreviewSize(Activity currentActivity,
493 Point[] sizes, double targetRatio) {
494 // Use a very small tolerance because we want an exact match.
495 final double ASPECT_TOLERANCE = 0.01;
496 if (sizes == null) return -1;
497
498 int optimalSizeIndex = -1;
Michael Kolb8872c232013-01-29 10:33:22 -0800499 double minDiff = Double.MAX_VALUE;
500
501 // Because of bugs of overlay and layout, we sometimes will try to
502 // layout the viewfinder in the portrait orientation and thus get the
503 // wrong size of preview surface. When we change the preview size, the
504 // new overlay will be created before the old one closed, which causes
505 // an exception. For now, just get the screen size.
506 Point point = getDefaultDisplaySize(currentActivity, new Point());
507 int targetHeight = Math.min(point.x, point.y);
508 // Try to find an size match aspect ratio and size
Ruben Brunk714d4d02013-10-09 16:01:16 -0700509 for (int i = 0; i < sizes.length; i++) {
510 Point size = sizes[i];
511 double ratio = (double) size.x / size.y;
Michael Kolb8872c232013-01-29 10:33:22 -0800512 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
Ruben Brunk714d4d02013-10-09 16:01:16 -0700513 if (Math.abs(size.y - targetHeight) < minDiff) {
514 optimalSizeIndex = i;
515 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800516 }
517 }
518 // Cannot find the one match the aspect ratio. This should not happen.
519 // Ignore the requirement.
Ruben Brunk714d4d02013-10-09 16:01:16 -0700520 if (optimalSizeIndex == -1) {
Michael Kolb8872c232013-01-29 10:33:22 -0800521 Log.w(TAG, "No preview size match the aspect ratio");
522 minDiff = Double.MAX_VALUE;
Ruben Brunk714d4d02013-10-09 16:01:16 -0700523 for (int i = 0; i < sizes.length; i++) {
524 Point size = sizes[i];
525 if (Math.abs(size.y - targetHeight) < minDiff) {
526 optimalSizeIndex = i;
527 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800528 }
529 }
530 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700531 return optimalSizeIndex;
Michael Kolb8872c232013-01-29 10:33:22 -0800532 }
533
534 // Returns the largest picture size which matches the given aspect ratio.
535 public static Size getOptimalVideoSnapshotPictureSize(
536 List<Size> sizes, double targetRatio) {
537 // Use a very small tolerance because we want an exact match.
538 final double ASPECT_TOLERANCE = 0.001;
539 if (sizes == null) return null;
540
541 Size optimalSize = null;
542
543 // Try to find a size matches aspect ratio and has the largest width
544 for (Size size : sizes) {
545 double ratio = (double) size.width / size.height;
546 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
547 if (optimalSize == null || size.width > optimalSize.width) {
548 optimalSize = size;
549 }
550 }
551
552 // Cannot find one that matches the aspect ratio. This should not happen.
553 // Ignore the requirement.
554 if (optimalSize == null) {
555 Log.w(TAG, "No picture size match the aspect ratio");
556 for (Size size : sizes) {
557 if (optimalSize == null || size.width > optimalSize.width) {
558 optimalSize = size;
559 }
560 }
561 }
562 return optimalSize;
563 }
564
565 public static void dumpParameters(Parameters parameters) {
566 String flattened = parameters.flatten();
567 StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
568 Log.d(TAG, "Dump all camera parameters:");
569 while (tokenizer.hasMoreElements()) {
570 Log.d(TAG, tokenizer.nextToken());
571 }
572 }
573
574 /**
575 * Returns whether the device is voice-capable (meaning, it can do MMS).
576 */
577 public static boolean isMmsCapable(Context context) {
578 TelephonyManager telephonyManager = (TelephonyManager)
579 context.getSystemService(Context.TELEPHONY_SERVICE);
580 if (telephonyManager == null) {
581 return false;
582 }
583
584 try {
585 Class<?> partypes[] = new Class[0];
586 Method sIsVoiceCapable = TelephonyManager.class.getMethod(
587 "isVoiceCapable", partypes);
588
589 Object arglist[] = new Object[0];
590 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
591 return (Boolean) retobj;
592 } catch (java.lang.reflect.InvocationTargetException ite) {
593 // Failure, must be another device.
594 // Assume that it is voice capable.
595 } catch (IllegalAccessException iae) {
596 // Failure, must be an other device.
597 // Assume that it is voice capable.
598 } catch (NoSuchMethodException nsme) {
599 }
600 return true;
601 }
602
603 // This is for test only. Allow the camera to launch the specific camera.
604 public static int getCameraFacingIntentExtras(Activity currentActivity) {
605 int cameraId = -1;
606
607 int intentCameraId =
Angus Kongb50b5cb2013-08-09 14:55:20 -0700608 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
Michael Kolb8872c232013-01-29 10:33:22 -0800609
610 if (isFrontCameraIntent(intentCameraId)) {
611 // Check if the front camera exist
612 int frontCameraId = CameraHolder.instance().getFrontCameraId();
613 if (frontCameraId != -1) {
614 cameraId = frontCameraId;
615 }
616 } else if (isBackCameraIntent(intentCameraId)) {
617 // Check if the back camera exist
618 int backCameraId = CameraHolder.instance().getBackCameraId();
619 if (backCameraId != -1) {
620 cameraId = backCameraId;
621 }
622 }
623 return cameraId;
624 }
625
626 private static boolean isFrontCameraIntent(int intentCameraId) {
627 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
628 }
629
630 private static boolean isBackCameraIntent(int intentCameraId) {
631 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
632 }
633
634 private static int sLocation[] = new int[2];
635
636 // This method is not thread-safe.
637 public static boolean pointInView(float x, float y, View v) {
638 v.getLocationInWindow(sLocation);
639 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
640 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
641 }
642
643 public static int[] getRelativeLocation(View reference, View view) {
644 reference.getLocationInWindow(sLocation);
645 int referenceX = sLocation[0];
646 int referenceY = sLocation[1];
647 view.getLocationInWindow(sLocation);
648 sLocation[0] -= referenceX;
649 sLocation[1] -= referenceY;
650 return sLocation;
651 }
652
653 public static boolean isUriValid(Uri uri, ContentResolver resolver) {
654 if (uri == null) return false;
655
656 try {
657 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
658 if (pfd == null) {
659 Log.e(TAG, "Fail to open URI. URI=" + uri);
660 return false;
661 }
662 pfd.close();
663 } catch (IOException ex) {
664 return false;
665 }
666 return true;
667 }
668
Michael Kolb8872c232013-01-29 10:33:22 -0800669 public static void dumpRect(RectF rect, String msg) {
670 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
671 + "," + rect.right + "," + rect.bottom + ")");
672 }
673
674 public static void rectFToRect(RectF rectF, Rect rect) {
675 rect.left = Math.round(rectF.left);
676 rect.top = Math.round(rectF.top);
677 rect.right = Math.round(rectF.right);
678 rect.bottom = Math.round(rectF.bottom);
679 }
680
Doris Liu36ebcb12013-10-28 14:44:24 -0700681 public static Rect rectFToRect(RectF rectF) {
682 Rect rect = new Rect();
683 rectFToRect(rectF, rect);
684 return rect;
685 }
686
687 public static RectF rectToRectF(Rect r) {
688 return new RectF(r.left, r.top, r.right, r.bottom);
689 }
690
Michael Kolb8872c232013-01-29 10:33:22 -0800691 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
692 int viewWidth, int viewHeight) {
693 // Need mirror for front camera.
694 matrix.setScale(mirror ? -1 : 1, 1);
695 // This is the value for android.hardware.Camera.setDisplayOrientation.
696 matrix.postRotate(displayOrientation);
697 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
698 // UI coordinates range from (0, 0) to (width, height).
699 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
700 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
701 }
702
Doris Liu36ebcb12013-10-28 14:44:24 -0700703 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
704 Rect previewRect) {
705 // Need mirror for front camera.
706 matrix.setScale(mirror ? -1 : 1, 1);
707 // This is the value for android.hardware.Camera.setDisplayOrientation.
708 matrix.postRotate(displayOrientation);
709
710 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
711 // We need to map camera driver coordinates to preview rect coordinates
712 Matrix mapping = new Matrix();
713 mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect),
714 Matrix.ScaleToFit.FILL);
715 matrix.setConcat(mapping, matrix);
716 }
717
Michael Kolb8872c232013-01-29 10:33:22 -0800718 public static String createJpegName(long dateTaken) {
719 synchronized (sImageFileNamer) {
720 return sImageFileNamer.generateName(dateTaken);
721 }
722 }
723
724 public static void broadcastNewPicture(Context context, Uri uri) {
725 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
726 // Keep compatibility
727 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
728 }
729
730 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
731 if (view.getVisibility() == View.VISIBLE) return;
732
733 view.setVisibility(View.VISIBLE);
734 Animation animation = new AlphaAnimation(startAlpha, endAlpha);
735 animation.setDuration(duration);
736 view.startAnimation(animation);
737 }
738
739 public static void fadeIn(View view) {
740 fadeIn(view, 0F, 1F, 400);
741
742 // We disabled the button in fadeOut(), so enable it here.
743 view.setEnabled(true);
744 }
745
746 public static void fadeOut(View view) {
747 if (view.getVisibility() != View.VISIBLE) return;
748
749 // Since the button is still clickable before fade-out animation
750 // ends, we disable the button first to block click.
751 view.setEnabled(false);
752 Animation animation = new AlphaAnimation(1F, 0F);
753 animation.setDuration(400);
754 view.startAnimation(animation);
755 view.setVisibility(View.GONE);
756 }
757
758 public static int getJpegRotation(int cameraId, int orientation) {
759 // See android.hardware.Camera.Parameters.setRotation for
760 // documentation.
761 int rotation = 0;
762 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
763 CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
764 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
765 rotation = (info.orientation - orientation + 360) % 360;
766 } else { // back-facing camera
767 rotation = (info.orientation + orientation) % 360;
768 }
769 }
770 return rotation;
771 }
772
Sascha Haeberling37f36112013-08-06 14:31:52 -0700773 /**
774 * Down-samples a jpeg byte array.
775 * @param data a byte array of jpeg data
776 * @param downSampleFactor down-sample factor
777 * @return decoded and down-sampled bitmap
778 */
779 public static Bitmap downSample(final byte[] data, int downSampleFactor) {
780 final BitmapFactory.Options opts = new BitmapFactory.Options();
781 // Downsample the image
782 opts.inSampleSize = downSampleFactor;
783 return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
784 }
785
Michael Kolb8872c232013-01-29 10:33:22 -0800786 public static void setGpsParameters(Parameters parameters, Location loc) {
787 // Clear previous GPS location from the parameters.
788 parameters.removeGpsData();
789
790 // We always encode GpsTimeStamp
791 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
792
793 // Set GPS location.
794 if (loc != null) {
795 double lat = loc.getLatitude();
796 double lon = loc.getLongitude();
797 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
798
799 if (hasLatLon) {
800 Log.d(TAG, "Set gps location");
801 parameters.setGpsLatitude(lat);
802 parameters.setGpsLongitude(lon);
803 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
804 if (loc.hasAltitude()) {
805 parameters.setGpsAltitude(loc.getAltitude());
806 } else {
807 // for NETWORK_PROVIDER location provider, we may have
808 // no altitude information, but the driver needs it, so
809 // we fake one.
810 parameters.setGpsAltitude(0);
811 }
812 if (loc.getTime() != 0) {
813 // Location.getTime() is UTC in milliseconds.
814 // gps-timestamp is UTC in seconds.
815 long utcTimeSeconds = loc.getTime() / 1000;
816 parameters.setGpsTimestamp(utcTimeSeconds);
817 }
818 } else {
819 loc = null;
820 }
821 }
822 }
823
ztenghui16a35202013-09-23 11:35:36 -0700824 /**
825 * For still image capture, we need to get the right fps range such that the
826 * camera can slow down the framerate to allow for less-noisy/dark
827 * viewfinder output in dark conditions.
828 *
829 * @param params Camera's parameters.
830 * @return null if no appropiate fps range can't be found. Otherwise, return
831 * the right range.
832 */
833 public static int[] getPhotoPreviewFpsRange(Parameters params) {
834 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
835 if (frameRates.size() == 0) {
836 Log.e(TAG, "No suppoted frame rates returned!");
837 return null;
838 }
839
840 // Find the lowest min rate in supported ranges who can cover 30fps.
841 int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000;
842 for (int[] rate : frameRates) {
843 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
844 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
845 if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
846 minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
847 minFps < lowestMinRate) {
848 lowestMinRate = minFps;
849 }
850 }
851
852 // Find all the modes with the lowest min rate found above, the pick the
853 // one with highest max rate.
854 int resultIndex = -1;
855 int highestMaxRate = 0;
856 for (int i = 0; i < frameRates.size(); i++) {
857 int[] rate = frameRates.get(i);
858 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
859 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
860 if (minFps == lowestMinRate && highestMaxRate < maxFps) {
861 highestMaxRate = maxFps;
862 resultIndex = i;
863 }
864 }
865
866 if (resultIndex >= 0) {
867 return frameRates.get(resultIndex);
868 }
869 Log.e(TAG, "Can't find an appropiate frame rate range!");
870 return null;
871 }
Angus Kongd82eae22013-06-11 12:00:15 -0700872
873 public static int[] getMaxPreviewFpsRange(Parameters params) {
874 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
875 if (frameRates != null && frameRates.size() > 0) {
876 // The list is sorted. Return the last element.
877 return frameRates.get(frameRates.size() - 1);
878 }
879 return new int[0];
880 }
881
Michael Kolb8872c232013-01-29 10:33:22 -0800882 private static class ImageFileNamer {
Igor Murashkin160b0362013-10-14 14:57:30 -0700883 private final SimpleDateFormat mFormat;
Michael Kolb8872c232013-01-29 10:33:22 -0800884
885 // The date (in milliseconds) used to generate the last name.
886 private long mLastDate;
887
888 // Number of names generated for the same second.
889 private int mSameSecondCount;
890
891 public ImageFileNamer(String format) {
892 mFormat = new SimpleDateFormat(format);
893 }
894
895 public String generateName(long dateTaken) {
896 Date date = new Date(dateTaken);
897 String result = mFormat.format(date);
898
899 // If the last name was generated for the same second,
900 // we append _1, _2, etc to the name.
901 if (dateTaken / 1000 == mLastDate / 1000) {
902 mSameSecondCount++;
903 result += "_" + mSameSecondCount;
904 } else {
905 mLastDate = dateTaken;
906 mSameSecondCount = 0;
907 }
908
909 return result;
910 }
911 }
Angus Kongb40738a2013-06-03 14:55:34 -0700912
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700913 public static void playVideo(Activity activity, Uri uri, String title) {
Angus Kongb40738a2013-06-03 14:55:34 -0700914 try {
ztenghui98b2a282013-10-11 17:15:40 -0700915 boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera();
916 if (!isSecureCamera) {
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -0700917 Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri)
ztenghui98b2a282013-10-11 17:15:40 -0700918 .putExtra(Intent.EXTRA_TITLE, title)
919 .putExtra(KEY_TREAT_UP_AS_BACK, true);
920 activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
921 } else {
922 // In order not to send out any intent to be intercepted and
923 // show the lock screen immediately, we just let the secure
924 // camera activity finish.
925 activity.finish();
926 }
Angus Kongb40738a2013-06-03 14:55:34 -0700927 } catch (ActivityNotFoundException e) {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700928 Toast.makeText(activity, activity.getString(R.string.video_err),
Angus Kongb40738a2013-06-03 14:55:34 -0700929 Toast.LENGTH_SHORT).show();
930 }
931 }
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700932
933 /**
934 * Starts GMM with the given location shown. If this fails, and GMM could
935 * not be found, we use a geo intent as a fallback.
936 *
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700937 * @param activity the activity to use for launching the Maps intent.
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700938 * @param latLong a 2-element array containing {latitude/longitude}.
939 */
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700940 public static void showOnMap(Activity activity, double[] latLong) {
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700941 try {
942 // We don't use "geo:latitude,longitude" because it only centers
943 // the MapView to the specified location, but we need a marker
944 // for further operations (routing to/from).
945 // The q=(lat, lng) syntax is suggested by geo-team.
946 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
947 latLong[0], latLong[1]);
948 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
949 MAPS_CLASS_NAME);
950 Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
951 Uri.parse(uri)).setComponent(compName);
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700952 activity.startActivityForResult(mapsIntent,
953 CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700954 } catch (ActivityNotFoundException e) {
955 // Use the "geo intent" if no GMM is installed
956 Log.e(TAG, "GMM activity not found!", e);
957 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
958 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700959 activity.startActivity(mapsIntent);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700960 }
961 }
Angus Kong7d2388d2013-09-18 23:56:01 -0700962
Angus Kongb21215a2013-09-20 18:41:46 -0700963 /**
964 * Dumps the stack trace.
965 *
966 * @param level How many levels of the stack are dumped. 0 means all.
967 * @return A {@link java.lang.String} of all the output with newline
968 * between each.
969 */
Angus Kong7d2388d2013-09-18 23:56:01 -0700970 public static String dumpStackTrace(int level) {
971 StackTraceElement[] elems = Thread.currentThread().getStackTrace();
972 // Ignore the first 3 elements.
Angus Kongb21215a2013-09-20 18:41:46 -0700973 level = (level == 0 ? elems.length : Math.min(level + 3, elems.length));
Angus Kong7d2388d2013-09-18 23:56:01 -0700974 String ret = new String();
975 for (int i = 3; i < level; i++) {
Angus Kongb21215a2013-09-20 18:41:46 -0700976 ret = ret + "\t" + elems[i].toString() + '\n';
Angus Kong7d2388d2013-09-18 23:56:01 -0700977 }
978 return ret;
979 }
Angus Kong690dc472013-09-21 14:48:51 -0700980
981 /**
982 * Launches apps supporting action {@link Intent.ACTION_MAIN} of category
983 * {@link Intent.CATEGORY_APP_GALLERY}. Note that
984 * {@link Intent.CATEGORY_APP_GALLERY} is only available on API level 15+.
985 *
986 * @param ctx The {@link android.content.Context} to launch the app.
987 * @return {@code true} on success.
988 */
989 public static boolean launchGallery(Context ctx) {
990 if (ApiHelper.HAS_APP_GALLERY) {
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -0700991 ctx.startActivity(IntentHelper.getGalleryIntent(ctx));
Angus Kong690dc472013-09-21 14:48:51 -0700992 return true;
993 }
994 return false;
995 }
Michael Kolb8872c232013-01-29 10:33:22 -0800996}