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