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