ActivityBase.java revision c94cd4a5206b05541de0dc8a4821c64d0edba352
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 final 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    protected 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
370        int width = right - left;
371        int height = bottom - top;
372        if (Util.getDisplayRotation(this) % 180 == 0) {
373            mCameraScreenNail.setPreviewFrameLayoutSize(width, height);
374        } else {
375            // Swap the width and height. Camera screen nail draw() is based on
376            // natural orientation, not the view system orientation.
377            mCameraScreenNail.setPreviewFrameLayoutSize(height, width);
378        }
379
380        // Find out the coordinates of the preview frame relative to GL
381        // root view.
382        View root = (View) getGLRoot();
383        int[] rootLocation = new int[2];
384        int[] viewLocation = new int[2];
385        root.getLocationInWindow(rootLocation);
386        v.getLocationInWindow(viewLocation);
387
388        int l = viewLocation[0] - rootLocation[0];
389        int t = viewLocation[1] - rootLocation[1];
390        int r = l + width;
391        int b = t + height;
392        Rect frame = new Rect(l, t, r, b);
393        Log.d(TAG, "set CameraRelativeFrame as " + frame);
394        mAppBridge.setCameraRelativeFrame(frame);
395    }
396
397    protected void setSingleTapUpListener(View singleTapArea) {
398        mSingleTapArea = singleTapArea;
399    }
400
401    private boolean onSingleTapUp(int x, int y) {
402        // Ignore if listener is null or the camera control is invisible.
403        if (mSingleTapArea == null || !mShowCameraAppView) return false;
404
405        int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
406                mSingleTapArea);
407        x -= relativeLocation[0];
408        y -= relativeLocation[1];
409        if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
410                && y < mSingleTapArea.getHeight()) {
411            onSingleTapUp(mSingleTapArea, x, y);
412            return true;
413        }
414        return false;
415    }
416
417    protected void onSingleTapUp(View view, int x, int y) {
418    }
419
420    protected void setSwipingEnabled(boolean enabled) {
421        mAppBridge.setSwipingEnabled(enabled);
422    }
423
424    protected void notifyScreenNailChanged() {
425        mAppBridge.notifyScreenNailChanged();
426    }
427
428    protected void onPreviewTextureCopied() {
429    }
430
431    //////////////////////////////////////////////////////////////////////////
432    //  The is the communication interface between the Camera Application and
433    //  the Gallery PhotoPage.
434    //////////////////////////////////////////////////////////////////////////
435
436    class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
437        private CameraScreenNail mCameraScreenNail;
438        private Server mServer;
439
440        @Override
441        public ScreenNail attachScreenNail() {
442            if (mCameraScreenNail == null) {
443                mCameraScreenNail = new CameraScreenNail(this);
444            }
445            return mCameraScreenNail;
446        }
447
448        @Override
449        public void detachScreenNail() {
450            mCameraScreenNail = null;
451        }
452
453        public CameraScreenNail getCameraScreenNail() {
454            return mCameraScreenNail;
455        }
456
457        // Return true if the tap is consumed.
458        @Override
459        public boolean onSingleTapUp(int x, int y) {
460            return ActivityBase.this.onSingleTapUp(x, y);
461        }
462
463        // This is used to notify that the screen nail will be drawn in full screen
464        // or not in next draw() call.
465        @Override
466        public void onFullScreenChanged(boolean full) {
467            ActivityBase.this.onFullScreenChanged(full);
468        }
469
470        @Override
471        public void requestRender() {
472            getGLRoot().requestRender();
473        }
474
475        @Override
476        public void onPreviewTextureCopied() {
477            ActivityBase.this.onPreviewTextureCopied();
478        }
479
480        @Override
481        public void setServer(Server s) {
482            mServer = s;
483        }
484
485        @Override
486        public boolean isPanorama() {
487            return ActivityBase.this.isPanoramaActivity();
488        }
489
490        private void setCameraRelativeFrame(Rect frame) {
491            if (mServer != null) mServer.setCameraRelativeFrame(frame);
492        }
493
494        private void switchWithCaptureAnimation(int offset) {
495            if (mServer != null) mServer.switchWithCaptureAnimation(offset);
496        }
497
498        private void setSwipingEnabled(boolean enabled) {
499            if (mServer != null) mServer.setSwipingEnabled(enabled);
500        }
501
502        private void notifyScreenNailChanged() {
503            if (mServer != null) mServer.notifyScreenNailChanged();
504        }
505    }
506}
507