summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/callcomposer/CameraComposerFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/callcomposer/CameraComposerFragment.java')
-rw-r--r--java/com/android/dialer/callcomposer/CameraComposerFragment.java378
1 files changed, 378 insertions, 0 deletions
diff --git a/java/com/android/dialer/callcomposer/CameraComposerFragment.java b/java/com/android/dialer/callcomposer/CameraComposerFragment.java
new file mode 100644
index 000000000..f2d0a94a7
--- /dev/null
+++ b/java/com/android/dialer/callcomposer/CameraComposerFragment.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2016 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.dialer.callcomposer;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Animatable;
+import android.hardware.Camera.CameraInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.dialer.callcomposer.camera.CameraManager;
+import com.android.dialer.callcomposer.camera.CameraManager.CameraManagerListener;
+import com.android.dialer.callcomposer.camera.CameraManager.MediaCallback;
+import com.android.dialer.callcomposer.camera.CameraPreview.CameraPreviewHost;
+import com.android.dialer.callcomposer.camera.camerafocus.RenderOverlay;
+import com.android.dialer.callcomposer.cameraui.CameraMediaChooserView;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.nano.DialerImpression;
+import com.android.dialer.util.PermissionsUtil;
+
+/** Fragment used to compose call with image from the user's camera. */
+public class CameraComposerFragment extends CallComposerFragment
+ implements CameraManagerListener, OnClickListener, CameraManager.MediaCallback {
+
+ private View permissionView;
+ private ImageButton exitFullscreen;
+ private ImageButton fullscreen;
+ private ImageButton swapCamera;
+ private ImageButton capture;
+ private ImageButton cancel;
+ private CameraMediaChooserView cameraView;
+ private RenderOverlay focus;
+ private View shutter;
+ private View allowPermission;
+ private CameraPreviewHost preview;
+ private ProgressBar loading;
+
+ private Uri cameraUri;
+ private boolean processingUri;
+ private String[] permissions = new String[] {Manifest.permission.CAMERA};
+ private CameraUriCallback uriCallback;
+
+ public static CameraComposerFragment newInstance() {
+ return new CameraComposerFragment();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle bundle) {
+ View root = inflater.inflate(R.layout.fragment_camera_composer, container, false);
+ permissionView = root.findViewById(R.id.permission_view);
+ loading = (ProgressBar) root.findViewById(R.id.loading);
+ cameraView = (CameraMediaChooserView) root.findViewById(R.id.camera_view);
+ shutter = cameraView.findViewById(R.id.camera_shutter_visual);
+ exitFullscreen = (ImageButton) cameraView.findViewById(R.id.camera_exit_fullscreen);
+ fullscreen = (ImageButton) cameraView.findViewById(R.id.camera_fullscreen);
+ swapCamera = (ImageButton) cameraView.findViewById(R.id.swap_camera_button);
+ capture = (ImageButton) cameraView.findViewById(R.id.camera_capture_button);
+ cancel = (ImageButton) cameraView.findViewById(R.id.camera_cancel_button);
+ focus = (RenderOverlay) cameraView.findViewById(R.id.focus_visual);
+ preview = (CameraPreviewHost) cameraView.findViewById(R.id.camera_preview);
+
+ exitFullscreen.setOnClickListener(this);
+ fullscreen.setOnClickListener(this);
+ swapCamera.setOnClickListener(this);
+ capture.setOnClickListener(this);
+ cancel.setOnClickListener(this);
+
+ if (!PermissionsUtil.hasPermission(getContext(), permission.CAMERA)) {
+ LogUtil.i("CameraComposerFragment.onCreateView", "Permission view shown.");
+ Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_DISPLAYED);
+ ImageView permissionImage = (ImageView) permissionView.findViewById(R.id.permission_icon);
+ TextView permissionText = (TextView) permissionView.findViewById(R.id.permission_text);
+ allowPermission = permissionView.findViewById(R.id.allow);
+
+ allowPermission.setOnClickListener(this);
+ permissionText.setText(R.string.camera_permission_text);
+ permissionImage.setImageResource(R.drawable.quantum_ic_camera_alt_white_48);
+ permissionImage.setColorFilter(
+ ContextCompat.getColor(getContext(), R.color.dialer_theme_color));
+ permissionView.setVisibility(View.VISIBLE);
+ } else {
+ setupCamera();
+ }
+
+ setTopView(cameraView);
+ return root;
+ }
+
+ private void setupCamera() {
+ CameraManager.get().setListener(this);
+ preview.setShown();
+ CameraManager.get().setRenderOverlay(focus);
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK);
+ setCameraUri(null);
+ }
+
+ @Override
+ public void onCameraError(int errorCode, Exception exception) {
+ LogUtil.e("CameraComposerFragment.onCameraError", "errorCode: ", errorCode, exception);
+ }
+
+ @Override
+ public void onCameraChanged() {
+ updateViewState();
+ }
+
+ @Override
+ public boolean shouldHide() {
+ return !processingUri && cameraUri == null;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == capture) {
+ float heightPercent = 1;
+ if (!getListener().isFullscreen()) {
+ heightPercent = Math.min((float) cameraView.getHeight() / preview.getView().getHeight(), 1);
+ }
+
+ showShutterEffect(shutter);
+ processingUri = true;
+ setCameraUri(null);
+ focus.getPieRenderer().clear();
+ CameraManager.get().takePicture(heightPercent, this);
+ } else if (view == swapCamera) {
+ ((Animatable) swapCamera.getDrawable()).start();
+ CameraManager.get().swapCamera();
+ } else if (view == cancel) {
+ processingUri = false;
+ setCameraUri(null);
+ } else if (view == exitFullscreen) {
+ getListener().showFullscreen(false);
+ fullscreen.setVisibility(View.VISIBLE);
+ exitFullscreen.setVisibility(View.GONE);
+ } else if (view == fullscreen) {
+ getListener().showFullscreen(true);
+ fullscreen.setVisibility(View.GONE);
+ exitFullscreen.setVisibility(View.VISIBLE);
+ } else if (view == allowPermission) {
+ // Checks to see if the user has permanently denied this permission. If this is the first
+ // time seeing this permission or they only pressed deny previously, they will see the
+ // permission request. If they permanently denied the permission, they will be sent to Dialer
+ // settings in order enable the permission.
+ if (PermissionsUtil.isFirstRequest(getContext(), permissions[0])
+ || shouldShowRequestPermissionRationale(permissions[0])) {
+ Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_REQUESTED);
+ LogUtil.i("CameraComposerFragment.onClick", "Camera permission requested.");
+ requestPermissions(permissions, CAMERA_PERMISSION);
+ } else {
+ Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_SETTINGS);
+ LogUtil.i("CameraComposerFragment.onClick", "Settings opened to enable permission.");
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setData(Uri.parse("package:" + getContext().getPackageName()));
+ startActivity(intent);
+ }
+ }
+ }
+
+ /**
+ * Called by {@link com.android.dialer.callcomposer.camera.ImagePersistTask} when the image is
+ * finished being cropped and stored on the device.
+ */
+ @Override
+ public void onMediaReady(Uri uri, String contentType, int width, int height) {
+ if (processingUri) {
+ processingUri = false;
+ setCameraUri(uri);
+ // If the user needed the URI before it was ready, uriCallback will be set and we should
+ // send the URI to them ASAP.
+ if (uriCallback != null) {
+ uriCallback.uriReady(uri);
+ uriCallback = null;
+ }
+ } else {
+ updateViewState();
+ }
+ }
+
+ /**
+ * Called by {@link com.android.dialer.callcomposer.camera.ImagePersistTask} when the image failed
+ * to crop or be stored on the device.
+ */
+ @Override
+ public void onMediaFailed(Exception exception) {
+ LogUtil.e("CallComposerFragment.onMediaFailed", null, exception);
+ Toast.makeText(getContext(), R.string.camera_media_failure, Toast.LENGTH_LONG).show();
+ setCameraUri(null);
+ processingUri = false;
+ if (uriCallback != null) {
+ loading.setVisibility(View.GONE);
+ uriCallback = null;
+ }
+ }
+
+ /**
+ * Usually called by {@link CameraManager} if the user does something to interrupt the picture
+ * while it's being taken (like switching the camera).
+ */
+ @Override
+ public void onMediaInfo(int what) {
+ if (what == MediaCallback.MEDIA_NO_DATA) {
+ Toast.makeText(getContext(), R.string.camera_media_failure, Toast.LENGTH_LONG).show();
+ }
+ setCameraUri(null);
+ processingUri = false;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ CameraManager.get().setListener(null);
+ }
+
+ private void showShutterEffect(final View shutterVisual) {
+ float maxAlpha = .7f;
+ int animationDurationMillis = 100;
+
+ AnimationSet animation = new AnimationSet(false /* shareInterpolator */);
+ Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha);
+ alphaInAnimation.setDuration(animationDurationMillis);
+ animation.addAnimation(alphaInAnimation);
+
+ Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f);
+ alphaOutAnimation.setStartOffset(animationDurationMillis);
+ alphaOutAnimation.setDuration(animationDurationMillis);
+ animation.addAnimation(alphaOutAnimation);
+
+ animation.setAnimationListener(
+ new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ shutterVisual.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ shutterVisual.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ });
+ shutterVisual.startAnimation(animation);
+ }
+
+ @NonNull
+ public String getMimeType() {
+ return "image/jpeg";
+ }
+
+ private void setCameraUri(Uri uri) {
+ cameraUri = uri;
+ // It's possible that if the user takes a picture and press back very quickly, the activity will
+ // no longer be alive and when the image cropping process completes, so we need to check that
+ // activity is still alive before trying to invoke it.
+ if (getListener() != null) {
+ updateViewState();
+ getListener().composeCall(this);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (PermissionsUtil.hasCameraPermissions(getContext())) {
+ permissionView.setVisibility(View.GONE);
+ setupCamera();
+ }
+ }
+
+ /** Updates the state of the buttons and overlays based on the current state of the view */
+ private void updateViewState() {
+ Assert.isNotNull(cameraView);
+ Assert.isNotNull(getContext());
+
+ boolean isCameraAvailable = CameraManager.get().isCameraAvailable();
+ boolean uriReadyOrProcessing = cameraUri != null || processingUri;
+
+ if (cameraUri == null && isCameraAvailable) {
+ CameraManager.get().resetPreview();
+ cancel.setVisibility(View.GONE);
+ }
+
+ if (!CameraManager.get().hasFrontAndBackCamera()) {
+ swapCamera.setVisibility(View.GONE);
+ } else {
+ swapCamera.setVisibility(uriReadyOrProcessing ? View.GONE : View.VISIBLE);
+ }
+
+ capture.setVisibility(uriReadyOrProcessing ? View.GONE : View.VISIBLE);
+ cancel.setVisibility(uriReadyOrProcessing ? View.VISIBLE : View.GONE);
+
+ if (uriReadyOrProcessing) {
+ fullscreen.setVisibility(View.GONE);
+ exitFullscreen.setVisibility(View.GONE);
+ } else if (getListener().isFullscreen()) {
+ exitFullscreen.setVisibility(View.VISIBLE);
+ fullscreen.setVisibility(View.GONE);
+ } else {
+ exitFullscreen.setVisibility(View.GONE);
+ fullscreen.setVisibility(View.VISIBLE);
+ }
+
+ swapCamera.setEnabled(isCameraAvailable);
+ capture.setEnabled(isCameraAvailable);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (permissions.length > 0 && permissions[0].equals(this.permissions[0])) {
+ PermissionsUtil.permissionRequested(getContext(), permissions[0]);
+ }
+ if (requestCode == CAMERA_PERMISSION
+ && grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_GRANTED);
+ LogUtil.i("CameraComposerFragment.onRequestPermissionsResult", "Permission granted.");
+ permissionView.setVisibility(View.GONE);
+ setupCamera();
+ } else if (requestCode == CAMERA_PERMISSION) {
+ Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_DENIED);
+ LogUtil.i("CameraComposerFragment.onRequestPermissionsResult", "Permission denied.");
+ }
+ }
+
+ public void getCameraUriWhenReady(CameraUriCallback callback) {
+ if (processingUri) {
+ loading.setVisibility(View.VISIBLE);
+ uriCallback = callback;
+ } else {
+ callback.uriReady(cameraUri);
+ }
+ }
+
+ /** Callback to let the caller know when the URI is ready. */
+ public interface CameraUriCallback {
+ void uriReady(Uri uri);
+ }
+}