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