Home | History | Annotate | Download | only in mediapicker
      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 
     62     CameraMediaChooser(final MediaPicker mediaPicker) {
     63         super(mediaPicker);
     64     }
     65 
     66     @Override
     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
     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
     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
    226     public int getIconResource() {
    227         return R.drawable.ic_camera_light;
    228     }
    229 
    230     @Override
    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
    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
    253     void onOpenedChanged(final boolean open) {
    254         super.onOpenedChanged(open);
    255         updateViewState();
    256     }
    257 
    258     @Override
    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 
    271     private void requestCameraPermission() {
    272         mMediaPicker.requestPermissions(new String[] { Manifest.permission.CAMERA },
    273                 MediaPicker.CAMERA_PERMISSION_REQUEST_CODE);
    274     }
    275 
    276     private void requestRecordAudioPermission() {
    277         mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
    278                 MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
    279     }
    280 
    281     @Override
    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 
    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
    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
    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 
    350     private void showErrorToastIfNeeded() {
    351         if (mErrorToast != 0 && mSelected) {
    352             UiUtils.showToastAtBottom(mErrorToast);
    353             mErrorToast = 0;
    354         }
    355     }
    356 
    357     @Override
    358     public void onCameraChanged() {
    359         updateViewState();
    360     }
    361 
    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 
    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 */
    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
    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      */
    478     private boolean isCameraAvailable() {
    479         return CameraManager.get().isCameraAvailable();
    480     }
    481 }
    482