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