blob: c5dc71af33d2925cce7a3ce64ca55db735ba7238 [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
Sascha Haeberling8e963a52013-08-06 11:43:02 -070019import java.io.Closeable;
20import java.io.IOException;
21import java.lang.reflect.Method;
22import java.text.SimpleDateFormat;
23import java.util.Date;
24import java.util.List;
Sascha Haeberlingfae11a12013-08-15 14:29:49 -070025import java.util.Locale;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070026import java.util.StringTokenizer;
27
Michael Kolb8872c232013-01-29 10:33:22 -080028import android.annotation.TargetApi;
29import android.app.Activity;
30import android.app.AlertDialog;
31import android.app.admin.DevicePolicyManager;
32import android.content.ActivityNotFoundException;
Sascha Haeberlingfae11a12013-08-15 14:29:49 -070033import android.content.ComponentName;
Michael Kolb8872c232013-01-29 10:33:22 -080034import android.content.ContentResolver;
35import android.content.Context;
36import android.content.DialogInterface;
37import android.content.Intent;
38import android.graphics.Bitmap;
39import android.graphics.BitmapFactory;
40import android.graphics.Matrix;
41import android.graphics.Point;
42import android.graphics.Rect;
43import android.graphics.RectF;
44import android.hardware.Camera;
45import android.hardware.Camera.CameraInfo;
46import android.hardware.Camera.Parameters;
47import android.hardware.Camera.Size;
48import android.location.Location;
49import android.net.Uri;
50import android.os.Build;
51import android.os.ParcelFileDescriptor;
52import android.telephony.TelephonyManager;
53import android.util.DisplayMetrics;
54import android.util.FloatMath;
55import android.util.Log;
56import android.util.TypedValue;
57import android.view.Display;
58import android.view.OrientationEventListener;
59import android.view.Surface;
60import android.view.View;
61import android.view.WindowManager;
62import android.view.animation.AlphaAnimation;
63import android.view.animation.Animation;
Angus Kongb40738a2013-06-03 14:55:34 -070064import android.widget.Toast;
Michael Kolb8872c232013-01-29 10:33:22 -080065
Angus Kongb50b5cb2013-08-09 14:55:20 -070066import com.android.camera.CameraDisabledException;
67import com.android.camera.CameraHardwareException;
68import com.android.camera.CameraHolder;
69import com.android.camera.CameraManager;
Sascha Haeberling37f36112013-08-06 14:31:52 -070070import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080071
72/**
73 * Collection of utility functions used in this package.
74 */
Angus Kongb50b5cb2013-08-09 14:55:20 -070075public class CameraUtil {
Michael Kolb8872c232013-01-29 10:33:22 -080076 private static final String TAG = "Util";
77
Angus Kongb50b5cb2013-08-09 14:55:20 -070078 // For creating crop intents.
79 public static final String KEY_RETURN_DATA = "return-data";
80 public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
81
Michael Kolb8872c232013-01-29 10:33:22 -080082 // Orientation hysteresis amount used in rounding, in degrees
83 public static final int ORIENTATION_HYSTERESIS = 5;
84
85 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
86 // See android.hardware.Camera.ACTION_NEW_PICTURE.
87 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
88 // See android.hardware.Camera.ACTION_NEW_VIDEO.
89 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
90
Dan Aminzadefca1c5e2013-08-14 17:36:56 -070091 // Broadcast Action: The camera application has become active in picture-taking mode.
92 public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
93 // Broadcast Action: The camera application is no longer in active picture-taking mode.
94 public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
95 // When the camera application is active in picture-taking mode, it listens for this intent,
96 // which upon receipt will trigger the shutter to capture a new picture, as if the user had
97 // pressed the shutter button.
98 public static final String ACTION_CAMERA_SHUTTER_CLICK =
99 "com.android.camera.action.SHUTTER_CLICK";
100
Michael Kolb8872c232013-01-29 10:33:22 -0800101 // Fields from android.hardware.Camera.Parameters
102 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
103 public static final String RECORDING_HINT = "recording-hint";
104 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
105 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
106 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
107 public static final String SCENE_MODE_HDR = "hdr";
108 public static final String TRUE = "true";
109 public static final String FALSE = "false";
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700110
111 // Fields for the show-on-maps-functionality
112 private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
113 private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
114
115 /** Has to be in sync with the receiving MovieActivity. */
116 public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
Michael Kolb8872c232013-01-29 10:33:22 -0800117
118 public static boolean isSupported(String value, List<String> supported) {
119 return supported == null ? false : supported.indexOf(value) >= 0;
120 }
121
122 public static boolean isAutoExposureLockSupported(Parameters params) {
123 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
124 }
125
126 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
127 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
128 }
129
130 public static boolean isVideoSnapshotSupported(Parameters params) {
131 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
132 }
133
134 public static boolean isCameraHdrSupported(Parameters params) {
135 List<String> supported = params.getSupportedSceneModes();
136 return (supported != null) && supported.contains(SCENE_MODE_HDR);
137 }
138
139 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
140 public static boolean isMeteringAreaSupported(Parameters params) {
141 if (ApiHelper.HAS_CAMERA_METERING_AREA) {
142 return params.getMaxNumMeteringAreas() > 0;
143 }
144 return false;
145 }
146
147 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
148 public static boolean isFocusAreaSupported(Parameters params) {
149 if (ApiHelper.HAS_CAMERA_FOCUS_AREA) {
150 return (params.getMaxNumFocusAreas() > 0
151 && isSupported(Parameters.FOCUS_MODE_AUTO,
152 params.getSupportedFocusModes()));
153 }
154 return false;
155 }
156
157 // Private intent extras. Test only.
158 private static final String EXTRAS_CAMERA_FACING =
159 "android.intent.extras.CAMERA_FACING";
160
161 private static float sPixelDensity = 1;
162 private static ImageFileNamer sImageFileNamer;
163
Angus Kongb50b5cb2013-08-09 14:55:20 -0700164 private CameraUtil() {
Michael Kolb8872c232013-01-29 10:33:22 -0800165 }
166
167 public static void initialize(Context context) {
168 DisplayMetrics metrics = new DisplayMetrics();
169 WindowManager wm = (WindowManager)
170 context.getSystemService(Context.WINDOW_SERVICE);
171 wm.getDefaultDisplay().getMetrics(metrics);
172 sPixelDensity = metrics.density;
173 sImageFileNamer = new ImageFileNamer(
174 context.getString(R.string.image_file_name_format));
175 }
176
177 public static int dpToPixel(int dp) {
178 return Math.round(sPixelDensity * dp);
179 }
180
181 // Rotates the bitmap by the specified degree.
182 // If a new bitmap is created, the original bitmap is recycled.
183 public static Bitmap rotate(Bitmap b, int degrees) {
184 return rotateAndMirror(b, degrees, false);
185 }
186
187 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
188 // original bitmap is recycled.
189 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
190 if ((degrees != 0 || mirror) && b != null) {
191 Matrix m = new Matrix();
192 // Mirror first.
193 // horizontal flip + rotation = -rotation + horizontal flip
194 if (mirror) {
195 m.postScale(-1, 1);
196 degrees = (degrees + 360) % 360;
197 if (degrees == 0 || degrees == 180) {
198 m.postTranslate(b.getWidth(), 0);
199 } else if (degrees == 90 || degrees == 270) {
200 m.postTranslate(b.getHeight(), 0);
201 } else {
202 throw new IllegalArgumentException("Invalid degrees=" + degrees);
203 }
204 }
205 if (degrees != 0) {
206 // clockwise
207 m.postRotate(degrees,
208 (float) b.getWidth() / 2, (float) b.getHeight() / 2);
209 }
210
211 try {
212 Bitmap b2 = Bitmap.createBitmap(
213 b, 0, 0, b.getWidth(), b.getHeight(), m, true);
214 if (b != b2) {
215 b.recycle();
216 b = b2;
217 }
218 } catch (OutOfMemoryError ex) {
219 // We have no memory to rotate. Return the original bitmap.
220 }
221 }
222 return b;
223 }
224
225 /*
226 * Compute the sample size as a function of minSideLength
227 * and maxNumOfPixels.
228 * minSideLength is used to specify that minimal width or height of a
229 * bitmap.
230 * maxNumOfPixels is used to specify the maximal size in pixels that is
231 * tolerable in terms of memory usage.
232 *
233 * The function returns a sample size based on the constraints.
234 * Both size and minSideLength can be passed in as -1
235 * which indicates no care of the corresponding constraint.
236 * The functions prefers returning a sample size that
237 * generates a smaller bitmap, unless minSideLength = -1.
238 *
239 * Also, the function rounds up the sample size to a power of 2 or multiple
240 * of 8 because BitmapFactory only honors sample size this way.
241 * For example, BitmapFactory downsamples an image by 2 even though the
242 * request is 3. So we round up the sample size to avoid OOM.
243 */
244 public static int computeSampleSize(BitmapFactory.Options options,
245 int minSideLength, int maxNumOfPixels) {
246 int initialSize = computeInitialSampleSize(options, minSideLength,
247 maxNumOfPixels);
248
249 int roundedSize;
250 if (initialSize <= 8) {
251 roundedSize = 1;
252 while (roundedSize < initialSize) {
253 roundedSize <<= 1;
254 }
255 } else {
256 roundedSize = (initialSize + 7) / 8 * 8;
257 }
258
259 return roundedSize;
260 }
261
262 private static int computeInitialSampleSize(BitmapFactory.Options options,
263 int minSideLength, int maxNumOfPixels) {
264 double w = options.outWidth;
265 double h = options.outHeight;
266
267 int lowerBound = (maxNumOfPixels < 0) ? 1 :
268 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
269 int upperBound = (minSideLength < 0) ? 128 :
270 (int) Math.min(Math.floor(w / minSideLength),
271 Math.floor(h / minSideLength));
272
273 if (upperBound < lowerBound) {
274 // return the larger one when there is no overlapping zone.
275 return lowerBound;
276 }
277
278 if (maxNumOfPixels < 0 && minSideLength < 0) {
279 return 1;
280 } else if (minSideLength < 0) {
281 return lowerBound;
282 } else {
283 return upperBound;
284 }
285 }
286
287 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
288 try {
289 BitmapFactory.Options options = new BitmapFactory.Options();
290 options.inJustDecodeBounds = true;
291 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
292 options);
293 if (options.mCancel || options.outWidth == -1
294 || options.outHeight == -1) {
295 return null;
296 }
297 options.inSampleSize = computeSampleSize(
298 options, -1, maxNumOfPixels);
299 options.inJustDecodeBounds = false;
300
301 options.inDither = false;
302 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
303 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
304 options);
305 } catch (OutOfMemoryError ex) {
306 Log.e(TAG, "Got oom exception ", ex);
307 return null;
308 }
309 }
310
311 public static void closeSilently(Closeable c) {
312 if (c == null) return;
313 try {
314 c.close();
315 } catch (Throwable t) {
316 // do nothing
317 }
318 }
319
320 public static void Assert(boolean cond) {
321 if (!cond) {
322 throw new AssertionError();
323 }
324 }
325
326 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
327 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
328 // Check if device policy has disabled the camera.
329 if (ApiHelper.HAS_GET_CAMERA_DISABLED) {
330 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
331 Context.DEVICE_POLICY_SERVICE);
332 if (dpm.getCameraDisabled(null)) {
333 throw new CameraDisabledException();
334 }
335 }
336 }
337
Angus Kong9ef99252013-07-18 18:04:19 -0700338 public static CameraManager.CameraProxy openCamera(
339 Activity activity, int cameraId)
Michael Kolb8872c232013-01-29 10:33:22 -0800340 throws CameraHardwareException, CameraDisabledException {
341 throwIfCameraDisabled(activity);
342
343 try {
344 return CameraHolder.instance().open(cameraId);
345 } catch (CameraHardwareException e) {
346 // In eng build, we throw the exception so that test tool
347 // can detect it and report it
348 if ("eng".equals(Build.TYPE)) {
349 throw new RuntimeException("openCamera failed", e);
350 } else {
351 throw e;
352 }
353 }
354 }
355
356 public static void showErrorAndFinish(final Activity activity, int msgId) {
357 DialogInterface.OnClickListener buttonListener =
358 new DialogInterface.OnClickListener() {
359 @Override
360 public void onClick(DialogInterface dialog, int which) {
361 activity.finish();
362 }
363 };
364 TypedValue out = new TypedValue();
365 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
366 new AlertDialog.Builder(activity)
367 .setCancelable(false)
368 .setTitle(R.string.camera_error_title)
369 .setMessage(msgId)
370 .setNeutralButton(R.string.dialog_ok, buttonListener)
371 .setIcon(out.resourceId)
372 .show();
373 }
374
375 public static <T> T checkNotNull(T object) {
376 if (object == null) throw new NullPointerException();
377 return object;
378 }
379
380 public static boolean equals(Object a, Object b) {
381 return (a == b) || (a == null ? false : a.equals(b));
382 }
383
384 public static int nextPowerOf2(int n) {
385 n -= 1;
386 n |= n >>> 16;
387 n |= n >>> 8;
388 n |= n >>> 4;
389 n |= n >>> 2;
390 n |= n >>> 1;
391 return n + 1;
392 }
393
394 public static float distance(float x, float y, float sx, float sy) {
395 float dx = x - sx;
396 float dy = y - sy;
397 return FloatMath.sqrt(dx * dx + dy * dy);
398 }
399
400 public static int clamp(int x, int min, int max) {
401 if (x > max) return max;
402 if (x < min) return min;
403 return x;
404 }
405
406 public static int getDisplayRotation(Activity activity) {
407 int rotation = activity.getWindowManager().getDefaultDisplay()
408 .getRotation();
409 switch (rotation) {
410 case Surface.ROTATION_0: return 0;
411 case Surface.ROTATION_90: return 90;
412 case Surface.ROTATION_180: return 180;
413 case Surface.ROTATION_270: return 270;
414 }
415 return 0;
416 }
417
418 public static int getDisplayOrientation(int degrees, int cameraId) {
419 // See android.hardware.Camera.setDisplayOrientation for
420 // documentation.
421 Camera.CameraInfo info = new Camera.CameraInfo();
422 Camera.getCameraInfo(cameraId, info);
423 int result;
424 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
425 result = (info.orientation + degrees) % 360;
426 result = (360 - result) % 360; // compensate the mirror
427 } else { // back-facing
428 result = (info.orientation - degrees + 360) % 360;
429 }
430 return result;
431 }
432
433 public static int getCameraOrientation(int cameraId) {
434 Camera.CameraInfo info = new Camera.CameraInfo();
435 Camera.getCameraInfo(cameraId, info);
436 return info.orientation;
437 }
438
439 public static int roundOrientation(int orientation, int orientationHistory) {
440 boolean changeOrientation = false;
441 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
442 changeOrientation = true;
443 } else {
444 int dist = Math.abs(orientation - orientationHistory);
445 dist = Math.min( dist, 360 - dist );
446 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
447 }
448 if (changeOrientation) {
449 return ((orientation + 45) / 90 * 90) % 360;
450 }
451 return orientationHistory;
452 }
453
454 @SuppressWarnings("deprecation")
455 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
456 private static Point getDefaultDisplaySize(Activity activity, Point size) {
457 Display d = activity.getWindowManager().getDefaultDisplay();
458 if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
459 d.getSize(size);
460 } else {
461 size.set(d.getWidth(), d.getHeight());
462 }
463 return size;
464 }
465
466 public static Size getOptimalPreviewSize(Activity currentActivity,
467 List<Size> sizes, double targetRatio) {
468 // Use a very small tolerance because we want an exact match.
469 final double ASPECT_TOLERANCE = 0.001;
470 if (sizes == null) return null;
471
472 Size optimalSize = null;
473 double minDiff = Double.MAX_VALUE;
474
475 // Because of bugs of overlay and layout, we sometimes will try to
476 // layout the viewfinder in the portrait orientation and thus get the
477 // wrong size of preview surface. When we change the preview size, the
478 // new overlay will be created before the old one closed, which causes
479 // an exception. For now, just get the screen size.
480 Point point = getDefaultDisplaySize(currentActivity, new Point());
481 int targetHeight = Math.min(point.x, point.y);
482 // Try to find an size match aspect ratio and size
483 for (Size size : sizes) {
484 double ratio = (double) size.width / size.height;
485 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
486 if (Math.abs(size.height - targetHeight) < minDiff) {
487 optimalSize = size;
488 minDiff = Math.abs(size.height - targetHeight);
489 }
490 }
491 // Cannot find the one match the aspect ratio. This should not happen.
492 // Ignore the requirement.
493 if (optimalSize == null) {
494 Log.w(TAG, "No preview size match the aspect ratio");
495 minDiff = Double.MAX_VALUE;
496 for (Size size : sizes) {
497 if (Math.abs(size.height - targetHeight) < minDiff) {
498 optimalSize = size;
499 minDiff = Math.abs(size.height - targetHeight);
500 }
501 }
502 }
503 return optimalSize;
504 }
505
506 // Returns the largest picture size which matches the given aspect ratio.
507 public static Size getOptimalVideoSnapshotPictureSize(
508 List<Size> sizes, double targetRatio) {
509 // Use a very small tolerance because we want an exact match.
510 final double ASPECT_TOLERANCE = 0.001;
511 if (sizes == null) return null;
512
513 Size optimalSize = null;
514
515 // Try to find a size matches aspect ratio and has the largest width
516 for (Size size : sizes) {
517 double ratio = (double) size.width / size.height;
518 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
519 if (optimalSize == null || size.width > optimalSize.width) {
520 optimalSize = size;
521 }
522 }
523
524 // Cannot find one that matches the aspect ratio. This should not happen.
525 // Ignore the requirement.
526 if (optimalSize == null) {
527 Log.w(TAG, "No picture size match the aspect ratio");
528 for (Size size : sizes) {
529 if (optimalSize == null || size.width > optimalSize.width) {
530 optimalSize = size;
531 }
532 }
533 }
534 return optimalSize;
535 }
536
537 public static void dumpParameters(Parameters parameters) {
538 String flattened = parameters.flatten();
539 StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
540 Log.d(TAG, "Dump all camera parameters:");
541 while (tokenizer.hasMoreElements()) {
542 Log.d(TAG, tokenizer.nextToken());
543 }
544 }
545
546 /**
547 * Returns whether the device is voice-capable (meaning, it can do MMS).
548 */
549 public static boolean isMmsCapable(Context context) {
550 TelephonyManager telephonyManager = (TelephonyManager)
551 context.getSystemService(Context.TELEPHONY_SERVICE);
552 if (telephonyManager == null) {
553 return false;
554 }
555
556 try {
557 Class<?> partypes[] = new Class[0];
558 Method sIsVoiceCapable = TelephonyManager.class.getMethod(
559 "isVoiceCapable", partypes);
560
561 Object arglist[] = new Object[0];
562 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
563 return (Boolean) retobj;
564 } catch (java.lang.reflect.InvocationTargetException ite) {
565 // Failure, must be another device.
566 // Assume that it is voice capable.
567 } catch (IllegalAccessException iae) {
568 // Failure, must be an other device.
569 // Assume that it is voice capable.
570 } catch (NoSuchMethodException nsme) {
571 }
572 return true;
573 }
574
575 // This is for test only. Allow the camera to launch the specific camera.
576 public static int getCameraFacingIntentExtras(Activity currentActivity) {
577 int cameraId = -1;
578
579 int intentCameraId =
Angus Kongb50b5cb2013-08-09 14:55:20 -0700580 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
Michael Kolb8872c232013-01-29 10:33:22 -0800581
582 if (isFrontCameraIntent(intentCameraId)) {
583 // Check if the front camera exist
584 int frontCameraId = CameraHolder.instance().getFrontCameraId();
585 if (frontCameraId != -1) {
586 cameraId = frontCameraId;
587 }
588 } else if (isBackCameraIntent(intentCameraId)) {
589 // Check if the back camera exist
590 int backCameraId = CameraHolder.instance().getBackCameraId();
591 if (backCameraId != -1) {
592 cameraId = backCameraId;
593 }
594 }
595 return cameraId;
596 }
597
598 private static boolean isFrontCameraIntent(int intentCameraId) {
599 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
600 }
601
602 private static boolean isBackCameraIntent(int intentCameraId) {
603 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
604 }
605
606 private static int sLocation[] = new int[2];
607
608 // This method is not thread-safe.
609 public static boolean pointInView(float x, float y, View v) {
610 v.getLocationInWindow(sLocation);
611 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
612 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
613 }
614
615 public static int[] getRelativeLocation(View reference, View view) {
616 reference.getLocationInWindow(sLocation);
617 int referenceX = sLocation[0];
618 int referenceY = sLocation[1];
619 view.getLocationInWindow(sLocation);
620 sLocation[0] -= referenceX;
621 sLocation[1] -= referenceY;
622 return sLocation;
623 }
624
625 public static boolean isUriValid(Uri uri, ContentResolver resolver) {
626 if (uri == null) return false;
627
628 try {
629 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
630 if (pfd == null) {
631 Log.e(TAG, "Fail to open URI. URI=" + uri);
632 return false;
633 }
634 pfd.close();
635 } catch (IOException ex) {
636 return false;
637 }
638 return true;
639 }
640
641 public static void viewUri(Uri uri, Context context) {
642 if (!isUriValid(uri, context.getContentResolver())) {
643 Log.e(TAG, "Uri invalid. uri=" + uri);
644 return;
645 }
646
647 try {
Angus Kongb50b5cb2013-08-09 14:55:20 -0700648 context.startActivity(new Intent(CameraUtil.REVIEW_ACTION, uri));
Michael Kolb8872c232013-01-29 10:33:22 -0800649 } catch (ActivityNotFoundException ex) {
650 try {
651 context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
652 } catch (ActivityNotFoundException e) {
653 Log.e(TAG, "review image fail. uri=" + uri, e);
654 }
655 }
656 }
657
658 public static void dumpRect(RectF rect, String msg) {
659 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
660 + "," + rect.right + "," + rect.bottom + ")");
661 }
662
663 public static void rectFToRect(RectF rectF, Rect rect) {
664 rect.left = Math.round(rectF.left);
665 rect.top = Math.round(rectF.top);
666 rect.right = Math.round(rectF.right);
667 rect.bottom = Math.round(rectF.bottom);
668 }
669
670 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
671 int viewWidth, int viewHeight) {
672 // Need mirror for front camera.
673 matrix.setScale(mirror ? -1 : 1, 1);
674 // This is the value for android.hardware.Camera.setDisplayOrientation.
675 matrix.postRotate(displayOrientation);
676 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
677 // UI coordinates range from (0, 0) to (width, height).
678 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
679 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
680 }
681
682 public static String createJpegName(long dateTaken) {
683 synchronized (sImageFileNamer) {
684 return sImageFileNamer.generateName(dateTaken);
685 }
686 }
687
688 public static void broadcastNewPicture(Context context, Uri uri) {
689 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
690 // Keep compatibility
691 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
692 }
693
694 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
695 if (view.getVisibility() == View.VISIBLE) return;
696
697 view.setVisibility(View.VISIBLE);
698 Animation animation = new AlphaAnimation(startAlpha, endAlpha);
699 animation.setDuration(duration);
700 view.startAnimation(animation);
701 }
702
703 public static void fadeIn(View view) {
704 fadeIn(view, 0F, 1F, 400);
705
706 // We disabled the button in fadeOut(), so enable it here.
707 view.setEnabled(true);
708 }
709
710 public static void fadeOut(View view) {
711 if (view.getVisibility() != View.VISIBLE) return;
712
713 // Since the button is still clickable before fade-out animation
714 // ends, we disable the button first to block click.
715 view.setEnabled(false);
716 Animation animation = new AlphaAnimation(1F, 0F);
717 animation.setDuration(400);
718 view.startAnimation(animation);
719 view.setVisibility(View.GONE);
720 }
721
722 public static int getJpegRotation(int cameraId, int orientation) {
723 // See android.hardware.Camera.Parameters.setRotation for
724 // documentation.
725 int rotation = 0;
726 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
727 CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
728 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
729 rotation = (info.orientation - orientation + 360) % 360;
730 } else { // back-facing camera
731 rotation = (info.orientation + orientation) % 360;
732 }
733 }
734 return rotation;
735 }
736
Sascha Haeberling37f36112013-08-06 14:31:52 -0700737 /**
738 * Down-samples a jpeg byte array.
739 * @param data a byte array of jpeg data
740 * @param downSampleFactor down-sample factor
741 * @return decoded and down-sampled bitmap
742 */
743 public static Bitmap downSample(final byte[] data, int downSampleFactor) {
744 final BitmapFactory.Options opts = new BitmapFactory.Options();
745 // Downsample the image
746 opts.inSampleSize = downSampleFactor;
747 return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
748 }
749
Michael Kolb8872c232013-01-29 10:33:22 -0800750 public static void setGpsParameters(Parameters parameters, Location loc) {
751 // Clear previous GPS location from the parameters.
752 parameters.removeGpsData();
753
754 // We always encode GpsTimeStamp
755 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
756
757 // Set GPS location.
758 if (loc != null) {
759 double lat = loc.getLatitude();
760 double lon = loc.getLongitude();
761 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
762
763 if (hasLatLon) {
764 Log.d(TAG, "Set gps location");
765 parameters.setGpsLatitude(lat);
766 parameters.setGpsLongitude(lon);
767 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
768 if (loc.hasAltitude()) {
769 parameters.setGpsAltitude(loc.getAltitude());
770 } else {
771 // for NETWORK_PROVIDER location provider, we may have
772 // no altitude information, but the driver needs it, so
773 // we fake one.
774 parameters.setGpsAltitude(0);
775 }
776 if (loc.getTime() != 0) {
777 // Location.getTime() is UTC in milliseconds.
778 // gps-timestamp is UTC in seconds.
779 long utcTimeSeconds = loc.getTime() / 1000;
780 parameters.setGpsTimestamp(utcTimeSeconds);
781 }
782 } else {
783 loc = null;
784 }
785 }
786 }
787
Angus Kongd82eae22013-06-11 12:00:15 -0700788
789 public static int[] getMaxPreviewFpsRange(Parameters params) {
790 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
791 if (frameRates != null && frameRates.size() > 0) {
792 // The list is sorted. Return the last element.
793 return frameRates.get(frameRates.size() - 1);
794 }
795 return new int[0];
796 }
797
Michael Kolb8872c232013-01-29 10:33:22 -0800798 private static class ImageFileNamer {
799 private SimpleDateFormat mFormat;
800
801 // The date (in milliseconds) used to generate the last name.
802 private long mLastDate;
803
804 // Number of names generated for the same second.
805 private int mSameSecondCount;
806
807 public ImageFileNamer(String format) {
808 mFormat = new SimpleDateFormat(format);
809 }
810
811 public String generateName(long dateTaken) {
812 Date date = new Date(dateTaken);
813 String result = mFormat.format(date);
814
815 // If the last name was generated for the same second,
816 // we append _1, _2, etc to the name.
817 if (dateTaken / 1000 == mLastDate / 1000) {
818 mSameSecondCount++;
819 result += "_" + mSameSecondCount;
820 } else {
821 mLastDate = dateTaken;
822 mSameSecondCount = 0;
823 }
824
825 return result;
826 }
827 }
Angus Kongb40738a2013-06-03 14:55:34 -0700828
829 public static void playVideo(Context context, Uri uri, String title) {
830 try {
831 Intent intent = new Intent(Intent.ACTION_VIEW)
832 .setDataAndType(uri, "video/*")
833 .putExtra(Intent.EXTRA_TITLE, title)
Sascha Haeberling8e963a52013-08-06 11:43:02 -0700834 .putExtra(KEY_TREAT_UP_AS_BACK, true);
Angus Kongb40738a2013-06-03 14:55:34 -0700835 context.startActivity(intent);
836 } catch (ActivityNotFoundException e) {
837 Toast.makeText(context, context.getString(R.string.video_err),
838 Toast.LENGTH_SHORT).show();
839 }
840 }
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700841
842 /**
843 * Starts GMM with the given location shown. If this fails, and GMM could
844 * not be found, we use a geo intent as a fallback.
845 *
846 * @param context the Android context to use for starting the activities.
847 * @param latLong a 2-element array containing {latitude/longitude}.
848 */
849 public static void showOnMap(Context context, double[] latLong) {
850 try {
851 // We don't use "geo:latitude,longitude" because it only centers
852 // the MapView to the specified location, but we need a marker
853 // for further operations (routing to/from).
854 // The q=(lat, lng) syntax is suggested by geo-team.
855 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
856 latLong[0], latLong[1]);
857 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
858 MAPS_CLASS_NAME);
859 Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
860 Uri.parse(uri)).setComponent(compName);
861 context.startActivity(mapsIntent);
862 } catch (ActivityNotFoundException e) {
863 // Use the "geo intent" if no GMM is installed
864 Log.e(TAG, "GMM activity not found!", e);
865 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
866 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
867 context.startActivity(mapsIntent);
868 }
869 }
Michael Kolb8872c232013-01-29 10:33:22 -0800870}