CameraActivity.java revision e256999a30f0bb2bf0eb6fbef4023c82d374a5d0
1/*
2 * Copyright (C) 2012 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.Activity;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.ServiceConnection;
27import android.content.pm.ActivityInfo;
28import android.content.res.Configuration;
29import android.graphics.drawable.ColorDrawable;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.IBinder;
34import android.provider.Settings;
35import android.util.Log;
36import android.view.KeyEvent;
37import android.view.LayoutInflater;
38import android.view.OrientationEventListener;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.Window;
42import android.view.WindowManager;
43import android.widget.ImageView;
44import android.widget.ProgressBar;
45
46import com.android.camera.data.CameraDataAdapter;
47import com.android.camera.data.CameraPreviewData;
48import com.android.camera.data.FixedFirstDataAdapter;
49import com.android.camera.data.FixedLastDataAdapter;
50import com.android.camera.data.LocalData;
51import com.android.camera.data.LocalDataAdapter;
52import com.android.camera.ui.CameraSwitcher;
53import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
54import com.android.camera.ui.FilmStripView;
55import com.android.gallery3d.R;
56import com.android.gallery3d.common.ApiHelper;
57import com.android.gallery3d.util.LightCycleHelper;
58import com.android.gallery3d.util.RefocusHelper;
59import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper;
60
61public class CameraActivity extends Activity
62    implements CameraSwitchListener {
63
64    private static final String TAG = "CAM_Activity";
65
66    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
67            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
68    public static final String ACTION_IMAGE_CAPTURE_SECURE =
69            "android.media.action.IMAGE_CAPTURE_SECURE";
70
71    // The intent extra for camera from secure lock screen. True if the gallery
72    // should only show newly captured pictures. sSecureAlbumId does not
73    // increment. This is used when switching between camera, camcorder, and
74    // panorama. If the extra is not set, it is in the normal camera mode.
75    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
76
77    /** This data adapter is used by FilmStirpView. */
78    private LocalDataAdapter mDataAdapter;
79    /** This data adapter represents the real local camera data. */
80    private LocalDataAdapter mWrappedDataAdapter;
81
82    private PanoramaStitchingManager mPanoramaManager;
83    private int mCurrentModuleIndex;
84    private CameraModule mCurrentModule;
85    private View mRootView;
86    private FilmStripView mFilmStripView;
87    private ProgressBar mBottomProgress;
88    private View mPanoStitchingPanel;
89    private int mResultCodeForTesting;
90    private Intent mResultDataForTesting;
91    private OnScreenHint mStorageHint;
92    private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
93    private boolean mAutoRotateScreen;
94    private boolean mSecureCamera;
95    // This is a hack to speed up the start of SecureCamera.
96    private static boolean sFirstStartAfterScreenOn = true;
97    private boolean mShowCameraPreview;
98    private int mLastRawOrientation;
99    private MyOrientationEventListener mOrientationListener;
100    private Handler mMainHandler;
101    private PanoramaViewHelper mPanoramaViewHelper;
102    private CameraPreviewData mCameraPreviewData;
103
104    private class MyOrientationEventListener
105        extends OrientationEventListener {
106        public MyOrientationEventListener(Context context) {
107            super(context);
108        }
109
110        @Override
111        public void onOrientationChanged(int orientation) {
112            // We keep the last known orientation. So if the user first orient
113            // the camera then point the camera to floor or sky, we still have
114            // the correct orientation.
115            if (orientation == ORIENTATION_UNKNOWN) return;
116            mLastRawOrientation = orientation;
117            mCurrentModule.onOrientationChanged(orientation);
118        }
119    }
120
121    private MediaSaveService mMediaSaveService;
122    private ServiceConnection mConnection = new ServiceConnection() {
123            @Override
124            public void onServiceConnected(ComponentName className, IBinder b) {
125                mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
126                mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
127            }
128            @Override
129            public void onServiceDisconnected(ComponentName className) {
130                mMediaSaveService = null;
131            }};
132
133    // close activity when screen turns off
134    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
135        @Override
136        public void onReceive(Context context, Intent intent) {
137            finish();
138        }
139    };
140
141    private static BroadcastReceiver sScreenOffReceiver;
142    private static class ScreenOffReceiver extends BroadcastReceiver {
143        @Override
144        public void onReceive(Context context, Intent intent) {
145            sFirstStartAfterScreenOn = true;
146        }
147    }
148
149    public static boolean isFirstStartAfterScreenOn() {
150        return sFirstStartAfterScreenOn;
151    }
152
153    public static void resetFirstStartAfterScreenOn() {
154        sFirstStartAfterScreenOn = false;
155    }
156
157    private FilmStripView.Listener mFilmStripListener =
158            new FilmStripView.Listener() {
159                @Override
160                public void onDataPromoted(int dataID) {
161                    removeData(dataID);
162                }
163
164                @Override
165                public void onDataDemoted(int dataID) {
166                    removeData(dataID);
167                }
168
169                @Override
170                public void onDataFullScreenChange(int dataID, boolean full) {
171                }
172
173                @Override
174                public void onSwitchMode(boolean toCamera) {
175                    mCurrentModule.onSwitchMode(toCamera);
176                }
177
178                @Override
179                public void onCurrentDataChanged(int dataID, boolean current) {
180                    if (!current) {
181                        hidePanoStitchingProgress();
182                    } else {
183                        LocalData currentData = mDataAdapter.getLocalData(dataID);
184                        if (currentData == null) {
185                            Log.w(TAG, "Current data ID not found.");
186                            hidePanoStitchingProgress();
187                            return;
188                        }
189                        Uri contentUri = currentData.getContentUri();
190                        if (contentUri == null) {
191                            hidePanoStitchingProgress();
192                            return;
193                        }
194                        int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
195                        if (panoStitchingProgress < 0) {
196                            hidePanoStitchingProgress();
197                            return;
198                        }
199                        showPanoStitchingProgress();
200                        updateStitchingProgress(panoStitchingProgress);
201                    }
202                }
203            };
204
205    private void hidePanoStitchingProgress() {
206        mPanoStitchingPanel.setVisibility(View.GONE);
207    }
208
209    private void showPanoStitchingProgress() {
210        mPanoStitchingPanel.setVisibility(View.VISIBLE);
211    }
212
213    private void updateStitchingProgress(int progress) {
214        mBottomProgress.setProgress(progress);
215    }
216
217    private Runnable mDeletionRunnable = new Runnable() {
218            @Override
219            public void run() {
220                mDataAdapter.executeDeletion(CameraActivity.this);
221            }
222        };
223
224    private ImageTaskManager.TaskListener mStitchingListener =
225            new ImageTaskManager.TaskListener() {
226                @Override
227                public void onTaskQueued(String filePath, final Uri imageUri) {
228                    mMainHandler.post(new Runnable() {
229                        @Override
230                        public void run() {
231                            notifyNewMedia(imageUri);
232                        }
233                    });
234                }
235
236                @Override
237                public void onTaskDone(String filePath, final Uri imageUri) {
238                    Log.v(TAG, "onTaskDone:" + filePath);
239                    mMainHandler.post(new Runnable() {
240                        @Override
241                        public void run() {
242                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
243                            int currentDataId = mFilmStripView.getCurrentId();
244
245                            if (currentDataId == doneID) {
246                                hidePanoStitchingProgress();
247                                updateStitchingProgress(0);
248                            }
249
250                            mDataAdapter.refresh(getContentResolver(), imageUri);
251                        }
252                    });
253                }
254
255                @Override
256                public void onTaskProgress(
257                        String filePath, final Uri imageUri, final int progress) {
258                    mMainHandler.post(new Runnable() {
259                        @Override
260                        public void run() {
261                            int currentDataId = mFilmStripView.getCurrentId();
262                            if (currentDataId == -1) {
263                                return;
264                            }
265                            if (imageUri.equals(
266                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
267                                updateStitchingProgress(progress);
268                            }
269                        }
270                    });
271                }
272            };
273
274    public MediaSaveService getMediaSaveService() {
275        return mMediaSaveService;
276    }
277
278    public void notifyNewMedia(Uri uri) {
279        ContentResolver cr = getContentResolver();
280        String mimeType = cr.getType(uri);
281        if (mimeType.startsWith("video/")) {
282            sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO, uri));
283            mDataAdapter.addNewVideo(cr, uri);
284        } else if (mimeType.startsWith("image/")) {
285            Util.broadcastNewPicture(this, uri);
286            mDataAdapter.addNewPhoto(cr, uri);
287        } else if (mimeType.startsWith("application/stitching-preview")) {
288            mDataAdapter.addNewPhoto(cr, uri);
289        } else {
290            android.util.Log.w(TAG, "Unknown new media with MIME type:"
291                    + mimeType + ", uri:" + uri);
292        }
293    }
294
295    private void removeData(int dataID) {
296        mDataAdapter.removeData(CameraActivity.this, dataID);
297        mMainHandler.removeCallbacks(mDeletionRunnable);
298        mMainHandler.postDelayed(mDeletionRunnable, 3000);
299    }
300
301    private void bindMediaSaveService() {
302        Intent intent = new Intent(this, MediaSaveService.class);
303        startService(intent);  // start service before binding it so the
304                               // service won't be killed if we unbind it.
305        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
306    }
307
308    private void unbindMediaSaveService() {
309        if (mMediaSaveService != null) {
310            mMediaSaveService.setListener(null);
311        }
312        if (mConnection != null) {
313            unbindService(mConnection);
314        }
315    }
316
317    @Override
318    public void onCreate(Bundle state) {
319        super.onCreate(state);
320        setContentView(R.layout.camera_filmstrip);
321        if (ApiHelper.HAS_ROTATION_ANIMATION) {
322            setRotationAnimation();
323        }
324        // Check if this is in the secure camera mode.
325        Intent intent = getIntent();
326        String action = intent.getAction();
327        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
328                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
329            mSecureCamera = true;
330        } else {
331            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
332        }
333
334        if (mSecureCamera) {
335            // Change the window flags so that secure camera can show when locked
336            Window win = getWindow();
337            WindowManager.LayoutParams params = win.getAttributes();
338            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
339            win.setAttributes(params);
340
341            // Filter for screen off so that we can finish secure camera activity
342            // when screen is off.
343            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
344            registerReceiver(mScreenOffReceiver, filter);
345            // TODO: This static screen off event receiver is a workaround to the
346            // double onResume() invocation (onResume->onPause->onResume). We should
347            // find a better solution to this.
348            if (sScreenOffReceiver == null) {
349                sScreenOffReceiver = new ScreenOffReceiver();
350                registerReceiver(sScreenOffReceiver, filter);
351            }
352        }
353        mPanoramaManager = new PanoramaStitchingManager(CameraActivity.this);
354        mPanoramaManager.addTaskListener(mStitchingListener);
355        LayoutInflater inflater = getLayoutInflater();
356        View rootLayout = inflater.inflate(R.layout.camera, null, false);
357        mRootView = rootLayout.findViewById(R.id.camera_app_root);
358        mPanoStitchingPanel = (View) findViewById(R.id.pano_stitching_progress_panel);
359        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
360        mCameraPreviewData = new CameraPreviewData(rootLayout,
361                FilmStripView.ImageData.SIZE_FULL,
362                FilmStripView.ImageData.SIZE_FULL);
363        // Put a CameraPreviewData at the first position.
364        mWrappedDataAdapter = new FixedFirstDataAdapter(
365                new CameraDataAdapter(new ColorDrawable(
366                        getResources().getColor(R.color.photo_placeholder))),
367                mCameraPreviewData);
368        mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
369        mFilmStripView.setViewGap(
370                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
371        mPanoramaViewHelper = new PanoramaViewHelper(this);
372        mPanoramaViewHelper.onCreate();
373        mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
374        // Set up the camera preview first so the preview shows up ASAP.
375        mFilmStripView.setListener(mFilmStripListener);
376        mCurrentModule = new PhotoModule();
377        mCurrentModule.init(this, mRootView);
378        mOrientationListener = new MyOrientationEventListener(this);
379        mMainHandler = new Handler(getMainLooper());
380        bindMediaSaveService();
381
382        if (!mSecureCamera) {
383            mDataAdapter = mWrappedDataAdapter;
384            mDataAdapter.requestLoad(getContentResolver());
385        } else {
386            // Put a lock placeholder as the last image by setting its date to 0.
387            ImageView v = (ImageView) getLayoutInflater().inflate(
388                    R.layout.secure_album_placeholder, null);
389            mDataAdapter = new FixedLastDataAdapter(
390                    mWrappedDataAdapter,
391                    new LocalData.LocalViewData(
392                            v,
393                            v.getDrawable().getIntrinsicWidth(),
394                            v.getDrawable().getIntrinsicHeight(),
395                            0, 0));
396            // Flush out all the original data.
397            mDataAdapter.flush();
398        }
399        mFilmStripView.setDataAdapter(mDataAdapter);
400    }
401
402    private void setRotationAnimation() {
403        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
404        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
405        Window win = getWindow();
406        WindowManager.LayoutParams winParams = win.getAttributes();
407        winParams.rotationAnimation = rotationAnimation;
408        win.setAttributes(winParams);
409    }
410
411    @Override
412    public void onUserInteraction() {
413        super.onUserInteraction();
414        mCurrentModule.onUserInteraction();
415    }
416
417    @Override
418    public void onPause() {
419        mOrientationListener.disable();
420        mCurrentModule.onPauseBeforeSuper();
421        super.onPause();
422        mCurrentModule.onPauseAfterSuper();
423    }
424
425    @Override
426    public void onResume() {
427        if (Settings.System.getInt(getContentResolver(),
428                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
429            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
430            mAutoRotateScreen = false;
431        } else {
432            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
433            mAutoRotateScreen = true;
434        }
435        mOrientationListener.enable();
436        mCurrentModule.onResumeBeforeSuper();
437        super.onResume();
438        mCurrentModule.onResumeAfterSuper();
439
440        setSwipingEnabled(true);
441    }
442
443    @Override
444    public void onStart() {
445        super.onStart();
446
447        mPanoramaViewHelper.onStart();
448    }
449
450    @Override
451    protected void onStop() {
452        super.onStop();
453        mPanoramaViewHelper.onStop();
454    }
455
456    @Override
457    public void onDestroy() {
458        unbindMediaSaveService();
459        if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
460        super.onDestroy();
461    }
462
463    @Override
464    public void onConfigurationChanged(Configuration config) {
465        super.onConfigurationChanged(config);
466        mCurrentModule.onConfigurationChanged(config);
467    }
468
469    @Override
470    public boolean onKeyDown(int keyCode, KeyEvent event) {
471        if (mCurrentModule.onKeyDown(keyCode, event)) return true;
472        // Prevent software keyboard or voice search from showing up.
473        if (keyCode == KeyEvent.KEYCODE_SEARCH
474                || keyCode == KeyEvent.KEYCODE_MENU) {
475            if (event.isLongPress()) return true;
476        }
477        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
478            return true;
479        }
480
481        return super.onKeyDown(keyCode, event);
482    }
483
484    @Override
485    public boolean onKeyUp(int keyCode, KeyEvent event) {
486        if (mCurrentModule.onKeyUp(keyCode, event)) return true;
487        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
488            return true;
489        }
490        return super.onKeyUp(keyCode, event);
491    }
492
493    public boolean isAutoRotateScreen() {
494        return mAutoRotateScreen;
495    }
496
497    protected void updateStorageSpace() {
498        mStorageSpace = Storage.getAvailableSpace();
499    }
500
501    protected long getStorageSpace() {
502        return mStorageSpace;
503    }
504
505    protected void updateStorageSpaceAndHint() {
506        updateStorageSpace();
507        updateStorageHint(mStorageSpace);
508    }
509
510    protected void updateStorageHint() {
511        updateStorageHint(mStorageSpace);
512    }
513
514    protected boolean updateStorageHintOnResume() {
515        return true;
516    }
517
518    protected void updateStorageHint(long storageSpace) {
519        String message = null;
520        if (storageSpace == Storage.UNAVAILABLE) {
521            message = getString(R.string.no_storage);
522        } else if (storageSpace == Storage.PREPARING) {
523            message = getString(R.string.preparing_sd);
524        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
525            message = getString(R.string.access_sd_fail);
526        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
527            message = getString(R.string.spaceIsLow_content);
528        }
529
530        if (message != null) {
531            if (mStorageHint == null) {
532                mStorageHint = OnScreenHint.makeText(this, message);
533            } else {
534                mStorageHint.setText(message);
535            }
536            mStorageHint.show();
537        } else if (mStorageHint != null) {
538            mStorageHint.cancel();
539            mStorageHint = null;
540        }
541    }
542
543    protected void setResultEx(int resultCode) {
544        mResultCodeForTesting = resultCode;
545        setResult(resultCode);
546    }
547
548    protected void setResultEx(int resultCode, Intent data) {
549        mResultCodeForTesting = resultCode;
550        mResultDataForTesting = data;
551        setResult(resultCode, data);
552    }
553
554    public int getResultCode() {
555        return mResultCodeForTesting;
556    }
557
558    public Intent getResultData() {
559        return mResultDataForTesting;
560    }
561
562    public boolean isSecureCamera() {
563        return mSecureCamera;
564    }
565
566    @Override
567    public void onCameraSelected(int i) {
568        if (mCurrentModuleIndex == i) return;
569
570        CameraHolder.instance().keep();
571        closeModule(mCurrentModule);
572        mCurrentModuleIndex = i;
573        switch (i) {
574            case CameraSwitcher.VIDEO_MODULE_INDEX:
575                mCurrentModule = new VideoModule();
576                break;
577            case CameraSwitcher.PHOTO_MODULE_INDEX:
578                mCurrentModule = new PhotoModule();
579                break;
580            case CameraSwitcher.LIGHTCYCLE_MODULE_INDEX:
581                mCurrentModule = LightCycleHelper.createPanoramaModule();
582                break;
583            case CameraSwitcher.REFOCUS_MODULE_INDEX:
584                mCurrentModule = RefocusHelper.createRefocusModule();
585                break;
586           default:
587               break;
588        }
589
590        openModule(mCurrentModule);
591        mCurrentModule.onOrientationChanged(mLastRawOrientation);
592        if (mMediaSaveService != null) {
593            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
594        }
595    }
596
597    private void openModule(CameraModule module) {
598        module.init(this, mRootView);
599        module.onResumeBeforeSuper();
600        module.onResumeAfterSuper();
601    }
602
603    private void closeModule(CameraModule module) {
604        module.onPauseBeforeSuper();
605        module.onPauseAfterSuper();
606        ((ViewGroup) mRootView).removeAllViews();
607    }
608
609    @Override
610    public void onShowSwitcherPopup() {
611    }
612
613    public void setSwipingEnabled(boolean enable) {
614        mCameraPreviewData.lockPreview(!enable);
615    }
616
617    // Accessor methods for getting latency times used in performance testing
618    public long getAutoFocusTime() {
619        return (mCurrentModule instanceof PhotoModule) ?
620                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
621    }
622
623    public long getShutterLag() {
624        return (mCurrentModule instanceof PhotoModule) ?
625                ((PhotoModule) mCurrentModule).mShutterLag : -1;
626    }
627
628    public long getShutterToPictureDisplayedTime() {
629        return (mCurrentModule instanceof PhotoModule) ?
630                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
631    }
632
633    public long getPictureDisplayedToJpegCallbackTime() {
634        return (mCurrentModule instanceof PhotoModule) ?
635                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
636    }
637
638    public long getJpegCallbackFinishTime() {
639        return (mCurrentModule instanceof PhotoModule) ?
640                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
641    }
642
643    public long getCaptureStartTime() {
644        return (mCurrentModule instanceof PhotoModule) ?
645                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
646    }
647
648    public boolean isRecording() {
649        return (mCurrentModule instanceof VideoModule) ?
650                ((VideoModule) mCurrentModule).isRecording() : false;
651    }
652}
653