| /* |
| * Copyright (C) 2009 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.camera; |
| |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Rect; |
| import android.hardware.Camera.Parameters; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import android.view.Window; |
| import android.view.WindowManager; |
| import android.view.animation.AlphaAnimation; |
| import android.view.animation.Animation; |
| import android.view.animation.DecelerateInterpolator; |
| |
| import com.android.camera.ui.LayoutChangeNotifier; |
| import com.android.camera.ui.PopupManager; |
| import com.android.gallery3d.app.AbstractGalleryActivity; |
| import com.android.gallery3d.app.AppBridge; |
| import com.android.gallery3d.app.FilmstripPage; |
| import com.android.gallery3d.app.GalleryActionBar; |
| import com.android.gallery3d.app.PhotoPage; |
| import com.android.gallery3d.common.ApiHelper; |
| import com.android.gallery3d.ui.ScreenNail; |
| import com.android.gallery3d.util.MediaSetUtils; |
| |
| /** |
| * Superclass of camera activity. |
| */ |
| public abstract class ActivityBase extends AbstractGalleryActivity |
| implements LayoutChangeNotifier.Listener { |
| |
| private static final String TAG = "ActivityBase"; |
| private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100; // milliseconds |
| private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = |
| "android.media.action.STILL_IMAGE_CAMERA_SECURE"; |
| public static final String ACTION_IMAGE_CAPTURE_SECURE = |
| "android.media.action.IMAGE_CAPTURE_SECURE"; |
| // The intent extra for camera from secure lock screen. True if the gallery |
| // should only show newly captured pictures. sSecureAlbumId does not |
| // increment. This is used when switching between camera, camcorder, and |
| // panorama. If the extra is not set, it is in the normal camera mode. |
| public static final String SECURE_CAMERA_EXTRA = "secure_camera"; |
| |
| private int mResultCodeForTesting; |
| private Intent mResultDataForTesting; |
| private OnScreenHint mStorageHint; |
| private View mSingleTapArea; |
| |
| protected boolean mOpenCameraFail; |
| protected boolean mCameraDisabled; |
| protected CameraManager.CameraProxy mCameraDevice; |
| protected Parameters mParameters; |
| // The activity is paused. The classes that extend this class should set |
| // mPaused the first thing in onResume/onPause. |
| protected boolean mPaused; |
| protected GalleryActionBar mActionBar; |
| |
| // multiple cameras support |
| protected int mNumberOfCameras; |
| protected int mCameraId; |
| // The activity is going to switch to the specified camera id. This is |
| // needed because texture copy is done in GL thread. -1 means camera is not |
| // switching. |
| protected int mPendingSwitchCameraId = -1; |
| |
| protected MyAppBridge mAppBridge; |
| protected ScreenNail mCameraScreenNail; // This shows camera preview. |
| // The view containing only camera related widgets like control panel, |
| // indicator bar, focus indicator and etc. |
| protected View mCameraAppView; |
| protected boolean mShowCameraAppView = true; |
| private Animation mCameraAppViewFadeIn; |
| private Animation mCameraAppViewFadeOut; |
| // Secure album id. This should be incremented every time the camera is |
| // launched from the secure lock screen. The id should be the same when |
| // switching between camera, camcorder, and panorama. |
| protected static int sSecureAlbumId; |
| // True if the camera is started from secure lock screen. |
| protected boolean mSecureCamera; |
| private static boolean sFirstStartAfterScreenOn = true; |
| |
| private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD; |
| private static final int UPDATE_STORAGE_HINT = 0; |
| private final Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case UPDATE_STORAGE_HINT: |
| updateStorageHint(); |
| return; |
| } |
| } |
| }; |
| |
| private final BroadcastReceiver mReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (action.equals(Intent.ACTION_MEDIA_MOUNTED) |
| || action.equals(Intent.ACTION_MEDIA_UNMOUNTED) |
| || action.equals(Intent.ACTION_MEDIA_CHECKING) |
| || action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { |
| updateStorageSpaceAndHint(); |
| } |
| } |
| }; |
| |
| // close activity when screen turns off |
| private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| finish(); |
| } |
| }; |
| |
| private static BroadcastReceiver sScreenOffReceiver; |
| private static class ScreenOffReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| sFirstStartAfterScreenOn = true; |
| } |
| } |
| |
| public static boolean isFirstStartAfterScreenOn() { |
| return sFirstStartAfterScreenOn; |
| } |
| |
| public static void resetFirstStartAfterScreenOn() { |
| sFirstStartAfterScreenOn = false; |
| } |
| |
| protected class CameraOpenThread extends Thread { |
| @Override |
| public void run() { |
| try { |
| mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId); |
| mParameters = mCameraDevice.getParameters(); |
| } catch (CameraHardwareException e) { |
| mOpenCameraFail = true; |
| } catch (CameraDisabledException e) { |
| mCameraDisabled = true; |
| } |
| } |
| } |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.disableToggleStatusBar(); |
| // Set a theme with action bar. It is not specified in manifest because |
| // we want to hide it by default. setTheme must happen before |
| // setContentView. |
| // |
| // This must be set before we call super.onCreate(), where the window's |
| // background is removed. |
| setTheme(R.style.Theme_Gallery); |
| getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
| if (ApiHelper.HAS_ACTION_BAR) { |
| requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); |
| } else { |
| requestWindowFeature(Window.FEATURE_NO_TITLE); |
| } |
| |
| // Check if this is in the secure camera mode. |
| Intent intent = getIntent(); |
| String action = intent.getAction(); |
| if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) { |
| mSecureCamera = true; |
| // Use a new album when this is started from the lock screen. |
| sSecureAlbumId++; |
| } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { |
| mSecureCamera = true; |
| } else { |
| mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); |
| } |
| if (mSecureCamera) { |
| IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); |
| registerReceiver(mScreenOffReceiver, filter); |
| if (sScreenOffReceiver == null) { |
| sScreenOffReceiver = new ScreenOffReceiver(); |
| getApplicationContext().registerReceiver(sScreenOffReceiver, filter); |
| } |
| } |
| super.onCreate(icicle); |
| } |
| |
| public boolean isPanoramaActivity() { |
| return false; |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| |
| installIntentFilter(); |
| if (updateStorageHintOnResume()) { |
| updateStorageSpace(); |
| mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200); |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| |
| if (mStorageHint != null) { |
| mStorageHint.cancel(); |
| mStorageHint = null; |
| } |
| |
| unregisterReceiver(mReceiver); |
| } |
| |
| @Override |
| public void setContentView(int layoutResID) { |
| super.setContentView(layoutResID); |
| // getActionBar() should be after setContentView |
| mActionBar = new GalleryActionBar(this); |
| mActionBar.hide(); |
| } |
| |
| @Override |
| public boolean onSearchRequested() { |
| return false; |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| // Prevent software keyboard or voice search from showing up. |
| if (keyCode == KeyEvent.KEYCODE_SEARCH |
| || keyCode == KeyEvent.KEYCODE_MENU) { |
| if (event.isLongPress()) return true; |
| } |
| if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { |
| return true; |
| } |
| |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| @Override |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) { |
| return true; |
| } |
| return super.onKeyUp(keyCode, event); |
| } |
| |
| protected void setResultEx(int resultCode) { |
| mResultCodeForTesting = resultCode; |
| setResult(resultCode); |
| } |
| |
| protected void setResultEx(int resultCode, Intent data) { |
| mResultCodeForTesting = resultCode; |
| mResultDataForTesting = data; |
| setResult(resultCode, data); |
| } |
| |
| public int getResultCode() { |
| return mResultCodeForTesting; |
| } |
| |
| public Intent getResultData() { |
| return mResultDataForTesting; |
| } |
| |
| @Override |
| protected void onDestroy() { |
| PopupManager.removeInstance(this); |
| if (mSecureCamera) unregisterReceiver(mScreenOffReceiver); |
| super.onDestroy(); |
| } |
| |
| protected void installIntentFilter() { |
| // install an intent filter to receive SD card related events. |
| IntentFilter intentFilter = |
| new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING); |
| intentFilter.addDataScheme("file"); |
| registerReceiver(mReceiver, intentFilter); |
| } |
| |
| protected void updateStorageSpace() { |
| mStorageSpace = Storage.getAvailableSpace(); |
| } |
| |
| protected long getStorageSpace() { |
| return mStorageSpace; |
| } |
| |
| protected void updateStorageSpaceAndHint() { |
| updateStorageSpace(); |
| updateStorageHint(mStorageSpace); |
| } |
| |
| protected void updateStorageHint() { |
| updateStorageHint(mStorageSpace); |
| } |
| |
| protected boolean updateStorageHintOnResume() { |
| return true; |
| } |
| |
| protected void updateStorageHint(long storageSpace) { |
| String message = null; |
| if (storageSpace == Storage.UNAVAILABLE) { |
| message = getString(R.string.no_storage); |
| } else if (storageSpace == Storage.PREPARING) { |
| message = getString(R.string.preparing_sd); |
| } else if (storageSpace == Storage.UNKNOWN_SIZE) { |
| message = getString(R.string.access_sd_fail); |
| } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) { |
| message = getString(R.string.spaceIsLow_content); |
| } |
| |
| if (message != null) { |
| if (mStorageHint == null) { |
| mStorageHint = OnScreenHint.makeText(this, message); |
| } else { |
| mStorageHint.setText(message); |
| } |
| mStorageHint.show(); |
| } else if (mStorageHint != null) { |
| mStorageHint.cancel(); |
| mStorageHint = null; |
| } |
| } |
| |
| protected void gotoGallery() { |
| // Move the next picture with capture animation. "1" means next. |
| mAppBridge.switchWithCaptureAnimation(1); |
| } |
| |
| // Call this after setContentView. |
| public ScreenNail createCameraScreenNail(boolean getPictures) { |
| mCameraAppView = findViewById(R.id.camera_app_root); |
| Bundle data = new Bundle(); |
| String path; |
| if (getPictures) { |
| if (mSecureCamera) { |
| path = "/secure/all/" + sSecureAlbumId; |
| } else { |
| path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID; |
| } |
| } else { |
| path = "/local/all/0"; // Use 0 so gallery does not show anything. |
| } |
| data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); |
| data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); |
| data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera); |
| |
| // Send an AppBridge to gallery to enable the camera preview. |
| if (mAppBridge != null) { |
| mCameraScreenNail.recycle(); |
| } |
| mAppBridge = new MyAppBridge(); |
| data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge); |
| if (getStateManager().getStateCount() == 0) { |
| getStateManager().startState(FilmstripPage.class, data); |
| } else { |
| getStateManager().switchState(getStateManager().getTopState(), |
| FilmstripPage.class, data); |
| } |
| mCameraScreenNail = mAppBridge.getCameraScreenNail(); |
| return mCameraScreenNail; |
| } |
| |
| // Call this after setContentView. |
| protected ScreenNail reuseCameraScreenNail(boolean getPictures) { |
| mCameraAppView = findViewById(R.id.camera_app_root); |
| Bundle data = new Bundle(); |
| String path; |
| if (getPictures) { |
| if (mSecureCamera) { |
| path = "/secure/all/" + sSecureAlbumId; |
| } else { |
| path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID; |
| } |
| } else { |
| path = "/local/all/0"; // Use 0 so gallery does not show anything. |
| } |
| data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path); |
| data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path); |
| data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera); |
| |
| // Send an AppBridge to gallery to enable the camera preview. |
| if (mAppBridge == null) { |
| mAppBridge = new MyAppBridge(); |
| } |
| data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge); |
| if (getStateManager().getStateCount() == 0) { |
| getStateManager().startState(FilmstripPage.class, data); |
| } |
| mCameraScreenNail = mAppBridge.getCameraScreenNail(); |
| return mCameraScreenNail; |
| } |
| |
| private class HideCameraAppView implements Animation.AnimationListener { |
| @Override |
| public void onAnimationEnd(Animation animation) { |
| // We cannot set this as GONE because we want to receive the |
| // onLayoutChange() callback even when we are invisible. |
| mCameraAppView.setVisibility(View.INVISIBLE); |
| } |
| |
| @Override |
| public void onAnimationRepeat(Animation animation) { |
| } |
| |
| @Override |
| public void onAnimationStart(Animation animation) { |
| } |
| } |
| |
| protected void updateCameraAppView() { |
| // Initialize the animation. |
| if (mCameraAppViewFadeIn == null) { |
| mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f); |
| mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); |
| mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator()); |
| |
| mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f); |
| mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME); |
| mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator()); |
| mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView()); |
| } |
| |
| if (mShowCameraAppView) { |
| mCameraAppView.setVisibility(View.VISIBLE); |
| // The "transparent region" is not recomputed when a sibling of |
| // SurfaceView changes visibility (unless it involves GONE). It's |
| // been broken since 1.0. Call requestLayout to work around it. |
| mCameraAppView.requestLayout(); |
| mCameraAppView.startAnimation(mCameraAppViewFadeIn); |
| } else { |
| mCameraAppView.startAnimation(mCameraAppViewFadeOut); |
| } |
| } |
| |
| protected void onFullScreenChanged(boolean full) { |
| if (mShowCameraAppView == full) return; |
| mShowCameraAppView = full; |
| if (mPaused || isFinishing()) return; |
| updateCameraAppView(); |
| } |
| |
| @Override |
| public GalleryActionBar getGalleryActionBar() { |
| return mActionBar; |
| } |
| |
| // Preview frame layout has changed. |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom) { |
| if (mAppBridge == null) return; |
| |
| int width = right - left; |
| int height = bottom - top; |
| if (ApiHelper.HAS_SURFACE_TEXTURE) { |
| CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail; |
| if (Util.getDisplayRotation(this) % 180 == 0) { |
| screenNail.setPreviewFrameLayoutSize(width, height); |
| } else { |
| // Swap the width and height. Camera screen nail draw() is based on |
| // natural orientation, not the view system orientation. |
| screenNail.setPreviewFrameLayoutSize(height, width); |
| } |
| notifyScreenNailChanged(); |
| } |
| } |
| |
| protected void setSingleTapUpListener(View singleTapArea) { |
| mSingleTapArea = singleTapArea; |
| } |
| |
| private boolean onSingleTapUp(int x, int y) { |
| // Ignore if listener is null or the camera control is invisible. |
| if (mSingleTapArea == null || !mShowCameraAppView) return false; |
| |
| int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(), |
| mSingleTapArea); |
| x -= relativeLocation[0]; |
| y -= relativeLocation[1]; |
| if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0 |
| && y < mSingleTapArea.getHeight()) { |
| onSingleTapUp(mSingleTapArea, x, y); |
| return true; |
| } |
| return false; |
| } |
| |
| protected void onSingleTapUp(View view, int x, int y) { |
| } |
| |
| public void setSwipingEnabled(boolean enabled) { |
| mAppBridge.setSwipingEnabled(enabled); |
| } |
| |
| public void notifyScreenNailChanged() { |
| mAppBridge.notifyScreenNailChanged(); |
| } |
| |
| protected void onPreviewTextureCopied() { |
| } |
| |
| protected void onCaptureTextureCopied() { |
| } |
| |
| protected void addSecureAlbumItemIfNeeded(boolean isVideo, Uri uri) { |
| if (mSecureCamera) { |
| int id = Integer.parseInt(uri.getLastPathSegment()); |
| mAppBridge.addSecureAlbumItem(isVideo, id); |
| } |
| } |
| |
| public boolean isSecureCamera() { |
| return mSecureCamera; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // The is the communication interface between the Camera Application and |
| // the Gallery PhotoPage. |
| ////////////////////////////////////////////////////////////////////////// |
| |
| class MyAppBridge extends AppBridge implements CameraScreenNail.Listener { |
| @SuppressWarnings("hiding") |
| private ScreenNail mCameraScreenNail; |
| private Server mServer; |
| |
| @Override |
| public ScreenNail attachScreenNail() { |
| if (mCameraScreenNail == null) { |
| if (ApiHelper.HAS_SURFACE_TEXTURE) { |
| mCameraScreenNail = new CameraScreenNail(this); |
| } else { |
| Bitmap b = BitmapFactory.decodeResource(getResources(), |
| R.drawable.wallpaper_picker_preview); |
| mCameraScreenNail = new StaticBitmapScreenNail(b); |
| } |
| } |
| return mCameraScreenNail; |
| } |
| |
| @Override |
| public void detachScreenNail() { |
| mCameraScreenNail = null; |
| } |
| |
| public ScreenNail getCameraScreenNail() { |
| return mCameraScreenNail; |
| } |
| |
| // Return true if the tap is consumed. |
| @Override |
| public boolean onSingleTapUp(int x, int y) { |
| return ActivityBase.this.onSingleTapUp(x, y); |
| } |
| |
| // This is used to notify that the screen nail will be drawn in full screen |
| // or not in next draw() call. |
| @Override |
| public void onFullScreenChanged(boolean full) { |
| ActivityBase.this.onFullScreenChanged(full); |
| } |
| |
| @Override |
| public void requestRender() { |
| getGLRoot().requestRenderForced(); |
| } |
| |
| @Override |
| public void onPreviewTextureCopied() { |
| ActivityBase.this.onPreviewTextureCopied(); |
| } |
| |
| @Override |
| public void onCaptureTextureCopied() { |
| ActivityBase.this.onCaptureTextureCopied(); |
| } |
| |
| @Override |
| public void setServer(Server s) { |
| mServer = s; |
| } |
| |
| @Override |
| public boolean isPanorama() { |
| return ActivityBase.this.isPanoramaActivity(); |
| } |
| |
| @Override |
| public boolean isStaticCamera() { |
| return !ApiHelper.HAS_SURFACE_TEXTURE; |
| } |
| |
| public void addSecureAlbumItem(boolean isVideo, int id) { |
| if (mServer != null) mServer.addSecureAlbumItem(isVideo, id); |
| } |
| |
| private void setCameraRelativeFrame(Rect frame) { |
| if (mServer != null) mServer.setCameraRelativeFrame(frame); |
| } |
| |
| private void switchWithCaptureAnimation(int offset) { |
| if (mServer != null) mServer.switchWithCaptureAnimation(offset); |
| } |
| |
| private void setSwipingEnabled(boolean enabled) { |
| if (mServer != null) mServer.setSwipingEnabled(enabled); |
| } |
| |
| private void notifyScreenNailChanged() { |
| if (mServer != null) mServer.notifyScreenNailChanged(); |
| } |
| } |
| } |