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