ActivityBase.java revision 72fe79a040df8f01b03a39bec2473f7b0de8228d
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.hardware.Camera.Parameters;
26import android.os.AsyncTask;
27import android.os.Bundle;
28import android.util.Log;
29import android.view.animation.DecelerateInterpolator;
30import android.view.KeyEvent;
31import android.view.Menu;
32import android.view.View;
33import android.view.Window;
34import android.view.WindowManager;
35
36import com.android.camera.ui.PopupManager;
37import com.android.camera.ui.RotateImageView;
38import com.android.gallery3d.app.AbstractGalleryActivity;
39import com.android.gallery3d.app.PhotoPage;
40import com.android.gallery3d.app.PhotoPage.PageTapListener;
41import com.android.gallery3d.app.GalleryActionBar;
42import com.android.gallery3d.app.StateManager;
43import com.android.gallery3d.util.MediaSetUtils;
44
45import java.io.File;
46
47/**
48 * Superclass of Camera and VideoCamera activities.
49 */
50abstract public class ActivityBase extends AbstractGalleryActivity
51        implements CameraScreenNail.PositionChangedListener,
52                View.OnLayoutChangeListener, PageTapListener {
53
54    private static final String TAG = "ActivityBase";
55    private static boolean LOGV = false;
56    private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100;  // milliseconds
57    private int mResultCodeForTesting;
58    private Intent mResultDataForTesting;
59    private OnScreenHint mStorageHint;
60    private UpdateCameraAppView mUpdateCameraAppView;
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 CameraScreenNail mCameraScreenNail; // This shows camera preview.
85    // The view containing only camera related widgets like control panel,
86    // indicator bar, focus indicator and etc.
87    protected View mCameraAppView;
88    protected boolean mShowCameraAppView = true;
89
90    protected class CameraOpenThread extends Thread {
91        @Override
92        public void run() {
93            try {
94                mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId);
95                mParameters = mCameraDevice.getParameters();
96            } catch (CameraHardwareException e) {
97                mOpenCameraFail = true;
98            } catch (CameraDisabledException e) {
99                mCameraDisabled = true;
100            }
101        }
102    }
103
104    @Override
105    public void onCreate(Bundle icicle) {
106        if (getResources().getConfiguration().orientation
107                == Configuration.ORIENTATION_LANDSCAPE) {
108            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
109        } else {
110            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
111        }
112        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
113        super.disableToggleStatusBar();
114        super.onCreate(icicle);
115    }
116
117    @Override
118    protected void onPause() {
119        super.onPause();
120        if (LOGV) Log.v(TAG, "onPause");
121        saveThumbnailToFile();
122
123        if (mLoadThumbnailTask != null) {
124            mLoadThumbnailTask.cancel(true);
125            mLoadThumbnailTask = null;
126        }
127
128        if (mStorageHint != null) {
129            mStorageHint.cancel();
130            mStorageHint = null;
131        }
132    }
133
134    @Override
135    public void setContentView(int layoutResID) {
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        setTheme(android.R.style.Theme_Holo);
140        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
141        requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
142
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    private 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 after panorama has swipe UI.
268        if (getStateManager().getStateCount() > 0) {
269            PhotoPage photoPage = (PhotoPage) getStateManager().getTopState();
270            // Move the next picture with capture animation. "1" means next.
271            photoPage.switchWithCaptureAnimation(1);
272        } else {
273            Util.viewUri(mThumbnail.getUri(), this);
274        }
275    }
276
277    protected void saveThumbnailToFile() {
278        if (mThumbnail != null && !mThumbnail.fromFile()) {
279            new SaveThumbnailTask().execute(mThumbnail);
280        }
281    }
282
283    private class SaveThumbnailTask extends AsyncTask<Thumbnail, Void, Void> {
284        @Override
285        protected Void doInBackground(Thumbnail... params) {
286            final int n = params.length;
287            final File filesDir = getFilesDir();
288            for (int i = 0; i < n; i++) {
289                params[i].saveLastThumbnailToFile(filesDir);
290            }
291            return null;
292        }
293    }
294
295    // Call this after setContentView.
296    protected void createCameraScreenNail(boolean getPictures) {
297        mCameraAppView = findViewById(R.id.camera_app_root);
298        Bundle data = new Bundle();
299        String path = "/local/all/";
300        // Intent mode does not show camera roll. Use 0 as a work around for
301        // invalid bucket id.
302        // TODO: add support of empty media set in gallery.
303        path += (getPictures ? MediaSetUtils.CAMERA_BUCKET_ID : "0");
304        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
305        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
306
307        // Send a CameraScreenNail to gallery to enable the camera preview.
308        CameraScreenNailHolder holder = new CameraScreenNailHolder(this);
309        data.putParcelable(PhotoPage.KEY_SCREENNAIL_HOLDER, holder);
310        getStateManager().startState(PhotoPage.class, data);
311        mCameraScreenNail = holder.getCameraScreenNail();
312        mCameraScreenNail.setPositionChangedListener(this);
313    }
314
315    private class HideCameraAppView implements Runnable {
316        @Override
317        public void run() {
318            mCameraAppView.setVisibility(View.GONE);
319        }
320    }
321    private class UpdateCameraAppView implements Runnable {
322        @Override
323        public void run() {
324            if (mShowCameraAppView) {
325                mCameraAppView.setVisibility(View.VISIBLE);
326                mCameraAppView.animate()
327                        .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
328                        .withLayer().alpha(1);
329            } else {
330                mCameraAppView.animate()
331                        .setDuration(CAMERA_APP_VIEW_TOGGLE_TIME)
332                        .withLayer().alpha(0).withEndAction(mHideCameraAppView);
333            }
334        }
335    }
336
337    @Override
338    public void onPositionChanged(int x, int y, int width, int height, boolean visible) {
339        if (!mPaused && !isFinishing()) {
340            View rootView = (View) getGLRoot();
341            int rootWidth = rootView.getWidth();
342            int rootHeight = rootView.getHeight();
343            boolean showCameraAppView;
344            // Check if the camera preview is in the center.
345            if (visible && (x == 0 && width == rootWidth) ||
346                    (y == 0 && height == rootHeight && Math.abs(x - (rootWidth - width) / 2) <= 1)) {
347                showCameraAppView = true;
348            } else {
349                showCameraAppView = false;
350            }
351
352            if (mShowCameraAppView != showCameraAppView) {
353                mShowCameraAppView = showCameraAppView;
354                // Initialize the animation.
355                if (mUpdateCameraAppView == null) {
356                    mUpdateCameraAppView = new UpdateCameraAppView();
357                    mHideCameraAppView = new HideCameraAppView();
358                    mCameraAppView.animate()
359                        .setInterpolator(new DecelerateInterpolator());
360                }
361                runOnUiThread(mUpdateCameraAppView);
362            }
363        }
364    }
365
366    @Override
367    public GalleryActionBar getGalleryActionBar() {
368        return mActionBar;
369    }
370
371    // Preview frame layout has changed. Move the preview to the center of the
372    // layout.
373    @Override
374    public void onLayoutChange(View v, int left, int top, int right, int bottom,
375            int oldLeft, int oldTop, int oldRight, int oldBottom) {
376        // Find out the left and top of the preview frame layout relative to GL
377        // root view.
378        View root = (View) getGLRoot();
379        int[] rootLocation = new int[2];
380        int[] viewLocation = new int[2];
381        root.getLocationInWindow(rootLocation);
382        v.getLocationInWindow(viewLocation);
383        int relativeLeft = viewLocation[0] - rootLocation[0];
384        int relativeTop = viewLocation[1] - rootLocation[1];
385
386        // Calculate the scale ratio between preview frame layout and GL root
387        // view.
388        int width = root.getWidth();
389        int height = root.getHeight();
390        float scale = Math.max((float) (right - left) / width,
391                (float) (bottom - top) / height);
392        float scalePx = width / 2f;
393        float scalePy = height / 2f;
394
395        // Calculate the translate distance.
396        float translateX = relativeLeft + (right - left - width) / 2f;
397        float translateY = relativeTop + (bottom - top - height) / 2f;
398
399        mCameraScreenNail.setMatrix(scale, scalePx, scalePy, translateX, translateY);
400    }
401
402    protected void setSingleTapUpListener(View singleTapArea) {
403        PhotoPage photoPage = (PhotoPage) getStateManager().getTopState();
404        photoPage.setPageTapListener(this);
405        mSingleTapArea = singleTapArea;
406    }
407
408    // Single tap up from PhotoPage.
409    @Override
410    public boolean onSingleTapUp(int x, int y) {
411        // Camera control is invisible. Ignore.
412        if (!mShowCameraAppView) return false;
413
414        int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
415                mSingleTapArea);
416        x -= relativeLocation[0];
417        y -= relativeLocation[1];
418        if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
419                && y < mSingleTapArea.getHeight()) {
420            onSingleTapUp(mSingleTapArea, x, y);
421            return true;
422        }
423        return false;
424    }
425
426    protected void onSingleTapUp(View view, int x, int y) {
427    }
428}
429