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