From 7f464b401c82501f37bb4097c4fb56b66dfb3270 Mon Sep 17 00:00:00 2001 From: Michael W Date: Mon, 23 Dec 2024 17:25:03 +0100 Subject: [PATCH] Messaging: Replace own camera handling with pickers We do not need to handle camera on our own - there are apps out there that are made for that! Change-Id: Ifeb70e46b17bff5eb8d85c8775589efac4f2c284 --- res/layout/mediapicker_camera_chooser.xml | 166 +-- .../ui/mediapicker/CameraManager.java | 1175 ----------------- .../ui/mediapicker/CameraMediaChooser.java | 409 +----- .../mediapicker/CameraMediaChooserView.java | 103 -- .../ui/mediapicker/CameraPreview.java | 152 --- .../ui/mediapicker/DocumentImagePicker.java | 4 +- .../ui/mediapicker/HardwareCameraPreview.java | 123 -- .../ui/mediapicker/ImagePersistTask.java | 173 --- .../messaging/ui/mediapicker/MediaPicker.java | 23 +- .../ui/mediapicker/SoftwareCameraPreview.java | 117 -- .../camerafocus/FocusIndicator.java | 25 - .../camerafocus/FocusOverlayManager.java | 590 --------- .../camerafocus/OverlayRenderer.java | 95 -- .../ui/mediapicker/camerafocus/PieItem.java | 183 --- .../mediapicker/camerafocus/PieRenderer.java | 825 ------------ .../ui/mediapicker/camerafocus/README.txt | 3 - .../camerafocus/RenderOverlay.java | 181 --- 17 files changed, 149 insertions(+), 4198 deletions(-) delete mode 100644 src/com/android/messaging/ui/mediapicker/CameraManager.java delete mode 100644 src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java delete mode 100644 src/com/android/messaging/ui/mediapicker/CameraPreview.java delete mode 100644 src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java delete mode 100644 src/com/android/messaging/ui/mediapicker/ImagePersistTask.java delete mode 100644 src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java delete mode 100644 src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java delete mode 100644 src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java delete mode 100644 src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java delete mode 100644 src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java delete mode 100644 src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java delete mode 100644 src/com/android/messaging/ui/mediapicker/camerafocus/README.txt delete mode 100644 src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java 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: - * - * 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 mFocusArea; // focus area in driver format - private List mMeteringArea; // metering area in driver format - private String mFocusMode; - private String mOverrideFocusMode; - private Parameters mParameters; - private final Handler mHandler; - private final Listener mListener; - - public interface Listener { - void autoFocus(); - void cancelAutoFocus(); - boolean capture(); - void setFocusParameters(); - } - - private class MainHandler extends Handler { - public MainHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case RESET_TOUCH_FOCUS: { - cancelAutoFocus(); - break; - } - } - } - } - - public FocusOverlayManager(Listener listener, Looper looper) { - mHandler = new MainHandler(looper); - mMatrix = new Matrix(); - mListener = listener; - } - - public void setFocusRenderer(PieRenderer renderer) { - mPieRenderer = renderer; - mInitialized = (mMatrix != null); - } - - public void setParameters(Parameters parameters) { - // parameters can only be null when onConfigurationChanged is called - // before camera is open. We will just return in this case, because - // parameters will be set again later with the right parameters after - // camera is open. - if (parameters == null) { - return; - } - mParameters = parameters; - mFocusAreaSupported = isFocusAreaSupported(parameters); - mMeteringAreaSupported = isMeteringAreaSupported(parameters); - mLockAeAwbNeeded = (isAutoExposureLockSupported(mParameters) || - isAutoWhiteBalanceLockSupported(mParameters)); - } - - public void setPreviewSize(int previewWidth, int previewHeight) { - if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) { - mPreviewWidth = previewWidth; - mPreviewHeight = previewHeight; - setMatrix(); - } - } - - public void setMirror(boolean mirror) { - mMirror = mirror; - setMatrix(); - } - - public void setDisplayOrientation(int displayOrientation) { - mDisplayOrientation = displayOrientation; - setMatrix(); - } - - private void setMatrix() { - if (mPreviewWidth != 0 && mPreviewHeight != 0) { - Matrix matrix = new Matrix(); - prepareMatrix(matrix, mMirror, mDisplayOrientation, - mPreviewWidth, mPreviewHeight); - // In face detection, the matrix converts the driver coordinates to UI - // coordinates. In tap focus, the inverted matrix converts the UI - // coordinates to driver coordinates. - matrix.invert(mMatrix); - mInitialized = (mPieRenderer != null); - } - } - - private void lockAeAwbIfNeeded() { - if (mLockAeAwbNeeded && !mAeAwbLock) { - mAeAwbLock = true; - mListener.setFocusParameters(); - } - } - - private void unlockAeAwbIfNeeded() { - if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) { - mAeAwbLock = false; - mListener.setFocusParameters(); - } - } - - public void onShutterDown() { - if (!mInitialized) { - return; - } - - boolean autoFocusCalled = false; - if (needAutoFocusCall()) { - // Do not focus if touch focus has been triggered. - if (mState != STATE_SUCCESS && mState != STATE_FAIL) { - autoFocus(); - autoFocusCalled = true; - } - } - - if (!autoFocusCalled) { - lockAeAwbIfNeeded(); - } - } - - public void onShutterUp() { - if (!mInitialized) { - return; - } - - if (needAutoFocusCall()) { - // User releases half-pressed focus key. - if (mState == STATE_FOCUSING || mState == STATE_SUCCESS - || mState == STATE_FAIL) { - cancelAutoFocus(); - } - } - - // Unlock AE and AWB after cancelAutoFocus. Camera API does not - // guarantee setParameters can be called during autofocus. - unlockAeAwbIfNeeded(); - } - - public void doSnap() { - if (!mInitialized) { - return; - } - - // If the user has half-pressed the shutter and focus is completed, we - // can take the photo right away. If the focus mode is infinity, we can - // also take the photo. - if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) { - capture(); - } else if (mState == STATE_FOCUSING) { - // Half pressing the shutter (i.e. the focus button event) will - // already have requested AF for us, so just request capture on - // focus here. - mState = STATE_FOCUSING_SNAP_ON_FINISH; - } else if (mState == STATE_IDLE) { - // We didn't do focus. This can happen if the user press focus key - // while the snapshot is still in progress. The user probably wants - // the next snapshot as soon as possible, so we just do a snapshot - // without focusing again. - capture(); - } - } - - public void onAutoFocus(boolean focused, boolean shutterButtonPressed) { - if (mState == STATE_FOCUSING_SNAP_ON_FINISH) { - // Take the picture no matter focus succeeds or fails. No need - // to play the AF sound if we're about to play the shutter - // sound. - if (focused) { - mState = STATE_SUCCESS; - } else { - mState = STATE_FAIL; - } - updateFocusUI(); - capture(); - } else if (mState == STATE_FOCUSING) { - // This happens when (1) user is half-pressing the focus key or - // (2) touch focus is triggered. Play the focus tone. Do not - // take the picture now. - if (focused) { - mState = STATE_SUCCESS; - } else { - mState = STATE_FAIL; - } - updateFocusUI(); - // If this is triggered by touch focus, cancel focus after a - // while. - if (mFocusArea != null) { - mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); - } - if (shutterButtonPressed) { - // Lock AE & AWB so users can half-press shutter and recompose. - lockAeAwbIfNeeded(); - } - } else if (mState == STATE_IDLE) { - // User has released the focus key before focus completes. - // Do nothing. - } - } - - public void onAutoFocusMoving(boolean moving) { - if (!mInitialized) { - return; - } - - // Ignore if we have requested autofocus. This method only handles - // continuous autofocus. - if (mState != STATE_IDLE) { - return; - } - - if (moving) { - mPieRenderer.showStart(); - } else { - mPieRenderer.showSuccess(true); - } - } - - private void initializeFocusAreas(int focusWidth, int focusHeight, - int x, int y, int previewWidth, int previewHeight) { - if (mFocusArea == null) { - mFocusArea = new ArrayList<>(); - mFocusArea.add(new Area(new Rect(), 1)); - } - - // Convert the coordinates to driver format. - calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight, - ((Area) mFocusArea.get(0)).rect); - } - - private void initializeMeteringAreas(int focusWidth, int focusHeight, - int x, int y, int previewWidth, int previewHeight) { - if (mMeteringArea == null) { - mMeteringArea = new ArrayList<>(); - mMeteringArea.add(new Area(new Rect(), 1)); - } - - // Convert the coordinates to driver format. - // AE area is bigger because exposure is sensitive and - // easy to over- or underexposure if area is too small. - calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight, - ((Area) mMeteringArea.get(0)).rect); - } - - public void onSingleTapUp(int x, int y) { - if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) { - return; - } - - // Let users be able to cancel previous touch focus. - if ((mFocusArea != null) && (mState == STATE_FOCUSING || - mState == STATE_SUCCESS || mState == STATE_FAIL)) { - cancelAutoFocus(); - } - // Initialize variables. - int focusWidth = mPieRenderer.getSize(); - int focusHeight = mPieRenderer.getSize(); - if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) { - return; - } - int previewWidth = mPreviewWidth; - int previewHeight = mPreviewHeight; - // Initialize mFocusArea. - if (mFocusAreaSupported) { - initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); - } - // Initialize mMeteringArea. - if (mMeteringAreaSupported) { - initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); - } - - // Use margin to set the focus indicator to the touched area. - mPieRenderer.setFocus(x, y); - - // Set the focus area and metering area. - mListener.setFocusParameters(); - if (mFocusAreaSupported) { - autoFocus(); - } else { // Just show the indicator in all other cases. - updateFocusUI(); - // Reset the metering area in 3 seconds. - mHandler.removeMessages(RESET_TOUCH_FOCUS); - mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); - } - } - - public void onPreviewStarted() { - mState = STATE_IDLE; - } - - public void onPreviewStopped() { - // If auto focus was in progress, it would have been stopped. - mState = STATE_IDLE; - resetTouchFocus(); - updateFocusUI(); - } - - public void onCameraReleased() { - onPreviewStopped(); - } - - private void autoFocus() { - LogUtil.v(TAG, "Start autofocus."); - mListener.autoFocus(); - mState = STATE_FOCUSING; - updateFocusUI(); - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - - private void cancelAutoFocus() { - LogUtil.v(TAG, "Cancel autofocus."); - - // Reset the tap area before calling mListener.cancelAutofocus. - // Otherwise, focus mode stays at auto and the tap area passed to the - // driver is not reset. - resetTouchFocus(); - mListener.cancelAutoFocus(); - mState = STATE_IDLE; - updateFocusUI(); - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - - private void capture() { - if (mListener.capture()) { - mState = STATE_IDLE; - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - } - - public String getFocusMode() { - if (mOverrideFocusMode != null) { - return mOverrideFocusMode; - } - List supportedFocusModes = mParameters.getSupportedFocusModes(); - - if (mFocusAreaSupported && mFocusArea != null) { - // Always use autofocus in tap-to-focus. - mFocusMode = Parameters.FOCUS_MODE_AUTO; - } else { - mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; - } - - if (!isSupported(mFocusMode, supportedFocusModes)) { - // For some reasons, the driver does not support the current - // focus mode. Fall back to auto. - if (isSupported(Parameters.FOCUS_MODE_AUTO, - mParameters.getSupportedFocusModes())) { - mFocusMode = Parameters.FOCUS_MODE_AUTO; - } else { - mFocusMode = mParameters.getFocusMode(); - } - } - return mFocusMode; - } - - public List getFocusAreas() { - return mFocusArea; - } - - public List getMeteringAreas() { - return mMeteringArea; - } - - public void updateFocusUI() { - if (!mInitialized) { - return; - } - FocusIndicator focusIndicator = mPieRenderer; - - if (mState == STATE_IDLE) { - if (mFocusArea == null) { - focusIndicator.clear(); - } else { - // Users touch on the preview and the indicator represents the - // metering area. Either focus area is not supported or - // autoFocus call is not required. - focusIndicator.showStart(); - } - } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) { - focusIndicator.showStart(); - } else { - if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) { - // TODO: check HAL behavior and decide if this can be removed. - focusIndicator.showSuccess(false); - } else if (mState == STATE_SUCCESS) { - focusIndicator.showSuccess(false); - } else if (mState == STATE_FAIL) { - focusIndicator.showFail(false); - } - } - } - - public void resetTouchFocus() { - if (!mInitialized) { - return; - } - - // Put focus indicator to the center. clear reset position - mPieRenderer.clear(); - - mFocusArea = null; - mMeteringArea = null; - } - - private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple, - int x, int y, int previewWidth, int previewHeight, Rect rect) { - int areaWidth = (int) (focusWidth * areaMultiple); - int areaHeight = (int) (focusHeight * areaMultiple); - int left = clamp(x - areaWidth / 2, 0, previewWidth - areaWidth); - int top = clamp(y - areaHeight / 2, 0, previewHeight - areaHeight); - - RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight); - mMatrix.mapRect(rectF); - rectFToRect(rectF, rect); - } - - /* package */ int getFocusState() { - return mState; - } - - public boolean isFocusCompleted() { - return mState == STATE_SUCCESS || mState == STATE_FAIL; - } - - public boolean isFocusingSnapOnFinish() { - return mState == STATE_FOCUSING_SNAP_ON_FINISH; - } - - public void removeMessages() { - mHandler.removeMessages(RESET_TOUCH_FOCUS); - } - - public void overrideFocusMode(String focusMode) { - mOverrideFocusMode = focusMode; - } - - public void setAeAwbLock(boolean lock) { - mAeAwbLock = lock; - } - - public boolean getAeAwbLock() { - return mAeAwbLock; - } - - private boolean needAutoFocusCall() { - String focusMode = getFocusMode(); - return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY) - || focusMode.equals(Parameters.FOCUS_MODE_FIXED) - || focusMode.equals(Parameters.FOCUS_MODE_EDOF)); - } - - public static boolean isAutoExposureLockSupported(Parameters params) { - return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED)); - } - - public static boolean isAutoWhiteBalanceLockSupported(Parameters params) { - return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED)); - } - - public static boolean isSupported(String value, List supported) { - return supported != null && supported.contains(value); - } - - public static boolean isMeteringAreaSupported(Parameters params) { - return params.getMaxNumMeteringAreas() > 0; - } - - public static boolean isFocusAreaSupported(Parameters params) { - return (params.getMaxNumFocusAreas() > 0 - && isSupported(Parameters.FOCUS_MODE_AUTO, - params.getSupportedFocusModes())); - } - - public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, - int viewWidth, int viewHeight) { - // Need mirror for front camera. - matrix.setScale(mirror ? -1 : 1, 1); - // This is the value for android.hardware.Camera.setDisplayOrientation. - matrix.postRotate(displayOrientation); - // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). - // UI coordinates range from (0, 0) to (width, height). - matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); - matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); - } - - public static int clamp(int x, int min, int max) { - Assert.isTrue(max >= min); - if (x > max) { - return max; - } - if (x < min) { - return min; - } - return x; - } - - public static void rectFToRect(RectF rectF, Rect rect) { - rect.left = Math.round(rectF.left); - rect.top = Math.round(rectF.top); - rect.right = Math.round(rectF.right); - rect.bottom = Math.round(rectF.bottom); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java deleted file mode 100644 index df6734f..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java +++ /dev/null @@ -1,95 +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.camerafocus; - -import android.content.Context; -import android.graphics.Canvas; -import android.view.MotionEvent; - -public abstract class OverlayRenderer implements RenderOverlay.Renderer { - - private static final String TAG = "CAM OverlayRenderer"; - protected RenderOverlay mOverlay; - - protected int mLeft, mTop, mRight, mBottom; - - protected boolean mVisible; - - public void setVisible(boolean vis) { - mVisible = vis; - update(); - } - - public boolean isVisible() { - return mVisible; - } - - // default does not handle touch - @Override - public boolean handlesTouch() { - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent evt) { - return false; - } - - public abstract void onDraw(Canvas canvas); - - public void draw(Canvas canvas) { - if (mVisible) { - onDraw(canvas); - } - } - - @Override - public void setOverlay(RenderOverlay overlay) { - mOverlay = overlay; - } - - @Override - public void layout(int left, int top, int right, int bottom) { - mLeft = left; - mRight = right; - mTop = top; - mBottom = bottom; - } - - protected Context getContext() { - if (mOverlay != null) { - return mOverlay.getContext(); - } else { - return null; - } - } - - public int getWidth() { - return mRight - mLeft; - } - - public int getHeight() { - return mBottom - mTop; - } - - protected void update() { - if (mOverlay != null) { - mOverlay.update(); - } - } - -} \ No newline at end of file diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java deleted file mode 100644 index 6e57cd1..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java +++ /dev/null @@ -1,183 +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.Canvas; -import android.graphics.Path; -import android.graphics.drawable.Drawable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Pie menu item - */ -public class PieItem { - - public interface OnClickListener { - void onClick(PieItem item); - } - - private Drawable mDrawable; - private final int level; - private float mCenter; - private float start; - private float sweep; - private float animate; - private int inner; - private int outer; - private boolean mSelected; - private boolean mEnabled; - private List mItems; - private Path mPath; - private OnClickListener mOnClickListener; - - // Gray out the view when disabled - private static final float ENABLED_ALPHA = 1; - private static final float DISABLED_ALPHA = (float) 0.3; - private boolean mChangeAlphaWhenDisabled = true; - - public PieItem(Drawable drawable, int level) { - mDrawable = drawable; - this.level = level; - setAlpha(1f); - mEnabled = true; - setAnimationAngle(getAnimationAngle()); - start = -1; - mCenter = -1; - } - - public boolean hasItems() { - return mItems != null; - } - - public List getItems() { - return mItems; - } - - public void addItem(PieItem item) { - if (mItems == null) { - mItems = new ArrayList<>(); - } - mItems.add(item); - } - - public void setPath(Path p) { - mPath = p; - } - - public Path getPath() { - return mPath; - } - - public void setAlpha(float alpha) { - mDrawable.setAlpha((int) (255 * alpha)); - } - - public void setAnimationAngle(float a) { - animate = a; - } - - public float getAnimationAngle() { - return animate; - } - - public void setEnabled(boolean enabled) { - mEnabled = enabled; - if (mChangeAlphaWhenDisabled) { - if (mEnabled) { - setAlpha(ENABLED_ALPHA); - } else { - setAlpha(DISABLED_ALPHA); - } - } - } - - public boolean isEnabled() { - return mEnabled; - } - - public void setSelected(boolean s) { - mSelected = s; - } - - public boolean isSelected() { - return mSelected; - } - - public int getLevel() { - return level; - } - - public void setGeometry(float st, float sw, int inside, int outside) { - start = st; - sweep = sw; - inner = inside; - outer = outside; - } - - public float getCenter() { - return mCenter; - } - - public float getStart() { - return start; - } - - public float getStartAngle() { - return start + animate; - } - - public float getSweep() { - return sweep; - } - - public int getInnerRadius() { - return inner; - } - - public int getOuterRadius() { - return outer; - } - - public void setOnClickListener(OnClickListener listener) { - mOnClickListener = listener; - } - - public void performClick() { - if (mOnClickListener != null) { - mOnClickListener.onClick(this); - } - } - - public int getIntrinsicWidth() { - return mDrawable.getIntrinsicWidth(); - } - - public int getIntrinsicHeight() { - return mDrawable.getIntrinsicHeight(); - } - - public void setBounds(int left, int top, int right, int bottom) { - mDrawable.setBounds(left, top, right, bottom); - } - - public void draw(Canvas canvas) { - mDrawable.draw(canvas); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java deleted file mode 100644 index d0178f3..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java +++ /dev/null @@ -1,825 +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.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.RectF; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.view.MotionEvent; -import android.view.ViewConfiguration; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.LinearInterpolator; -import android.view.animation.Transformation; -import com.android.messaging.R; - -import java.util.ArrayList; -import java.util.List; - -public class PieRenderer extends OverlayRenderer - implements FocusIndicator { - // Sometimes continuous autofocus starts and stops several times quickly. - // These states are used to make sure the animation is run for at least some - // time. - private volatile int mState; - private final ScaleAnimation mAnimation = new ScaleAnimation(); - private static final int STATE_IDLE = 0; - private static final int STATE_FOCUSING = 1; - private static final int STATE_FINISHING = 2; - private static final int STATE_PIE = 8; - - private final Runnable mDisappear = new Disappear(); - private final Animation.AnimationListener mEndAction = new EndAction(); - private static final int SCALING_UP_TIME = 600; - private static final int SCALING_DOWN_TIME = 100; - private static final int DISAPPEAR_TIMEOUT = 200; - private static final int DIAL_HORIZONTAL = 157; - - private static final long PIE_FADE_IN_DURATION = 200; - private static final long PIE_XFADE_DURATION = 200; - private static final long PIE_SELECT_FADE_DURATION = 300; - - private static final int MSG_OPEN = 0; - private static final int MSG_CLOSE = 1; - private static final float PIE_SWEEP = (float) (Math.PI * 2 / 3); - // geometry - private Point mCenter; - private int mRadius; - private int mRadiusInc; - - // the detection if touch is inside a slice is offset - // inbounds by this amount to allow the selection to show before the - // finger covers it - private int mTouchOffset; - - private List mItems; - - private PieItem mOpenItem; - - private Paint mSelectedPaint; - private Paint mSubPaint; - - // touch handling - private PieItem mCurrentItem; - - private Paint mFocusPaint; - private int mSuccessColor; - private int mFailColor; - private int mCircleSize; - private int mFocusX; - private int mFocusY; - private int mCenterX; - private int mCenterY; - - private int mDialAngle; - private RectF mCircle; - private RectF mDial; - private Point mPoint1; - private Point mPoint2; - private int mStartAnimationAngle; - private boolean mFocused; - private int mInnerOffset; - private int mOuterStroke; - private int mInnerStroke; - private boolean mTapMode; - private boolean mBlockFocus; - private int mTouchSlopSquared; - private Point mDown; - private boolean mOpening; - private LinearAnimation mXFade; - private LinearAnimation mFadeIn; - private volatile boolean mFocusCancelled; - - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_OPEN: - if (mListener != null) { - mListener.onPieOpened(mCenter.x, mCenter.y); - } - break; - case MSG_CLOSE: - if (mListener != null) { - mListener.onPieClosed(); - } - break; - } - } - }; - - private PieListener mListener; - - public interface PieListener { - void onPieOpened(int centerX, int centerY); - void onPieClosed(); - } - - public void setPieListener(PieListener pl) { - mListener = pl; - } - - public PieRenderer(Context context) { - init(context); - } - - private void init(Context ctx) { - setVisible(false); - mItems = new ArrayList<>(); - Resources res = ctx.getResources(); - mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start); - mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset); - mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment); - mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset); - mCenter = new Point(0, 0); - mSelectedPaint = new Paint(); - mSelectedPaint.setColor(Color.argb(255, 51, 181, 229)); - mSelectedPaint.setAntiAlias(true); - mSubPaint = new Paint(); - mSubPaint.setAntiAlias(true); - mSubPaint.setColor(Color.argb(200, 250, 230, 128)); - mFocusPaint = new Paint(); - mFocusPaint.setAntiAlias(true); - mFocusPaint.setColor(Color.WHITE); - mFocusPaint.setStyle(Paint.Style.STROKE); - mSuccessColor = Color.GREEN; - mFailColor = Color.RED; - mCircle = new RectF(); - mDial = new RectF(); - mPoint1 = new Point(); - mPoint2 = new Point(); - mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset); - mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke); - mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke); - mState = STATE_IDLE; - mBlockFocus = false; - mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop(); - mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared; - mDown = new Point(); - } - - public boolean showsItems() { - return mTapMode; - } - - public void addItem(PieItem item) { - // add the item to the pie itself - mItems.add(item); - } - - public void removeItem(PieItem item) { - mItems.remove(item); - } - - public void clearItems() { - mItems.clear(); - } - - public void showInCenter() { - if ((mState == STATE_PIE) && isVisible()) { - mTapMode = false; - show(false); - } else { - if (mState != STATE_IDLE) { - cancelFocus(); - } - mState = STATE_PIE; - setCenter(mCenterX, mCenterY); - mTapMode = true; - show(true); - } - } - - public void hide() { - show(false); - } - - /** - * guaranteed has center set - */ - private void show(boolean show) { - if (show) { - mState = STATE_PIE; - // ensure clean state - mCurrentItem = null; - mOpenItem = null; - for (PieItem item : mItems) { - item.setSelected(false); - } - layoutPie(); - fadeIn(); - } else { - mState = STATE_IDLE; - mTapMode = false; - if (mXFade != null) { - mXFade.cancel(); - } - } - setVisible(show); - mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE); - } - - private void fadeIn() { - mFadeIn = new LinearAnimation(0, 1); - mFadeIn.setDuration(PIE_FADE_IN_DURATION); - mFadeIn.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mFadeIn = null; - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mFadeIn.startNow(); - mOverlay.startAnimation(mFadeIn); - } - - public void setCenter(int x, int y) { - mCenter.x = x; - mCenter.y = y; - // when using the pie menu, align the focus ring - alignFocus(x, y); - } - - private void layoutPie() { - int rgap = 2; - int inner = mRadius + rgap; - int outer = mRadius + mRadiusInc - rgap; - int gap = 1; - layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap); - } - - private void layoutItems(List items, float centerAngle, int inner, - int outer, int gap) { - float emptyangle = PIE_SWEEP / 16; - float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size(); - float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2; - // check if we have custom geometry - // first item we find triggers custom sweep for all - // this allows us to re-use the path - for (PieItem item : items) { - if (item.getCenter() >= 0) { - sweep = item.getSweep(); - break; - } - } - Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap, - outer, inner, mCenter); - for (PieItem item : items) { - // shared between items - item.setPath(path); - if (item.getCenter() >= 0) { - angle = item.getCenter(); - } - int w = item.getIntrinsicWidth(); - int h = item.getIntrinsicHeight(); - // move views to outer border - int r = inner + (outer - inner) * 2 / 3; - int x = (int) (r * Math.cos(angle)); - int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2; - x = mCenter.x + x - w / 2; - item.setBounds(x, y, x + w, y + h); - float itemstart = angle - sweep / 2; - item.setGeometry(itemstart, sweep, inner, outer); - if (item.hasItems()) { - layoutItems(item.getItems(), angle, inner, - outer + mRadiusInc / 2, gap); - } - angle += sweep; - } - } - - private Path makeSlice(float start, float end, int outer, int inner, Point center) { - RectF bb = - new RectF(center.x - outer, center.y - outer, center.x + outer, - center.y + outer); - RectF bbi = - new RectF(center.x - inner, center.y - inner, center.x + inner, - center.y + inner); - Path path = new Path(); - path.arcTo(bb, start, end - start, true); - path.arcTo(bbi, end, start - end); - path.close(); - return path; - } - - /** - * converts a - * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock) - * @return skia angle - */ - private float getDegrees(double angle) { - return (float) (360 - 180 * angle / Math.PI); - } - - private void startFadeOut() { - mOverlay.animate().alpha(0).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - deselect(); - show(false); - mOverlay.setAlpha(1); - super.onAnimationEnd(animation); - } - }).setDuration(PIE_SELECT_FADE_DURATION); - } - - @Override - public void onDraw(Canvas canvas) { - float alpha = 1; - if (mXFade != null) { - alpha = mXFade.getValue(); - } else if (mFadeIn != null) { - alpha = mFadeIn.getValue(); - } - int state = canvas.save(); - if (mFadeIn != null) { - float sf = 0.9f + alpha * 0.1f; - canvas.scale(sf, sf, mCenter.x, mCenter.y); - } - drawFocus(canvas); - if (mState == STATE_FINISHING) { - canvas.restoreToCount(state); - return; - } - if ((mOpenItem == null) || (mXFade != null)) { - // draw base menu - for (PieItem item : mItems) { - drawItem(canvas, item, alpha); - } - } - if (mOpenItem != null) { - for (PieItem inner : mOpenItem.getItems()) { - drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1); - } - } - canvas.restoreToCount(state); - } - - private void drawItem(Canvas canvas, PieItem item, float alpha) { - if (mState == STATE_PIE) { - if (item.getPath() != null) { - if (item.isSelected()) { - Paint p = mSelectedPaint; - int state = canvas.save(); - float r = getDegrees(item.getStartAngle()); - canvas.rotate(r, mCenter.x, mCenter.y); - canvas.drawPath(item.getPath(), p); - canvas.restoreToCount(state); - } - alpha = alpha * (item.isEnabled() ? 1 : 0.3f); - // draw the item view - item.setAlpha(alpha); - item.draw(canvas); - } - } - } - - @Override - public boolean onTouchEvent(MotionEvent evt) { - float x = evt.getX(); - float y = evt.getY(); - int action = evt.getActionMasked(); - PointF polar = getPolar(x, y, !(mTapMode)); - if (MotionEvent.ACTION_DOWN == action) { - mDown.x = (int) evt.getX(); - mDown.y = (int) evt.getY(); - mOpening = false; - if (mTapMode) { - PieItem item = findItem(polar); - if ((item != null) && (mCurrentItem != item)) { - mState = STATE_PIE; - onEnter(item); - } - } else { - setCenter((int) x, (int) y); - show(true); - } - return true; - } else if (MotionEvent.ACTION_UP == action) { - if (isVisible()) { - PieItem item = mCurrentItem; - if (mTapMode) { - item = findItem(polar); - if (item != null && mOpening) { - mOpening = false; - return true; - } - } - if (item == null) { - mTapMode = false; - show(false); - } else if (!mOpening - && !item.hasItems()) { - item.performClick(); - startFadeOut(); - mTapMode = false; - } - return true; - } - } else if (MotionEvent.ACTION_CANCEL == action) { - if (isVisible() || mTapMode) { - show(false); - } - deselect(); - return false; - } else if (MotionEvent.ACTION_MOVE == action) { - if (polar.y < mRadius) { - if (mOpenItem != null) { - mOpenItem = null; - } else { - deselect(); - } - return false; - } - PieItem item = findItem(polar); - boolean moved = hasMoved(evt); - if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) { - // only select if we didn't just open or have moved past slop - mOpening = false; - if (moved) { - // switch back to swipe mode - mTapMode = false; - } - onEnter(item); - } - } - return false; - } - - private boolean hasMoved(MotionEvent e) { - return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x) - + (e.getY() - mDown.y) * (e.getY() - mDown.y); - } - - /** - * enter a slice for a view - * updates model only - */ - private void onEnter(PieItem item) { - if (mCurrentItem != null) { - mCurrentItem.setSelected(false); - } - if (item != null && item.isEnabled()) { - item.setSelected(true); - mCurrentItem = item; - if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) { - openCurrentItem(); - } - } else { - mCurrentItem = null; - } - } - - private void deselect() { - if (mCurrentItem != null) { - mCurrentItem.setSelected(false); - } - if (mOpenItem != null) { - mOpenItem = null; - } - mCurrentItem = null; - } - - private void openCurrentItem() { - if ((mCurrentItem != null) && mCurrentItem.hasItems()) { - mCurrentItem.setSelected(false); - mOpenItem = mCurrentItem; - mOpening = true; - mXFade = new LinearAnimation(1, 0); - mXFade.setDuration(PIE_XFADE_DURATION); - mXFade.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mXFade = null; - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mXFade.startNow(); - mOverlay.startAnimation(mXFade); - } - } - - private PointF getPolar(float x, float y, boolean useOffset) { - PointF res = new PointF(); - // get angle and radius from x/y - res.x = (float) Math.PI / 2; - x = x - mCenter.x; - y = mCenter.y - y; - res.y = (float) Math.sqrt(x * x + y * y); - if (x != 0) { - res.x = (float) Math.atan2(y, x); - if (res.x < 0) { - res.x = (float) (2 * Math.PI + res.x); - } - } - res.y = res.y + (useOffset ? mTouchOffset : 0); - return res; - } - - /** - * @param polar x: angle, y: dist - * @return the item at angle/dist or null - */ - private PieItem findItem(PointF polar) { - // find the matching item: - List items = (mOpenItem != null) ? mOpenItem.getItems() : mItems; - for (PieItem item : items) { - if (inside(polar, item)) { - return item; - } - } - return null; - } - - private boolean inside(PointF polar, PieItem item) { - return (item.getInnerRadius() < polar.y) - && (item.getStartAngle() < polar.x) - && (item.getStartAngle() + item.getSweep() > polar.x) - && (!mTapMode || (item.getOuterRadius() > polar.y)); - } - - @Override - public boolean handlesTouch() { - return true; - } - - // focus specific code - - public void setBlockFocus(boolean blocked) { - mBlockFocus = blocked; - if (blocked) { - clear(); - } - } - - public void setFocus(int x, int y) { - mFocusX = x; - mFocusY = y; - setCircle(mFocusX, mFocusY); - } - - public void alignFocus(int x, int y) { - mOverlay.removeCallbacks(mDisappear); - mAnimation.cancel(); - mAnimation.reset(); - mFocusX = x; - mFocusY = y; - mDialAngle = DIAL_HORIZONTAL; - setCircle(x, y); - mFocused = false; - } - - public int getSize() { - return 2 * mCircleSize; - } - - private int getRandomRange() { - return (int) (-60 + 120 * Math.random()); - } - - @Override - public void layout(int l, int t, int r, int b) { - super.layout(l, t, r, b); - mCenterX = (r - l) / 2; - mCenterY = (b - t) / 2; - mFocusX = mCenterX; - mFocusY = mCenterY; - setCircle(mFocusX, mFocusY); - if (isVisible() && mState == STATE_PIE) { - setCenter(mCenterX, mCenterY); - layoutPie(); - } - } - - private void setCircle(int cx, int cy) { - mCircle.set(cx - mCircleSize, cy - mCircleSize, - cx + mCircleSize, cy + mCircleSize); - mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset, - cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset); - } - - public void drawFocus(Canvas canvas) { - if (mBlockFocus) { - return; - } - mFocusPaint.setStrokeWidth(mOuterStroke); - canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint); - if (mState == STATE_PIE) { - return; - } - int color = mFocusPaint.getColor(); - if (mState == STATE_FINISHING) { - mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor); - } - mFocusPaint.setStrokeWidth(mInnerStroke); - drawLine(canvas, mDialAngle, mFocusPaint); - drawLine(canvas, mDialAngle + 45, mFocusPaint); - drawLine(canvas, mDialAngle + 180, mFocusPaint); - drawLine(canvas, mDialAngle + 225, mFocusPaint); - canvas.save(); - // rotate the arc instead of its offset to better use framework's shape caching - canvas.rotate(mDialAngle, mFocusX, mFocusY); - canvas.drawArc(mDial, 0, 45, false, mFocusPaint); - canvas.drawArc(mDial, 180, 45, false, mFocusPaint); - canvas.restore(); - mFocusPaint.setColor(color); - } - - private void drawLine(Canvas canvas, int angle, Paint p) { - convertCart(angle, mCircleSize - mInnerOffset, mPoint1); - convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2); - canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY, - mPoint2.x + mFocusX, mPoint2.y + mFocusY, p); - } - - private static void convertCart(int angle, int radius, Point out) { - double a = 2 * Math.PI * (angle % 360) / 360; - out.x = (int) (radius * Math.cos(a) + 0.5); - out.y = (int) (radius * Math.sin(a) + 0.5); - } - - @Override - public void showStart() { - if (mState == STATE_PIE) { - return; - } - cancelFocus(); - mStartAnimationAngle = 67; - int range = getRandomRange(); - startAnimation(SCALING_UP_TIME, - false, mStartAnimationAngle, mStartAnimationAngle + range); - mState = STATE_FOCUSING; - } - - @Override - public void showSuccess(boolean timeout) { - if (mState == STATE_FOCUSING) { - startAnimation(SCALING_DOWN_TIME, - timeout, mStartAnimationAngle); - mState = STATE_FINISHING; - mFocused = true; - } - } - - @Override - public void showFail(boolean timeout) { - if (mState == STATE_FOCUSING) { - startAnimation(SCALING_DOWN_TIME, - timeout, mStartAnimationAngle); - mState = STATE_FINISHING; - mFocused = false; - } - } - - private void cancelFocus() { - mFocusCancelled = true; - mOverlay.removeCallbacks(mDisappear); - if (mAnimation != null) { - mAnimation.cancel(); - } - mFocusCancelled = false; - mFocused = false; - mState = STATE_IDLE; - } - - @Override - public void clear() { - if (mState == STATE_PIE) { - return; - } - cancelFocus(); - mOverlay.post(mDisappear); - } - - private void startAnimation(long duration, boolean timeout, - float toScale) { - startAnimation(duration, timeout, mDialAngle, - toScale); - } - - private void startAnimation(long duration, boolean timeout, - float fromScale, float toScale) { - setVisible(true); - mAnimation.reset(); - mAnimation.setDuration(duration); - mAnimation.setScale(fromScale, toScale); - mAnimation.setAnimationListener(timeout ? mEndAction : null); - mOverlay.startAnimation(mAnimation); - update(); - } - - private class EndAction implements Animation.AnimationListener { - @Override - public void onAnimationEnd(Animation animation) { - // Keep the focus indicator for some time. - if (!mFocusCancelled) { - mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationStart(Animation animation) { - } - } - - private class Disappear implements Runnable { - @Override - public void run() { - if (mState == STATE_PIE) { - return; - } - setVisible(false); - mFocusX = mCenterX; - mFocusY = mCenterY; - mState = STATE_IDLE; - setCircle(mFocusX, mFocusY); - mFocused = false; - } - } - - private class ScaleAnimation extends Animation { - private float mFrom = 1f; - private float mTo = 1f; - - public ScaleAnimation() { - setFillAfter(true); - } - - public void setScale(float from, float to) { - mFrom = from; - mTo = to; - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - mDialAngle = (int) (mFrom + (mTo - mFrom) * interpolatedTime); - } - } - - - private class LinearAnimation extends Animation { - private final float mFrom; - private final float mTo; - private float mValue; - - public LinearAnimation(float from, float to) { - setFillAfter(true); - setInterpolator(new LinearInterpolator()); - mFrom = from; - mTo = to; - } - - public float getValue() { - return mValue; - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - mValue = (mFrom + (mTo - mFrom) * interpolatedTime); - } - } - -} diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt b/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt deleted file mode 100644 index ed4e783..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -The files in this package were copied from the android-4.4.4_r1 branch of ASOP from the folders -com/android/camera/ and com/android/camera/ui from files with the same name. Some modifications -have been made to remove unneeded features and adjust to our needs. \ No newline at end of file diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java b/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java deleted file mode 100644 index 9702607..0000000 --- a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java +++ /dev/null @@ -1,181 +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.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.List; - -public class RenderOverlay extends FrameLayout { - - interface Renderer { - - boolean handlesTouch(); - boolean onTouchEvent(MotionEvent evt); - void setOverlay(RenderOverlay overlay); - void layout(int left, int top, int right, int bottom); - void draw(Canvas canvas); - - } - - private final RenderView mRenderView; - private final List mClients; - - // reverse list of touch clients - private final List mTouchClients; - private final int[] mPosition = new int[2]; - - public RenderOverlay(Context context, AttributeSet attrs) { - super(context, attrs); - mRenderView = new RenderView(context); - addView(mRenderView, new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); - mClients = new ArrayList<>(10); - mTouchClients = new ArrayList<>(10); - setWillNotDraw(false); - - addRenderer(new PieRenderer(context)); - } - - public PieRenderer getPieRenderer() { - for (Renderer renderer : mClients) { - if (renderer instanceof PieRenderer) { - return (PieRenderer) renderer; - } - } - return null; - } - - public void addRenderer(Renderer renderer) { - mClients.add(renderer); - renderer.setOverlay(this); - if (renderer.handlesTouch()) { - mTouchClients.add(0, renderer); - } - renderer.layout(getLeft(), getTop(), getRight(), getBottom()); - } - - public void addRenderer(int pos, Renderer renderer) { - mClients.add(pos, renderer); - renderer.setOverlay(this); - renderer.layout(getLeft(), getTop(), getRight(), getBottom()); - } - - public void remove(Renderer renderer) { - mClients.remove(renderer); - renderer.setOverlay(null); - } - - public int getClientSize() { - return mClients.size(); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent m) { - return false; - } - - public boolean directDispatchTouch(MotionEvent m, Renderer target) { - mRenderView.setTouchTarget(target); - boolean res = super.dispatchTouchEvent(m); - mRenderView.setTouchTarget(null); - return res; - } - - private void adjustPosition() { - getLocationInWindow(mPosition); - } - - public int getWindowPositionX() { - return mPosition[0]; - } - - public int getWindowPositionY() { - return mPosition[1]; - } - - public void update() { - mRenderView.invalidate(); - } - - private class RenderView extends View { - - private Renderer mTouchTarget; - - public RenderView(Context context) { - super(context); - setWillNotDraw(false); - } - - public void setTouchTarget(Renderer target) { - mTouchTarget = target; - } - - @Override - public boolean onTouchEvent(MotionEvent evt) { - if (mTouchTarget != null) { - return mTouchTarget.onTouchEvent(evt); - } - if (mTouchClients != null) { - boolean res = false; - for (Renderer client : mTouchClients) { - res |= client.onTouchEvent(evt); - } - return res; - } - return false; - } - - @Override - public void onLayout(boolean changed, int left, int top, int right, int bottom) { - adjustPosition(); - super.onLayout(changed, left, top, right, bottom); - if (mClients == null) { - return; - } - for (Renderer renderer : mClients) { - renderer.layout(left, top, right, bottom); - } - } - - @Override - public void draw(@NonNull Canvas canvas) { - super.draw(canvas); - if (mClients == null) { - return; - } - boolean redraw = false; - for (Renderer renderer : mClients) { - renderer.draw(canvas); - redraw = redraw || ((OverlayRenderer) renderer).isVisible(); - } - if (redraw) { - invalidate(); - } - } - } - -}