blob: 4e4143ef87226fa700424ffe6057baabab414f48 [file] [log] [blame]
/*
* 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();
}
}
}