• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
run()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
onCreate(Bundle icicle)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 
isPanoramaActivity()144     public boolean isPanoramaActivity() {
145         return false;
146     }
147 
148     @Override
onResume()149     protected void onResume() {
150         super.onResume();
151         LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
152         manager.registerReceiver(mDeletePictureReceiver, mDeletePictureFilter);
153     }
154 
155     @Override
onPause()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
setContentView(int layoutResID)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
onSearchRequested()184     public boolean onSearchRequested() {
185         return false;
186     }
187 
188     @Override
onKeyDown(int keyCode, KeyEvent event)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 
setResultEx(int resultCode)199     protected void setResultEx(int resultCode) {
200         mResultCodeForTesting = resultCode;
201         setResult(resultCode);
202     }
203 
setResultEx(int resultCode, Intent data)204     protected void setResultEx(int resultCode, Intent data) {
205         mResultCodeForTesting = resultCode;
206         mResultDataForTesting = data;
207         setResult(resultCode, data);
208     }
209 
getResultCode()210     public int getResultCode() {
211         return mResultCodeForTesting;
212     }
213 
getResultData()214     public Intent getResultData() {
215         return mResultDataForTesting;
216     }
217 
218     @Override
onDestroy()219     protected void onDestroy() {
220         PopupManager.removeInstance(this);
221         super.onDestroy();
222     }
223 
224     @Override
onCreateOptionsMenu(Menu menu)225     public boolean onCreateOptionsMenu(Menu menu) {
226         super.onCreateOptionsMenu(menu);
227         return getStateManager().createOptionsMenu(menu);
228     }
229 
updateStorageHint(long storageSpace)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 
updateThumbnailView()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 
getLastThumbnail()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 
getLastThumbnailUncached()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 
LoadThumbnailTask(boolean lookAtCache)285         public LoadThumbnailTask(boolean lookAtCache) {
286             mLookAtCache = lookAtCache;
287         }
288 
289         @Override
doInBackground(Void... params)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
onPostExecute(Thumbnail thumbnail)319         protected void onPostExecute(Thumbnail thumbnail) {
320             if (isCancelled()) return;
321             mThumbnail = thumbnail;
322             updateThumbnailView();
323         }
324     }
325 
gotoGallery()326     protected void gotoGallery() {
327         // Move the next picture with capture animation. "1" means next.
328         mAppBridge.switchWithCaptureAnimation(1);
329     }
330 
saveThumbnailToFile()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
doInBackground(Thumbnail... params)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.
createCameraScreenNail(boolean getPictures)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
run()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 
updateCameraAppView()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 
onFullScreenChanged(boolean full)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
getGalleryActionBar()417     public GalleryActionBar getGalleryActionBar() {
418         return mActionBar;
419     }
420 
421     // Preview frame layout has changed.
422     @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)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 
setSingleTapUpListener(View singleTapArea)460     protected void setSingleTapUpListener(View singleTapArea) {
461         mSingleTapArea = singleTapArea;
462     }
463 
onSingleTapUp(int x, int y)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 
onSingleTapUp(View view, int x, int y)480     protected void onSingleTapUp(View view, int x, int y) {
481     }
482 
setSwipingEnabled(boolean enabled)483     protected void setSwipingEnabled(boolean enabled) {
484         mAppBridge.setSwipingEnabled(enabled);
485     }
486 
notifyScreenNailChanged()487     protected void notifyScreenNailChanged() {
488         mAppBridge.notifyScreenNailChanged();
489     }
490 
onPreviewTextureCopied()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
attachScreenNail()504         public ScreenNail attachScreenNail() {
505             if (mCameraScreenNail == null) {
506                 mCameraScreenNail = new CameraScreenNail(this);
507             }
508             return mCameraScreenNail;
509         }
510 
511         @Override
detachScreenNail()512         public void detachScreenNail() {
513             mCameraScreenNail = null;
514         }
515 
getCameraScreenNail()516         public CameraScreenNail getCameraScreenNail() {
517             return mCameraScreenNail;
518         }
519 
520         // Return true if the tap is consumed.
521         @Override
onSingleTapUp(int x, int y)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
onFullScreenChanged(boolean full)529         public void onFullScreenChanged(boolean full) {
530             ActivityBase.this.onFullScreenChanged(full);
531         }
532 
533         @Override
requestRender()534         public void requestRender() {
535             getGLRoot().requestRender();
536         }
537 
538         @Override
onPreviewTextureCopied()539         public void onPreviewTextureCopied() {
540             ActivityBase.this.onPreviewTextureCopied();
541         }
542 
543         @Override
setServer(Server s)544         public void setServer(Server s) {
545             mServer = s;
546         }
547 
548         @Override
isPanorama()549         public boolean isPanorama() {
550             return ActivityBase.this.isPanoramaActivity();
551         }
552 
setCameraRelativeFrame(Rect frame)553         private void setCameraRelativeFrame(Rect frame) {
554             if (mServer != null) mServer.setCameraRelativeFrame(frame);
555         }
556 
switchWithCaptureAnimation(int offset)557         private void switchWithCaptureAnimation(int offset) {
558             if (mServer != null) mServer.switchWithCaptureAnimation(offset);
559         }
560 
setSwipingEnabled(boolean enabled)561         private void setSwipingEnabled(boolean enabled) {
562             if (mServer != null) mServer.setSwipingEnabled(enabled);
563         }
564 
notifyScreenNailChanged()565         private void notifyScreenNailChanged() {
566             if (mServer != null) mServer.notifyScreenNailChanged();
567         }
568     }
569 }
570