diff --git a/res/layout/mediapicker_camera_chooser.xml b/res/layout/mediapicker_camera_chooser.xml
index f370568..4db0fae 100644
--- a/res/layout/mediapicker_camera_chooser.xml
+++ b/res/layout/mediapicker_camera_chooser.xml
@@ -15,129 +15,80 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-
+ android:background="@android:color/white">
-
+ android:layout_height="match_parent">
-
-
-
-
-
-
-
-
-
+ android:layout_gravity="top|center"
+ android:layout_marginTop="16dp"
+ android:importantForAccessibility="no"
+ android:text="@string/mediapicker_cameraChooserDescription"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/mediapicker_enabled"
+ app:layout_constraintTop_toTopOf="@id/mediapicker_enabled" />
-
+
-
-
-
-
-
-
-
+ android:layout_marginEnd="32dp"
+ android:contentDescription="@null"
+ android:importantForAccessibility="no"
+ android:scaleX="1.33"
+ android:scaleY="1.33"
+ android:src="@drawable/ic_camera_light"
+ app:tint="@color/primary_color" />
+
-
+
+
+ android:layout_gravity="center"
+ android:layout_marginStart="32dp"
+ android:contentDescription="@null"
+ android:importantForAccessibility="no"
+ android:scaleType="center"
+ android:scaleX="1.33"
+ android:scaleY="1.33"
+ android:src="@drawable/ic_mp_video_small_light"
+ app:tint="@color/primary_color" />
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
diff --git a/src/com/android/messaging/ui/mediapicker/CameraManager.java b/src/com/android/messaging/ui/mediapicker/CameraManager.java
deleted file mode 100644
index b1b9501..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraManager.java
+++ /dev/null
@@ -1,1175 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * Copyright (C) 2024 The LineageOS Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Looper;
-import androidx.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.WindowManager;
-
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.mediapicker.camerafocus.FocusOverlayManager;
-import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Class which manages interactions with the camera, but does not do any UI. This class is
- * designed to be a singleton to ensure there is one component managing the camera and releasing
- * the native resources.
- * In order to acquire a camera, a caller must:
- *
- *
Call selectCamera to select front or back camera
- *
Call setSurface to control where the preview is shown
- *
Call openCamera to request the camera start preview
- *
- * Callers should call onPause and onResume to ensure that the camera is release while the activity
- * is not active.
- * This class is not thread safe. It should only be called from one thread (the UI thread or test
- * thread)
- */
-class CameraManager implements FocusOverlayManager.Listener {
- /**
- * Wrapper around the framework camera API to allow mocking different hardware scenarios while
- * unit testing
- */
- interface CameraWrapper {
- int getNumberOfCameras();
- void getCameraInfo(int index, CameraInfo cameraInfo);
- Camera open(int cameraId);
- /** Add a wrapper for release because a final method cannot be mocked */
- void release(Camera camera);
- }
-
- /**
- * Callbacks for the camera manager listener
- */
- interface CameraManagerListener {
- void onCameraError(int errorCode, Exception e);
- void onCameraChanged();
- }
-
- /**
- * Callback when taking image or video
- */
- interface MediaCallback {
- int MEDIA_CAMERA_CHANGED = 1;
- int MEDIA_NO_DATA = 2;
-
- void onMediaReady(Uri uriToMedia, String contentType, int width, int height);
- void onMediaFailed(Exception exception);
- void onMediaInfo(int what);
- }
-
- // Error codes
- static final int ERROR_OPENING_CAMERA = 1;
- static final int ERROR_SHOWING_PREVIEW = 2;
- static final int ERROR_INITIALIZING_VIDEO = 3;
- static final int ERROR_STORAGE_FAILURE = 4;
- static final int ERROR_RECORDING_VIDEO = 5;
- static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 6;
- static final int ERROR_TAKING_PICTURE = 7;
-
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final int NO_CAMERA_SELECTED = -1;
-
- private static CameraManager sInstance;
-
- /** Default camera wrapper which directs calls to the framework APIs */
- private static CameraWrapper sCameraWrapper = new CameraWrapper() {
- @Override
- public int getNumberOfCameras() {
- return Camera.getNumberOfCameras();
- }
-
- @Override
- public void getCameraInfo(final int index, final CameraInfo cameraInfo) {
- Camera.getCameraInfo(index, cameraInfo);
- }
-
- @Override
- public Camera open(final int cameraId) {
- return Camera.open(cameraId);
- }
-
- @Override
- public void release(final Camera camera) {
- camera.release();
- }
- };
-
- /** The CameraInfo for the currently selected camera */
- private final CameraInfo mCameraInfo;
-
- /**
- * The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet
- */
- private int mCameraIndex;
-
- /** True if the device has front and back cameras */
- private final boolean mHasFrontAndBackCamera;
-
- /** True if the camera should be open (may not yet be actually open) */
- private boolean mOpenRequested;
-
- /** True if the camera is requested to be in video mode */
- private boolean mVideoModeRequested;
-
- /** The media recorder for video mode */
- private MmsVideoRecorder mMediaRecorder;
-
- /** Callback to call with video recording updates */
- private MediaCallback mVideoCallback;
-
- /** The preview view to show the preview on */
- private CameraPreview mCameraPreview;
-
- /** The helper classs to handle orientation changes */
- private OrientationHandler mOrientationHandler;
-
- /** Tracks whether the preview has hardware acceleration */
- private boolean mIsHardwareAccelerationSupported;
-
- /**
- * The task for opening the camera, so it doesn't block the UI thread
- * Using AsyncTask rather than SafeAsyncTask because the tasks need to be serialized, but don't
- * need to be on the UI thread
- * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may
- * need to create a dedicated thread, or synchronize the threads in the thread pool
- */
- private AsyncTask mOpenCameraTask;
-
- /**
- * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if
- * no open task is pending
- */
- private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
-
- /** The instance of the currently opened camera */
- private Camera mCamera;
-
- /** The rotation of the screen relative to the camera's natural orientation */
- private int mRotation;
-
- /** The callback to notify when errors or other events occur */
- private CameraManagerListener mListener;
-
- /** True if the camera is currently in the process of taking an image */
- private boolean mTakingPicture;
-
- /** Provides subscription-related data to access per-subscription configurations. */
- private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
-
- /** Manages auto focus visual and behavior */
- private final FocusOverlayManager mFocusOverlayManager;
-
- private CameraManager() {
- mCameraInfo = new CameraInfo();
- mCameraIndex = NO_CAMERA_SELECTED;
-
- // Check to see if a front and back camera exist
- boolean hasFrontCamera = false;
- boolean hasBackCamera = false;
- final CameraInfo cameraInfo = new CameraInfo();
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- try {
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
- hasFrontCamera = true;
- } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- hasBackCamera = true;
- }
- if (hasFrontCamera && hasBackCamera) {
- break;
- }
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "Unable to load camera info", e);
- }
- mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera;
- mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper());
-
- // Assume the best until we are proven otherwise
- mIsHardwareAccelerationSupported = true;
- }
-
- /** Gets the singleton instance */
- static CameraManager get() {
- if (sInstance == null) {
- sInstance = new CameraManager();
- }
- return sInstance;
- }
-
- /** Allows tests to inject a custom camera wrapper */
- @VisibleForTesting
- static void setCameraWrapper(final CameraWrapper cameraWrapper) {
- sCameraWrapper = cameraWrapper;
- sInstance = null;
- }
-
- /**
- * Sets the surface to use to display the preview
- * This must only be called AFTER the CameraPreview has a texture ready
- * @param preview The preview surface view
- */
- void setSurface(final CameraPreview preview) {
- if (preview == mCameraPreview) {
- return;
- }
-
- if (preview != null) {
- Assert.isTrue(preview.isValid());
- preview.setOnTouchListener((view, motionEvent) -> {
- if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) ==
- MotionEvent.ACTION_UP) {
- mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight());
- mFocusOverlayManager.onSingleTapUp(
- (int) motionEvent.getX() + view.getLeft(),
- (int) motionEvent.getY() + view.getTop());
- }
- return true;
- });
- }
- mCameraPreview = preview;
- tryShowPreview();
- }
-
- void setRenderOverlay(final RenderOverlay renderOverlay) {
- mFocusOverlayManager.setFocusRenderer(renderOverlay != null ?
- renderOverlay.getPieRenderer() : null);
- }
-
- /** Convenience function to swap between front and back facing cameras */
- void swapCamera() {
- Assert.isTrue(mCameraIndex >= 0);
- selectCamera(mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT ?
- CameraInfo.CAMERA_FACING_BACK :
- CameraInfo.CAMERA_FACING_FRONT);
- }
-
- /**
- * Selects the first camera facing the desired direction, or the first camera if there is no
- * camera in the desired direction
- * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants
- * @return True if a camera was selected, or false if selecting a camera failed
- */
- boolean selectCamera(final int desiredFacing) {
- try {
- // We already selected a camera facing that direction
- if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) {
- return true;
- }
-
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- Assert.isTrue(cameraCount > 0);
-
- mCameraIndex = NO_CAMERA_SELECTED;
- setCamera(null);
- final CameraInfo cameraInfo = new CameraInfo();
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == desiredFacing) {
- mCameraIndex = i;
- sCameraWrapper.getCameraInfo(i, mCameraInfo);
- break;
- }
- }
-
- // There's no camera in the desired facing direction, just select the first camera
- // regardless of direction
- if (mCameraIndex < 0) {
- mCameraIndex = 0;
- sCameraWrapper.getCameraInfo(0, mCameraInfo);
- }
-
- if (mOpenRequested) {
- // The camera is open, so reopen with the newly selected camera
- openCamera();
- }
- return true;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCamera", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- return false;
- }
- }
-
- int getCameraIndex() {
- return mCameraIndex;
- }
-
- void selectCameraByIndex(final int cameraIndex) {
- if (mCameraIndex == cameraIndex) {
- return;
- }
-
- try {
- mCameraIndex = cameraIndex;
- sCameraWrapper.getCameraInfo(mCameraIndex, mCameraInfo);
- if (mOpenRequested) {
- openCamera();
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCameraByIndex", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
-
- @VisibleForTesting
- CameraInfo getCameraInfo() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- return null;
- }
- return mCameraInfo;
- }
-
- /** @return True if this device has camera capabilities */
- boolean hasAnyCamera() {
- return sCameraWrapper.getNumberOfCameras() > 0;
- }
-
- /** @return True if the device has both a front and back camera */
- boolean hasFrontAndBackCamera() {
- return mHasFrontAndBackCamera;
- }
-
- /**
- * Opens the camera on a separate thread and initiates the preview if one is available
- */
- void openCamera() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- // Ensure a selected camera if none is currently selected. This may happen if the
- // camera chooser is not the default media chooser.
- selectCamera(CameraInfo.CAMERA_FACING_BACK);
- }
- mOpenRequested = true;
- // We're already opening the camera or already have the camera handle, nothing more to do
- if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) {
- return;
- }
-
- // True if the task to open the camera has to be delayed until the current one completes
- boolean delayTask = false;
-
- // Cancel any previous open camera tasks
- if (mOpenCameraTask != null) {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- delayTask = true;
- }
-
- mPendingOpenCameraIndex = mCameraIndex;
- mOpenCameraTask = new AsyncTask<>() {
- private Exception mException;
-
- @Override
- protected Camera doInBackground(final Integer... params) {
- try {
- final int cameraIndex = params[0];
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opening camera " + mCameraIndex);
- }
- return sCameraWrapper.open(cameraIndex);
- } catch (final Exception e) {
- LogUtil.e(TAG, "Exception while opening camera", e);
- mException = e;
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(final Camera camera) {
- // If we completed, but no longer want this camera, then release the camera
- if (mOpenCameraTask != this || !mOpenRequested) {
- releaseCamera(camera);
- cleanup();
- return;
- }
-
- cleanup();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opened camera " + mCameraIndex + " " + (camera != null));
- }
-
- setCamera(camera);
- if (camera == null) {
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, mException);
- }
- LogUtil.e(TAG, "Error opening camera");
- }
- }
-
- @Override
- protected void onCancelled() {
- super.onCancelled();
- cleanup();
- }
-
- private void cleanup() {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) {
- // If there's another task waiting on this one to complete, start it now
- mOpenCameraTask.execute(mCameraIndex);
- } else {
- mOpenCameraTask = null;
- }
-
- }
- };
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Start opening camera " + mCameraIndex);
- }
-
- if (!delayTask) {
- mOpenCameraTask.execute(mCameraIndex);
- }
- }
-
- boolean isVideoMode() {
- return mVideoModeRequested;
- }
-
- boolean isRecording() {
- return mVideoModeRequested && mVideoCallback != null;
- }
-
- void setVideoMode(final boolean videoMode) {
- if (mVideoModeRequested == videoMode) {
- return;
- }
- mVideoModeRequested = videoMode;
- tryInitOrCleanupVideoMode();
- }
-
- /** Closes the camera releasing the resources it uses */
- void closeCamera() {
- mOpenRequested = false;
- setCamera(null);
- }
-
- /** Temporarily closes the camera if it is open */
- void onPause() {
- setCamera(null);
- }
-
- /** Reopens the camera if it was opened when onPause was called */
- void onResume() {
- if (mOpenRequested) {
- openCamera();
- }
- }
-
- /**
- * Sets the listener which will be notified of errors or other events in the camera
- * @param listener The listener to notify
- */
- void setListener(final CameraManagerListener listener) {
- Assert.isMainThread();
- mListener = listener;
- if (!mIsHardwareAccelerationSupported && mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
-
- void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
- mSubscriptionDataProvider = provider;
- }
-
- void takePicture(final float heightPercent, @NonNull final MediaCallback callback) {
- Assert.isTrue(!mVideoModeRequested);
- Assert.isTrue(!mTakingPicture);
- Assert.notNull(callback);
- if (mCamera == null) {
- // The caller should have checked isCameraAvailable first, but just in case, protect
- // against a null camera by notifying the callback that taking the picture didn't work
- callback.onMediaFailed(null);
- return;
- }
- final Camera.PictureCallback jpegCallback = (bytes, camera) -> {
- mTakingPicture = false;
- if (mCamera != camera) {
- // This may happen if the camera was changed between front/back while the
- // picture is being taken.
- callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED);
- return;
- }
-
- if (bytes == null) {
- callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA);
- return;
- }
-
- final Camera.Size size = camera.getParameters().getPictureSize();
- int width;
- int height;
- if (mRotation == 90 || mRotation == 270) {
- width = size.height;
- height = size.width;
- } else {
- width = size.width;
- height = size.height;
- }
- new ImagePersistTask(
- width, height, heightPercent, bytes, mCameraPreview.getContext(), callback)
- .executeOnThreadPool();
- };
-
- mTakingPicture = true;
- try {
- mCamera.takePicture(
- null /* shutter */,
- null /* raw */,
- null /* postView */,
- jpegCallback);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.takePicture", e);
- mTakingPicture = false;
- if (mListener != null) {
- mListener.onCameraError(ERROR_TAKING_PICTURE, e);
- }
- }
- }
-
- void startVideo(final MediaCallback callback) {
- Assert.notNull(callback);
- Assert.isTrue(!isRecording());
- mVideoCallback = callback;
- tryStartVideoCapture();
- }
-
- /**
- * Asynchronously releases a camera
- * @param camera The camera to release
- */
- private void releaseCamera(final Camera camera) {
- if (camera == null) {
- return;
- }
-
- mFocusOverlayManager.onCameraReleased();
-
- new AsyncTask() {
- @Override
- protected Void doInBackground(final Void... params) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Releasing camera " + mCameraIndex);
- }
- sCameraWrapper.release(camera);
- return null;
- }
- }.execute();
- }
-
- private void releaseMediaRecorder(final boolean cleanupFile) {
- if (mMediaRecorder == null) {
- return;
- }
- mVideoModeRequested = false;
-
- if (cleanupFile) {
- mMediaRecorder.cleanupTempFile();
- if (mVideoCallback != null) {
- final MediaCallback callback = mVideoCallback;
- mVideoCallback = null;
- // Notify the callback that we've stopped recording
- callback.onMediaReady(null /*uri*/, null /*contentType*/, 0 /*width*/,
- 0 /*height*/);
- }
- }
-
- mMediaRecorder.closeVideoFileDescriptor();
- mMediaRecorder.release();
- mMediaRecorder = null;
-
- if (mCamera != null) {
- try {
- mCamera.reconnect();
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- restoreRequestedOrientation();
- }
-
- /** Updates the orientation of the camera to match the orientation of the device */
- private void updateCameraOrientation() {
- if (mCamera == null || mCameraPreview == null || mTakingPicture) {
- return;
- }
-
- final WindowManager windowManager =
- (WindowManager) mCameraPreview.getContext().getSystemService(
- Context.WINDOW_SERVICE);
-
- int degrees = 0;
- switch (windowManager.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_0: degrees = 0; break;
- case Surface.ROTATION_90: degrees = 90; break;
- case Surface.ROTATION_180: degrees = 180; break;
- case Surface.ROTATION_270: degrees = 270; break;
- }
-
- // The display orientation of the camera (this controls the preview image).
- int orientation;
-
- // The clockwise rotation angle relative to the orientation of the camera. This affects
- // pictures returned by the camera in Camera.PictureCallback.
- int rotation;
- if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
- orientation = (mCameraInfo.orientation + degrees) % 360;
- rotation = orientation;
- // compensate the mirror but only for orientation
- orientation = (360 - orientation) % 360;
- } else { // back-facing
- orientation = (mCameraInfo.orientation - degrees + 360) % 360;
- rotation = orientation;
- }
- mRotation = rotation;
- if (mMediaRecorder == null) {
- try {
- mCamera.setDisplayOrientation(orientation);
- final Camera.Parameters params = mCamera.getParameters();
- params.setRotation(rotation);
- mCamera.setParameters(params);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.updateCameraOrientation", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- }
-
- /** Sets the current camera, releasing any previously opened camera */
- private void setCamera(final Camera camera) {
- if (mCamera == camera) {
- return;
- }
-
- releaseMediaRecorder(true /* cleanupFile */);
- releaseCamera(mCamera);
- mCamera = camera;
- tryShowPreview();
- if (mListener != null) {
- mListener.onCameraChanged();
- }
- }
-
- /** Shows the preview if the camera is open and the preview is loaded */
- private void tryShowPreview() {
- if (mCameraPreview == null || mCamera == null) {
- if (mOrientationHandler != null) {
- mOrientationHandler.disable();
- mOrientationHandler = null;
- }
- releaseMediaRecorder(true /* cleanupFile */);
- mFocusOverlayManager.onPreviewStopped();
- return;
- }
- try {
- mCamera.stopPreview();
- updateCameraOrientation();
-
- final Camera.Parameters params = mCamera.getParameters();
- final Camera.Size pictureSize = chooseBestPictureSize();
- final Camera.Size previewSize = chooseBestPreviewSize(pictureSize);
- params.setPreviewSize(previewSize.width, previewSize.height);
- params.setPictureSize(pictureSize.width, pictureSize.height);
- logCameraSize("Setting preview size: ", previewSize);
- logCameraSize("Setting picture size: ", pictureSize);
- mCameraPreview.setSize(previewSize, mCameraInfo.orientation);
- for (final String focusMode : params.getSupportedFocusModes()) {
- if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
- // Use continuous focus if available
- params.setFocusMode(focusMode);
- break;
- }
- }
-
- mCamera.setParameters(params);
- mCameraPreview.startPreview(mCamera);
- mCamera.startPreview();
- mCamera.setAutoFocusMoveCallback((start, camera) ->
- mFocusOverlayManager.onAutoFocusMoving(start));
- mFocusOverlayManager.setParameters(mCamera.getParameters());
- mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK);
- mFocusOverlayManager.onPreviewStarted();
- tryInitOrCleanupVideoMode();
- if (mOrientationHandler == null) {
- mOrientationHandler = new OrientationHandler(mCameraPreview.getContext());
- mOrientationHandler.enable();
- }
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- }
- }
-
- private void tryInitOrCleanupVideoMode() {
- if (!mVideoModeRequested || mCamera == null || mCameraPreview == null) {
- releaseMediaRecorder(true /* cleanupFile */);
- return;
- }
-
- if (mMediaRecorder != null) {
- return;
- }
-
- try {
- mCamera.unlock();
- final int maxMessageSize = getMmsConfig().getMaxMessageSize();
- mMediaRecorder = new MmsVideoRecorder(mCamera, mCameraIndex, mRotation, maxMessageSize);
- mMediaRecorder.prepare();
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "FileNotFoundException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_STORAGE_FAILURE, e);
- }
- setVideoMode(false);
- return;
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- }
-
- tryStartVideoCapture();
- }
-
- private void tryStartVideoCapture() {
- if (mMediaRecorder == null || mVideoCallback == null) {
- return;
- }
-
- mMediaRecorder.setOnErrorListener((mediaRecorder, what, extra) -> {
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, null);
- }
- restoreRequestedOrientation();
- });
-
- mMediaRecorder.setOnInfoListener((mediaRecorder, what, extra) -> {
- if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
- what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
- stopVideo();
- }
- });
-
- try {
- mMediaRecorder.start();
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- lockOrientation();
- } catch (final IllegalStateException e) {
- LogUtil.e(TAG, "IllegalStateException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- }
- }
-
- void stopVideo() {
- int width = ImageRequest.UNSPECIFIED_SIZE;
- int height = ImageRequest.UNSPECIFIED_SIZE;
- Uri uri = null;
- String contentType = null;
- try {
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mMediaRecorder.stop();
- width = mMediaRecorder.getVideoWidth();
- height = mMediaRecorder.getVideoHeight();
- uri = mMediaRecorder.getVideoUri();
- contentType = mMediaRecorder.getContentType();
- } catch (final RuntimeException e) {
- // MediaRecorder.stop will throw a RuntimeException if the video was too short, let the
- // finally clause call the callback with null uri and handle cleanup
- LogUtil.e(TAG, "RuntimeException in CameraManager.stopVideo", e);
- } finally {
- final MediaCallback videoCallback = mVideoCallback;
- mVideoCallback = null;
- releaseMediaRecorder(false /* cleanupFile */);
- if (uri == null) {
- tryInitOrCleanupVideoMode();
- }
- videoCallback.onMediaReady(uri, contentType, width, height);
- }
- }
-
- boolean isCameraAvailable() {
- return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported;
- }
-
- /**
- * External components call into this to report if hardware acceleration is supported. When
- * hardware acceleration isn't supported, we need to report an error through the listener
- * interface
- * @param isHardwareAccelerationSupported True if the preview is rendering in a hardware
- * accelerated view.
- */
- void reportHardwareAccelerationSupported(final boolean isHardwareAccelerationSupported) {
- Assert.isMainThread();
- if (mIsHardwareAccelerationSupported == isHardwareAccelerationSupported) {
- // If the value hasn't changed nothing more to do
- return;
- }
-
- mIsHardwareAccelerationSupported = isHardwareAccelerationSupported;
- if (!isHardwareAccelerationSupported) {
- LogUtil.e(TAG, "Software rendering - cannot open camera");
- if (mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
- }
-
- /** Returns the scale factor to scale the width/height to max allowed in MmsConfig */
- private float getScaleFactorForMaxAllowedSize(final int width, final int height,
- final int maxWidth, final int maxHeight) {
- if (maxWidth <= 0 || maxHeight <= 0) {
- // MmsConfig initialization runs asynchronously on application startup, so there's a
- // chance (albeit a very slight one) that we don't have it yet.
- LogUtil.w(LogUtil.BUGLE_TAG, "Max image size not loaded in MmsConfig");
- return 1.0f;
- }
-
- if (width <= maxWidth && height <= maxHeight) {
- // Already meeting requirements.
- return 1.0f;
- }
-
- return Math.min(maxWidth * 1.0f / width, maxHeight * 1.0f / height);
- }
-
- private MmsConfig getMmsConfig() {
- final int subId = mSubscriptionDataProvider != null ?
- mSubscriptionDataProvider.getConversationSelfSubId() :
- ParticipantData.DEFAULT_SELF_SUB_ID;
- return MmsConfig.get(subId);
- }
-
- /**
- * Choose the best picture size by trying to find a size close to the MmsConfig's max size,
- * which is closest to the screen aspect ratio
- */
- private Camera.Size chooseBestPictureSize() {
- final Context context = mCameraPreview.getContext();
- final Resources resources = context.getResources();
- final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- final int displayOrientation = resources.getConfiguration().orientation;
- int cameraOrientation = mCameraInfo.orientation;
-
- int screenWidth;
- int screenHeight;
- if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
- // Rotate the camera orientation 90 degrees to compensate for the rotated display
- // metrics. Direction doesn't matter because we're just using it for width/height
- cameraOrientation += 90;
- }
-
- // Check the camera orientation relative to the display.
- // For 0, 180, 360, the screen width/height are the display width/height
- // For 90, 270, the screen width/height are inverted from the display
- if (cameraOrientation % 180 == 0) {
- screenWidth = displayMetrics.widthPixels;
- screenHeight = displayMetrics.heightPixels;
- } else {
- screenWidth = displayMetrics.heightPixels;
- screenHeight = displayMetrics.widthPixels;
- }
-
- final MmsConfig mmsConfig = getMmsConfig();
- final int maxWidth = mmsConfig.getMaxImageWidth();
- final int maxHeight = mmsConfig.getMaxImageHeight();
-
- // Constrain the size within the max width/height defined by MmsConfig.
- final float scaleFactor = getScaleFactorForMaxAllowedSize(screenWidth, screenHeight,
- maxWidth, maxHeight);
- screenWidth *= scaleFactor;
- screenHeight *= scaleFactor;
-
- final float aspectRatio = screenWidth / (float) screenHeight;
- final List sizes = new ArrayList<>(
- mCamera.getParameters().getSupportedPictureSizes());
- final int maxPixels = maxWidth * maxHeight;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(maxWidth, maxHeight, aspectRatio, maxPixels));
-
- return sizes.get(0);
- }
-
- /**
- * Chose the best preview size based on the picture size. Try to find a size with the same
- * aspect ratio and size as the picture if possible
- */
- private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) {
- final List sizes = new ArrayList<>(
- mCamera.getParameters().getSupportedPreviewSizes());
- final float aspectRatio = pictureSize.width / (float) pictureSize.height;
- final int capturePixels = pictureSize.width * pictureSize.height;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE,
- aspectRatio, capturePixels));
-
- return sizes.get(0);
- }
-
- private class OrientationHandler extends OrientationEventListener {
- OrientationHandler(final Context context) {
- super(context);
- }
-
- @Override
- public void onOrientationChanged(final int orientation) {
- updateCameraOrientation();
- }
- }
-
- private static class SizeComparator implements Comparator {
- private static final int PREFER_LEFT = -1;
- private static final int PREFER_RIGHT = 1;
-
- // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit
- private final int mMaxWidth;
- private final int mMaxHeight;
-
- // The desired aspect ratio
- private final float mTargetAspectRatio;
-
- // The desired size (width x height) to try to match
- private final int mTargetPixels;
-
- public SizeComparator(final int maxWidth, final int maxHeight,
- final float targetAspectRatio, final int targetPixels) {
- mMaxWidth = maxWidth;
- mMaxHeight = maxHeight;
- mTargetAspectRatio = targetAspectRatio;
- mTargetPixels = targetPixels;
- }
-
- /**
- * Returns a negative value if left is a better choice than right, or a positive value if
- * right is a better choice is better than left. 0 if they are equal
- */
- @Override
- public int compare(final Camera.Size left, final Camera.Size right) {
- // If one size is less than the max size prefer it over the other
- if ((left.width <= mMaxWidth && left.height <= mMaxHeight) !=
- (right.width <= mMaxWidth && right.height <= mMaxHeight)) {
- return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT;
- }
-
- // If one is closer to the target aspect ratio, prefer it.
- final float leftAspectRatio = left.width / (float) left.height;
- final float rightAspectRatio = right.width / (float) right.height;
- final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio);
- final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio);
- if (leftAspectRatioDiff != rightAspectRatioDiff) {
- return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ?
- PREFER_LEFT : PREFER_RIGHT;
- }
-
- // At this point they have the same aspect ratio diff and are either both bigger
- // than the max size or both smaller than the max size, so prefer the one closest
- // to target size
- final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels);
- final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels);
- return leftDiff - rightDiff;
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void autoFocus() {
- if (mCamera == null) {
- return;
- }
-
- try {
- mCamera.autoFocus((success, camera) -> mFocusOverlayManager.onAutoFocus(success,
- false /* shutterDown */));
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.autoFocus", e);
- // If autofocus fails, the camera should have called the callback with success=false,
- // but some throw an exception here
- mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void cancelAutoFocus() {
- if (mCamera == null) {
- return;
- }
- try {
- mCamera.cancelAutoFocus();
- } catch (final RuntimeException e) {
- // Ignore
- LogUtil.e(TAG, "RuntimeException in CameraManager.cancelAutoFocus", e);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public boolean capture() {
- return false;
- }
-
- @Override // From FocusOverlayManager.Listener
- public void setFocusParameters() {
- if (mCamera == null) {
- return;
- }
- try {
- final Camera.Parameters parameters = mCamera.getParameters();
- parameters.setFocusMode(mFocusOverlayManager.getFocusMode());
- if (parameters.getMaxNumFocusAreas() > 0) {
- // Don't set focus areas (even to null) if focus areas aren't supported, camera may
- // crash
- parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas());
- }
- parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas());
- mCamera.setParameters(parameters);
- } catch (final RuntimeException e) {
- // This occurs when the device is out of space or when the camera is locked
- LogUtil.e(TAG, "RuntimeException in CameraManager setFocusParameters");
- }
- }
-
- private void logCameraSize(final String prefix, final Camera.Size size) {
- // Log the camera size and aspect ratio for help when examining bug reports for camera
- // failures
- LogUtil.i(TAG, prefix + size.width + "x" + size.height +
- " (" + (size.width / (float) size.height) + ")");
- }
-
-
- private Integer mSavedOrientation = null;
-
- private void lockOrientation() {
- // when we start recording, lock our orientation
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- final WindowManager windowManager =
- (WindowManager) a.getSystemService(Context.WINDOW_SERVICE);
- final int rotation = windowManager.getDefaultDisplay().getRotation();
-
- mSavedOrientation = a.getRequestedOrientation();
- switch (rotation) {
- case Surface.ROTATION_0:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- break;
- case Surface.ROTATION_90:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- break;
- case Surface.ROTATION_180:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
- break;
- case Surface.ROTATION_270:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- break;
- }
-
- }
-
- private void restoreRequestedOrientation() {
- if (mSavedOrientation != null) {
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- if (a != null) {
- a.setRequestedOrientation(mSavedOrientation);
- }
- mSavedOrientation = null;
- }
- }
-
- static boolean hasCameraPermission() {
- return OsUtil.hasPermission(Manifest.permission.CAMERA);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
index 9a21bc1..fde1f11 100644
--- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
+++ b/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
@@ -17,55 +17,82 @@
package com.android.messaging.ui.mediapicker;
+import static com.android.messaging.util.ContentType.VIDEO_UNSPECIFIED;
+
import android.Manifest;
-import android.content.Context;
+import android.app.Activity;
import android.content.pm.PackageManager;
import android.graphics.Rect;
-import android.hardware.Camera;
import android.net.Uri;
-import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.widget.Chronometer;
-import android.widget.ImageButton;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
import com.android.messaging.R;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.MessagingContentProvider;
import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.ui.mediapicker.CameraManager.MediaCallback;
-import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Chooser which allows the user to take pictures or video without leaving the current app/activity
*/
-class CameraMediaChooser extends MediaChooser implements
- CameraManager.CameraManagerListener {
- private CameraPreview.CameraPreviewHost mCameraPreviewHost;
- private ImageButton mFullScreenButton;
- private ImageButton mSwapCameraButton;
- private ImageButton mSwapModeButton;
- private ImageButton mCaptureButton;
- private ImageButton mCancelVideoButton;
- private Chronometer mVideoCounter;
- private boolean mVideoCancelled;
- private int mErrorToast;
+class CameraMediaChooser extends MediaChooser {
private View mEnabledView;
private View mMissingPermissionView;
+ private final ActivityResultLauncher mPictureLauncher;
+ private final ActivityResultLauncher mVideoLauncher;
+ private final DocumentImagePicker mDocumentImagePicker;
+ private Uri mOutputUri;
CameraMediaChooser(final MediaPicker mediaPicker) {
super(mediaPicker);
+ mDocumentImagePicker = new DocumentImagePicker(mMediaPicker, data -> {
+ if (mBindingRef.isBound()) {
+ mMediaPicker.dispatchPendingItemAdded(data);
+ }
+ });
+ mPictureLauncher = mediaPicker.registerForActivityResult(
+ new ActivityResultContracts.TakePicture(), result -> {
+ if (result) {
+ List list = new ArrayList<>();
+ list.add(mOutputUri);
+ mDocumentImagePicker.onDocumentsPicked(list);
+ }
+ });
+ mVideoLauncher = mediaPicker.registerForActivityResult(
+ new ActivityResultContracts.CaptureVideo(), result -> {
+ if (result) {
+ final Rect startRect = new Rect();
+ // It's possible to throw out the chooser while taking the
+ // picture/video. In that case, still use the attachment, just
+ // skip the startRect
+ if (mView != null) {
+ mView.getGlobalVisibleRect(startRect);
+ }
+ mMediaPicker.dispatchItemsSelected(
+ new MediaPickerMessagePartData(startRect, VIDEO_UNSPECIFIED,
+ mOutputUri, MessagingContentProvider.UNSPECIFIED_SIZE,
+ MessagingContentProvider.UNSPECIFIED_SIZE),
+ true /* dismissMediaPicker */);
+ }
+ });
}
@Override
public int getSupportedMediaTypes() {
- if (CameraManager.get().hasAnyCamera()) {
+ Activity activity = mMediaPicker.getActivity();
+ if (activity == null) {
+ return MediaPicker.MEDIA_TYPE_NONE;
+ }
+ PackageManager pm = activity.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
} else {
return MediaPicker.MEDIA_TYPE_NONE;
@@ -74,129 +101,33 @@ class CameraMediaChooser extends MediaChooser implements
@Override
public View destroyView() {
- CameraManager.get().closeCamera();
- CameraManager.get().setListener(null);
- CameraManager.get().setSubscriptionDataProvider(null);
return super.destroyView();
}
@Override
protected View createView(final ViewGroup container) {
- CameraManager.get().setListener(this);
- CameraManager.get().setSubscriptionDataProvider(this);
- CameraManager.get().setVideoMode(false);
final LayoutInflater inflater = getLayoutInflater();
- final CameraMediaChooserView view = (CameraMediaChooserView) inflater.inflate(
+ final View view = inflater.inflate(
R.layout.mediapicker_camera_chooser,
container /* root */,
false /* attachToRoot */);
- mCameraPreviewHost = (CameraPreview.CameraPreviewHost) view.findViewById(
- R.id.camera_preview);
- mCameraPreviewHost.getView().setOnTouchListener((view1, motionEvent) -> {
- if (CameraManager.get().isVideoMode()) {
- // Prevent the swipe down in video mode because video is always captured in
- // full screen
- return true;
- }
-
- return false;
- });
-
- final View shutterVisual = view.findViewById(R.id.camera_shutter_visual);
-
- mFullScreenButton = (ImageButton) view.findViewById(R.id.camera_fullScreen_button);
- mFullScreenButton.setOnClickListener(view12 -> mMediaPicker.setFullScreen(true));
-
- mSwapCameraButton = (ImageButton) view.findViewById(R.id.camera_swapCamera_button);
- mSwapCameraButton.setOnClickListener(view13 -> CameraManager.get().swapCamera());
-
- mCaptureButton = (ImageButton) view.findViewById(R.id.camera_capture_button);
- mCaptureButton.setOnClickListener(v -> {
- final float heightPercent = Math.min(mMediaPicker.getViewPager().getHeight() /
- (float) mCameraPreviewHost.getView().getHeight(), 1);
-
- if (CameraManager.get().isRecording()) {
- CameraManager.get().stopVideo();
- } else {
- final MediaCallback callback = new MediaCallback() {
- @Override
- public void onMediaReady(
- final Uri uriToVideo, final String contentType,
- final int width, final int height) {
- mVideoCounter.stop();
- if (mVideoCancelled || uriToVideo == null) {
- mVideoCancelled = false;
- } else {
- final Rect startRect = new Rect();
- // It's possible to throw out the chooser while taking the
- // picture/video. In that case, still use the attachment, just
- // skip the startRect
- if (mView != null) {
- mView.getGlobalVisibleRect(startRect);
- }
- mMediaPicker.dispatchItemsSelected(
- new MediaPickerMessagePartData(startRect, contentType,
- uriToVideo, width, height),
- true /* dismissMediaPicker */);
- }
- updateViewState();
- }
-
- @Override
- public void onMediaFailed(final Exception exception) {
- UiUtils.showToastAtBottom(R.string.camera_media_failure);
- updateViewState();
- }
-
- @Override
- public void onMediaInfo(final int what) {
- if (what == MediaCallback.MEDIA_NO_DATA) {
- UiUtils.showToastAtBottom(R.string.camera_media_failure);
- }
- updateViewState();
- }
- };
- if (CameraManager.get().isVideoMode()) {
- CameraManager.get().startVideo(callback);
- mVideoCounter.setBase(SystemClock.elapsedRealtime());
- mVideoCounter.start();
- updateViewState();
- } else {
- showShutterEffect(shutterVisual);
- CameraManager.get().takePicture(heightPercent, callback);
- updateViewState();
- }
- }
- });
-
- mSwapModeButton = (ImageButton) view.findViewById(R.id.camera_swap_mode_button);
- mSwapModeButton.setOnClickListener(view14 -> {
- final boolean isSwitchingToVideo = !CameraManager.get().isVideoMode();
- if (isSwitchingToVideo && !OsUtil.hasRecordAudioPermission()) {
- requestRecordAudioPermission();
- } else {
- onSwapMode();
- }
- });
-
- mCancelVideoButton = (ImageButton) view.findViewById(R.id.camera_cancel_button);
- mCancelVideoButton.setOnClickListener(view15 -> {
- mVideoCancelled = true;
- CameraManager.get().stopVideo();
- mMediaPicker.dismiss(true);
- });
-
- mVideoCounter = (Chronometer) view.findViewById(R.id.camera_video_counter);
-
- CameraManager.get().setRenderOverlay((RenderOverlay) view.findViewById(R.id.focus_visual));
-
mEnabledView = view.findViewById(R.id.mediapicker_enabled);
mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
-
- // Must set mView before calling updateViewState because it operates on mView
- mView = view;
- updateViewState();
- updateForPermissionState(CameraManager.hasCameraPermission());
+ View takePictureView = view.findViewById(R.id.take_picture);
+ takePictureView.setOnClickListener(v -> {
+ mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
+ mPictureLauncher.launch(mOutputUri);
+ });
+ View takeVideoView = view.findViewById(R.id.take_video);
+ takeVideoView.setOnClickListener(v -> {
+ if (!OsUtil.hasRecordAudioPermission()) {
+ requestRecordAudioPermission();
+ } else {
+ mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
+ mVideoLauncher.launch(mOutputUri);
+ }
+ });
+ updateForPermissionState(hasCameraPermission());
return view;
}
@@ -210,36 +141,11 @@ class CameraMediaChooser extends MediaChooser implements
return R.string.mediapicker_cameraChooserDescription;
}
- /**
- * Updates the view when entering or leaving full-screen camera mode
- */
- @Override
- void onFullScreenChanged(final boolean fullScreen) {
- super.onFullScreenChanged(fullScreen);
- if (!fullScreen && CameraManager.get().isVideoMode()) {
- CameraManager.get().setVideoMode(false);
- }
- updateViewState();
- }
-
- /**
- * Initializes the control to a default state when it is opened / closed
- * @param open True if the control is opened
- */
- @Override
- void onOpenedChanged(final boolean open) {
- super.onOpenedChanged(open);
- updateViewState();
- }
-
@Override
protected void setSelected(final boolean selected) {
super.setSelected(selected);
if (selected) {
- if (CameraManager.hasCameraPermission()) {
- // If an error occurred before the chooser was selected, show it now
- showErrorToastIfNeeded();
- } else {
+ if (!hasCameraPermission()) {
requestCameraPermission();
}
}
@@ -261,17 +167,6 @@ class CameraMediaChooser extends MediaChooser implements
if (requestCode == MediaPicker.CAMERA_PERMISSION_REQUEST_CODE) {
final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
updateForPermissionState(permissionGranted);
- if (permissionGranted) {
- mCameraPreviewHost.onCameraPermissionGranted();
- }
- } else if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
- Assert.isFalse(CameraManager.get().isVideoMode());
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // Switch to video mode
- onSwapMode();
- } else {
- // Stay in still-photo mode
- }
}
}
@@ -285,174 +180,12 @@ class CameraMediaChooser extends MediaChooser implements
mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
}
- @Override
- public boolean canSwipeDown() {
- if (CameraManager.get().isVideoMode()) {
- return true;
- }
- return super.canSwipeDown();
- }
-
- /**
- * Handles an error from the camera manager by showing the appropriate error message to the user
- * @param errorCode One of the CameraManager.ERROR_* constants
- * @param e The exception which caused the error, if any
- */
- @Override
- public void onCameraError(final int errorCode, final Exception e) {
- switch (errorCode) {
- case CameraManager.ERROR_OPENING_CAMERA:
- case CameraManager.ERROR_SHOWING_PREVIEW:
- mErrorToast = R.string.camera_error_opening;
- break;
- case CameraManager.ERROR_INITIALIZING_VIDEO:
- mErrorToast = R.string.camera_error_video_init_fail;
- updateViewState();
- break;
- case CameraManager.ERROR_STORAGE_FAILURE:
- mErrorToast = R.string.camera_error_storage_fail;
- updateViewState();
- break;
- case CameraManager.ERROR_TAKING_PICTURE:
- mErrorToast = R.string.camera_error_failure_taking_picture;
- break;
- default:
- mErrorToast = R.string.camera_error_unknown;
- LogUtil.w(LogUtil.BUGLE_TAG, "Unknown camera error:" + errorCode);
- break;
- }
- showErrorToastIfNeeded();
- }
-
- private void showErrorToastIfNeeded() {
- if (mErrorToast != 0 && mSelected) {
- UiUtils.showToastAtBottom(mErrorToast);
- mErrorToast = 0;
- }
- }
-
- @Override
- public void onCameraChanged() {
- updateViewState();
- }
-
- private void onSwapMode() {
- CameraManager.get().setVideoMode(!CameraManager.get().isVideoMode());
- if (CameraManager.get().isVideoMode()) {
- mMediaPicker.setFullScreen(true);
-
- // For now we start recording immediately
- mCaptureButton.performClick();
- }
- updateViewState();
- }
-
- private void showShutterEffect(final View shutterVisual) {
- final float maxAlpha = getContext().getResources().getFraction(
- R.fraction.camera_shutter_max_alpha, 1 /* base */, 1 /* pBase */);
-
- // Divide by 2 so each half of the animation adds up to the full duration
- final int animationDuration = getContext().getResources().getInteger(
- R.integer.camera_shutter_duration) / 2;
-
- final AnimationSet animation = new AnimationSet(false /* shareInterpolator */);
- final Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha);
- alphaInAnimation.setDuration(animationDuration);
- animation.addAnimation(alphaInAnimation);
-
- final Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f);
- alphaOutAnimation.setStartOffset(animationDuration);
- alphaOutAnimation.setDuration(animationDuration);
- animation.addAnimation(alphaOutAnimation);
-
- animation.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(final Animation animation) {
- shutterVisual.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(final Animation animation) {
- shutterVisual.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationRepeat(final Animation animation) {
- }
- });
- shutterVisual.startAnimation(animation);
- }
-
- /** Updates the state of the buttons and overlays based on the current state of the view */
- private void updateViewState() {
- if (mView == null) {
- return;
- }
-
- final Context context = getContext();
- if (context == null) {
- // Context is null if the fragment was already removed from the activity
- return;
- }
- final boolean fullScreen = mMediaPicker.isFullScreen();
- final boolean videoMode = CameraManager.get().isVideoMode();
- final boolean isRecording = CameraManager.get().isRecording();
- final boolean isCameraAvailable = isCameraAvailable();
- final Camera.CameraInfo cameraInfo = CameraManager.get().getCameraInfo();
- final boolean frontCamera = cameraInfo != null && cameraInfo.facing ==
- Camera.CameraInfo.CAMERA_FACING_FRONT;
-
- mView.setSystemUiVisibility(
- fullScreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE :
- View.SYSTEM_UI_FLAG_VISIBLE);
-
- mFullScreenButton.setVisibility(!fullScreen ? View.VISIBLE : View.GONE);
- mFullScreenButton.setEnabled(isCameraAvailable);
- mSwapCameraButton.setVisibility(
- fullScreen && !isRecording && CameraManager.get().hasFrontAndBackCamera() ?
- View.VISIBLE : View.GONE);
- mSwapCameraButton.setImageResource(frontCamera ?
- R.drawable.ic_camera_front_light :
- R.drawable.ic_camera_rear_light);
- mSwapCameraButton.setEnabled(isCameraAvailable);
-
- mCancelVideoButton.setVisibility(isRecording ? View.VISIBLE : View.GONE);
- mVideoCounter.setVisibility(isRecording ? View.VISIBLE : View.GONE);
-
- mSwapModeButton.setImageResource(videoMode ?
- R.drawable.ic_camera_light :
- R.drawable.ic_mp_video_small_light);
- mSwapModeButton.setContentDescription(context.getString(videoMode ?
- R.string.camera_switch_to_still_mode : R.string.camera_switch_to_video_mode));
- mSwapModeButton.setVisibility(isRecording ? View.GONE : View.VISIBLE);
- mSwapModeButton.setEnabled(isCameraAvailable);
-
- if (isRecording) {
- mCaptureButton.setImageResource(R.drawable.ic_mp_capture_stop_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_stop_recording));
- } else if (videoMode) {
- mCaptureButton.setImageResource(R.drawable.ic_mp_video_small_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_start_recording));
- } else {
- mCaptureButton.setImageResource(R.drawable.ic_checkmark_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_take_picture));
- }
- mCaptureButton.setEnabled(isCameraAvailable);
- }
-
@Override
int getActionBarTitleResId() {
return 0;
}
- /**
- * Returns if the camera is currently ready camera is loaded and not taking a picture.
- * otherwise we should avoid taking another picture, swapping camera or recording video.
- */
- private boolean isCameraAvailable() {
- return CameraManager.get().isCameraAvailable();
+ private static boolean hasCameraPermission() {
+ return OsUtil.hasPermission(Manifest.permission.CAMERA);
}
}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
deleted file mode 100644
index 6cb2e37..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * Copyright (C) 2024 The LineageOS Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.hardware.Camera;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.PersistentInstanceState;
-import com.android.messaging.util.ThreadUtil;
-
-public class CameraMediaChooserView extends FrameLayout implements PersistentInstanceState {
- private static final String KEY_CAMERA_INDEX = "camera_index";
-
- // True if we have at least queued an update to the view tree to support software rendering
- // fallback
- private boolean mIsSoftwareFallbackActive;
-
- public CameraMediaChooserView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- final Bundle bundle = new Bundle();
- bundle.putInt(KEY_CAMERA_INDEX, CameraManager.get().getCameraIndex());
- return bundle;
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof Bundle)) {
- return;
- }
-
- final Bundle bundle = (Bundle) state;
- CameraManager.get().selectCameraByIndex(bundle.getInt(KEY_CAMERA_INDEX));
- }
-
- @Override
- public Parcelable saveState() {
- return onSaveInstanceState();
- }
-
- @Override
- public void restoreState(final Parcelable restoredState) {
- onRestoreInstanceState(restoredState);
- }
-
- @Override
- public void resetState() {
- CameraManager.get().selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
- }
-
- @Override
- protected void onDraw(@NonNull final Canvas canvas) {
- super.onDraw(canvas);
- // If the canvas isn't hardware accelerated, we have to replace the HardwareCameraPreview
- // with a SoftwareCameraPreview which supports software rendering
- if (!canvas.isHardwareAccelerated() && !mIsSoftwareFallbackActive) {
- mIsSoftwareFallbackActive = true;
- // Post modifying the tree since we can't modify the view tree during a draw pass
- ThreadUtil.getMainThreadHandler().post(() -> {
- final HardwareCameraPreview cameraPreview =
- (HardwareCameraPreview) findViewById(R.id.camera_preview);
- if (cameraPreview == null) {
- return;
- }
- final ViewGroup parent = ((ViewGroup) cameraPreview.getParent());
- final int index = parent.indexOfChild(cameraPreview);
- final SoftwareCameraPreview softwareCameraPreview =
- new SoftwareCameraPreview(getContext());
- // Be sure to remove the hardware view before adding the software view to
- // prevent having 2 camera previews active at the same time
- parent.removeView(cameraPreview);
- parent.addView(softwareCameraPreview, index);
- });
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraPreview.java b/src/com/android/messaging/ui/mediapicker/CameraPreview.java
deleted file mode 100644
index ecac978..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraPreview.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.hardware.Camera;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import com.android.messaging.util.Assert;
-
-import java.io.IOException;
-
-/**
- * Contains shared code for SoftwareCameraPreview and HardwareCameraPreview, cannot use inheritance
- * because those classes must inherit from separate Views, so those classes delegate calls to this
- * helper class. Specifics for each implementation are in CameraPreviewHost
- */
-public class CameraPreview {
- public interface CameraPreviewHost {
- View getView();
- boolean isValid();
- void startPreview(final Camera camera) throws IOException;
- void onCameraPermissionGranted();
-
- }
-
- private int mCameraWidth = -1;
- private int mCameraHeight = -1;
-
- private final CameraPreviewHost mHost;
-
- public CameraPreview(final CameraPreviewHost host) {
- Assert.notNull(host);
- Assert.notNull(host.getView());
- mHost = host;
- }
-
- public void setSize(final Camera.Size size, final int orientation) {
- switch (orientation) {
- case 0:
- case 180:
- mCameraWidth = size.width;
- mCameraHeight = size.height;
- break;
- case 90:
- case 270:
- default:
- mCameraWidth = size.height;
- mCameraHeight = size.width;
- }
- mHost.getView().requestLayout();
- }
-
- public int getWidthMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mCameraHeight >= 0) {
- final int width = View.MeasureSpec.getSize(widthMeasureSpec);
- return MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
- } else {
- return widthMeasureSpec;
- }
- }
-
- public int getHeightMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mCameraHeight >= 0) {
- final int orientation = getContext().getResources().getConfiguration().orientation;
- final int width = View.MeasureSpec.getSize(widthMeasureSpec);
- final float aspectRatio = (float) mCameraWidth / (float) mCameraHeight;
- int height;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- height = (int) (width * aspectRatio);
- } else {
- height = (int) (width / aspectRatio);
- }
- return View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
- } else {
- return heightMeasureSpec;
- }
- }
-
- public void onVisibilityChanged(final int visibility) {
- if (CameraManager.hasCameraPermission()) {
- if (visibility == View.VISIBLE) {
- CameraManager.get().openCamera();
- } else {
- CameraManager.get().closeCamera();
- }
- }
- }
-
- public Context getContext() {
- return mHost.getView().getContext();
- }
-
- public void setOnTouchListener(final View.OnTouchListener listener) {
- mHost.getView().setOnTouchListener(listener);
- }
-
- public int getHeight() {
- return mHost.getView().getHeight();
- }
-
- public void onAttachedToWindow() {
- if (CameraManager.hasCameraPermission()) {
- CameraManager.get().openCamera();
- }
- }
-
- public void onDetachedFromWindow() {
- CameraManager.get().closeCamera();
- }
-
- public void onRestoreInstanceState() {
- if (CameraManager.hasCameraPermission()) {
- CameraManager.get().openCamera();
- }
- }
-
- public void onCameraPermissionGranted() {
- CameraManager.get().openCamera();
- }
-
- /**
- * @return True if the view is valid and prepared for the camera to start showing the preview
- */
- public boolean isValid() {
- return mHost.isValid();
- }
-
- /**
- * Starts the camera preview on the current surface. Abstracts out the differences in API
- * from the CameraManager
- * @throws IOException Which is caught by the CameraManager to display an error
- */
- public void startPreview(final Camera camera) throws IOException {
- mHost.startPreview(camera);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
index ccfb090..3d3a7ce 100644
--- a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
+++ b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
@@ -26,6 +26,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.fragment.app.Fragment;
import com.android.messaging.Factory;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
import com.android.messaging.datamodel.data.PendingAttachmentData;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.util.BugleGservicesKeys;
@@ -110,7 +111,8 @@ public class DocumentImagePicker {
new SafeAsyncTask() {
@Override
protected String doInBackgroundTimed(final Void... params) {
- if (FileUtil.isInPrivateDir(documentUri)) {
+ if (FileUtil.isInPrivateDir(documentUri) &&
+ !MediaScratchFileProvider.isMediaScratchSpaceUri(documentUri)) {
// hacker sending private app data. Bail out
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.ERROR)) {
LogUtil.e(LogUtil.BUGLE_TAG, "Aborting attach of private app data ("
diff --git a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
deleted file mode 100644
index 3db624e..0000000
--- a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * Copyright (C) 2024 The LineageOS Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.view.TextureView;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import java.io.IOException;
-
-/**
- * A hardware accelerated preview texture for the camera. This is the preferred CameraPreview
- * because it animates smoother. When hardware acceleration isn't available, SoftwareCameraPreview
- * is used.
- *
- * There is a significant amount of duplication between HardwareCameraPreview and
- * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
- * implementations of the shared methods are delegated to CameraPreview
- */
-public class HardwareCameraPreview extends TextureView implements CameraPreview.CameraPreviewHost {
- private final CameraPreview mPreview;
-
- public HardwareCameraPreview(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mPreview = new CameraPreview(this);
- setSurfaceTextureListener(new SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(@NonNull final SurfaceTexture surfaceTexture,
- final int i, final int i2) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(@NonNull final SurfaceTexture surfaceTexture,
- final int i, final int i2) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(@NonNull final SurfaceTexture surfaceTexture) {
- CameraManager.get().setSurface(null);
- return true;
- }
-
- @Override
- public void onSurfaceTextureUpdated(@NonNull final SurfaceTexture surfaceTexture) {
- CameraManager.get().setSurface(mPreview);
- }
- });
- }
-
- @Override
- protected void onVisibilityChanged(final View changedView, final int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- mPreview.onVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mPreview.onDetachedFromWindow();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mPreview.onAttachedToWindow();
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- super.onRestoreInstanceState(state);
- mPreview.onRestoreInstanceState();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public View getView() {
- return this;
- }
-
- @Override
- public boolean isValid() {
- return getSurfaceTexture() != null;
- }
-
- @Override
- public void startPreview(final Camera camera) throws IOException {
- camera.setPreviewTexture(getSurfaceTexture());
- }
-
- @Override
- public void onCameraPermissionGranted() {
- mPreview.onCameraPermissionGranted();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java b/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
deleted file mode 100644
index 8478385..0000000
--- a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * Copyright (C) 2024 The LineageOS Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.net.Uri;
-
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.exif.ExifInterface;
-import com.android.messaging.util.exif.ExifTag;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class ImagePersistTask extends SafeAsyncTask {
- private static final String JPEG_EXTENSION = "jpg";
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private int mWidth;
- private int mHeight;
- private final float mHeightPercent;
- private final byte[] mBytes;
- private final Context mContext;
- private final CameraManager.MediaCallback mCallback;
- private Uri mOutputUri;
- private Exception mException;
-
- public ImagePersistTask(
- final int width,
- final int height,
- final float heightPercent,
- final byte[] bytes,
- final Context context,
- final CameraManager.MediaCallback callback) {
- Assert.isTrue(heightPercent >= 0 && heightPercent <= 1);
- Assert.notNull(bytes);
- Assert.notNull(context);
- Assert.notNull(callback);
- mWidth = width;
- mHeight = height;
- mHeightPercent = heightPercent;
- mBytes = bytes;
- mContext = context;
- mCallback = callback;
- // TODO: We probably want to store directly in MMS storage to prevent this
- // intermediate step
- mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(JPEG_EXTENSION);
- }
-
- @Override
- protected Void doInBackgroundTimed(final Void... params) {
- OutputStream outputStream = null;
- Bitmap bitmap = null;
- Bitmap clippedBitmap = null;
- try {
- outputStream =
- mContext.getContentResolver().openOutputStream(mOutputUri);
- if (mHeightPercent != 1.0f) {
- int orientation = androidx.exifinterface.media.ExifInterface.ORIENTATION_UNDEFINED;
- final ExifInterface exifInterface = new ExifInterface();
- try {
- exifInterface.readExif(mBytes);
- final Integer orientationValue =
- exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION);
- if (orientationValue != null) {
- orientation = orientationValue.intValue();
- }
- // The thumbnail is of the full image, but we're cropping it, so just clear
- // the thumbnail
- exifInterface.setCompressedThumbnail((byte[]) null);
- } catch (IOException e) {
- // Couldn't get exif tags, not the end of the world
- }
- bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length);
- final int clippedWidth;
- final int clippedHeight;
- if (ExifInterface.getOrientationParams(orientation).invertDimensions) {
- Assert.equals(mWidth, bitmap.getHeight());
- Assert.equals(mHeight, bitmap.getWidth());
- clippedWidth = (int) (mHeight * mHeightPercent);
- clippedHeight = mWidth;
- } else {
- Assert.equals(mWidth, bitmap.getWidth());
- Assert.equals(mHeight, bitmap.getHeight());
- clippedWidth = mWidth;
- clippedHeight = (int) (mHeight * mHeightPercent);
- }
- final int offsetTop = (bitmap.getHeight() - clippedHeight) / 2;
- final int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2;
- mWidth = clippedWidth;
- mHeight = clippedHeight;
- clippedBitmap = Bitmap.createBitmap(clippedWidth, clippedHeight,
- Bitmap.Config.ARGB_8888);
- clippedBitmap.setDensity(bitmap.getDensity());
- final Canvas clippedBitmapCanvas = new Canvas(clippedBitmap);
- final Matrix matrix = new Matrix();
- matrix.postTranslate(-offsetLeft, -offsetTop);
- clippedBitmapCanvas.drawBitmap(bitmap, matrix, null /* paint */);
- clippedBitmapCanvas.save();
- // EXIF data can take a big chunk of the file size and is often cleared by the
- // carrier, only store orientation since that's critical
- ExifTag orientationTag = exifInterface.getTag(ExifInterface.TAG_ORIENTATION);
- exifInterface.clearExif();
- exifInterface.setTag(orientationTag);
- exifInterface.writeExif(clippedBitmap, outputStream);
- } else {
- outputStream.write(mBytes);
- }
- } catch (final IOException e) {
- mOutputUri = null;
- mException = e;
- LogUtil.e(TAG, "Unable to persist image to temp storage " + e);
- } finally {
- if (bitmap != null) {
- bitmap.recycle();
- }
-
- if (clippedBitmap != null) {
- clippedBitmap.recycle();
- }
-
- if (outputStream != null) {
- try {
- outputStream.flush();
- } catch (final IOException e) {
- mOutputUri = null;
- mException = e;
- LogUtil.e(TAG, "error trying to flush and close the outputStream" + e);
- } finally {
- try {
- outputStream.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- }
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(final Void aVoid) {
- if (mOutputUri != null) {
- mCallback.onMediaReady(mOutputUri, ContentType.IMAGE_JPEG, mWidth, mHeight);
- } else {
- Assert.notNull(mException);
- mCallback.onMediaFailed(mException);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
index 970dae3..97948b7 100644
--- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java
+++ b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
@@ -30,6 +30,7 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.PagerAdapter;
@@ -179,7 +180,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
};
mOpen = false;
- setSupportedMediaTypes(MEDIA_TYPE_ALL);
}
private boolean mIsAttached;
@@ -190,6 +190,7 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
mIsAttached = true;
+ setSupportedMediaTypes(MEDIA_TYPE_ALL);
if (mStartingMediaTypeOnAttach != MEDA_TYPE_INVALID) {
// open() was previously called. Do the pending open now.
doOpen(mStartingMediaTypeOnAttach, mAnimateOnAttach);
@@ -215,11 +216,9 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
mTabStrip.setBackgroundColor(mThemeColor);
for (final MediaChooser chooser : mChoosers) {
chooser.onCreateTabButton(inflater, mTabStrip);
- final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
- MEDIA_TYPE_NONE;
final ImageButton tabButton = chooser.getTabButton();
if (tabButton != null) {
- tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
+ tabButton.setVisibility(View.GONE); // we make them visible in onViewCreated
mTabStrip.addView(tabButton);
}
}
@@ -260,10 +259,23 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
return mMediaPickerPanel;
}
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ for (final MediaChooser chooser : mChoosers) {
+ final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
+ MEDIA_TYPE_NONE;
+ final ImageButton tabButton = chooser.getTabButton();
+ if (tabButton != null) {
+ tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
+ }
+ }
+ }
+
@Override
public void onPause() {
super.onPause();
- CameraManager.get().onPause();
for (final MediaChooser chooser : mEnabledChoosers) {
chooser.onPause();
}
@@ -272,7 +284,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
@Override
public void onResume() {
super.onResume();
- CameraManager.get().onResume();
for (final MediaChooser chooser : mEnabledChoosers) {
chooser.onResume();
diff --git a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
deleted file mode 100644
index 672d451..0000000
--- a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * Copyright (C) 2024 The LineageOS Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker;
-
-import android.content.Context;
-import android.hardware.Camera;
-import android.os.Parcelable;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import java.io.IOException;
-
-/**
- * A software rendered preview surface for the camera. This renders slower and causes more jank, so
- * HardwareCameraPreview is preferred if possible.
- *
- * There is a significant amount of duplication between HardwareCameraPreview and
- * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
- * implementations of the shared methods are delegated to CameraPreview
- */
-public class SoftwareCameraPreview extends SurfaceView implements CameraPreview.CameraPreviewHost {
- private final CameraPreview mPreview;
-
- public SoftwareCameraPreview(final Context context) {
- super(context);
- mPreview = new CameraPreview(this);
- getHolder().addCallback(new SurfaceHolder.Callback() {
- @Override
- public void surfaceCreated(@NonNull final SurfaceHolder surfaceHolder) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void surfaceChanged(@NonNull final SurfaceHolder surfaceHolder, final int format,
- final int width, final int height) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void surfaceDestroyed(@NonNull final SurfaceHolder surfaceHolder) {
- CameraManager.get().setSurface(null);
- }
- });
- }
-
-
- @Override
- protected void onVisibilityChanged(@NonNull final View changedView, final int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- mPreview.onVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mPreview.onDetachedFromWindow();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mPreview.onAttachedToWindow();
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- super.onRestoreInstanceState(state);
- mPreview.onRestoreInstanceState();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public View getView() {
- return this;
- }
-
- @Override
- public boolean isValid() {
- return getHolder() != null;
- }
-
- @Override
- public void startPreview(final Camera camera) throws IOException {
- camera.setPreviewDisplay(getHolder());
- }
-
- @Override
- public void onCameraPermissionGranted() {
- mPreview.onCameraPermissionGranted();
- }
-}
-
-
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
deleted file mode 100644
index 50f0926..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * Copyright (C) 2024 The LineageOS Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-public interface FocusIndicator {
- void showStart();
- void showSuccess(boolean timeout);
- void showFail(boolean timeout);
- void clear();
-}
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
deleted file mode 100644
index 7542164..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
+++ /dev/null
@@ -1,590 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * Copyright (C) 2024 The LineageOS Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.messaging.ui.mediapicker.camerafocus;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.Camera.Area;
-import android.hardware.Camera.Parameters;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/* A class that handles everything about focus in still picture mode.
- * This also handles the metering area because it is the same as focus area.
- *
- * The test cases:
- * (1) The camera has continuous autofocus. Move the camera. Take a picture when
- * CAF is not in progress.
- * (2) The camera has continuous autofocus. Move the camera. Take a picture when
- * CAF is in progress.
- * (3) The camera has face detection. Point the camera at some faces. Hold the
- * shutter. Release to take a picture.
- * (4) The camera has face detection. Point the camera at some faces. Single tap
- * the shutter to take a picture.
- * (5) The camera has autofocus. Single tap the shutter to take a picture.
- * (6) The camera has autofocus. Hold the shutter. Release to take a picture.
- * (7) The camera has no autofocus. Single tap the shutter and take a picture.
- * (8) The camera has autofocus and supports focus area. Touch the screen to
- * trigger autofocus. Take a picture.
- * (9) The camera has autofocus and supports focus area. Touch the screen to
- * trigger autofocus. Wait until it times out.
- * (10) The camera has no autofocus and supports metering area. Touch the screen
- * to change metering area.
- */
-public class FocusOverlayManager {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final String TRUE = "true";
- private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
- private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED =
- "auto-whitebalance-lock-supported";
-
- private static final int RESET_TOUCH_FOCUS = 0;
- private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
-
- private int mState = STATE_IDLE;
- private static final int STATE_IDLE = 0; // Focus is not active.
- private static final int STATE_FOCUSING = 1; // Focus is in progress.
- // Focus is in progress and the camera should take a picture after focus finishes.
- private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
- private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
- private static final int STATE_FAIL = 4; // Focus finishes and fails.
-
- private boolean mInitialized;
- private boolean mFocusAreaSupported;
- private boolean mMeteringAreaSupported;
- private boolean mLockAeAwbNeeded;
- private boolean mAeAwbLock;
- private final Matrix mMatrix;
-
- private PieRenderer mPieRenderer;
-
- private int mPreviewWidth; // The width of the preview frame layout.
- private int mPreviewHeight; // The height of the preview frame layout.
- private boolean mMirror; // true if the camera is front-facing.
- private int mDisplayOrientation;
- private List