ActivityBase.java revision 252da3c55a35c77fa4be4d51f79dac4aaf7cdb77
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    public boolean isPanoramaActivity() {
125        return false;
126    }
127
128    @Override
129    protected void onPause() {
130        super.onPause();
131        if (LOGV) Log.v(TAG, "onPause");
132        saveThumbnailToFile();
133
134        if (mLoadThumbnailTask != null) {
135            mLoadThumbnailTask.cancel(true);
136            mLoadThumbnailTask = null;
137        }
138
139        if (mStorageHint != null) {
140            mStorageHint.cancel();
141            mStorageHint = null;
142        }
143    }
144
145    @Override
146    public void setContentView(int layoutResID) {
147        super.setContentView(layoutResID);
148        // getActionBar() should be after setContentView
149        mActionBar = new GalleryActionBar(this);
150        mActionBar.hide();
151    }
152
153    @Override
154    public boolean onSearchRequested() {
155        return false;
156    }
157
158    @Override
159    public boolean onKeyDown(int keyCode, KeyEvent event) {
160        // Prevent software keyboard or voice search from showing up.
161        if (keyCode == KeyEvent.KEYCODE_SEARCH
162                || keyCode == KeyEvent.KEYCODE_MENU) {
163            if (event.isLongPress()) return true;
164        }
165
166        return super.onKeyDown(keyCode, event);
167    }
168
169    protected void setResultEx(int resultCode) {
170        mResultCodeForTesting = resultCode;
171        setResult(resultCode);
172    }
173
174    protected void setResultEx(int resultCode, Intent data) {
175        mResultCodeForTesting = resultCode;
176        mResultDataForTesting = data;
177        setResult(resultCode, data);
178    }
179
180    public int getResultCode() {
181        return mResultCodeForTesting;
182    }
183
184    public Intent getResultData() {
185        return mResultDataForTesting;
186    }
187
188    @Override
189    protected void onDestroy() {
190        PopupManager.removeInstance(this);
191        super.onDestroy();
192    }
193
194    @Override
195    public boolean onCreateOptionsMenu(Menu menu) {
196        super.onCreateOptionsMenu(menu);
197        return getStateManager().createOptionsMenu(menu);
198    }
199
200    protected void updateStorageHint(long storageSpace) {
201        String message = null;
202        if (storageSpace == Storage.UNAVAILABLE) {
203            message = getString(R.string.no_storage);
204        } else if (storageSpace == Storage.PREPARING) {
205            message = getString(R.string.preparing_sd);
206        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
207            message = getString(R.string.access_sd_fail);
208        } else if (storageSpace < Storage.LOW_STORAGE_THRESHOLD) {
209            message = getString(R.string.spaceIsLow_content);
210        }
211
212        if (message != null) {
213            if (mStorageHint == null) {
214                mStorageHint = OnScreenHint.makeText(this, message);
215            } else {
216                mStorageHint.setText(message);
217            }
218            mStorageHint.show();
219        } else if (mStorageHint != null) {
220            mStorageHint.cancel();
221            mStorageHint = null;
222        }
223    }
224
225    protected void updateThumbnailView() {
226        if (mThumbnail != null) {
227            mThumbnailView.setBitmap(mThumbnail.getBitmap());
228            mThumbnailView.setVisibility(View.VISIBLE);
229        } else {
230            mThumbnailView.setBitmap(null);
231            mThumbnailView.setVisibility(View.GONE);
232        }
233    }
234
235    protected void getLastThumbnail() {
236        mThumbnail = ThumbnailHolder.getLastThumbnail(getContentResolver());
237        // Suppose users tap the thumbnail view, go to the gallery, delete the
238        // image, and coming back to the camera. Thumbnail file will be invalid.
239        // Since the new thumbnail will be loaded in another thread later, the
240        // view should be set to gone to prevent from opening the invalid image.
241        updateThumbnailView();
242        if (mThumbnail == null) {
243            mLoadThumbnailTask = new LoadThumbnailTask().execute();
244        }
245    }
246
247    private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> {
248        @Override
249        protected Thumbnail doInBackground(Void... params) {
250            // Load the thumbnail from the file.
251            ContentResolver resolver = getContentResolver();
252            Thumbnail t = Thumbnail.getLastThumbnailFromFile(getFilesDir(), resolver);
253
254            if (isCancelled()) return null;
255
256            if (t == null) {
257                // Load the thumbnail from the media provider.
258                t = Thumbnail.getLastThumbnailFromContentResolver(resolver);
259            }
260            return t;
261        }
262
263        @Override
264        protected void onPostExecute(Thumbnail thumbnail) {
265            mThumbnail = thumbnail;
266            updateThumbnailView();
267        }
268    }
269
270    protected void gotoGallery() {
271        // Move the next picture with capture animation. "1" means next.
272        mAppBridge.switchWithCaptureAnimation(1);
273    }
274
275    protected void saveThumbnailToFile() {
276        if (mThumbnail != null && !mThumbnail.fromFile()) {
277            new SaveThumbnailTask().execute(mThumbnail);
278        }
279    }
280
281    private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> {
282        @Override
283        protected Void doInBackground(Thumbnail... params) {
284            final int n = params.length;
285            final File filesDir = getFilesDir();
286            for (int i = 0; i < n; i++) {
287                params[i].saveLastThumbnailToFile(filesDir);
288            }
289            return null;
290        }
291    }
292
293    // Call this after setContentView.
294    protected void createCameraScreenNail(boolean getPictures) {
295        mCameraAppView = findViewById(R.id.camera_app_root);
296        Bundle data = new Bundle();
297        String path = "/local/all/";
298        // Intent mode does not show camera roll. Use 0 as a work around for
299        // invalid bucket id.
300        // TODO: add support of empty media set in gallery.
301        path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0");
302        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
303        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
304
305        // Send an AppBridge to gallery to enable the camera preview.
306        mAppBridge = new MyAppBridge();
307        data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
308        getStateManager().startState(PhotoPage.class, data);
309        mCameraScreenNail = mAppBridge.getCameraScreenNail();
310    }
311
312    private class HideCameraAppView implements Runnable {
313        @Override
314        public void run() {
315            // We cannot set this as GONE because we want to receive the
316            // onLayoutChange() callback even when we are invisible.
317            mCameraAppView.setVisibility(View.INVISIBLE);
318        }
319    }
320
321    private void updateCameraAppView() {
322        if (mShowCameraAppView) {
323            mCameraAppView.setVisibility(View.VISIBLE);
324            // The "transparent region" is not recomputed when a sibling of
325            // SurfaceView changes visibility (unless it involves GONE). It's
326            // been broken since 1.0. Call requestLayout to work around it.
327            mCameraAppView.requestLayout();
328            // withEndAction(null) prevents the pending end action
329            // mHideCameraAppView from being executed.
330            mCameraAppView.animate()
331                    .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
332                    .withLayer().alpha(1).withEndAction(null);
333        } else {
334            mCameraAppView.animate()
335                    .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
336                    .withLayer().alpha(0).withEndAction(mHideCameraAppView);
337        }
338    }
339
340    private void onFullScreenChanged(boolean full) {
341        if (mShowCameraAppView == full) return;
342        mShowCameraAppView = full;
343        if (mPaused || isFinishing()) return;
344        // Initialize the animation.
345        if (mHideCameraAppView == null) {
346            mHideCameraAppView = new HideCameraAppView();
347            mCameraAppView.animate()
348                .setInterpolator(new DecelerateInterpolator());
349        }
350        updateCameraAppView();
351    }
352
353    @Override
354    public GalleryActionBar getGalleryActionBar() {
355        return mActionBar;
356    }
357
358    // Preview frame layout has changed.
359    @Override
360    public void onLayoutChange(View v, int left, int top, int right, int bottom,
361            int oldLeft, int oldTop, int oldRight, int oldBottom) {
362        if (mAppBridge == null) return;
363
364        if (left == oldLeft && top == oldTop && right == oldRight
365                && bottom == oldBottom) {
366            return;
367        }
368
369        // Find out the coordinates of the preview frame relative to GL
370        // root view.
371        View root = (View) getGLRoot();
372        int[] rootLocation = new int[2];
373        int[] viewLocation = new int[2];
374        root.getLocationInWindow(rootLocation);
375        v.getLocationInWindow(viewLocation);
376
377        int l = viewLocation[0] - rootLocation[0];
378        int t = viewLocation[1] - rootLocation[1];
379        int r = l + (right - left);
380        int b = t + (bottom - top);
381        Rect frame = new Rect(l, t, r, b);
382        Log.d(TAG, "set CameraRelativeFrame as " + frame);
383        mAppBridge.setCameraRelativeFrame(frame);
384    }
385
386    protected void setSingleTapUpListener(View singleTapArea) {
387        mSingleTapArea = singleTapArea;
388    }
389
390    private boolean onSingleTapUp(int x, int y) {
391        // Ignore if listener is null or the camera control is invisible.
392        if (mSingleTapArea == null || !mShowCameraAppView) return false;
393
394        int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
395                mSingleTapArea);
396        x -= relativeLocation[0];
397        y -= relativeLocation[1];
398        if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
399                && y < mSingleTapArea.getHeight()) {
400            onSingleTapUp(mSingleTapArea, x, y);
401            return true;
402        }
403        return false;
404    }
405
406    protected void onSingleTapUp(View view, int x, int y) {
407    }
408
409    protected void setSwipingEnabled(boolean enabled) {
410        mAppBridge.setSwipingEnabled(enabled);
411    }
412
413    protected void notifyScreenNailChanged() {
414        mAppBridge.notifyScreenNailChanged();
415    }
416
417    protected void onPreviewTextureCopied() {
418    }
419
420    //////////////////////////////////////////////////////////////////////////
421    //  The is the communication interface between the Camera Application and
422    //  the Gallery PhotoPage.
423    //////////////////////////////////////////////////////////////////////////
424
425    class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
426        private CameraScreenNail mCameraScreenNail;
427        private Server mServer;
428
429        @Override
430        public ScreenNail attachScreenNail() {
431            if (mCameraScreenNail == null) {
432                mCameraScreenNail = new CameraScreenNail(this);
433            }
434            return mCameraScreenNail;
435        }
436
437        @Override
438        public void detachScreenNail() {
439            mCameraScreenNail = null;
440        }
441
442        public CameraScreenNail getCameraScreenNail() {
443            return mCameraScreenNail;
444        }
445
446        // Return true if the tap is consumed.
447        @Override
448        public boolean onSingleTapUp(int x, int y) {
449            return ActivityBase.this.onSingleTapUp(x, y);
450        }
451
452        // This is used to notify that the screen nail will be drawn in full screen
453        // or not in next draw() call.
454        @Override
455        public void onFullScreenChanged(boolean full) {
456            ActivityBase.this.onFullScreenChanged(full);
457        }
458
459        @Override
460        public void requestRender() {
461            getGLRoot().requestRender();
462        }
463
464        @Override
465        public void onPreviewTextureCopied() {
466            ActivityBase.this.onPreviewTextureCopied();
467        }
468
469        @Override
470        public void setServer(Server s) {
471            mServer = s;
472        }
473
474        @Override
475        public boolean isPanorama() {
476            return ActivityBase.this.isPanoramaActivity();
477        }
478
479        private void setCameraRelativeFrame(Rect frame) {
480            if (mServer != null) mServer.setCameraRelativeFrame(frame);
481        }
482
483        private void switchWithCaptureAnimation(int offset) {
484            if (mServer != null) mServer.switchWithCaptureAnimation(offset);
485        }
486
487        private void setSwipingEnabled(boolean enabled) {
488            if (mServer != null) mServer.setSwipingEnabled(enabled);
489        }
490
491        private void notifyScreenNailChanged() {
492            if (mServer != null) mServer.notifyScreenNailChanged();
493        }
494    }
495}
496