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