CameraActivity.java revision 33035c19cc47adfb738c20cef66652ae451c8e10
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.SharedPreferences;
29import android.content.pm.ActivityInfo;
30import android.content.res.Configuration;
31import android.graphics.drawable.ColorDrawable;
32import android.net.Uri;
33import android.os.AsyncTask;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.IBinder;
37import android.preference.PreferenceManager;
38import android.provider.MediaStore;
39import android.provider.Settings;
40import android.util.Log;
41import android.view.KeyEvent;
42import android.view.LayoutInflater;
43import android.view.Menu;
44import android.view.MenuInflater;
45import android.view.MenuItem;
46import android.view.OrientationEventListener;
47import android.view.View;
48import android.view.ViewGroup;
49import android.view.Window;
50import android.view.WindowManager;
51import android.widget.FrameLayout;
52import android.widget.ImageView;
53import android.widget.ProgressBar;
54import android.widget.ShareActionProvider;
55
56import com.android.camera.data.CameraDataAdapter;
57import com.android.camera.data.CameraPreviewData;
58import com.android.camera.data.FixedFirstDataAdapter;
59import com.android.camera.data.FixedLastDataAdapter;
60import com.android.camera.data.LocalData;
61import com.android.camera.data.LocalDataAdapter;
62import com.android.camera.data.MediaDetails;
63import com.android.camera.data.SimpleViewData;
64import com.android.camera.ui.CameraSwitcher;
65import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
66import com.android.camera.ui.DetailsDialog;
67import com.android.camera.ui.FilmStripView;
68import com.android.camera.util.ApiHelper;
69import com.android.camera.util.CameraUtil;
70import com.android.camera.util.PhotoSphereHelper;
71import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
72import com.android.camera2.R;
73
74public class CameraActivity extends Activity
75    implements CameraSwitchListener {
76
77    private static final String TAG = "CAM_Activity";
78
79    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
80            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
81    public static final String ACTION_IMAGE_CAPTURE_SECURE =
82            "android.media.action.IMAGE_CAPTURE_SECURE";
83    public static final String ACTION_TRIM_VIDEO =
84            "com.android.camera.action.TRIM";
85    public static final String MEDIA_ITEM_PATH = "media-item-path";
86
87    private static final String PREF_STARTUP_MODULE_INDEX = "camera.startup_module";
88
89    // The intent extra for camera from secure lock screen. True if the gallery
90    // should only show newly captured pictures. sSecureAlbumId does not
91    // increment. This is used when switching between camera, camcorder, and
92    // panorama. If the extra is not set, it is in the normal camera mode.
93    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
94
95    // Supported operations at FilmStripView. Different data has different
96    // set of supported operations.
97    private static final int SUPPORT_DELETE = 1 << 0;
98    private static final int SUPPORT_ROTATE = 1 << 1;
99    private static final int SUPPORT_INFO = 1 << 2;
100    private static final int SUPPORT_CROP = 1 << 3;
101    private static final int SUPPORT_SETAS = 1 << 4;
102    private static final int SUPPORT_EDIT = 1 << 5;
103    private static final int SUPPORT_TRIM = 1 << 6;
104    private static final int SUPPORT_SHARE = 1 << 7;
105    private static final int SUPPORT_SHARE_PANORAMA360 = 1 << 8;
106    private static final int SUPPORT_SHOW_ON_MAP = 1 << 9;
107    private static final int SUPPORT_ALL = 0xffffffff;
108
109    /** This data adapter is used by FilmStripView. */
110    private LocalDataAdapter mDataAdapter;
111    /** This data adapter represents the real local camera data. */
112    private LocalDataAdapter mWrappedDataAdapter;
113
114    private PanoramaStitchingManager mPanoramaManager;
115    private int mCurrentModuleIndex;
116    private CameraModule mCurrentModule;
117    private FrameLayout mLayoutRoot;
118    private FrameLayout mAboveFilmstripControlLayout;
119    private View mCameraModuleRootView;
120    private FilmStripView mFilmStripView;
121    private ProgressBar mBottomProgress;
122    private View mPanoStitchingPanel;
123    private int mResultCodeForTesting;
124    private Intent mResultDataForTesting;
125    private OnScreenHint mStorageHint;
126    private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
127    private boolean mAutoRotateScreen;
128    private boolean mSecureCamera;
129    // This is a hack to speed up the start of SecureCamera.
130    private static boolean sFirstStartAfterScreenOn = true;
131    private boolean mShowCameraPreview;
132    private int mLastRawOrientation;
133    private MyOrientationEventListener mOrientationListener;
134    private Handler mMainHandler;
135    private PanoramaViewHelper mPanoramaViewHelper;
136    private CameraPreviewData mCameraPreviewData;
137    private ActionBar mActionBar;
138    private Menu mActionBarMenu;
139    private ViewGroup mUndoDeletionBar;
140
141    private ShareActionProvider mStandardShareActionProvider;
142    private Intent mStandardShareIntent;
143    private ShareActionProvider mPanoramaShareActionProvider;
144    private Intent mPanoramaShareIntent;
145
146    public void gotoGallery() {
147        mFilmStripView.getController().goToNextItem();
148    }
149
150    private class MyOrientationEventListener
151        extends OrientationEventListener {
152        public MyOrientationEventListener(Context context) {
153            super(context);
154        }
155
156        @Override
157        public void onOrientationChanged(int orientation) {
158            // We keep the last known orientation. So if the user first orient
159            // the camera then point the camera to floor or sky, we still have
160            // the correct orientation.
161            if (orientation == ORIENTATION_UNKNOWN) return;
162            mLastRawOrientation = orientation;
163            mCurrentModule.onOrientationChanged(orientation);
164        }
165    }
166
167    private MediaSaveService mMediaSaveService;
168    private ServiceConnection mConnection = new ServiceConnection() {
169            @Override
170            public void onServiceConnected(ComponentName className, IBinder b) {
171                mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
172                mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
173            }
174            @Override
175            public void onServiceDisconnected(ComponentName className) {
176                if (mMediaSaveService != null) {
177                    mMediaSaveService.setListener(null);
178                    mMediaSaveService = null;
179                }
180            }};
181
182    // close activity when screen turns off
183    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
184        @Override
185        public void onReceive(Context context, Intent intent) {
186            finish();
187        }
188    };
189
190    private static BroadcastReceiver sScreenOffReceiver;
191    private static class ScreenOffReceiver extends BroadcastReceiver {
192        @Override
193        public void onReceive(Context context, Intent intent) {
194            sFirstStartAfterScreenOn = true;
195        }
196    }
197
198    public static boolean isFirstStartAfterScreenOn() {
199        return sFirstStartAfterScreenOn;
200    }
201
202    public static void resetFirstStartAfterScreenOn() {
203        sFirstStartAfterScreenOn = false;
204    }
205
206    private FilmStripView.Listener mFilmStripListener =
207            new FilmStripView.Listener() {
208                @Override
209                public void onDataPromoted(int dataID) {
210                    removeData(dataID);
211                }
212
213                @Override
214                public void onDataDemoted(int dataID) {
215                    removeData(dataID);
216                }
217
218                @Override
219                public void onDataFullScreenChange(int dataID, boolean full) {
220                }
221
222                @Override
223                public void onSwitchMode(boolean toCamera) {
224                    mCurrentModule.onSwitchMode(toCamera);
225                    if (toCamera) {
226                        mActionBar.hide();
227                    } else {
228                        mActionBar.show();
229                    }
230                }
231
232                @Override
233                public void onCurrentDataChanged(final int dataID, final boolean current) {
234                    runOnUiThread(new Runnable() {
235                        @Override
236                        public void run() {
237                            if (!current) {
238                                hidePanoStitchingProgress();
239                            } else {
240                                LocalData currentData = mDataAdapter.getLocalData(dataID);
241                                if (currentData == null) {
242                                    Log.w(TAG, "Current data ID not found.");
243                                    hidePanoStitchingProgress();
244                                    return;
245                                }
246                                updateActionBarMenu(dataID);
247
248                                Uri contentUri = currentData.getContentUri();
249                                if (contentUri == null) {
250                                    hidePanoStitchingProgress();
251                                    return;
252                                }
253                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(
254                                    contentUri);
255                                if (panoStitchingProgress < 0) {
256                                    hidePanoStitchingProgress();
257                                    return;
258                                }
259                                showPanoStitchingProgress();
260                                updateStitchingProgress(panoStitchingProgress);
261                            }
262                        }
263                    });
264                }
265
266                @Override
267                public boolean onToggleActionBarVisibility() {
268                    if (mActionBar.isShowing()) {
269                        mActionBar.hide();
270                    } else {
271                        // In the preview, don't show the action bar if that is
272                        // a capture intent.
273                        if (!isCaptureIntent()) {
274                            mActionBar.show();
275                        }
276                    }
277                    return mActionBar.isShowing();
278                }
279            };
280
281    private void hidePanoStitchingProgress() {
282        mPanoStitchingPanel.setVisibility(View.GONE);
283    }
284
285    private void showPanoStitchingProgress() {
286        mPanoStitchingPanel.setVisibility(View.VISIBLE);
287    }
288
289    private void updateStitchingProgress(int progress) {
290        mBottomProgress.setProgress(progress);
291    }
292
293    private void setStandardShareIntent(Uri contentUri, String mimeType) {
294        if (mStandardShareIntent == null) {
295            mStandardShareIntent = new Intent(Intent.ACTION_SEND);
296        }
297        mStandardShareIntent.setType(mimeType);
298        mStandardShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
299        if (mStandardShareActionProvider != null) {
300            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
301        }
302    }
303
304    private void setPanoramaShareIntent(Uri contentUri) {
305        if (mPanoramaShareIntent == null) {
306            mPanoramaShareIntent = new Intent(Intent.ACTION_SEND);
307        }
308        mPanoramaShareIntent.setType("application/vnd.google.panorama360+jpg");
309        mPanoramaShareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
310        if (mPanoramaShareActionProvider != null) {
311            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
312        }
313    }
314
315    /**
316     * According to the data type, make the menu items for supported operations
317     * visible.
318     * @param dataID the data ID of the current item.
319     */
320    private void updateActionBarMenu(int dataID) {
321        LocalData currentData = mDataAdapter.getLocalData(dataID);
322        int type = currentData.getLocalDataType();
323
324        if (mActionBarMenu == null) {
325            return;
326        }
327
328        int supported = 0;
329        switch (type) {
330            case LocalData.LOCAL_IMAGE:
331                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
332                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
333                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
334                break;
335            case LocalData.LOCAL_VIDEO:
336                supported |= SUPPORT_DELETE | SUPPORT_INFO | SUPPORT_TRIM
337                        | SUPPORT_SHARE;
338                break;
339            case LocalData.LOCAL_PHOTO_SPHERE:
340                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
341                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
342                        | SUPPORT_SHARE | SUPPORT_SHOW_ON_MAP;
343                break;
344            case LocalData.LOCAL_360_PHOTO_SPHERE:
345                supported |= SUPPORT_DELETE | SUPPORT_ROTATE | SUPPORT_INFO
346                        | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_EDIT
347                        | SUPPORT_SHARE | SUPPORT_SHARE_PANORAMA360
348                        | SUPPORT_SHOW_ON_MAP;
349                break;
350            default:
351                break;
352        }
353
354        setMenuItemVisible(mActionBarMenu, R.id.action_delete,
355                (supported & SUPPORT_DELETE) != 0);
356        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_ccw,
357                (supported & SUPPORT_ROTATE) != 0);
358        setMenuItemVisible(mActionBarMenu, R.id.action_rotate_cw,
359                (supported & SUPPORT_ROTATE) != 0);
360        setMenuItemVisible(mActionBarMenu, R.id.action_details,
361                (supported & SUPPORT_INFO) != 0);
362        setMenuItemVisible(mActionBarMenu, R.id.action_crop,
363                (supported & SUPPORT_CROP) != 0);
364        setMenuItemVisible(mActionBarMenu, R.id.action_setas,
365                (supported & SUPPORT_SETAS) != 0);
366        setMenuItemVisible(mActionBarMenu, R.id.action_edit,
367                (supported & SUPPORT_EDIT) != 0);
368        setMenuItemVisible(mActionBarMenu, R.id.action_trim,
369                (supported & SUPPORT_TRIM) != 0);
370
371        boolean standardShare = (supported & SUPPORT_SHARE) != 0;
372        boolean panoramaShare = (supported & SUPPORT_SHARE_PANORAMA360) != 0;
373        setMenuItemVisible(mActionBarMenu, R.id.action_share, standardShare);
374        setMenuItemVisible(mActionBarMenu, R.id.action_share_panorama, panoramaShare);
375
376        if (panoramaShare) {
377            // For 360 PhotoSphere, relegate standard share to the overflow menu
378            MenuItem item = mActionBarMenu.findItem(R.id.action_share);
379            if (item != null) {
380                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
381                item.setTitle(getResources().getString(R.string.share_as_photo));
382            }
383            // And, promote "share as panorama" to action bar
384            item = mActionBarMenu.findItem(R.id.action_share_panorama);
385            if (item != null) {
386                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
387            }
388            setPanoramaShareIntent(currentData.getContentUri());
389        }
390        if (standardShare) {
391            if (!panoramaShare) {
392                MenuItem item = mActionBarMenu.findItem(R.id.action_share);
393                if (item != null) {
394                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
395                    item.setTitle(getResources().getString(R.string.share));
396                }
397            }
398            setStandardShareIntent(currentData.getContentUri(), currentData.getMimeType());
399        }
400
401        boolean itemHasLocation = currentData.getLatLong() != null;
402        setMenuItemVisible(mActionBarMenu, R.id.action_show_on_map,
403                itemHasLocation && (supported & SUPPORT_SHOW_ON_MAP) != 0);
404    }
405
406    private void setMenuItemVisible(Menu menu, int itemId, boolean visible) {
407        MenuItem item = menu.findItem(itemId);
408        if (item != null)
409            item.setVisible(visible);
410    }
411
412    private Runnable mDeletionRunnable = new Runnable() {
413            @Override
414            public void run() {
415                hideUndoDeletionBar();
416                mDataAdapter.executeDeletion(CameraActivity.this);
417            }
418        };
419
420    private ImageTaskManager.TaskListener mStitchingListener =
421            new ImageTaskManager.TaskListener() {
422                @Override
423                public void onTaskQueued(String filePath, final Uri imageUri) {
424                    mMainHandler.post(new Runnable() {
425                        @Override
426                        public void run() {
427                            notifyNewMedia(imageUri);
428                        }
429                    });
430                }
431
432                @Override
433                public void onTaskDone(String filePath, final Uri imageUri) {
434                    Log.v(TAG, "onTaskDone:" + filePath);
435                    mMainHandler.post(new Runnable() {
436                        @Override
437                        public void run() {
438                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
439                            int currentDataId = mFilmStripView.getCurrentId();
440
441                            if (currentDataId == doneID) {
442                                hidePanoStitchingProgress();
443                                updateStitchingProgress(0);
444                            }
445
446                            mDataAdapter.refresh(getContentResolver(), imageUri);
447                        }
448                    });
449                }
450
451                @Override
452                public void onTaskProgress(
453                        String filePath, final Uri imageUri, final int progress) {
454                    mMainHandler.post(new Runnable() {
455                        @Override
456                        public void run() {
457                            int currentDataId = mFilmStripView.getCurrentId();
458                            if (currentDataId == -1) {
459                                return;
460                            }
461                            if (imageUri.equals(
462                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
463                                updateStitchingProgress(progress);
464                            }
465                        }
466                    });
467                }
468            };
469
470    public MediaSaveService getMediaSaveService() {
471        return mMediaSaveService;
472    }
473
474    public void notifyNewMedia(Uri uri) {
475        ContentResolver cr = getContentResolver();
476        String mimeType = cr.getType(uri);
477        if (mimeType.startsWith("video/")) {
478            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri));
479            mDataAdapter.addNewVideo(cr, uri);
480        } else if (mimeType.startsWith("image/")) {
481            CameraUtil.broadcastNewPicture(this, uri);
482            mDataAdapter.addNewPhoto(cr, uri);
483        } else if (mimeType.startsWith("application/stitching-preview")) {
484            mDataAdapter.addNewPhoto(cr, uri);
485        } else {
486            android.util.Log.w(TAG, "Unknown new media with MIME type:"
487                    + mimeType + ", uri:" + uri);
488        }
489    }
490
491    private void removeData(int dataID) {
492        mDataAdapter.removeData(CameraActivity.this, dataID);
493        showUndoDeletionBar();
494        mMainHandler.removeCallbacks(mDeletionRunnable);
495        mMainHandler.postDelayed(mDeletionRunnable, 3000);
496    }
497
498    private void bindMediaSaveService() {
499        Intent intent = new Intent(this, MediaSaveService.class);
500        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
501    }
502
503    private void unbindMediaSaveService() {
504        if (mConnection != null) {
505            unbindService(mConnection);
506        }
507    }
508
509    @Override
510    public boolean onCreateOptionsMenu(Menu menu) {
511        // Inflate the menu items for use in the action bar
512        MenuInflater inflater = getMenuInflater();
513        inflater.inflate(R.menu.operations, menu);
514        mActionBarMenu = menu;
515
516        // Configure the standard share action provider
517        MenuItem item = menu.findItem(R.id.action_share);
518        mStandardShareActionProvider = (ShareActionProvider) item.getActionProvider();
519        mStandardShareActionProvider.setShareHistoryFileName("standard_share_history.xml");
520        if (mStandardShareIntent != null) {
521            mStandardShareActionProvider.setShareIntent(mStandardShareIntent);
522        }
523
524        // Configure the panorama share action provider
525        item = menu.findItem(R.id.action_share_panorama);
526        mPanoramaShareActionProvider = (ShareActionProvider) item.getActionProvider();
527        mPanoramaShareActionProvider.setShareHistoryFileName("panorama_share_history.xml");
528        if (mPanoramaShareIntent != null) {
529            mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent);
530        }
531
532        return super.onCreateOptionsMenu(menu);
533    }
534
535    @Override
536    public boolean onOptionsItemSelected(MenuItem item) {
537        int currentDataId = mFilmStripView.getCurrentId();
538        if (currentDataId < 0) {
539            return false;
540        }
541        final LocalData localData = mDataAdapter.getLocalData(currentDataId);
542
543        // Handle presses on the action bar items
544        switch (item.getItemId()) {
545            case android.R.id.home:
546                // ActionBar's Up/Home button was clicked
547                mFilmStripView.getController().goToFirstItem();
548                return true;
549            case R.id.action_delete:
550                removeData(currentDataId);
551                return true;
552            case R.id.action_edit:
553                launchEditor(localData);
554                return true;
555            case R.id.action_trim: {
556                // This is going to be handled by the Gallery app.
557                Intent intent = new Intent(ACTION_TRIM_VIDEO);
558                LocalData currentData = mDataAdapter.getLocalData(
559                        mFilmStripView.getCurrentId());
560                intent.setData(currentData.getContentUri());
561                // We need the file path to wrap this into a RandomAccessFile.
562                intent.putExtra(MEDIA_ITEM_PATH, currentData.getPath());
563                startActivity(intent);
564                return true;
565            }
566            case R.id.action_rotate_ccw:
567                localData.rotate90Degrees(this, mDataAdapter, currentDataId, false);
568                return true;
569            case R.id.action_rotate_cw:
570                localData.rotate90Degrees(this, mDataAdapter, currentDataId, true);
571                return true;
572            case R.id.action_crop:
573                // TODO: add the functionality.
574                return true;
575            case R.id.action_setas: {
576                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA)
577                        .setDataAndType(localData.getContentUri(),
578                                localData.getMimeType())
579                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
580                intent.putExtra("mimeType", intent.getType());
581                startActivity(Intent.createChooser(
582                        intent, getString(R.string.set_as)));
583                return true;
584            }
585            case R.id.action_details:
586                (new AsyncTask<Void, Void, MediaDetails>() {
587                    @Override
588                    protected MediaDetails doInBackground(Void... params) {
589                        return localData.getMediaDetails(CameraActivity.this);
590                    }
591
592                    @Override
593                    protected void onPostExecute(MediaDetails mediaDetails) {
594                        DetailsDialog.create(CameraActivity.this, mediaDetails).show();
595                    }
596                }).execute();
597                return true;
598            case R.id.action_show_on_map:
599                double[] latLong = localData.getLatLong();
600                if (latLong != null) {
601                  CameraUtil.showOnMap(this, latLong);
602                }
603                return true;
604            default:
605                return super.onOptionsItemSelected(item);
606        }
607    }
608
609    private boolean isCaptureIntent() {
610        if (MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())
611                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
612                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
613            return true;
614        } else {
615            return false;
616        }
617    }
618
619    @Override
620    public void onCreate(Bundle state) {
621        super.onCreate(state);
622        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
623        setContentView(R.layout.camera_filmstrip);
624        mActionBar = getActionBar();
625        // Hide action bar first since we are in full screen mode first.
626        mActionBar.hide();
627
628        if (ApiHelper.HAS_ROTATION_ANIMATION) {
629            setRotationAnimation();
630        }
631        // Check if this is in the secure camera mode.
632        Intent intent = getIntent();
633        String action = intent.getAction();
634        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)
635                || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
636            mSecureCamera = true;
637        } else {
638            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
639        }
640
641        if (mSecureCamera) {
642            // Change the window flags so that secure camera can show when locked
643            Window win = getWindow();
644            WindowManager.LayoutParams params = win.getAttributes();
645            params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
646            win.setAttributes(params);
647
648            // Filter for screen off so that we can finish secure camera activity
649            // when screen is off.
650            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
651            registerReceiver(mScreenOffReceiver, filter);
652            // TODO: This static screen off event receiver is a workaround to the
653            // double onResume() invocation (onResume->onPause->onResume). We should
654            // find a better solution to this.
655            if (sScreenOffReceiver == null) {
656                sScreenOffReceiver = new ScreenOffReceiver();
657                registerReceiver(sScreenOffReceiver, filter);
658            }
659        }
660        mLayoutRoot = (FrameLayout) findViewById(R.id.camera_layout_root);
661
662        mAboveFilmstripControlLayout =
663                (FrameLayout) findViewById(R.id.camera_above_filmstrip_layout);
664        mAboveFilmstripControlLayout.setFitsSystemWindows(true);
665        mAboveFilmstripControlLayout.setSystemUiVisibility(
666                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
667                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
668        mPanoramaManager = new PanoramaStitchingManager(CameraActivity.this);
669        mPanoramaManager.addTaskListener(mStitchingListener);
670        LayoutInflater inflater = getLayoutInflater();
671        View rootLayout = inflater.inflate(R.layout.camera, null, false);
672        mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root);
673        mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
674        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
675        mCameraPreviewData = new CameraPreviewData(rootLayout,
676                FilmStripView.ImageData.SIZE_FULL,
677                FilmStripView.ImageData.SIZE_FULL);
678        // Put a CameraPreviewData at the first position.
679        mWrappedDataAdapter = new FixedFirstDataAdapter(
680                new CameraDataAdapter(new ColorDrawable(
681                        getResources().getColor(R.color.photo_placeholder))),
682                mCameraPreviewData);
683        mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
684        mFilmStripView.setViewGap(
685                getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
686        mPanoramaViewHelper = new PanoramaViewHelper(this);
687        mPanoramaViewHelper.onCreate();
688        mFilmStripView.setPanoramaViewHelper(mPanoramaViewHelper);
689        // Set up the camera preview first so the preview shows up ASAP.
690        mFilmStripView.setListener(mFilmStripListener);
691
692        int moduleIndex = -1;
693        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
694                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
695            moduleIndex = CameraSwitcher.VIDEO_MODULE_INDEX;
696        } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
697                || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
698                        .getAction())
699                || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
700                || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
701            moduleIndex = CameraSwitcher.PHOTO_MODULE_INDEX;
702        } else {
703            // If the activity has not been started using an explicit intent,
704            // read the module index from the last time the user changed modes
705            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
706            moduleIndex = prefs.getInt(PREF_STARTUP_MODULE_INDEX, -1);
707            if (moduleIndex < 0) {
708                moduleIndex = CameraSwitcher.PHOTO_MODULE_INDEX;
709            }
710        }
711        setModuleFromIndex(moduleIndex);
712
713        mCurrentModule.init(this, mCameraModuleRootView);
714        mOrientationListener = new MyOrientationEventListener(this);
715        mMainHandler = new Handler(getMainLooper());
716
717        if (!mSecureCamera) {
718            mDataAdapter = mWrappedDataAdapter;
719            mFilmStripView.setDataAdapter(mDataAdapter);
720            mDataAdapter.requestLoad(getContentResolver());
721        } else {
722            // Put a lock placeholder as the last image by setting its date to 0.
723            ImageView v = (ImageView) getLayoutInflater().inflate(
724                    R.layout.secure_album_placeholder, null);
725            mDataAdapter = new FixedLastDataAdapter(
726                    mWrappedDataAdapter,
727                    new SimpleViewData(
728                            v,
729                            v.getDrawable().getIntrinsicWidth(),
730                            v.getDrawable().getIntrinsicHeight(),
731                            0, 0));
732            // Flush out all the original data.
733            mDataAdapter.flush();
734            mFilmStripView.setDataAdapter(mDataAdapter);
735        }
736    }
737
738    private void setRotationAnimation() {
739        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
740        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
741        Window win = getWindow();
742        WindowManager.LayoutParams winParams = win.getAttributes();
743        winParams.rotationAnimation = rotationAnimation;
744        win.setAttributes(winParams);
745    }
746
747    @Override
748    public void onUserInteraction() {
749        super.onUserInteraction();
750        mCurrentModule.onUserInteraction();
751    }
752
753    @Override
754    public void onPause() {
755        mOrientationListener.disable();
756        mCurrentModule.onPauseBeforeSuper();
757        super.onPause();
758        mCurrentModule.onPauseAfterSuper();
759    }
760
761    @Override
762    public void onResume() {
763        if (Settings.System.getInt(getContentResolver(),
764                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
765            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
766            mAutoRotateScreen = false;
767        } else {
768            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
769            mAutoRotateScreen = true;
770        }
771        mOrientationListener.enable();
772        mCurrentModule.onResumeBeforeSuper();
773        super.onResume();
774        mCurrentModule.onResumeAfterSuper();
775
776        setSwipingEnabled(true);
777    }
778
779    @Override
780    public void onStart() {
781        super.onStart();
782        bindMediaSaveService();
783        mPanoramaViewHelper.onStart();
784    }
785
786    @Override
787    protected void onStop() {
788        super.onStop();
789        mPanoramaViewHelper.onStop();
790        unbindMediaSaveService();
791    }
792
793    @Override
794    public void onDestroy() {
795        if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
796        super.onDestroy();
797    }
798
799    @Override
800    public void onConfigurationChanged(Configuration config) {
801        super.onConfigurationChanged(config);
802        mCurrentModule.onConfigurationChanged(config);
803    }
804
805    @Override
806    public boolean onKeyDown(int keyCode, KeyEvent event) {
807        if (mCurrentModule.onKeyDown(keyCode, event)) return true;
808        // Prevent software keyboard or voice search from showing up.
809        if (keyCode == KeyEvent.KEYCODE_SEARCH
810                || keyCode == KeyEvent.KEYCODE_MENU) {
811            if (event.isLongPress()) return true;
812        }
813        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
814            return true;
815        }
816
817        return super.onKeyDown(keyCode, event);
818    }
819
820    @Override
821    public boolean onKeyUp(int keyCode, KeyEvent event) {
822        if (mCurrentModule.onKeyUp(keyCode, event)) return true;
823        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
824            return true;
825        }
826        return super.onKeyUp(keyCode, event);
827    }
828
829    @Override
830    public void onBackPressed() {
831        if (!mFilmStripView.inCameraFullscreen()) {
832            mFilmStripView.getController().goToFirstItem();
833        } else if (!mCurrentModule.onBackPressed()) {
834            super.onBackPressed();
835        }
836    }
837
838    public boolean isAutoRotateScreen() {
839        return mAutoRotateScreen;
840    }
841
842    protected void updateStorageSpace() {
843        mStorageSpace = Storage.getAvailableSpace();
844    }
845
846    protected long getStorageSpace() {
847        return mStorageSpace;
848    }
849
850    protected void updateStorageSpaceAndHint() {
851        updateStorageSpace();
852        updateStorageHint(mStorageSpace);
853    }
854
855    protected void updateStorageHint() {
856        updateStorageHint(mStorageSpace);
857    }
858
859    protected boolean updateStorageHintOnResume() {
860        return true;
861    }
862
863    protected void updateStorageHint(long storageSpace) {
864        String message = null;
865        if (storageSpace == Storage.UNAVAILABLE) {
866            message = getString(R.string.no_storage);
867        } else if (storageSpace == Storage.PREPARING) {
868            message = getString(R.string.preparing_sd);
869        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
870            message = getString(R.string.access_sd_fail);
871        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
872            message = getString(R.string.spaceIsLow_content);
873        }
874
875        if (message != null) {
876            if (mStorageHint == null) {
877                mStorageHint = OnScreenHint.makeText(this, message);
878            } else {
879                mStorageHint.setText(message);
880            }
881            mStorageHint.show();
882        } else if (mStorageHint != null) {
883            mStorageHint.cancel();
884            mStorageHint = null;
885        }
886    }
887
888    protected void setResultEx(int resultCode) {
889        mResultCodeForTesting = resultCode;
890        setResult(resultCode);
891    }
892
893    protected void setResultEx(int resultCode, Intent data) {
894        mResultCodeForTesting = resultCode;
895        mResultDataForTesting = data;
896        setResult(resultCode, data);
897    }
898
899    public int getResultCode() {
900        return mResultCodeForTesting;
901    }
902
903    public Intent getResultData() {
904        return mResultDataForTesting;
905    }
906
907    public boolean isSecureCamera() {
908        return mSecureCamera;
909    }
910
911    @Override
912    public void onCameraSelected(int moduleIndex) {
913        if (mCurrentModuleIndex == moduleIndex) return;
914
915        CameraHolder.instance().keep();
916        closeModule(mCurrentModule);
917        setModuleFromIndex(moduleIndex);
918
919        openModule(mCurrentModule);
920        mCurrentModule.onOrientationChanged(mLastRawOrientation);
921        if (mMediaSaveService != null) {
922            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
923        }
924
925        // Store the module index so we can use it the next time the Camera
926        // starts up.
927        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
928        prefs.edit().putInt(PREF_STARTUP_MODULE_INDEX, moduleIndex).apply();
929    }
930
931    /**
932     * Sets the mCurrentModuleIndex, creates a new module instance for the
933     * given index an sets it as mCurrentModule.
934     */
935    private void setModuleFromIndex(int moduleIndex) {
936        mCurrentModuleIndex = moduleIndex;
937        switch (moduleIndex) {
938            case CameraSwitcher.VIDEO_MODULE_INDEX:
939                mCurrentModule = new VideoModule();
940                break;
941            case CameraSwitcher.PHOTO_MODULE_INDEX:
942                mCurrentModule = new PhotoModule();
943                break;
944            case CameraSwitcher.LIGHTCYCLE_MODULE_INDEX:
945                mCurrentModule = PhotoSphereHelper.createPanoramaModule();
946                break;
947            default:
948                break;
949        }
950    }
951
952    /**
953     * Launches an ACTION_EDIT intent for the given local data item.
954     */
955    public void launchEditor(LocalData data) {
956        Intent intent = new Intent(Intent.ACTION_EDIT)
957                .setDataAndType(data.getContentUri(), data.getMimeType())
958                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
959        startActivity(Intent.createChooser(intent, null));
960    }
961
962    private void openModule(CameraModule module) {
963        module.init(this, mCameraModuleRootView);
964        module.onResumeBeforeSuper();
965        module.onResumeAfterSuper();
966    }
967
968    private void closeModule(CameraModule module) {
969        module.onPauseBeforeSuper();
970        module.onPauseAfterSuper();
971        ((ViewGroup) mCameraModuleRootView).removeAllViews();
972    }
973
974    private void showUndoDeletionBar() {
975        if (mUndoDeletionBar == null) {
976            Log.v(TAG, "showing undo bar");
977            ViewGroup v = (ViewGroup) getLayoutInflater().inflate(
978                    R.layout.undo_bar, mAboveFilmstripControlLayout, true);
979            mUndoDeletionBar = (ViewGroup) v.findViewById(R.id.camera_undo_deletion_bar);
980            View button = mUndoDeletionBar.findViewById(R.id.camera_undo_deletion_button);
981            button.setOnClickListener(new View.OnClickListener() {
982                @Override
983                public void onClick(View view) {
984                    mDataAdapter.undoDataRemoval();
985                    mMainHandler.removeCallbacks(mDeletionRunnable);
986                    hideUndoDeletionBar();
987                }
988            });
989        }
990        mUndoDeletionBar.setAlpha(0f);
991        mUndoDeletionBar.setVisibility(View.VISIBLE);
992        mUndoDeletionBar.animate().setDuration(200).alpha(1f).start();
993    }
994
995    private void hideUndoDeletionBar() {
996        Log.v(TAG, "Hiding undo deletion bar");
997        if (mUndoDeletionBar != null) {
998            mUndoDeletionBar.animate()
999                    .setDuration(200)
1000                    .alpha(0f)
1001                    .withEndAction(new Runnable() {
1002                        @Override
1003                        public void run() {
1004                            mUndoDeletionBar.setVisibility(View.GONE);
1005                        }
1006                    })
1007                    .start();
1008        }
1009    }
1010
1011    @Override
1012    public void onShowSwitcherPopup() {
1013    }
1014
1015    public void setSwipingEnabled(boolean enable) {
1016        mCameraPreviewData.lockPreview(!enable);
1017    }
1018
1019    // Accessor methods for getting latency times used in performance testing
1020    public long getAutoFocusTime() {
1021        return (mCurrentModule instanceof PhotoModule) ?
1022                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
1023    }
1024
1025    public long getShutterLag() {
1026        return (mCurrentModule instanceof PhotoModule) ?
1027                ((PhotoModule) mCurrentModule).mShutterLag : -1;
1028    }
1029
1030    public long getShutterToPictureDisplayedTime() {
1031        return (mCurrentModule instanceof PhotoModule) ?
1032                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
1033    }
1034
1035    public long getPictureDisplayedToJpegCallbackTime() {
1036        return (mCurrentModule instanceof PhotoModule) ?
1037                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
1038    }
1039
1040    public long getJpegCallbackFinishTime() {
1041        return (mCurrentModule instanceof PhotoModule) ?
1042                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
1043    }
1044
1045    public long getCaptureStartTime() {
1046        return (mCurrentModule instanceof PhotoModule) ?
1047                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
1048    }
1049
1050    public boolean isRecording() {
1051        return (mCurrentModule instanceof VideoModule) ?
1052                ((VideoModule) mCurrentModule).isRecording() : false;
1053    }
1054}
1055