• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.dialer.callcomposer;
18 
19 import android.Manifest;
20 import android.Manifest.permission;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.graphics.drawable.Animatable;
24 import android.hardware.Camera.CameraInfo;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.provider.Settings;
28 import android.support.annotation.NonNull;
29 import android.support.annotation.Nullable;
30 import android.support.v4.content.ContextCompat;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.View.OnClickListener;
34 import android.view.ViewGroup;
35 import android.view.animation.AlphaAnimation;
36 import android.view.animation.Animation;
37 import android.view.animation.AnimationSet;
38 import android.widget.ImageButton;
39 import android.widget.ImageView;
40 import android.widget.ProgressBar;
41 import android.widget.TextView;
42 import android.widget.Toast;
43 import com.android.dialer.callcomposer.camera.CameraManager;
44 import com.android.dialer.callcomposer.camera.CameraManager.CameraManagerListener;
45 import com.android.dialer.callcomposer.camera.CameraManager.MediaCallback;
46 import com.android.dialer.callcomposer.camera.CameraPreview.CameraPreviewHost;
47 import com.android.dialer.callcomposer.camera.camerafocus.RenderOverlay;
48 import com.android.dialer.callcomposer.cameraui.CameraMediaChooserView;
49 import com.android.dialer.common.Assert;
50 import com.android.dialer.common.LogUtil;
51 import com.android.dialer.logging.DialerImpression;
52 import com.android.dialer.logging.Logger;
53 import com.android.dialer.util.PermissionsUtil;
54 
55 /** Fragment used to compose call with image from the user's camera. */
56 public class CameraComposerFragment extends CallComposerFragment
57     implements CameraManagerListener, OnClickListener, CameraManager.MediaCallback {
58 
59   private static final String CAMERA_DIRECTION_KEY = "camera_direction";
60   private static final String CAMERA_URI_KEY = "camera_key";
61 
62   private View permissionView;
63   private ImageButton exitFullscreen;
64   private ImageButton fullscreen;
65   private ImageButton swapCamera;
66   private ImageButton capture;
67   private ImageButton cancel;
68   private CameraMediaChooserView cameraView;
69   private RenderOverlay focus;
70   private View shutter;
71   private View allowPermission;
72   private CameraPreviewHost preview;
73   private ProgressBar loading;
74   private ImageView previewImageView;
75 
76   private Uri cameraUri;
77   private boolean processingUri;
78   private String[] permissions = new String[] {Manifest.permission.CAMERA};
79   private CameraUriCallback uriCallback;
80   private int cameraDirection = CameraInfo.CAMERA_FACING_BACK;
81 
newInstance()82   public static CameraComposerFragment newInstance() {
83     return new CameraComposerFragment();
84   }
85 
86   @Nullable
87   @Override
onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle bundle)88   public View onCreateView(
89       LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle bundle) {
90     View root = inflater.inflate(R.layout.fragment_camera_composer, container, false);
91     permissionView = root.findViewById(R.id.permission_view);
92     loading = (ProgressBar) root.findViewById(R.id.loading);
93     cameraView = (CameraMediaChooserView) root.findViewById(R.id.camera_view);
94     shutter = cameraView.findViewById(R.id.camera_shutter_visual);
95     exitFullscreen = (ImageButton) cameraView.findViewById(R.id.camera_exit_fullscreen);
96     fullscreen = (ImageButton) cameraView.findViewById(R.id.camera_fullscreen);
97     swapCamera = (ImageButton) cameraView.findViewById(R.id.swap_camera_button);
98     capture = (ImageButton) cameraView.findViewById(R.id.camera_capture_button);
99     cancel = (ImageButton) cameraView.findViewById(R.id.camera_cancel_button);
100     focus = (RenderOverlay) cameraView.findViewById(R.id.focus_visual);
101     preview = (CameraPreviewHost) cameraView.findViewById(R.id.camera_preview);
102     previewImageView = (ImageView) root.findViewById(R.id.preview_image_view);
103 
104     exitFullscreen.setOnClickListener(this);
105     fullscreen.setOnClickListener(this);
106     swapCamera.setOnClickListener(this);
107     capture.setOnClickListener(this);
108     cancel.setOnClickListener(this);
109 
110     if (!PermissionsUtil.hasPermission(getContext(), permission.CAMERA)) {
111       LogUtil.i("CameraComposerFragment.onCreateView", "Permission view shown.");
112       Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_DISPLAYED);
113       ImageView permissionImage = (ImageView) permissionView.findViewById(R.id.permission_icon);
114       TextView permissionText = (TextView) permissionView.findViewById(R.id.permission_text);
115       allowPermission = permissionView.findViewById(R.id.allow);
116 
117       allowPermission.setOnClickListener(this);
118       permissionText.setText(R.string.camera_permission_text);
119       permissionImage.setImageResource(R.drawable.quantum_ic_camera_alt_white_48);
120       permissionImage.setColorFilter(
121           ContextCompat.getColor(getContext(), R.color.dialer_theme_color));
122       permissionView.setVisibility(View.VISIBLE);
123     } else {
124       if (bundle != null) {
125         cameraDirection = bundle.getInt(CAMERA_DIRECTION_KEY);
126         cameraUri = bundle.getParcelable(CAMERA_URI_KEY);
127       }
128       setupCamera();
129     }
130     return root;
131   }
132 
setupCamera()133   private void setupCamera() {
134     CameraManager.get().setListener(this);
135     preview.setShown();
136     CameraManager.get().setRenderOverlay(focus);
137     CameraManager.get().selectCamera(cameraDirection);
138     setCameraUri(cameraUri);
139   }
140 
141   @Override
onCameraError(int errorCode, Exception exception)142   public void onCameraError(int errorCode, Exception exception) {
143     LogUtil.e("CameraComposerFragment.onCameraError", "errorCode: ", errorCode, exception);
144   }
145 
146   @Override
onCameraChanged()147   public void onCameraChanged() {
148     updateViewState();
149   }
150 
151   @Override
shouldHide()152   public boolean shouldHide() {
153     return !processingUri && cameraUri == null;
154   }
155 
156   @Override
clearComposer()157   public void clearComposer() {
158     processingUri = false;
159     setCameraUri(null);
160   }
161 
162   @Override
onClick(View view)163   public void onClick(View view) {
164     if (view == capture) {
165       float heightPercent = 1;
166       if (!getListener().isFullscreen() && !getListener().isLandscapeLayout()) {
167         heightPercent = Math.min((float) cameraView.getHeight() / preview.getView().getHeight(), 1);
168       }
169 
170       showShutterEffect(shutter);
171       processingUri = true;
172       setCameraUri(null);
173       focus.getPieRenderer().clear();
174       CameraManager.get().takePicture(heightPercent, this);
175     } else if (view == swapCamera) {
176       ((Animatable) swapCamera.getDrawable()).start();
177       CameraManager.get().swapCamera();
178       cameraDirection = CameraManager.get().getCameraInfo().facing;
179     } else if (view == cancel) {
180       clearComposer();
181     } else if (view == exitFullscreen) {
182       getListener().showFullscreen(false);
183       fullscreen.setVisibility(View.VISIBLE);
184       exitFullscreen.setVisibility(View.GONE);
185     } else if (view == fullscreen) {
186       getListener().showFullscreen(true);
187       fullscreen.setVisibility(View.GONE);
188       exitFullscreen.setVisibility(View.VISIBLE);
189     } else if (view == allowPermission) {
190       // Checks to see if the user has permanently denied this permission. If this is the first
191       // time seeing this permission or they only pressed deny previously, they will see the
192       // permission request. If they permanently denied the permission, they will be sent to Dialer
193       // settings in order enable the permission.
194       if (PermissionsUtil.isFirstRequest(getContext(), permissions[0])
195           || shouldShowRequestPermissionRationale(permissions[0])) {
196         Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_REQUESTED);
197         LogUtil.i("CameraComposerFragment.onClick", "Camera permission requested.");
198         requestPermissions(permissions, CAMERA_PERMISSION);
199       } else {
200         Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_SETTINGS);
201         LogUtil.i("CameraComposerFragment.onClick", "Settings opened to enable permission.");
202         Intent intent = new Intent(Intent.ACTION_VIEW);
203         intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
204         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
205         intent.setData(Uri.parse("package:" + getContext().getPackageName()));
206         startActivity(intent);
207       }
208     }
209   }
210 
211   /**
212    * Called by {@link com.android.dialer.callcomposer.camera.ImagePersistTask} when the image is
213    * finished being cropped and stored on the device.
214    */
215   @Override
onMediaReady(Uri uri, String contentType, int width, int height)216   public void onMediaReady(Uri uri, String contentType, int width, int height) {
217     if (processingUri) {
218       processingUri = false;
219       setCameraUri(uri);
220       // If the user needed the URI before it was ready, uriCallback will be set and we should
221       // send the URI to them ASAP.
222       if (uriCallback != null) {
223         uriCallback.uriReady(uri);
224         uriCallback = null;
225       }
226     } else {
227       updateViewState();
228     }
229   }
230 
231   /**
232    * Called by {@link com.android.dialer.callcomposer.camera.ImagePersistTask} when the image failed
233    * to crop or be stored on the device.
234    */
235   @Override
onMediaFailed(Exception exception)236   public void onMediaFailed(Exception exception) {
237     LogUtil.e("CallComposerFragment.onMediaFailed", null, exception);
238     Toast.makeText(getContext(), R.string.camera_media_failure, Toast.LENGTH_LONG).show();
239     setCameraUri(null);
240     processingUri = false;
241     if (uriCallback != null) {
242       loading.setVisibility(View.GONE);
243       uriCallback = null;
244     }
245   }
246 
247   /**
248    * Usually called by {@link CameraManager} if the user does something to interrupt the picture
249    * while it's being taken (like switching the camera).
250    */
251   @Override
onMediaInfo(int what)252   public void onMediaInfo(int what) {
253     if (what == MediaCallback.MEDIA_NO_DATA) {
254       Toast.makeText(getContext(), R.string.camera_media_failure, Toast.LENGTH_LONG).show();
255     }
256     setCameraUri(null);
257     processingUri = false;
258   }
259 
260   @Override
onDestroy()261   public void onDestroy() {
262     super.onDestroy();
263     CameraManager.get().setListener(null);
264   }
265 
showShutterEffect(final View shutterVisual)266   private void showShutterEffect(final View shutterVisual) {
267     float maxAlpha = .7f;
268     int animationDurationMillis = 100;
269 
270     AnimationSet animation = new AnimationSet(false /* shareInterpolator */);
271     Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha);
272     alphaInAnimation.setDuration(animationDurationMillis);
273     animation.addAnimation(alphaInAnimation);
274 
275     Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f);
276     alphaOutAnimation.setStartOffset(animationDurationMillis);
277     alphaOutAnimation.setDuration(animationDurationMillis);
278     animation.addAnimation(alphaOutAnimation);
279 
280     animation.setAnimationListener(
281         new Animation.AnimationListener() {
282           @Override
283           public void onAnimationStart(Animation animation) {
284             shutterVisual.setVisibility(View.VISIBLE);
285           }
286 
287           @Override
288           public void onAnimationEnd(Animation animation) {
289             shutterVisual.setVisibility(View.GONE);
290           }
291 
292           @Override
293           public void onAnimationRepeat(Animation animation) {}
294         });
295     shutterVisual.startAnimation(animation);
296   }
297 
298   @NonNull
getMimeType()299   public String getMimeType() {
300     return "image/jpeg";
301   }
302 
setCameraUri(Uri uri)303   private void setCameraUri(Uri uri) {
304     cameraUri = uri;
305     // It's possible that if the user takes a picture and press back very quickly, the activity will
306     // no longer be alive and when the image cropping process completes, so we need to check that
307     // activity is still alive before trying to invoke it.
308     if (getListener() != null) {
309       updateViewState();
310       getListener().composeCall(this);
311     }
312   }
313 
314   @Override
onResume()315   public void onResume() {
316     super.onResume();
317     if (PermissionsUtil.hasCameraPermissions(getContext())) {
318       permissionView.setVisibility(View.GONE);
319       setupCamera();
320     }
321   }
322 
323   /** Updates the state of the buttons and overlays based on the current state of the view */
updateViewState()324   private void updateViewState() {
325     Assert.isNotNull(cameraView);
326     Assert.isNotNull(getContext());
327 
328     boolean isCameraAvailable = CameraManager.get().isCameraAvailable();
329     boolean uriReadyOrProcessing = cameraUri != null || processingUri;
330 
331     if (cameraUri != null) {
332       previewImageView.setImageURI(cameraUri);
333       previewImageView.setVisibility(View.VISIBLE);
334       previewImageView.setScaleX(cameraDirection == CameraInfo.CAMERA_FACING_FRONT ? -1 : 1);
335     } else {
336       previewImageView.setVisibility(View.GONE);
337     }
338 
339     if (cameraUri == null && isCameraAvailable) {
340       CameraManager.get().resetPreview();
341       cancel.setVisibility(View.GONE);
342     }
343 
344     if (!CameraManager.get().hasFrontAndBackCamera()) {
345       swapCamera.setVisibility(View.GONE);
346     } else {
347       swapCamera.setVisibility(uriReadyOrProcessing ? View.GONE : View.VISIBLE);
348     }
349 
350     capture.setVisibility(uriReadyOrProcessing ? View.GONE : View.VISIBLE);
351     cancel.setVisibility(uriReadyOrProcessing ? View.VISIBLE : View.GONE);
352 
353     if (uriReadyOrProcessing || getListener().isLandscapeLayout()) {
354       fullscreen.setVisibility(View.GONE);
355       exitFullscreen.setVisibility(View.GONE);
356     } else if (getListener().isFullscreen()) {
357       exitFullscreen.setVisibility(View.VISIBLE);
358       fullscreen.setVisibility(View.GONE);
359     } else {
360       exitFullscreen.setVisibility(View.GONE);
361       fullscreen.setVisibility(View.VISIBLE);
362     }
363 
364     swapCamera.setEnabled(isCameraAvailable);
365     capture.setEnabled(isCameraAvailable);
366   }
367 
368   @Override
onSaveInstanceState(Bundle outState)369   public void onSaveInstanceState(Bundle outState) {
370     super.onSaveInstanceState(outState);
371     outState.putInt(CAMERA_DIRECTION_KEY, cameraDirection);
372     outState.putParcelable(CAMERA_URI_KEY, cameraUri);
373   }
374 
375   @Override
onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)376   public void onRequestPermissionsResult(
377       int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
378     if (permissions.length > 0 && permissions[0].equals(this.permissions[0])) {
379       PermissionsUtil.permissionRequested(getContext(), permissions[0]);
380     }
381     if (requestCode == CAMERA_PERMISSION
382         && grantResults.length > 0
383         && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
384       Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_GRANTED);
385       LogUtil.i("CameraComposerFragment.onRequestPermissionsResult", "Permission granted.");
386       permissionView.setVisibility(View.GONE);
387       setupCamera();
388     } else if (requestCode == CAMERA_PERMISSION) {
389       Logger.get(getContext()).logImpression(DialerImpression.Type.CAMERA_PERMISSION_DENIED);
390       LogUtil.i("CameraComposerFragment.onRequestPermissionsResult", "Permission denied.");
391     }
392   }
393 
getCameraUriWhenReady(CameraUriCallback callback)394   public void getCameraUriWhenReady(CameraUriCallback callback) {
395     if (processingUri) {
396       loading.setVisibility(View.VISIBLE);
397       uriCallback = callback;
398     } else {
399       callback.uriReady(cameraUri);
400     }
401   }
402 
403   /** Callback to let the caller know when the URI is ready. */
404   public interface CameraUriCallback {
uriReady(Uri uri)405     void uriReady(Uri uri);
406   }
407 }
408