/* * 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.R; 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; } } public 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, ActivityBase.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(); } } }