summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src/com/android/systemui/quicksettings/CameraTile.java
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/quicksettings/CameraTile.java')
-rw-r--r--packages/SystemUI/src/com/android/systemui/quicksettings/CameraTile.java499
1 files changed, 499 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/quicksettings/CameraTile.java b/packages/SystemUI/src/com/android/systemui/quicksettings/CameraTile.java
new file mode 100644
index 0000000..02114b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/quicksettings/CameraTile.java
@@ -0,0 +1,499 @@
+package com.android.systemui.quicksettings;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.PanelView;
+import com.android.systemui.statusbar.phone.QuickSettingsContainerView;
+import com.android.systemui.statusbar.phone.QuickSettingsController;
+
+public class CameraTile extends QuickSettingsTile {
+ private static final String DEFAULT_IMAGE_FILE_NAME_FORMAT = "'IMG'_yyyyMMdd_HHmmss";
+ private static final int CAMERA_ID = 0;
+
+ private Handler mHandler;
+ private TextView mTextView;
+ private FrameLayout mSurfaceLayout;
+ private SurfaceView mSurfaceView;
+ private View mFlashView;
+
+ private Camera mCamera;
+ private CameraOrientationListener mCameraOrientationListener = null;
+ private int mOrientation;
+ private int mJpegRotation;
+ private int mDisplayRotation;
+ private Camera.Size mCameraSize;
+ private boolean mCameraStarted;
+ private boolean mCameraBusy;
+
+ private Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
+ private Camera.Parameters mParams;
+
+ private Storage mStorage = new Storage();
+ private SimpleDateFormat mImageNameFormatter;
+
+ private Runnable mStartRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mCamera != null) {
+ return;
+ }
+
+ Camera.getCameraInfo(CAMERA_ID, mCameraInfo);
+
+ try {
+ mCamera = Camera.open(CAMERA_ID);
+ } catch (Exception e) {
+ Toast.makeText(mContext, R.string.quick_settings_camera_error_connect,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // Orientation listener to rotate the camera preview
+ if (mCameraOrientationListener == null) {
+ mCameraOrientationListener = new CameraOrientationListener(mContext);
+ }
+ mCameraOrientationListener.enable();
+
+ mParams = mCamera.getParameters();
+
+ // Use smallest preview size that is bigger than the tile view
+ Camera.Size previewSize = mParams.getPreviewSize();
+ for (Camera.Size size : mParams.getSupportedPreviewSizes()) {
+ if ((size.width > mTile.getWidth() && size.height > mTile.getHeight()) &&
+ (size.width < previewSize.width && size.height < previewSize.height)) {
+ previewSize = size;
+ }
+ }
+ mParams.setPreviewSize(previewSize.width, previewSize.height);
+
+ // Use largest picture size
+ Camera.Size pictureSize = mParams.getPictureSize();
+ for (Camera.Size size : mParams.getSupportedPictureSizes()) {
+ if (size.width > pictureSize.width && size.height > pictureSize.height) {
+ pictureSize = size;
+ }
+ }
+ mCameraSize = pictureSize;
+ mParams.setPictureSize(pictureSize.width, pictureSize.height);
+
+ // Try focus with continuous modes first, then basic autofocus
+ List<String> focusModes = mParams.getSupportedFocusModes();
+ if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+ } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
+ mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+ } else if (mParams.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
+ mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
+ }
+
+ mCamera.setParameters(mParams);
+ updateOrientation();
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ final PanelView panel = getContainingPanel();
+ if (panel != null && panel.isFullyExpanded()) {
+ mHandler.postDelayed(this, 100);
+ } else {
+ mHandler.post(mReleaseCameraRunnable);
+ }
+ }
+ }, 100);
+
+ mTextView.setVisibility(View.GONE);
+ mSurfaceView = new CameraPreview(mContext, mCamera);
+ mSurfaceView.setVisibility(View.VISIBLE);
+ mSurfaceLayout.addView(mSurfaceView, 0);
+ }
+ };
+
+ private Runnable mTakePictureRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mCamera == null) {
+ return;
+ }
+
+ // Repeat until the preview has started and we can
+ // take picture
+ if (!mCameraStarted) {
+ mHandler.postDelayed(this, 200);
+ return;
+ }
+
+ // To avoid crashes don't post new picture requests
+ // if previous request has not returned
+ if (mCameraBusy) {
+ return;
+ }
+ mCameraBusy = true;
+
+ // Display flash animation above the preview
+ mFlashView.setVisibility(View.VISIBLE);
+ mFlashView.animate().alpha(0f).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mFlashView.setVisibility(View.GONE);
+ mFlashView.setAlpha(1f);
+ }
+ });
+
+ // Update the JPEG rotation\
+ if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ mJpegRotation = (mCameraInfo.orientation - mOrientation + 360) % 360;
+ } else {
+ mJpegRotation = (mCameraInfo.orientation + mOrientation) % 360;
+ }
+
+ mParams.setRotation(mJpegRotation);
+ mCamera.setParameters(mParams);
+
+ // Request a picture
+ try {
+ mCamera.takePicture(null, null, new Camera.PictureCallback() {
+ @Override
+ public void onPictureTaken(byte[] data, Camera camera) {
+ mCameraBusy = false;
+
+ long time = System.currentTimeMillis();
+
+ int orientation = (mOrientation + mDisplayRotation) % 360;
+
+ mStorage.addImage(mContext.getContentResolver(),
+ mImageNameFormatter.format(new Date(time)),
+ time, orientation, data, mCameraSize.width,
+ mCameraSize.height);
+
+ mCamera.startPreview();
+ }
+ });
+ } catch (RuntimeException e) {
+ // This can happen if user is pressing the
+ // tile too fast, nothing we can do
+ }
+ }
+ };
+
+ private Runnable mReleaseCameraRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mCamera == null) {
+ return;
+ }
+
+ mCamera.stopPreview();
+ mCamera.release();
+ mCamera = null;
+ mCameraStarted = false;
+ mCameraOrientationListener.disable();
+
+ mTextView.setVisibility(View.VISIBLE);
+ mSurfaceView.setVisibility(View.GONE);
+ mSurfaceLayout.removeView(mSurfaceView);
+ mSurfaceView = null;
+ }
+ };
+
+ private Runnable mAutoFocusRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mCameraStarted) {
+ mCamera.autoFocus(null);
+ }
+ }
+ };
+
+ public CameraTile(Context context, QuickSettingsController qsc, Handler handler) {
+ super(context, qsc, R.layout.quick_settings_tile_camera);
+ mHandler = handler;
+
+ String imageFileNameFormat = DEFAULT_IMAGE_FILE_NAME_FORMAT;
+ try {
+ final Resources camRes = context.getPackageManager()
+ .getResourcesForApplication("com.android.gallery3d");
+ int imageFileNameFormatResId = camRes.getIdentifier(
+ "image_file_name_format", "string", "com.android.gallery3d");
+ imageFileNameFormat = camRes.getString(imageFileNameFormatResId);
+ } catch (PackageManager.NameNotFoundException ex) {
+ // Use default
+ } catch (Resources.NotFoundException ex) {
+ // Use default
+ }
+ mImageNameFormatter = new SimpleDateFormat(imageFileNameFormat);
+
+ }
+
+ @Override
+ void onPostCreate() {
+ mOnLongClick = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (mCamera != null) {
+ return false;
+ }
+
+ Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+ startSettingsActivity(intent);
+ return true;
+ }
+ };
+
+ mTextView = (TextView) mTile.findViewById(R.id.camera_text);
+ mSurfaceLayout = (FrameLayout) mTile.findViewById(R.id.camera_surface_holder);
+ mFlashView = mTile.findViewById(R.id.camera_surface_flash_overlay);
+
+ super.onPostCreate();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mCamera == null) {
+ mHandler.post(mStartRunnable);
+ } else {
+ mHandler.post(mTakePictureRunnable);
+ }
+ }
+
+ private PanelView getContainingPanel() {
+ ViewParent parent = mContainer;
+ while (parent != null) {
+ if (parent instanceof PanelView) {
+ return (PanelView) parent;
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ private void updateOrientation() {
+ final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ int rotation = wm.getDefaultDisplay().getRotation();
+
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ default:
+ mDisplayRotation = 0;
+ break;
+ case Surface.ROTATION_90:
+ mDisplayRotation = 90;
+ break;
+ case Surface.ROTATION_180:
+ mDisplayRotation = 180;
+ break;
+ case Surface.ROTATION_270:
+ mDisplayRotation = 270;
+ break;
+ }
+
+ int cameraOrientation;
+
+ if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ cameraOrientation = (mCameraInfo.orientation + mDisplayRotation) % 360;
+ cameraOrientation = (360 - cameraOrientation) % 360; // compensate the mirror
+ } else {
+ cameraOrientation = (mCameraInfo.orientation - mDisplayRotation + 360) % 360;
+ }
+
+ mCamera.setDisplayOrientation(cameraOrientation);
+ }
+
+ private class CameraOrientationListener extends OrientationEventListener {
+ public CameraOrientationListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ if (mCamera == null || orientation == ORIENTATION_UNKNOWN) {
+ return;
+ }
+
+ mOrientation = (orientation + 45) / 90 * 90;
+ updateOrientation();
+ }
+ }
+
+ private class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
+ private SurfaceHolder mHolder;
+ private Camera mCamera;
+
+ public CameraPreview(Context context, Camera camera) {
+ super(context);
+ mCamera = camera;
+
+ // Install a SurfaceHolder.Callback so we get notified when the
+ // underlying surface is created and destroyed.
+ mHolder = getHolder();
+ mHolder.addCallback(this);
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ // The Surface has been created, now tell the camera where
+ // to draw the preview.
+ try {
+ mCamera.setPreviewDisplay(holder);
+ mCamera.startPreview();
+ mCameraStarted = true;
+ mCameraBusy = false;
+ mHandler.postDelayed(mAutoFocusRunnable, 200);
+ } catch (IOException e) {
+ // Try release camera
+ mCamera.release();
+ }
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ }
+ }
+
+ private class Storage {
+ private static final String TAG = "CameraStorage";
+ private String mRoot = Environment.getExternalStorageDirectory().toString();
+ private Storage() {}
+
+ public String writeFile(String title, byte[] data) {
+ String path = generateFilepath(title);
+ FileOutputStream out = null;
+
+ try {
+ out = new FileOutputStream(path);
+ out.write(data);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to write data", e);
+ } finally {
+ try {
+ out.close();
+ } catch (Exception e) {
+ // Do nothing here
+ }
+ }
+ return path;
+ }
+
+ // Save the image and add it to media store.
+ public Uri addImage(ContentResolver resolver, String title, long date,
+ int orientation, byte[] jpeg, int width, int height) {
+ // Save the image.
+ String path = writeFile(title, jpeg);
+ return addImage(resolver, title, date, orientation, jpeg.length,
+ path, width, height);
+ }
+
+ // Add the image to media store.
+ public Uri addImage(ContentResolver resolver, String title, long date,
+ int orientation, int jpegLength, String path, int width, int height) {
+
+ try {
+ ExifInterface exif = new ExifInterface(path);
+ switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ orientation = 90;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ orientation = 180;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ orientation = 270;
+ break;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to read exif", e);
+ }
+
+ if ((mJpegRotation + orientation) % 180 != 0) {
+ int temp = width;
+ width = height;
+ height = width;
+ }
+
+ // Insert into MediaStore.
+ ContentValues values = new ContentValues(9);
+ values.put(ImageColumns.TITLE, title);
+ values.put(ImageColumns.DISPLAY_NAME, title + ".jpg");
+ values.put(ImageColumns.DATE_TAKEN, date);
+ values.put(ImageColumns.MIME_TYPE, "image/jpeg");
+
+ // Clockwise rotation in degrees. 0, 90, 180, or 270.
+ values.put(ImageColumns.ORIENTATION, orientation);
+ values.put(ImageColumns.DATA, path);
+ values.put(ImageColumns.SIZE, jpegLength);
+ values.put(MediaColumns.WIDTH, width);
+ values.put(MediaColumns.HEIGHT, height);
+
+ Uri uri = null;
+
+ try {
+ uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
+ } catch (Throwable th) {
+ // This can happen when the external volume is already mounted, but
+ // MediaScanner has not notify MediaProvider to add that volume.
+ // The picture is still safe and MediaScanner will find it and
+ // insert it into MediaProvider. The only problem is that the user
+ // cannot click the thumbnail to review the picture.
+ Log.e(TAG, "Failed to write MediaStore" + th);
+ }
+ return uri;
+ }
+
+ private String generateDCIM() {
+ return new File(mRoot, Environment.DIRECTORY_DCIM).toString();
+ }
+
+ public String generateDirectory() {
+ return generateDCIM() + "/Camera";
+ }
+
+ private String generateFilepath(String title) {
+ return generateDirectory() + '/' + title + ".jpg";
+ }
+
+ public String generateBucketId() {
+ return String.valueOf(generateDirectory().toLowerCase().hashCode());
+ }
+
+ public int generateBucketIdInt() {
+ return generateDirectory().toLowerCase().hashCode();
+ }
+ }
+}