ActivityBase.java revision a96d8603560c2131d1555df82b8f64c6eb06909c
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        requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
177
178        super.onCreate(icicle);
179    }
180
181    public boolean isPanoramaActivity() {
182        return false;
183    }
184
185    @Override
186    protected void onResume() {
187        super.onResume();
188        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
189        manager.registerReceiver(mDeletePictureReceiver, mDeletePictureFilter);
190
191        installIntentFilter();
192        if(updateStorageHintOnResume()) {
193            updateStorageSpace();
194            mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200);
195        }
196    }
197
198    @Override
199    protected void onPause() {
200        super.onPause();
201        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
202        manager.unregisterReceiver(mDeletePictureReceiver);
203
204        if (LOGV) Log.v(TAG, "onPause");
205        saveThumbnailToFile();
206
207        if (mLoadThumbnailTask != null) {
208            mLoadThumbnailTask.cancel(true);
209            mLoadThumbnailTask = null;
210        }
211
212        if (mStorageHint != null) {
213            mStorageHint.cancel();
214            mStorageHint = null;
215        }
216
217        unregisterReceiver(mReceiver);
218    }
219
220    @Override
221    public void setContentView(int layoutResID) {
222        super.setContentView(layoutResID);
223        // getActionBar() should be after setContentView
224        mActionBar = new GalleryActionBar(this);
225        mActionBar.hide();
226    }
227
228    @Override
229    public boolean onSearchRequested() {
230        return false;
231    }
232
233    @Override
234    public boolean onKeyDown(int keyCode, KeyEvent event) {
235        // Prevent software keyboard or voice search from showing up.
236        if (keyCode == KeyEvent.KEYCODE_SEARCH
237                || keyCode == KeyEvent.KEYCODE_MENU) {
238            if (event.isLongPress()) return true;
239        }
240        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
241            return true;
242        }
243
244        return super.onKeyDown(keyCode, event);
245    }
246
247    @Override
248    public boolean onKeyUp(int keyCode, KeyEvent event) {
249        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
250            return true;
251        }
252        return super.onKeyUp(keyCode, event);
253    }
254
255    protected void setResultEx(int resultCode) {
256        mResultCodeForTesting = resultCode;
257        setResult(resultCode);
258    }
259
260    protected void setResultEx(int resultCode, Intent data) {
261        mResultCodeForTesting = resultCode;
262        mResultDataForTesting = data;
263        setResult(resultCode, data);
264    }
265
266    public int getResultCode() {
267        return mResultCodeForTesting;
268    }
269
270    public Intent getResultData() {
271        return mResultDataForTesting;
272    }
273
274    @Override
275    protected void onDestroy() {
276        PopupManager.removeInstance(this);
277        super.onDestroy();
278    }
279
280    protected void installIntentFilter() {
281        // install an intent filter to receive SD card related events.
282        IntentFilter intentFilter =
283                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
284        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
285        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
286        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
287        intentFilter.addDataScheme("file");
288        registerReceiver(mReceiver, intentFilter);
289    }
290
291    protected void updateStorageSpace() {
292        mStorageSpace = Storage.getAvailableSpace();
293    }
294
295    protected long getStorageSpace() {
296        return mStorageSpace;
297    }
298
299    protected void updateStorageSpaceAndHint() {
300        updateStorageSpace();
301        updateStorageHint(mStorageSpace);
302    }
303
304    protected void updateStorageHint() {
305        updateStorageHint(mStorageSpace);
306    }
307
308    protected boolean updateStorageHintOnResume() {
309        return true;
310    }
311
312    protected void updateStorageHint(long storageSpace) {
313        String message = null;
314        if (storageSpace == Storage.UNAVAILABLE) {
315            message = getString(R.string.no_storage);
316        } else if (storageSpace == Storage.PREPARING) {
317            message = getString(R.string.preparing_sd);
318        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
319            message = getString(R.string.access_sd_fail);
320        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
321            message = getString(R.string.spaceIsLow_content);
322        }
323
324        if (message != null) {
325            if (mStorageHint == null) {
326                mStorageHint = OnScreenHint.makeText(this, message);
327            } else {
328                mStorageHint.setText(message);
329            }
330            mStorageHint.show();
331        } else if (mStorageHint != null) {
332            mStorageHint.cancel();
333            mStorageHint = null;
334        }
335    }
336
337    protected void updateThumbnailView() {
338        if (mThumbnail != null) {
339            mThumbnailView.setBitmap(mThumbnail.getBitmap());
340            mThumbnailView.setVisibility(View.VISIBLE);
341        } else {
342            mThumbnailView.setBitmap(null);
343            mThumbnailView.setVisibility(View.GONE);
344        }
345    }
346
347    protected void getLastThumbnail() {
348        mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver());
349        // Suppose users tap the thumbnail view, go to the gallery, delete the
350        // image, and coming back to the camera. Thumbnail file will be invalid.
351        // Since the new thumbnail will be loaded in another thread later, the
352        // view should be set to gone to prevent from opening the invalid image.
353        updateThumbnailView();
354        if (mThumbnail == null) {
355            mLoadThumbnailTask = new LoadThumbnailTask(true).execute();
356        }
357    }
358
359    protected void getLastThumbnailUncached() {
360        if (mLoadThumbnailTask != null) mLoadThumbnailTask.cancel(true);
361        mLoadThumbnailTask = new LoadThumbnailTask(false).execute();
362    }
363
364    private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> {
365        private boolean mLookAtCache;
366
367        public LoadThumbnailTask(boolean lookAtCache) {
368            mLookAtCache = lookAtCache;
369        }
370
371        @Override
372        protected Thumbnail doInBackground(Void... params) {
373            // Load the thumbnail from the file.
374            ContentResolver resolver = getContentResolver();
375            Thumbnail t = null;
376            if (mLookAtCache) {
377                t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver);
378            }
379
380            if (isCancelled()) return null;
381
382            if (t == null) {
383                Thumbnail result[] = new Thumbnail[1];
384                // Load the thumbnail from the media provider.
385                int code = Thumbnail.getLastThumbnailFromContentResolver(
386                        resolver, result);
387                switch (code) {
388                    case Thumbnail.THUMBNAIL_FOUND:
389                        return result[0];
390                    case Thumbnail.THUMBNAIL_NOT_FOUND:
391                        return null;
392                    case Thumbnail.THUMBNAIL_DELETED:
393                        cancel(true);
394                        return null;
395                }
396            }
397            return t;
398        }
399
400        @Override
401        protected void onPostExecute(Thumbnail thumbnail) {
402            if (isCancelled()) return;
403            mThumbnail = thumbnail;
404            updateThumbnailView();
405        }
406    }
407
408    protected void gotoGallery() {
409        // Move the next picture with capture animation. "1" means next.
410        mAppBridge.switchWithCaptureAnimation(1);
411    }
412
413    protected void saveThumbnailToFile() {
414        if (mThumbnail != null && !mThumbnail.fromFile()) {
415            new SaveThumbnailTask().execute(mThumbnail);
416        }
417    }
418
419    private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> {
420        @Override
421        protected Void doInBackground(Thumbnail... params) {
422            final int n = params.length;
423            final File filesDir = getFilesDir();
424            for (int i = 0; i < n; i++) {
425                params[i].saveLastThumbnailToFile(filesDir);
426            }
427            return null;
428        }
429    }
430
431    // Call this after setContentView.
432    protected void createCameraScreenNail(boolean getPictures) {
433        mCameraAppView = findViewById(R.id.camera_app_root);
434        Bundle data = new Bundle();
435        String path = "/local/all/";
436        // Intent mode does not show camera roll. Use 0 as a work around for
437        // invalid bucket id.
438        // TODO: add support of empty media set in gallery.
439        path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0");
440        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
441        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
442
443        // Send an AppBridge to gallery to enable the camera preview.
444        mAppBridge = new MyAppBridge();
445        data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
446        getStateManager().startState(PhotoPage.class, data);
447        mCameraScreenNail = mAppBridge.getCameraScreenNail();
448    }
449
450    private class HideCameraAppView implements Animation.AnimationListener {
451        @Override
452        public void onAnimationEnd(Animation animation) {
453            // We cannot set this as GONE because we want to receive the
454            // onLayoutChange() callback even when we are invisible.
455            mCameraAppView.setVisibility(View.INVISIBLE);
456        }
457
458        @Override
459        public void onAnimationRepeat(Animation animation) {
460        }
461
462        @Override
463        public void onAnimationStart(Animation animation) {
464        }
465    }
466
467    protected void updateCameraAppView() {
468        // Initialize the animation.
469        if (mCameraAppViewFadeIn == null) {
470            mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f);
471            mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
472            mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator());
473
474            mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f);
475            mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
476            mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator());
477            mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView());
478        }
479
480        if (mShowCameraAppView) {
481            mCameraAppView.setVisibility(View.VISIBLE);
482            // The "transparent region" is not recomputed when a sibling of
483            // SurfaceView changes visibility (unless it involves GONE). It's
484            // been broken since 1.0. Call requestLayout to work around it.
485            mCameraAppView.requestLayout();
486            mCameraAppView.startAnimation(mCameraAppViewFadeIn);
487        } else {
488            mCameraAppView.startAnimation(mCameraAppViewFadeOut);
489        }
490    }
491
492    protected void onFullScreenChanged(boolean full) {
493        if (mShowCameraAppView == full) return;
494        mShowCameraAppView = full;
495        if (mPaused || isFinishing()) return;
496        updateCameraAppView();
497
498        // If we received DELETE_PICTURE broadcasts while the Camera UI is
499        // hidden, we update the thumbnail now.
500        if (full && mUpdateThumbnailDelayed) {
501            getLastThumbnailUncached();
502            mUpdateThumbnailDelayed = false;
503        }
504    }
505
506    @Override
507    public GalleryActionBar getGalleryActionBar() {
508        return mActionBar;
509    }
510
511    // Preview frame layout has changed.
512    @Override
513    public void onLayoutChange(View v, int left, int top, int right, int bottom) {
514        if (mAppBridge == null) return;
515
516        int width = right - left;
517        int height = bottom - top;
518        if (ApiHelper.HAS_SURFACE_TEXTURE) {
519            CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail;
520            if (Util.getDisplayRotation(this) % 180 == 0) {
521                screenNail.setPreviewFrameLayoutSize(width, height);
522            } else {
523                // Swap the width and height. Camera screen nail draw() is based on
524                // natural orientation, not the view system orientation.
525                screenNail.setPreviewFrameLayoutSize(height, width);
526            }
527        }
528
529        // Find out the coordinates of the preview frame relative to GL
530        // root view.
531        View root = (View) getGLRoot();
532        int[] rootLocation = new int[2];
533        int[] viewLocation = new int[2];
534        root.getLocationInWindow(rootLocation);
535        v.getLocationInWindow(viewLocation);
536
537        int l = viewLocation[0] - rootLocation[0];
538        int t = viewLocation[1] - rootLocation[1];
539        int r = l + width;
540        int b = t + height;
541        Rect frame = new Rect(l, t, r, b);
542        Log.d(TAG, "set CameraRelativeFrame as " + frame);
543        mAppBridge.setCameraRelativeFrame(frame);
544    }
545
546    protected void setSingleTapUpListener(View singleTapArea) {
547        mSingleTapArea = singleTapArea;
548    }
549
550    private boolean onSingleTapUp(int x, int y) {
551        // Ignore if listener is null or the camera control is invisible.
552        if (mSingleTapArea == null || !mShowCameraAppView) return false;
553
554        int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
555                mSingleTapArea);
556        x -= relativeLocation[0];
557        y -= relativeLocation[1];
558        if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
559                && y < mSingleTapArea.getHeight()) {
560            onSingleTapUp(mSingleTapArea, x, y);
561            return true;
562        }
563        return false;
564    }
565
566    protected void onSingleTapUp(View view, int x, int y) {
567    }
568
569    protected void setSwipingEnabled(boolean enabled) {
570        mAppBridge.setSwipingEnabled(enabled);
571    }
572
573    protected void notifyScreenNailChanged() {
574        mAppBridge.notifyScreenNailChanged();
575    }
576
577    protected void onPreviewTextureCopied() {
578    }
579
580    //////////////////////////////////////////////////////////////////////////
581    //  The is the communication interface between the Camera Application and
582    //  the Gallery PhotoPage.
583    //////////////////////////////////////////////////////////////////////////
584
585    class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
586        @SuppressWarnings("hiding")
587        private ScreenNail mCameraScreenNail;
588        private Server mServer;
589
590        @Override
591        public ScreenNail attachScreenNail() {
592            if (mCameraScreenNail == null) {
593                if (ApiHelper.HAS_SURFACE_TEXTURE) {
594                    mCameraScreenNail = new CameraScreenNail(this);
595                } else {
596                    Bitmap b = BitmapFactory.decodeResource(getResources(),
597                            R.drawable.wallpaper_picker_preview);
598                    mCameraScreenNail = new StaticBitmapScreenNail(b);
599                }
600            }
601            return mCameraScreenNail;
602        }
603
604        @Override
605        public void detachScreenNail() {
606            mCameraScreenNail = null;
607        }
608
609        public ScreenNail getCameraScreenNail() {
610            return mCameraScreenNail;
611        }
612
613        // Return true if the tap is consumed.
614        @Override
615        public boolean onSingleTapUp(int x, int y) {
616            return ActivityBase.this.onSingleTapUp(x, y);
617        }
618
619        // This is used to notify that the screen nail will be drawn in full screen
620        // or not in next draw() call.
621        @Override
622        public void onFullScreenChanged(boolean full) {
623            ActivityBase.this.onFullScreenChanged(full);
624        }
625
626        @Override
627        public void requestRender() {
628            getGLRoot().requestRender();
629        }
630
631        @Override
632        public void onPreviewTextureCopied() {
633            ActivityBase.this.onPreviewTextureCopied();
634        }
635
636        @Override
637        public void setServer(Server s) {
638            mServer = s;
639        }
640
641        @Override
642        public boolean isPanorama() {
643            return ActivityBase.this.isPanoramaActivity();
644        }
645
646        @Override
647        public boolean isStaticCamera() {
648            return !ApiHelper.HAS_SURFACE_TEXTURE;
649        }
650
651        private void setCameraRelativeFrame(Rect frame) {
652            if (mServer != null) mServer.setCameraRelativeFrame(frame);
653        }
654
655        private void switchWithCaptureAnimation(int offset) {
656            if (mServer != null) mServer.switchWithCaptureAnimation(offset);
657        }
658
659        private void setSwipingEnabled(boolean enabled) {
660            if (mServer != null) mServer.setSwipingEnabled(enabled);
661        }
662
663        private void notifyScreenNailChanged() {
664            if (mServer != null) mServer.notifyScreenNailChanged();
665        }
666    }
667}
668