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