Initial checkin of AOSP Messaging app.

b/23110861

Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a
This commit is contained in:
Mike Dodd
2015-08-12 08:58:28 -07:00
parent 8b3e2b9c1b
commit 461a34b466
1645 changed files with 186271 additions and 0 deletions
@@ -0,0 +1,481 @@
/*
* 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.Manifest;
import android.content.Context;
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.MotionEvent;
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 com.android.messaging.R;
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;
/**
* 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;
private View mEnabledView;
private View mMissingPermissionView;
CameraMediaChooser(final MediaPicker mediaPicker) {
super(mediaPicker);
}
@Override
public int getSupportedMediaTypes() {
if (CameraManager.get().hasAnyCamera()) {
return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
} else {
return MediaPicker.MEDIA_TYPE_NONE;
}
}
@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(
R.layout.mediapicker_camera_chooser,
container /* root */,
false /* attachToRoot */);
mCameraPreviewHost = (CameraPreview.CameraPreviewHost) view.findViewById(
R.id.camera_preview);
mCameraPreviewHost.getView().setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(final View view, final MotionEvent 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(new View.OnClickListener() {
@Override
public void onClick(final View view) {
mMediaPicker.setFullScreen(true);
}
});
mSwapCameraButton = (ImageButton) view.findViewById(R.id.camera_swapCamera_button);
mSwapCameraButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
CameraManager.get().swapCamera();
}
});
mCaptureButton = (ImageButton) view.findViewById(R.id.camera_capture_button);
mCaptureButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
final float heightPercent = Math.min(mMediaPicker.getViewPager().getHeight() /
(float) mCameraPreviewHost.getView().getHeight(), 1);
if (CameraManager.get().isRecording()) {
CameraManager.get().stopVideo();
} else {
final CameraManager.MediaCallback callback = new CameraManager.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(new View.OnClickListener() {
@Override
public void onClick(final View view) {
final boolean isSwitchingToVideo = !CameraManager.get().isVideoMode();
if (isSwitchingToVideo && !OsUtil.hasRecordAudioPermission()) {
requestRecordAudioPermission();
} else {
onSwapMode();
}
}
});
mCancelVideoButton = (ImageButton) view.findViewById(R.id.camera_cancel_button);
mCancelVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
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());
return view;
}
@Override
public int getIconResource() {
return R.drawable.ic_camera_light;
}
@Override
public int getIconDescriptionResource() {
return R.string.mediapicker_cameraChooserDescription;
}
/**
* Updates the view when entering or leaving full-screen camera mode
* @param fullScreen
*/
@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 {
requestCameraPermission();
}
}
}
private void requestCameraPermission() {
mMediaPicker.requestPermissions(new String[] { Manifest.permission.CAMERA },
MediaPicker.CAMERA_PERMISSION_REQUEST_CODE);
}
private void requestRecordAudioPermission() {
mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
}
@Override
protected void onRequestPermissionsResult(
final int requestCode, final String permissions[], final int[] grantResults) {
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
}
}
}
private void updateForPermissionState(final boolean granted) {
// onRequestPermissionsResult can sometimes get called before createView().
if (mEnabledView == null) {
return;
}
mEnabledView.setVisibility(granted ? View.VISIBLE : View.GONE);
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_mp_camera_small_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_large_light);
mCaptureButton.setContentDescription(context.getString(
R.string.camera_stop_recording));
} else if (videoMode) {
mCaptureButton.setImageResource(R.drawable.ic_mp_video_large_light);
mCaptureButton.setContentDescription(context.getString(
R.string.camera_start_recording));
} else {
mCaptureButton.setImageResource(R.drawable.ic_checkmark_large_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();
}
}