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