• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.messaging.ui.mediapicker;
18 
19 import android.Manifest;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.graphics.Rect;
23 import android.hardware.Camera;
24 import android.net.Uri;
25 import android.os.SystemClock;
26 import android.view.LayoutInflater;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.animation.AlphaAnimation;
31 import android.view.animation.Animation;
32 import android.view.animation.AnimationSet;
33 import android.widget.Chronometer;
34 import android.widget.ImageButton;
35 
36 import com.android.messaging.R;
37 import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
38 import com.android.messaging.ui.mediapicker.CameraManager.MediaCallback;
39 import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
40 import com.android.messaging.util.Assert;
41 import com.android.messaging.util.LogUtil;
42 import com.android.messaging.util.OsUtil;
43 import com.android.messaging.util.UiUtils;
44 
45 /**
46  * Chooser which allows the user to take pictures or video without leaving the current app/activity
47  */
48 class CameraMediaChooser extends MediaChooser implements
49         CameraManager.CameraManagerListener {
50     private CameraPreview.CameraPreviewHost mCameraPreviewHost;
51     private ImageButton mFullScreenButton;
52     private ImageButton mSwapCameraButton;
53     private ImageButton mSwapModeButton;
54     private ImageButton mCaptureButton;
55     private ImageButton mCancelVideoButton;
56     private Chronometer mVideoCounter;
57     private boolean mVideoCancelled;
58     private int mErrorToast;
59     private View mEnabledView;
60     private View mMissingPermissionView;
61 
CameraMediaChooser(final MediaPicker mediaPicker)62     CameraMediaChooser(final MediaPicker mediaPicker) {
63         super(mediaPicker);
64     }
65 
66     @Override
getSupportedMediaTypes()67     public int getSupportedMediaTypes() {
68         if (CameraManager.get().hasAnyCamera()) {
69             return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
70         } else {
71             return MediaPicker.MEDIA_TYPE_NONE;
72         }
73     }
74 
75     @Override
destroyView()76     public View destroyView() {
77         CameraManager.get().closeCamera();
78         CameraManager.get().setListener(null);
79         CameraManager.get().setSubscriptionDataProvider(null);
80         return super.destroyView();
81     }
82 
83     @Override
createView(final ViewGroup container)84     protected View createView(final ViewGroup container) {
85         CameraManager.get().setListener(this);
86         CameraManager.get().setSubscriptionDataProvider(this);
87         CameraManager.get().setVideoMode(false);
88         final LayoutInflater inflater = getLayoutInflater();
89         final CameraMediaChooserView view = (CameraMediaChooserView) inflater.inflate(
90                 R.layout.mediapicker_camera_chooser,
91                 container /* root */,
92                 false /* attachToRoot */);
93         mCameraPreviewHost = (CameraPreview.CameraPreviewHost) view.findViewById(
94                 R.id.camera_preview);
95         mCameraPreviewHost.getView().setOnTouchListener(new View.OnTouchListener() {
96             @Override
97             public boolean onTouch(final View view, final MotionEvent motionEvent) {
98                 if (CameraManager.get().isVideoMode()) {
99                     // Prevent the swipe down in video mode because video is always captured in
100                     // full screen
101                     return true;
102                 }
103 
104                 return false;
105             }
106         });
107 
108         final View shutterVisual = view.findViewById(R.id.camera_shutter_visual);
109 
110         mFullScreenButton = (ImageButton) view.findViewById(R.id.camera_fullScreen_button);
111         mFullScreenButton.setOnClickListener(new View.OnClickListener() {
112             @Override
113             public void onClick(final View view) {
114                 mMediaPicker.setFullScreen(true);
115             }
116         });
117 
118         mSwapCameraButton = (ImageButton) view.findViewById(R.id.camera_swapCamera_button);
119         mSwapCameraButton.setOnClickListener(new View.OnClickListener() {
120             @Override
121             public void onClick(final View view) {
122                 CameraManager.get().swapCamera();
123             }
124         });
125 
126         mCaptureButton = (ImageButton) view.findViewById(R.id.camera_capture_button);
127         mCaptureButton.setOnClickListener(new View.OnClickListener() {
128             @Override
129             public void onClick(final View v) {
130                 final float heightPercent = Math.min(mMediaPicker.getViewPager().getHeight() /
131                         (float) mCameraPreviewHost.getView().getHeight(), 1);
132 
133                 if (CameraManager.get().isRecording()) {
134                     CameraManager.get().stopVideo();
135                 } else {
136                     final CameraManager.MediaCallback callback = new CameraManager.MediaCallback() {
137                         @Override
138                         public void onMediaReady(
139                                 final Uri uriToVideo, final String contentType,
140                                 final int width, final int height) {
141                             mVideoCounter.stop();
142                             if (mVideoCancelled || uriToVideo == null) {
143                                 mVideoCancelled = false;
144                             } else {
145                                 final Rect startRect = new Rect();
146                                 // It's possible to throw out the chooser while taking the
147                                 // picture/video.  In that case, still use the attachment, just
148                                 // skip the startRect
149                                 if (mView != null) {
150                                     mView.getGlobalVisibleRect(startRect);
151                                 }
152                                 mMediaPicker.dispatchItemsSelected(
153                                         new MediaPickerMessagePartData(startRect, contentType,
154                                                 uriToVideo, width, height),
155                                         true /* dismissMediaPicker */);
156                             }
157                             updateViewState();
158                         }
159 
160                         @Override
161                         public void onMediaFailed(final Exception exception) {
162                             UiUtils.showToastAtBottom(R.string.camera_media_failure);
163                             updateViewState();
164                         }
165 
166                         @Override
167                         public void onMediaInfo(final int what) {
168                             if (what == MediaCallback.MEDIA_NO_DATA) {
169                                 UiUtils.showToastAtBottom(R.string.camera_media_failure);
170                             }
171                             updateViewState();
172                         }
173                     };
174                     if (CameraManager.get().isVideoMode()) {
175                         CameraManager.get().startVideo(callback);
176                         mVideoCounter.setBase(SystemClock.elapsedRealtime());
177                         mVideoCounter.start();
178                         updateViewState();
179                     } else {
180                         showShutterEffect(shutterVisual);
181                         CameraManager.get().takePicture(heightPercent, callback);
182                         updateViewState();
183                     }
184                 }
185             }
186         });
187 
188         mSwapModeButton = (ImageButton) view.findViewById(R.id.camera_swap_mode_button);
189         mSwapModeButton.setOnClickListener(new View.OnClickListener() {
190             @Override
191             public void onClick(final View view) {
192                 final boolean isSwitchingToVideo = !CameraManager.get().isVideoMode();
193                 if (isSwitchingToVideo && !OsUtil.hasRecordAudioPermission()) {
194                     requestRecordAudioPermission();
195                 } else {
196                     onSwapMode();
197                 }
198             }
199         });
200 
201         mCancelVideoButton = (ImageButton) view.findViewById(R.id.camera_cancel_button);
202         mCancelVideoButton.setOnClickListener(new View.OnClickListener() {
203             @Override
204             public void onClick(final View view) {
205                 mVideoCancelled = true;
206                 CameraManager.get().stopVideo();
207                 mMediaPicker.dismiss(true);
208             }
209         });
210 
211         mVideoCounter = (Chronometer) view.findViewById(R.id.camera_video_counter);
212 
213         CameraManager.get().setRenderOverlay((RenderOverlay) view.findViewById(R.id.focus_visual));
214 
215         mEnabledView = view.findViewById(R.id.mediapicker_enabled);
216         mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
217 
218         // Must set mView before calling updateViewState because it operates on mView
219         mView = view;
220         updateViewState();
221         updateForPermissionState(CameraManager.hasCameraPermission());
222         return view;
223     }
224 
225     @Override
getIconResource()226     public int getIconResource() {
227         return R.drawable.ic_camera_light;
228     }
229 
230     @Override
getIconDescriptionResource()231     public int getIconDescriptionResource() {
232         return R.string.mediapicker_cameraChooserDescription;
233     }
234 
235     /**
236      * Updates the view when entering or leaving full-screen camera mode
237      * @param fullScreen
238      */
239     @Override
onFullScreenChanged(final boolean fullScreen)240     void onFullScreenChanged(final boolean fullScreen) {
241         super.onFullScreenChanged(fullScreen);
242         if (!fullScreen && CameraManager.get().isVideoMode()) {
243             CameraManager.get().setVideoMode(false);
244         }
245         updateViewState();
246     }
247 
248     /**
249      * Initializes the control to a default state when it is opened / closed
250      * @param open True if the control is opened
251      */
252     @Override
onOpenedChanged(final boolean open)253     void onOpenedChanged(final boolean open) {
254         super.onOpenedChanged(open);
255         updateViewState();
256     }
257 
258     @Override
setSelected(final boolean selected)259     protected void setSelected(final boolean selected) {
260         super.setSelected(selected);
261         if (selected) {
262             if (CameraManager.hasCameraPermission()) {
263                 // If an error occurred before the chooser was selected, show it now
264                 showErrorToastIfNeeded();
265             } else {
266                 requestCameraPermission();
267             }
268         }
269     }
270 
requestCameraPermission()271     private void requestCameraPermission() {
272         mMediaPicker.requestPermissions(new String[] { Manifest.permission.CAMERA },
273                 MediaPicker.CAMERA_PERMISSION_REQUEST_CODE);
274     }
275 
requestRecordAudioPermission()276     private void requestRecordAudioPermission() {
277         mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
278                 MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
279     }
280 
281     @Override
onRequestPermissionsResult( final int requestCode, final String permissions[], final int[] grantResults)282     protected void onRequestPermissionsResult(
283             final int requestCode, final String permissions[], final int[] grantResults) {
284         if (requestCode == MediaPicker.CAMERA_PERMISSION_REQUEST_CODE) {
285             final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
286             updateForPermissionState(permissionGranted);
287             if (permissionGranted) {
288                 mCameraPreviewHost.onCameraPermissionGranted();
289             }
290         } else if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
291             Assert.isFalse(CameraManager.get().isVideoMode());
292             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
293                 // Switch to video mode
294                 onSwapMode();
295             } else {
296                 // Stay in still-photo mode
297             }
298         }
299     }
300 
updateForPermissionState(final boolean granted)301     private void updateForPermissionState(final boolean granted) {
302         // onRequestPermissionsResult can sometimes get called before createView().
303         if (mEnabledView == null) {
304             return;
305         }
306 
307         mEnabledView.setVisibility(granted ? View.VISIBLE : View.GONE);
308         mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
309     }
310 
311     @Override
canSwipeDown()312     public boolean canSwipeDown() {
313         if (CameraManager.get().isVideoMode()) {
314             return true;
315         }
316         return super.canSwipeDown();
317     }
318 
319     /**
320      * Handles an error from the camera manager by showing the appropriate error message to the user
321      * @param errorCode One of the CameraManager.ERROR_* constants
322      * @param e The exception which caused the error, if any
323      */
324     @Override
onCameraError(final int errorCode, final Exception e)325     public void onCameraError(final int errorCode, final Exception e) {
326         switch (errorCode) {
327             case CameraManager.ERROR_OPENING_CAMERA:
328             case CameraManager.ERROR_SHOWING_PREVIEW:
329                 mErrorToast = R.string.camera_error_opening;
330                 break;
331             case CameraManager.ERROR_INITIALIZING_VIDEO:
332                 mErrorToast = R.string.camera_error_video_init_fail;
333                 updateViewState();
334                 break;
335             case CameraManager.ERROR_STORAGE_FAILURE:
336                 mErrorToast = R.string.camera_error_storage_fail;
337                 updateViewState();
338                 break;
339             case CameraManager.ERROR_TAKING_PICTURE:
340                 mErrorToast = R.string.camera_error_failure_taking_picture;
341                 break;
342             default:
343                 mErrorToast = R.string.camera_error_unknown;
344                 LogUtil.w(LogUtil.BUGLE_TAG, "Unknown camera error:" + errorCode);
345                 break;
346         }
347         showErrorToastIfNeeded();
348     }
349 
showErrorToastIfNeeded()350     private void showErrorToastIfNeeded() {
351         if (mErrorToast != 0 && mSelected) {
352             UiUtils.showToastAtBottom(mErrorToast);
353             mErrorToast = 0;
354         }
355     }
356 
357     @Override
onCameraChanged()358     public void onCameraChanged() {
359         updateViewState();
360     }
361 
onSwapMode()362     private void onSwapMode() {
363         CameraManager.get().setVideoMode(!CameraManager.get().isVideoMode());
364         if (CameraManager.get().isVideoMode()) {
365             mMediaPicker.setFullScreen(true);
366 
367             // For now we start recording immediately
368             mCaptureButton.performClick();
369         }
370         updateViewState();
371     }
372 
showShutterEffect(final View shutterVisual)373     private void showShutterEffect(final View shutterVisual) {
374         final float maxAlpha = getContext().getResources().getFraction(
375                 R.fraction.camera_shutter_max_alpha, 1 /* base */, 1 /* pBase */);
376 
377         // Divide by 2 so each half of the animation adds up to the full duration
378         final int animationDuration = getContext().getResources().getInteger(
379                 R.integer.camera_shutter_duration) / 2;
380 
381         final AnimationSet animation = new AnimationSet(false /* shareInterpolator */);
382         final Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha);
383         alphaInAnimation.setDuration(animationDuration);
384         animation.addAnimation(alphaInAnimation);
385 
386         final Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f);
387         alphaOutAnimation.setStartOffset(animationDuration);
388         alphaOutAnimation.setDuration(animationDuration);
389         animation.addAnimation(alphaOutAnimation);
390 
391         animation.setAnimationListener(new Animation.AnimationListener() {
392             @Override
393             public void onAnimationStart(final Animation animation) {
394                 shutterVisual.setVisibility(View.VISIBLE);
395             }
396 
397             @Override
398             public void onAnimationEnd(final Animation animation) {
399                 shutterVisual.setVisibility(View.GONE);
400             }
401 
402             @Override
403             public void onAnimationRepeat(final Animation animation) {
404             }
405         });
406         shutterVisual.startAnimation(animation);
407     }
408 
409     /** Updates the state of the buttons and overlays based on the current state of the view */
updateViewState()410     private void updateViewState() {
411         if (mView == null) {
412             return;
413         }
414 
415         final Context context = getContext();
416         if (context == null) {
417             // Context is null if the fragment was already removed from the activity
418             return;
419         }
420         final boolean fullScreen = mMediaPicker.isFullScreen();
421         final boolean videoMode = CameraManager.get().isVideoMode();
422         final boolean isRecording = CameraManager.get().isRecording();
423         final boolean isCameraAvailable = isCameraAvailable();
424         final Camera.CameraInfo cameraInfo = CameraManager.get().getCameraInfo();
425         final boolean frontCamera = cameraInfo != null && cameraInfo.facing ==
426                 Camera.CameraInfo.CAMERA_FACING_FRONT;
427 
428         mView.setSystemUiVisibility(
429                 fullScreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE :
430                 View.SYSTEM_UI_FLAG_VISIBLE);
431 
432         mFullScreenButton.setVisibility(!fullScreen ? View.VISIBLE : View.GONE);
433         mFullScreenButton.setEnabled(isCameraAvailable);
434         mSwapCameraButton.setVisibility(
435                 fullScreen && !isRecording && CameraManager.get().hasFrontAndBackCamera() ?
436                         View.VISIBLE : View.GONE);
437         mSwapCameraButton.setImageResource(frontCamera ?
438                 R.drawable.ic_camera_front_light :
439                 R.drawable.ic_camera_rear_light);
440         mSwapCameraButton.setEnabled(isCameraAvailable);
441 
442         mCancelVideoButton.setVisibility(isRecording ? View.VISIBLE : View.GONE);
443         mVideoCounter.setVisibility(isRecording ? View.VISIBLE : View.GONE);
444 
445         mSwapModeButton.setImageResource(videoMode ?
446                 R.drawable.ic_mp_camera_small_light :
447                 R.drawable.ic_mp_video_small_light);
448         mSwapModeButton.setContentDescription(context.getString(videoMode ?
449                 R.string.camera_switch_to_still_mode : R.string.camera_switch_to_video_mode));
450         mSwapModeButton.setVisibility(isRecording ? View.GONE : View.VISIBLE);
451         mSwapModeButton.setEnabled(isCameraAvailable);
452 
453         if (isRecording) {
454             mCaptureButton.setImageResource(R.drawable.ic_mp_capture_stop_large_light);
455             mCaptureButton.setContentDescription(context.getString(
456                     R.string.camera_stop_recording));
457         } else if (videoMode) {
458             mCaptureButton.setImageResource(R.drawable.ic_mp_video_large_light);
459             mCaptureButton.setContentDescription(context.getString(
460                     R.string.camera_start_recording));
461         } else {
462             mCaptureButton.setImageResource(R.drawable.ic_checkmark_large_light);
463             mCaptureButton.setContentDescription(context.getString(
464                     R.string.camera_take_picture));
465         }
466         mCaptureButton.setEnabled(isCameraAvailable);
467     }
468 
469     @Override
getActionBarTitleResId()470     int getActionBarTitleResId() {
471         return 0;
472     }
473 
474     /**
475      * Returns if the camera is currently ready camera is loaded and not taking a picture.
476      * otherwise we should avoid taking another picture, swapping camera or recording video.
477      */
isCameraAvailable()478     private boolean isCameraAvailable() {
479         return CameraManager.get().isCameraAvailable();
480     }
481 }
482