Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2009 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.camera;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.graphics.Rect;
     25 import android.hardware.Camera.Parameters;
     26 import android.os.AsyncTask;
     27 import android.os.Bundle;
     28 import android.support.v4.content.LocalBroadcastManager;
     29 import android.util.Log;
     30 import android.view.KeyEvent;
     31 import android.view.Menu;
     32 import android.view.View;
     33 import android.view.Window;
     34 import android.view.WindowManager;
     35 import android.view.animation.DecelerateInterpolator;
     36 
     37 import com.android.camera.ui.CameraPicker;
     38 import com.android.camera.ui.PopupManager;
     39 import com.android.camera.ui.RotateImageView;
     40 import com.android.gallery3d.app.AbstractGalleryActivity;
     41 import com.android.gallery3d.app.AppBridge;
     42 import com.android.gallery3d.app.GalleryActionBar;
     43 import com.android.gallery3d.app.PhotoPage;
     44 import com.android.gallery3d.ui.ScreenNail;
     45 import com.android.gallery3d.util.MediaSetUtils;
     46 
     47 import java.io.File;
     48 
     49 /**
     50  * Superclass of Camera and VideoCamera activities.
     51  */
     52 abstract public class ActivityBase extends AbstractGalleryActivity
     53         implements View.OnLayoutChangeListener {
     54 
     55     private static final String TAG = "ActivityBase";
     56     private static final boolean LOGV = false;
     57     private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100;  // milliseconds
     58     private static final String ACTION_DELETE_PICTURE =
     59             "com.android.gallery3d.action.DELETE_PICTURE";
     60     private int mResultCodeForTesting;
     61     private Intent mResultDataForTesting;
     62     private OnScreenHint mStorageHint;
     63     private HideCameraAppView mHideCameraAppView;
     64     private View mSingleTapArea;
     65 
     66     // The bitmap of the last captured picture thumbnail and the URI of the
     67     // original picture.
     68     protected Thumbnail mThumbnail;
     69     protected int mThumbnailViewWidth; // layout width of the thumbnail
     70     protected AsyncTask<Void, Void, Thumbnail> mLoadThumbnailTask;
     71     // An imageview showing the last captured picture thumbnail.
     72     protected RotateImageView mThumbnailView;
     73     protected CameraPicker mCameraPicker;
     74 
     75     protected boolean mOpenCameraFail;
     76     protected boolean mCameraDisabled;
     77     protected CameraManager.CameraProxy mCameraDevice;
     78     protected Parameters mParameters;
     79     // The activity is paused. The classes that extend this class should set
     80     // mPaused the first thing in onResume/onPause.
     81     protected boolean mPaused;
     82     protected GalleryActionBar mActionBar;
     83 
     84     // multiple cameras support
     85     protected int mNumberOfCameras;
     86     protected int mCameraId;
     87     // The activity is going to switch to the specified camera id. This is
     88     // needed because texture copy is done in GL thread. -1 means camera is not
     89     // switching.
     90     protected int mPendingSwitchCameraId = -1;
     91 
     92     protected MyAppBridge mAppBridge;
     93     protected CameraScreenNail mCameraScreenNail; // This shows camera preview.
     94     // The view containing only camera related widgets like control panel,
     95     // indicator bar, focus indicator and etc.
     96     protected View mCameraAppView;
     97     protected boolean mShowCameraAppView = true;
     98     private boolean mUpdateThumbnailDelayed;
     99     private IntentFilter mDeletePictureFilter =
    100             new IntentFilter(ACTION_DELETE_PICTURE);
    101     private BroadcastReceiver mDeletePictureReceiver =
    102             new BroadcastReceiver() {
    103                 @Override
    104                 public void onReceive(Context context, Intent intent) {
    105                     if (mShowCameraAppView) {
    106                         getLastThumbnailUncached();
    107                     } else {
    108                         mUpdateThumbnailDelayed = true;
    109                     }
    110                 }
    111             };
    112 
    113     protected class CameraOpenThread extends Thread {
    114         @Override
    115         public void run() {
    116             try {
    117                 mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId);
    118                 mParameters = mCameraDevice.getParameters();
    119             } catch (CameraHardwareException e) {
    120                 mOpenCameraFail = true;
    121             } catch (CameraDisabledException e) {
    122                 mCameraDisabled = true;
    123             }
    124         }
    125     }
    126 
    127     @Override
    128     public void onCreate(Bundle icicle) {
    129         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
    130         super.disableToggleStatusBar();
    131         // Set a theme with action bar. It is not specified in manifest because
    132         // we want to hide it by default. setTheme must happen before
    133         // setContentView.
    134         //
    135         // This must be set before we call super.onCreate(), where the window's
    136         // background is removed.
    137         setTheme(R.style.Theme_Gallery);
    138         getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    139         requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
    140 
    141         super.onCreate(icicle);
    142     }
    143 
    144     public boolean isPanoramaActivity() {
    145         return false;
    146     }
    147 
    148     @Override
    149     protected void onResume() {
    150         super.onResume();
    151         LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
    152         manager.registerReceiver(mDeletePictureReceiver, mDeletePictureFilter);
    153     }
    154 
    155     @Override
    156     protected void onPause() {
    157         super.onPause();
    158         LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
    159         manager.unregisterReceiver(mDeletePictureReceiver);
    160 
    161         if (LOGV) Log.v(TAG, "onPause");
    162         saveThumbnailToFile();
    163 
    164         if (mLoadThumbnailTask != null) {
    165             mLoadThumbnailTask.cancel(true);
    166             mLoadThumbnailTask = null;
    167         }
    168 
    169         if (mStorageHint != null) {
    170             mStorageHint.cancel();
    171             mStorageHint = null;
    172         }
    173     }
    174 
    175     @Override
    176     public void setContentView(int layoutResID) {
    177         super.setContentView(layoutResID);
    178         // getActionBar() should be after setContentView
    179         mActionBar = new GalleryActionBar(this);
    180         mActionBar.hide();
    181     }
    182 
    183     @Override
    184     public boolean onSearchRequested() {
    185         return false;
    186     }
    187 
    188     @Override
    189     public boolean onKeyDown(int keyCode, KeyEvent event) {
    190         // Prevent software keyboard or voice search from showing up.
    191         if (keyCode == KeyEvent.KEYCODE_SEARCH
    192                 || keyCode == KeyEvent.KEYCODE_MENU) {
    193             if (event.isLongPress()) return true;
    194         }
    195 
    196         return super.onKeyDown(keyCode, event);
    197     }
    198 
    199     protected void setResultEx(int resultCode) {
    200         mResultCodeForTesting = resultCode;
    201         setResult(resultCode);
    202     }
    203 
    204     protected void setResultEx(int resultCode, Intent data) {
    205         mResultCodeForTesting = resultCode;
    206         mResultDataForTesting = data;
    207         setResult(resultCode, data);
    208     }
    209 
    210     public int getResultCode() {
    211         return mResultCodeForTesting;
    212     }
    213 
    214     public Intent getResultData() {
    215         return mResultDataForTesting;
    216     }
    217 
    218     @Override
    219     protected void onDestroy() {
    220         PopupManager.removeInstance(this);
    221         super.onDestroy();
    222     }
    223 
    224     @Override
    225     public boolean onCreateOptionsMenu(Menu menu) {
    226         super.onCreateOptionsMenu(menu);
    227         return getStateManager().createOptionsMenu(menu);
    228     }
    229 
    230     protected void updateStorageHint(long storageSpace) {
    231         String message = null;
    232         if (storageSpace == Storage.UNAVAILABLE) {
    233             message = getString(R.string.no_storage);
    234         } else if (storageSpace == Storage.PREPARING) {
    235             message = getString(R.string.preparing_sd);
    236         } else if (storageSpace == Storage.UNKNOWN_SIZE) {
    237             message = getString(R.string.access_sd_fail);
    238         } else if (storageSpace < Storage.LOW_STORAGE_THRESHOLD) {
    239             message = getString(R.string.spaceIsLow_content);
    240         }
    241 
    242         if (message != null) {
    243             if (mStorageHint == null) {
    244                 mStorageHint = OnScreenHint.makeText(this, message);
    245             } else {
    246                 mStorageHint.setText(message);
    247             }
    248             mStorageHint.show();
    249         } else if (mStorageHint != null) {
    250             mStorageHint.cancel();
    251             mStorageHint = null;
    252         }
    253     }
    254 
    255     protected void updateThumbnailView() {
    256         if (mThumbnail != null) {
    257             mThumbnailView.setBitmap(mThumbnail.getBitmap());
    258             mThumbnailView.setVisibility(View.VISIBLE);
    259         } else {
    260             mThumbnailView.setBitmap(null);
    261             mThumbnailView.setVisibility(View.GONE);
    262         }
    263     }
    264 
    265     protected void getLastThumbnail() {
    266         mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver());
    267         // Suppose users tap the thumbnail view, go to the gallery, delete the
    268         // image, and coming back to the camera. Thumbnail file will be invalid.
    269         // Since the new thumbnail will be loaded in another thread later, the
    270         // view should be set to gone to prevent from opening the invalid image.
    271         updateThumbnailView();
    272         if (mThumbnail == null) {
    273             mLoadThumbnailTask = new LoadThumbnailTask(true).execute();
    274         }
    275     }
    276 
    277     protected void getLastThumbnailUncached() {
    278         if (mLoadThumbnailTask != null) mLoadThumbnailTask.cancel(true);
    279         mLoadThumbnailTask = new LoadThumbnailTask(false).execute();
    280     }
    281 
    282     private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> {
    283         private boolean mLookAtCache;
    284 
    285         public LoadThumbnailTask(boolean lookAtCache) {
    286             mLookAtCache = lookAtCache;
    287         }
    288 
    289         @Override
    290         protected Thumbnail doInBackground(Void... params) {
    291             // Load the thumbnail from the file.
    292             ContentResolver resolver = getContentResolver();
    293             Thumbnail t = null;
    294             if (mLookAtCache) {
    295                 t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver);
    296             }
    297 
    298             if (isCancelled()) return null;
    299 
    300             if (t == null) {
    301                 Thumbnail result[] = new Thumbnail[1];
    302                 // Load the thumbnail from the media provider.
    303                 int code = Thumbnail.getLastThumbnailFromContentResolver(
    304                         resolver, result);
    305                 switch (code) {
    306                     case Thumbnail.THUMBNAIL_FOUND:
    307                         return result[0];
    308                     case Thumbnail.THUMBNAIL_NOT_FOUND:
    309                         return null;
    310                     case Thumbnail.THUMBNAIL_DELETED:
    311                         cancel(true);
    312                         return null;
    313                 }
    314             }
    315             return t;
    316         }
    317 
    318         @Override
    319         protected void onPostExecute(Thumbnail thumbnail) {
    320             if (isCancelled()) return;
    321             mThumbnail = thumbnail;
    322             updateThumbnailView();
    323         }
    324     }
    325 
    326     protected void gotoGallery() {
    327         // Move the next picture with capture animation. "1" means next.
    328         mAppBridge.switchWithCaptureAnimation(1);
    329     }
    330 
    331     protected void saveThumbnailToFile() {
    332         if (mThumbnail != null && !mThumbnail.fromFile()) {
    333             new SaveThumbnailTask().execute(mThumbnail);
    334         }
    335     }
    336 
    337     private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> {
    338         @Override
    339         protected Void doInBackground(Thumbnail... params) {
    340             final int n = params.length;
    341             final File filesDir = getFilesDir();
    342             for (int i = 0; i < n; i++) {
    343                 params[i].saveLastThumbnailToFile(filesDir);
    344             }
    345             return null;
    346         }
    347     }
    348 
    349     // Call this after setContentView.
    350     protected void createCameraScreenNail(boolean getPictures) {
    351         mCameraAppView = findViewById(R.id.camera_app_root);
    352         Bundle data = new Bundle();
    353         String path = "/local/all/";
    354         // Intent mode does not show camera roll. Use 0 as a work around for
    355         // invalid bucket id.
    356         // TODO: add support of empty media set in gallery.
    357         path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0");
    358         data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
    359         data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
    360 
    361         // Send an AppBridge to gallery to enable the camera preview.
    362         mAppBridge = new MyAppBridge();
    363         data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
    364         getStateManager().startState(PhotoPage.class, data);
    365         mCameraScreenNail = mAppBridge.getCameraScreenNail();
    366     }
    367 
    368     private class HideCameraAppView implements Runnable {
    369         @Override
    370         public void run() {
    371             // We cannot set this as GONE because we want to receive the
    372             // onLayoutChange() callback even when we are invisible.
    373             mCameraAppView.setVisibility(View.INVISIBLE);
    374         }
    375     }
    376 
    377     protected void updateCameraAppView() {
    378         if (mShowCameraAppView) {
    379             mCameraAppView.setVisibility(View.VISIBLE);
    380             // The "transparent region" is not recomputed when a sibling of
    381             // SurfaceView changes visibility (unless it involves GONE). It's
    382             // been broken since 1.0. Call requestLayout to work around it.
    383             mCameraAppView.requestLayout();
    384             // withEndAction(null) prevents the pending end action
    385             // mHideCameraAppView from being executed.
    386             mCameraAppView.animate()
    387                     .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
    388                     .withLayer().alpha(1).withEndAction(null);
    389         } else {
    390             mCameraAppView.animate()
    391                     .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
    392                     .withLayer().alpha(0).withEndAction(mHideCameraAppView);
    393         }
    394     }
    395 
    396     private void onFullScreenChanged(boolean full) {
    397         if (mShowCameraAppView == full) return;
    398         mShowCameraAppView = full;
    399         if (mPaused || isFinishing()) return;
    400         // Initialize the animation.
    401         if (mHideCameraAppView == null) {
    402             mHideCameraAppView = new HideCameraAppView();
    403             mCameraAppView.animate()
    404                 .setInterpolator(new DecelerateInterpolator());
    405         }
    406         updateCameraAppView();
    407 
    408         // If we received DELETE_PICTURE broadcasts while the Camera UI is
    409         // hidden, we update the thumbnail now.
    410         if (full && mUpdateThumbnailDelayed) {
    411             getLastThumbnailUncached();
    412             mUpdateThumbnailDelayed = false;
    413         }
    414     }
    415 
    416     @Override
    417     public GalleryActionBar getGalleryActionBar() {
    418         return mActionBar;
    419     }
    420 
    421     // Preview frame layout has changed.
    422     @Override
    423     public void onLayoutChange(View v, int left, int top, int right, int bottom,
    424             int oldLeft, int oldTop, int oldRight, int oldBottom) {
    425         if (mAppBridge == null) return;
    426 
    427         if (left == oldLeft && top == oldTop && right == oldRight
    428                 && bottom == oldBottom) {
    429             return;
    430         }
    431 
    432 
    433         int width = right - left;
    434         int height = bottom - top;
    435         if (Util.getDisplayRotation(this) % 180 == 0) {
    436             mCameraScreenNail.setPreviewFrameLayoutSize(width, height);
    437         } else {
    438             // Swap the width and height. Camera screen nail draw() is based on
    439             // natural orientation, not the view system orientation.
    440             mCameraScreenNail.setPreviewFrameLayoutSize(height, width);
    441         }
    442 
    443         // Find out the coordinates of the preview frame relative to GL
    444         // root view.
    445         View root = (View) getGLRoot();
    446         int[] rootLocation = new int[2];
    447         int[] viewLocation = new int[2];
    448         root.getLocationInWindow(rootLocation);
    449         v.getLocationInWindow(viewLocation);
    450 
    451         int l = viewLocation[0] - rootLocation[0];
    452         int t = viewLocation[1] - rootLocation[1];
    453         int r = l + width;
    454         int b = t + height;
    455         Rect frame = new Rect(l, t, r, b);
    456         Log.d(TAG, "set CameraRelativeFrame as " + frame);
    457         mAppBridge.setCameraRelativeFrame(frame);
    458     }
    459 
    460     protected void setSingleTapUpListener(View singleTapArea) {
    461         mSingleTapArea = singleTapArea;
    462     }
    463 
    464     private boolean onSingleTapUp(int x, int y) {
    465         // Ignore if listener is null or the camera control is invisible.
    466         if (mSingleTapArea == null || !mShowCameraAppView) return false;
    467 
    468         int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
    469                 mSingleTapArea);
    470         x -= relativeLocation[0];
    471         y -= relativeLocation[1];
    472         if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
    473                 && y < mSingleTapArea.getHeight()) {
    474             onSingleTapUp(mSingleTapArea, x, y);
    475             return true;
    476         }
    477         return false;
    478     }
    479 
    480     protected void onSingleTapUp(View view, int x, int y) {
    481     }
    482 
    483     protected void setSwipingEnabled(boolean enabled) {
    484         mAppBridge.setSwipingEnabled(enabled);
    485     }
    486 
    487     protected void notifyScreenNailChanged() {
    488         mAppBridge.notifyScreenNailChanged();
    489     }
    490 
    491     protected void onPreviewTextureCopied() {
    492     }
    493 
    494     //////////////////////////////////////////////////////////////////////////
    495     //  The is the communication interface between the Camera Application and
    496     //  the Gallery PhotoPage.
    497     //////////////////////////////////////////////////////////////////////////
    498 
    499     class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
    500         private CameraScreenNail mCameraScreenNail;
    501         private Server mServer;
    502 
    503         @Override
    504         public ScreenNail attachScreenNail() {
    505             if (mCameraScreenNail == null) {
    506                 mCameraScreenNail = new CameraScreenNail(this);
    507             }
    508             return mCameraScreenNail;
    509         }
    510 
    511         @Override
    512         public void detachScreenNail() {
    513             mCameraScreenNail = null;
    514         }
    515 
    516         public CameraScreenNail getCameraScreenNail() {
    517             return mCameraScreenNail;
    518         }
    519 
    520         // Return true if the tap is consumed.
    521         @Override
    522         public boolean onSingleTapUp(int x, int y) {
    523             return ActivityBase.this.onSingleTapUp(x, y);
    524         }
    525 
    526         // This is used to notify that the screen nail will be drawn in full screen
    527         // or not in next draw() call.
    528         @Override
    529         public void onFullScreenChanged(boolean full) {
    530             ActivityBase.this.onFullScreenChanged(full);
    531         }
    532 
    533         @Override
    534         public void requestRender() {
    535             getGLRoot().requestRender();
    536         }
    537 
    538         @Override
    539         public void onPreviewTextureCopied() {
    540             ActivityBase.this.onPreviewTextureCopied();
    541         }
    542 
    543         @Override
    544         public void setServer(Server s) {
    545             mServer = s;
    546         }
    547 
    548         @Override
    549         public boolean isPanorama() {
    550             return ActivityBase.this.isPanoramaActivity();
    551         }
    552 
    553         private void setCameraRelativeFrame(Rect frame) {
    554             if (mServer != null) mServer.setCameraRelativeFrame(frame);
    555         }
    556 
    557         private void switchWithCaptureAnimation(int offset) {
    558             if (mServer != null) mServer.switchWithCaptureAnimation(offset);
    559         }
    560 
    561         private void setSwipingEnabled(boolean enabled) {
    562             if (mServer != null) mServer.setSwipingEnabled(enabled);
    563         }
    564 
    565         private void notifyScreenNailChanged() {
    566             if (mServer != null) mServer.notifyScreenNailChanged();
    567         }
    568     }
    569 }
    570