OSDN Git Service

Protect against destroying uncreated values
[android-x86/packages-apps-Camera2.git] / src / com / android / camera / CameraActivity.java
index 08768b2..bf48898 100644 (file)
@@ -17,6 +17,7 @@
 
 package com.android.camera;
 
+import android.Manifest;
 import android.animation.Animator;
 import android.app.ActionBar;
 import android.app.Activity;
@@ -28,6 +29,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
@@ -61,7 +63,6 @@ import android.view.View.OnSystemUiVisibilityChangeListener;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.ShareActionProvider;
@@ -102,12 +103,16 @@ import com.android.camera.data.SessionItem;
 import com.android.camera.data.VideoDataFactory;
 import com.android.camera.data.VideoItemFactory;
 import com.android.camera.debug.Log;
+import com.android.camera.device.ActiveCameraDeviceTracker;
+import com.android.camera.device.CameraId;
 import com.android.camera.filmstrip.FilmstripContentPanel;
 import com.android.camera.filmstrip.FilmstripController;
 import com.android.camera.module.ModuleController;
 import com.android.camera.module.ModulesInfo;
 import com.android.camera.one.OneCameraException;
 import com.android.camera.one.OneCameraManager;
+import com.android.camera.one.OneCameraModule;
+import com.android.camera.one.OneCameraOpener;
 import com.android.camera.one.config.OneCameraFeatureConfig;
 import com.android.camera.one.config.OneCameraFeatureConfigCreator;
 import com.android.camera.session.CaptureSession;
@@ -116,6 +121,7 @@ import com.android.camera.session.CaptureSessionManager.SessionListener;
 import com.android.camera.settings.AppUpgrader;
 import com.android.camera.settings.CameraSettingsActivity;
 import com.android.camera.settings.Keys;
+import com.android.camera.settings.PictureSizeLoader;
 import com.android.camera.settings.ResolutionSetting;
 import com.android.camera.settings.ResolutionUtil;
 import com.android.camera.settings.SettingsManager;
@@ -152,7 +158,7 @@ import com.bumptech.glide.GlideBuilder;
 import com.bumptech.glide.MemoryCategory;
 import com.bumptech.glide.load.DecodeFormat;
 import com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor;
-import com.bumptech.glide.load.engine.prefill.PreFillType;
+
 import com.google.common.base.Optional;
 import com.google.common.logging.eventprotos;
 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
@@ -182,13 +188,13 @@ public class CameraActivity extends QuickActivity
     // panorama. If the extra is not set, it is in the normal camera mode.
     public static final String SECURE_CAMERA_EXTRA = "secure_camera";
 
-    public static final String MODULE_SCOPE_PREFIX = "_preferences_module_";
-    public static final String CAMERA_SCOPE_PREFIX = "_preferences_camera_";
-
     private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
     private static final long SCREEN_DELAY_MS = 2 * 60 * 1000; // 2 mins.
     /** Load metadata for 10 items ahead of our current. */
     private static final int FILMSTRIP_PRELOAD_AHEAD_ITEMS = 10;
+    private static final int PERMISSIONS_ACTIVITY_REQUEST_CODE = 1;
+    private static final int PERMISSIONS_RESULT_CODE_OK = 1;
+    private static final int PERMISSIONS_RESULT_CODE_FAILED = 2;
 
     /** Should be used wherever a context is needed. */
     private Context mAppContext;
@@ -212,7 +218,9 @@ public class CameraActivity extends QuickActivity
     private PhotoItemFactory mPhotoItemFactory;
     private LocalFilmstripDataAdapter mDataAdapter;
 
-    private OneCameraManager mCameraManager;
+    private ActiveCameraDeviceTracker mActiveCameraDeviceTracker;
+    private OneCameraOpener mOneCameraOpener;
+    private OneCameraManager mOneCameraManager;
     private SettingsManager mSettingsManager;
     private ResolutionSetting mResolutionSetting;
     private ModeListView mModeListView;
@@ -241,6 +249,8 @@ public class CameraActivity extends QuickActivity
     private ViewGroup mUndoDeletionBar;
     private boolean mIsUndoingDeletion = false;
     private boolean mIsActivityRunning = false;
+    private FatalErrorHandler mFatalErrorHandler;
+    private boolean mHasCriticalPermissions;
 
     private final Uri[] mNfcPushUris = new Uri[1];
 
@@ -511,7 +521,12 @@ public class CameraActivity extends QuickActivity
         }
         if (mCurrentModule != null) {
             resetExposureCompensationToDefault(camera);
-            mCurrentModule.onCameraAvailable(camera);
+            try {
+                mCurrentModule.onCameraAvailable(camera);
+            } catch (RuntimeException ex) {
+                Log.e(TAG, "Error connecting to camera", ex);
+                mFatalErrorHandler.onCameraOpenFailure();
+            }
         } else {
             Log.v(TAG, "mCurrentModule null, not invoking onCameraAvailable");
         }
@@ -528,35 +543,26 @@ public class CameraActivity extends QuickActivity
 
     @Override
     public void onCameraDisabled(int cameraId) {
-        UsageStatistics.instance().cameraFailure(
-                eventprotos.CameraFailure.FailureReason.SECURITY, null,
-                UsageStatistics.NONE, UsageStatistics.NONE);
         Log.w(TAG, "Camera disabled: " + cameraId);
-        CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
+        mFatalErrorHandler.onCameraDisabledFailure();
     }
 
     @Override
     public void onDeviceOpenFailure(int cameraId, String info) {
-        UsageStatistics.instance().cameraFailure(
-                eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
-                UsageStatistics.NONE, UsageStatistics.NONE);
         Log.w(TAG, "Camera open failure: " + info);
-        CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
+        mFatalErrorHandler.onCameraOpenFailure();
     }
 
     @Override
     public void onDeviceOpenedAlready(int cameraId, String info) {
         Log.w(TAG, "Camera open already: " + cameraId + "," + info);
-        CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
+        mFatalErrorHandler.onGenericCameraAccessFailure();
     }
 
     @Override
     public void onReconnectionFailure(CameraAgent mgr, String info) {
-        UsageStatistics.instance().cameraFailure(
-                eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
-                UsageStatistics.NONE, UsageStatistics.NONE);
         Log.w(TAG, "Camera reconnection failure:" + info);
-        CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
+        mFatalErrorHandler.onCameraReconnectFailure();
     }
 
     private static class MainHandler extends Handler {
@@ -777,7 +783,7 @@ public class CameraActivity extends QuickActivity
                     int currentIndex = mFilmstripController.getCurrentAdapterIndex();
                     for (Integer index : indexes) {
                         if (index == currentIndex) {
-                            updateBottomControlsByData(mDataAdapter.getItemAt(index));
+                            updateUiByData(index);
                             // Currently we have only 1 data can be matched.
                             // No need to look for more, break.
                             break;
@@ -820,24 +826,26 @@ public class CameraActivity extends QuickActivity
         mCameraAppUI.getFilmstripBottomControls().hideProgress();
     }
 
-    private void showSessionProgress(CharSequence message) {
+    private void showSessionProgress(int messageId) {
         CameraAppUI.BottomPanel controls = mCameraAppUI.getFilmstripBottomControls();
-        controls.setProgressText(message);
+        controls.setProgressText(messageId > 0 ? getString(messageId) : "");
         controls.hideControls();
         controls.hideProgressError();
         controls.showProgress();
     }
 
-    private void showProcessError(CharSequence message) {
-        mCameraAppUI.getFilmstripBottomControls().showProgressError(message);
+    private void showProcessError(int messageId) {
+        mCameraAppUI.getFilmstripBottomControls().showProgressError(
+                messageId > 0 ? getString(messageId) : "");
     }
 
     private void updateSessionProgress(int progress) {
         mCameraAppUI.getFilmstripBottomControls().setProgress(progress);
     }
 
-    private void updateSessionProgressText(CharSequence message) {
-        mCameraAppUI.getFilmstripBottomControls().setProgressText(message);
+    private void updateSessionProgressText(int messageId) {
+        mCameraAppUI.getFilmstripBottomControls().setProgressText(
+                messageId > 0 ? getString(messageId) : "");
     }
 
     private void setupNfcBeamPush() {
@@ -883,8 +891,10 @@ public class CameraActivity extends QuickActivity
                     if (!Storage.isSessionUri(uri)) {
                         return;
                     }
-                    SessionItem newData = new SessionItem(getApplicationContext(), uri);
-                    mDataAdapter.addOrUpdate(newData);
+                    Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri);
+                    if (newData.isPresent()) {
+                        mDataAdapter.addOrUpdate(newData.get());
+                    }
                 }
 
                 @Override
@@ -911,17 +921,22 @@ public class CameraActivity extends QuickActivity
                         return;
                     }
 
-                    // Make the PhotoItem aware of the session placeholder, to
-                    // allow it to make a smooth transition to its content.
-                    newData.setSessionPlaceholderBitmap(
-                            Storage.getPlaceholderForSession(sessionUri));
-
                     final int pos = mDataAdapter.findByContentUri(sessionUri);
                     if (pos == -1) {
                         // We do not have a placeholder for this image, perhaps
                         // due to the activity crashing or being killed.
                         mDataAdapter.addOrUpdate(newData);
                     } else {
+                        // Make the PhotoItem aware of the session placeholder, to
+                        // allow it to make a smooth transition to its content if it
+                        // the session item is currently visible.
+                        FilmstripItem oldSessionData = mDataAdapter.getFilmstripItemAt(pos);
+                        if (mCameraAppUI.getFilmstripVisibility() == View.VISIBLE
+                                && mFilmstripController.isVisible(oldSessionData)) {
+                            Log.v(TAG, "session item visible, setting transition placeholder");
+                            newData.setSessionPlaceholderBitmap(
+                                    Storage.getPlaceholderForSession(sessionUri));
+                        }
                         mDataAdapter.updateItemAt(pos, newData);
                     }
                 }
@@ -943,14 +958,14 @@ public class CameraActivity extends QuickActivity
                 }
 
                 @Override
-                public void onSessionProgressText(final Uri uri, final CharSequence message) {
+                public void onSessionProgressText(final Uri uri, final int messageId) {
                     int currentIndex = mFilmstripController.getCurrentAdapterIndex();
                     if (currentIndex == -1) {
                         return;
                     }
                     if (uri.equals(
                             mDataAdapter.getItemAt(currentIndex).getData().getUri())) {
-                        updateSessionProgressText(message);
+                        updateSessionProgressText(messageId);
                     }
                 }
 
@@ -967,7 +982,8 @@ public class CameraActivity extends QuickActivity
                 }
 
                 @Override
-                public void onSessionFailed(Uri uri, CharSequence reason) {
+                public void onSessionFailed(Uri uri, int failureMessageId,
+                        boolean removeFromFilmstrip) {
                     Log.v(TAG, "onSessionFailed:" + uri);
 
                     int failedIndex = mDataAdapter.findByContentUri(uri);
@@ -975,16 +991,20 @@ public class CameraActivity extends QuickActivity
 
                     if (currentIndex == failedIndex) {
                         updateSessionProgress(0);
-                        showProcessError(reason);
+                        showProcessError(failureMessageId);
+                        mDataAdapter.refresh(uri);
                     }
-                    if (reason.equals("content")) {
-                        UsageStatistics.instance().storageWarning(Storage.ACCESS_FAILURE);
-                        CameraUtil.showError(CameraActivity.this, R.string.media_storage_failure,
-                                R.string.feedback_description_save_photo, false);
+                    if (removeFromFilmstrip) {
+                        mFatalErrorHandler.onMediaStorageFailure();
+                        mDataAdapter.removeAt(failedIndex);
                     }
+                }
 
-                    // HERE
-                    mDataAdapter.refresh(uri);
+                @Override
+                public void onSessionCanceled(Uri uri) {
+                    Log.v(TAG, "onSessionCanceled:" + uri);
+                    int failedIndex = mDataAdapter.findByContentUri(uri);
+                    mDataAdapter.removeAt(failedIndex);
                 }
 
                 @Override
@@ -1026,27 +1046,26 @@ public class CameraActivity extends QuickActivity
     }
 
     @Override
-    public int getCurrentCameraId() {
-        return mCameraController.getCurrentCameraId();
-    }
-
-    @Override
     public String getModuleScope() {
         ModuleAgent agent = mModuleManager.getModuleAgent(mCurrentModeIndex);
-        return MODULE_SCOPE_PREFIX + agent.getScopeNamespace();
+        return SettingsManager.getModuleSettingScope(agent.getScopeNamespace());
     }
 
     @Override
     public String getCameraScope() {
-        int currentCameraId = getCurrentCameraId();
-        if (currentCameraId < 0) {
-            // if an unopen camera i.e. negative ID is returned, which we've observed in
-            // some automated scenarios, just return it as a valid separate scope
-            // this could cause user issues, so log a stack trace noting the call path
-            // which resulted in this scenario.
-            Log.w(TAG, "getting camera scope with no open camera, using id: " + currentCameraId);
+        // if an unopen camera i.e. negative ID is returned, which we've observed in
+        // some automated scenarios, just return it as a valid separate scope
+        // this could cause user issues, so log a stack trace noting the call path
+        // which resulted in this scenario.
+
+        CameraId cameraId =  mCameraController.getCurrentCameraId();
+
+        if(cameraId == null) {
+            Log.e(TAG,  "Retrieving Camera Setting Scope with -1");
+            return SettingsManager.getCameraSettingScope("-1");
         }
-        return CAMERA_SCOPE_PREFIX + Integer.toString(currentCameraId);
+
+        return SettingsManager.getCameraSettingScope(cameraId.getValue());
     }
 
     @Override
@@ -1310,8 +1329,8 @@ public class CameraActivity extends QuickActivity
     }
 
     @Override
-    public OneCameraManager getCameraManager() {
-        return mCameraManager;
+    public OneCameraOpener getCameraOpener() {
+        return mOneCameraOpener;
     }
 
     private void removeItemAt(int index) {
@@ -1397,8 +1416,7 @@ public class CameraActivity extends QuickActivity
                         Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
                         finish();
                     } else {
-                        CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
-                                R.string.feedback_description_camera_access, true);
+                        mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA);
                     }
                 }
             };
@@ -1421,7 +1439,13 @@ public class CameraActivity extends QuickActivity
         mSoundPlayer = new SoundPlayer(mAppContext);
         mFeatureConfig = OneCameraFeatureConfigCreator.createDefault(getContentResolver(),
                 getServices().getMemoryManager());
-
+        mFatalErrorHandler = new FatalErrorHandlerImpl(this);
+        checkPermissions();
+        if (!mHasCriticalPermissions) {
+            Log.v(TAG, "onCreate: Missing critical permissions.");
+            finish();
+            return;
+        }
         profile.mark();
         if (!Glide.isSetup()) {
             Context context = getAndroidContext();
@@ -1434,37 +1458,37 @@ public class CameraActivity extends QuickActivity
             // As a camera we will use a large amount of memory
             // for displaying images.
             glide.setMemoryCategory(MemoryCategory.HIGH);
-
-            // Prefill glides bitmap pool to prevent excessive jank
-            // when loading large images.
-            glide.preFillBitmapPool(
-                new PreFillType.Builder(GlideFilmstripManager.MAXIMUM_TEXTURE_SIZE)
-                  .setWeight(5),
-                  // It's more important for jank and GC to have
-                  // A larger weight of max texture size images than
-                  // media store sized images.
-                new PreFillType.Builder(
-                      GlideFilmstripManager.MEDIASTORE_THUMB_WIDTH,
-                      GlideFilmstripManager.MEDIASTORE_THUMB_HEIGHT));
         }
         profile.mark("Glide.setup");
+
+        mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
         try {
-            mCameraManager = OneCameraManager.get(
-                    mFeatureConfig, mAppContext, ResolutionUtil.getDisplayMetrics(this));
+            mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
+                    mFeatureConfig,
+                    mAppContext,
+                    mActiveCameraDeviceTracker,
+                    ResolutionUtil.getDisplayMetrics(this));
+            mOneCameraManager = OneCameraModule.provideOneCameraManager();
         } catch (OneCameraException e) {
-            // Log error and continue. Modules requiring OneCamera should check
-            // and handle if null by showing error dialog or other treatment.
+            // Log error and continue start process while showing error dialog..
             Log.e(TAG, "Creating camera manager failed.", e);
-            CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
+            mFatalErrorHandler.onGenericCameraAccessFailure();
         }
         profile.mark("OneCameraManager.get");
-        mCameraController = new CameraController(mAppContext, this, mMainHandler,
-                CameraAgentFactory.getAndroidCameraAgent(mAppContext,
-                        CameraAgentFactory.CameraApi.API_1),
-                CameraAgentFactory.getAndroidCameraAgent(mAppContext,
-                        CameraAgentFactory.CameraApi.AUTO));
-        mCameraController.setCameraExceptionHandler(
-                new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
+
+        try {
+            mCameraController = new CameraController(mAppContext, this, mMainHandler,
+                    CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+                            CameraAgentFactory.CameraApi.API_1),
+                    CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+                            CameraAgentFactory.CameraApi.AUTO),
+                    mActiveCameraDeviceTracker);
+            mCameraController.setCameraExceptionHandler(
+                    new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
+        } catch (AssertionError e) {
+            Log.e(TAG, "Creating camera controller failed.", e);
+            mFatalErrorHandler.onGenericCameraAccessFailure();
+        }
 
         // TODO: Try to move all the resources allocation to happen as soon as
         // possible so we can call module.init() at the earliest time.
@@ -1474,9 +1498,21 @@ public class CameraActivity extends QuickActivity
 
         AppUpgrader appUpgrader = new AppUpgrader(this);
         appUpgrader.upgrade(mSettingsManager);
+
+        // Make sure the picture sizes are correctly cached for the current OS
+        // version.
+        profile.mark();
+        try {
+            (new PictureSizeLoader(mAppContext)).computePictureSizes();
+        } catch (AssertionError e) {
+            Log.e(TAG, "Creating camera controller failed.", e);
+            mFatalErrorHandler.onGenericCameraAccessFailure();
+        }
+        profile.mark("computePictureSizes");
         Keys.setDefaults(mSettingsManager, mAppContext);
 
-        mResolutionSetting = new ResolutionSetting(mSettingsManager, mCameraManager);
+        mResolutionSetting = new ResolutionSetting(mSettingsManager, mOneCameraManager,
+                getContentResolver());
 
         getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
         // We suppress this flag via theme when drawing the system preview
@@ -1577,13 +1613,6 @@ public class CameraActivity extends QuickActivity
               new PhotoDataFactory());
         mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
               new VideoDataFactory());
-        mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
-              mPhotoItemFactory, mVideoItemFactory);
-        mDataAdapter.setLocalDataListener(mFilmstripItemListener);
-
-        mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
-                mDataAdapter);
-
         mCameraAppUI.getFilmstripContentPanel().setFilmstripListener(mFilmstripListener);
         if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
                                         Keys.KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING)) {
@@ -1598,44 +1627,7 @@ public class CameraActivity extends QuickActivity
         mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
         profile.mark("Init CurrentModule");
 
-        if (!mSecureCamera) {
-            mFilmstripController.setDataAdapter(mDataAdapter);
-            if (!isCaptureIntent()) {
-                mDataAdapter.requestLoad(new Callback<Void>() {
-                    @Override
-                    public void onCallback(Void result) {
-                        fillTemporarySessions();
-                    }
-                });
-            }
-        } else {
-            // Put a lock placeholder as the last image by setting its date to
-            // 0.
-            ImageView v = (ImageView) getLayoutInflater().inflate(
-                    R.layout.secure_album_placeholder, null);
-            v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
-            v.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View view) {
-                    UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
-                            NavigationChange.InteractionCause.BUTTON);
-                    startGallery();
-                    finish();
-                }
-            });
-            v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
-            mDataAdapter = new FixedLastProxyAdapter(
-                    mAppContext,
-                    mDataAdapter,
-                    new PlaceholderItem(
-                            v,
-                            FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
-                            v.getDrawable().getIntrinsicWidth(),
-                            v.getDrawable().getIntrinsicHeight()));
-            // Flush out all the original data.
-            mDataAdapter.clear();
-            mFilmstripController.setDataAdapter(mDataAdapter);
-        }
+        preloadFilmstripItems();
 
         setupNfcBeamPush();
 
@@ -1662,7 +1654,12 @@ public class CameraActivity extends QuickActivity
 
         mMotionManager = getServices().getMotionManager();
 
-        mFirstRunDialog = new FirstRunDialog(this, new FirstRunDialog.FirstRunDialogListener() {
+        mFirstRunDialog = new FirstRunDialog(this,
+              getAndroidContext(),
+              mResolutionSetting,
+              mSettingsManager,
+              mOneCameraManager,
+              new FirstRunDialog.FirstRunDialogListener() {
             @Override
             public void onFirstRunStateReady() {
                 // Run normal resume tasks.
@@ -1679,8 +1676,7 @@ public class CameraActivity extends QuickActivity
 
             @Override
             public void onCameraAccessException() {
-                CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
-                        R.string.feedback_description_camera_access, true);
+                mFatalErrorHandler.onGenericCameraAccessFailure();
             }
         });
         profile.stop();
@@ -1828,7 +1824,9 @@ public class CameraActivity extends QuickActivity
         mLocalImagesObserver.setForegroundChangeListener(null);
         mLocalImagesObserver.setActivityPaused(true);
         mLocalVideosObserver.setActivityPaused(true);
-        mPreloader.cancelAllLoads();
+        if (mPreloader != null) {
+            mPreloader.cancelAllLoads();
+        }
         resetScreenOn();
 
         mMotionManager.stop();
@@ -1847,7 +1845,9 @@ public class CameraActivity extends QuickActivity
         } else {
             // Close the camera and wait for the operation done.
             Log.v(TAG, "onPause closing camera");
-            mCameraController.closeCamera(true);
+            if (mCameraController != null) {
+                mCameraController.closeCamera(true);
+            }
         }
 
         profile.stop();
@@ -1856,17 +1856,113 @@ public class CameraActivity extends QuickActivity
     @Override
     public void onResumeTasks() {
         mPaused = false;
+        checkPermissions();
+        if (!mHasCriticalPermissions) {
+            Log.v(TAG, "onResume: Missing critical permissions.");
+            finish();
+            return;
+        }
+        if (!mSecureCamera) {
+            // Show the dialog if necessary. The rest resume logic will be invoked
+            // at the onFirstRunStateReady() callback.
+            try {
+                mFirstRunDialog.showIfNecessary();
+            } catch (AssertionError e) {
+                Log.e(TAG, "Creating camera controller failed.", e);
+                mFatalErrorHandler.onGenericCameraAccessFailure();
+            }
+        } else {
+            // In secure mode from lockscreen, we go straight to camera and will
+            // show first run dialog next time user enters launcher.
+            Log.v(TAG, "in secure mode, skipping first run dialog check");
+            resume();
+        }
+    }
 
-        // Show the dialog if necessary. The rest resume logic will be invoked
-        // at the onFirstRunStateReady() callback.
-        mFirstRunDialog.showIfNecessary();
+    /**
+     * Checks if any of the needed Android runtime permissions are missing.
+     * If they are, then launch the permissions activity under one of the following conditions:
+     * a) The permissions dialogs have not run yet. We will ask for permission only once.
+     * b) If the missing permissions are critical to the app running, we will display a fatal error dialog.
+     * Critical permissions are: camera, microphone and storage. The app cannot run without them.
+     * Non-critical permission is location.
+     */
+    private void checkPermissions() {
+        if (!ApiHelper.isMOrHigher()) {
+            Log.v(TAG, "not running on M, skipping permission checks");
+            mHasCriticalPermissions = true;
+            return;
+        }
+
+        if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
+                checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
+                checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+            mHasCriticalPermissions = true;
+        } else {
+            mHasCriticalPermissions = false;
+        }
+
+        if ((checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
+                !mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_HAS_SEEN_PERMISSIONS_DIALOGS)) ||
+                !mHasCriticalPermissions) {
+            Intent intent = new Intent(this, PermissionsActivity.class);
+            startActivity(intent);
+            finish();
+        }
+    }
+
+    private void preloadFilmstripItems() {
+        if (mDataAdapter == null) {
+            mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
+                    mPhotoItemFactory, mVideoItemFactory);
+            mDataAdapter.setLocalDataListener(mFilmstripItemListener);
+            mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
+                    mDataAdapter);
+            if (!mSecureCamera) {
+                mFilmstripController.setDataAdapter(mDataAdapter);
+                if (!isCaptureIntent()) {
+                    mDataAdapter.requestLoad(new Callback<Void>() {
+                        @Override
+                        public void onCallback(Void result) {
+                            fillTemporarySessions();
+                        }
+                    });
+                }
+            } else {
+                // Put a lock placeholder as the last image by setting its date to
+                // 0.
+                ImageView v = (ImageView) getLayoutInflater().inflate(
+                        R.layout.secure_album_placeholder, null);
+                v.setTag(R.id.mediadata_tag_viewtype, FilmstripItemType.SECURE_ALBUM_PLACEHOLDER.ordinal());
+                v.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        UsageStatistics.instance().changeScreen(NavigationChange.Mode.GALLERY,
+                                NavigationChange.InteractionCause.BUTTON);
+                        startGallery();
+                        finish();
+                    }
+                });
+                v.setContentDescription(getString(R.string.accessibility_unlock_to_camera));
+                mDataAdapter = new FixedLastProxyAdapter(
+                        mAppContext,
+                        mDataAdapter,
+                        new PlaceholderItem(
+                                v,
+                                FilmstripItemType.SECURE_ALBUM_PLACEHOLDER,
+                                v.getDrawable().getIntrinsicWidth(),
+                                v.getDrawable().getIntrinsicHeight()));
+                // Flush out all the original data.
+                mDataAdapter.clear();
+                mFilmstripController.setDataAdapter(mDataAdapter);
+            }
+        }
     }
 
     private void resume() {
         Profile profile = mProfiler.create("CameraActivity.resume").start();
         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
         Log.v(TAG, "Build info: " + Build.DISPLAY);
-
         updateStorageSpaceAndHint(null);
 
         mLastLayoutOrientation = getResources().getConfiguration().orientation;
@@ -1920,7 +2016,9 @@ public class CameraActivity extends QuickActivity
                     break;
             }
         }
-        UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode());
+        UsageStatistics.instance().foregrounded(source, currentUserInterfaceMode(),
+                isKeyguardSecure(), isKeyguardLocked(),
+                mStartupOnCreate, mExecutionStartNanoTime);
 
         mGalleryIntent = IntentHelper.getGalleryIntent(mAppContext);
         if (ApiHelper.isLOrHigher()) {
@@ -2063,19 +2161,34 @@ public class CameraActivity extends QuickActivity
             unregisterReceiver(mShutdownReceiver);
         }
 
+        // Ensure anything that checks for "isPaused" returns true.
+        mPaused = true;
+
         mSettingsManager.removeAllListeners();
-        mCameraController.removeCallbackReceiver();
-        mCameraController.setCameraExceptionHandler(null);
-        getContentResolver().unregisterContentObserver(mLocalImagesObserver);
-        getContentResolver().unregisterContentObserver(mLocalVideosObserver);
+        if (mCameraController != null) {
+            mCameraController.removeCallbackReceiver();
+            mCameraController.setCameraExceptionHandler(null);
+        }
+        if (mLocalImagesObserver != null) {
+            getContentResolver().unregisterContentObserver(mLocalImagesObserver);
+        }
+        if (mLocalVideosObserver != null) {
+            getContentResolver().unregisterContentObserver(mLocalVideosObserver);
+        }
         getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
-        mCameraAppUI.onDestroy();
-        mModeListView.setVisibilityChangedListener(null);
+        if (mCameraAppUI != null) {
+            mCameraAppUI.onDestroy();
+        }
+        if (mModeListView != null) {
+            mModeListView.setVisibilityChangedListener(null);
+        }
         mCameraController = null;
         mSettingsManager = null;
         mOrientationManager = null;
         mButtonManager = null;
-        mSoundPlayer.release();
+        if (mSoundPlayer != null) {
+          mSoundPlayer.release();
+        }
         CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
         CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
     }
@@ -2262,7 +2375,7 @@ public class CameraActivity extends QuickActivity
         if (message != null) {
             Log.w(TAG, "Storage warning: " + message);
             if (mStorageHint == null) {
-                mStorageHint = OnScreenHint.makeText(message);
+                mStorageHint = OnScreenHint.makeText(CameraActivity.this, message);
             } else {
                 mStorageHint.setText(message);
             }
@@ -2405,6 +2518,11 @@ public class CameraActivity extends QuickActivity
         return CameraServicesImpl.instance();
     }
 
+    @Override
+    public FatalErrorHandler getFatalErrorHandler() {
+        return mFatalErrorHandler;
+    }
+
     public List<String> getSupportedModeNames() {
         List<Integer> indices = mModuleManager.getSupportedModeIndexList();
         List<String> supported = new ArrayList<String>();
@@ -2558,6 +2676,12 @@ public class CameraActivity extends QuickActivity
                 @Override
                 public void onClick(View view) {
                     mDataAdapter.undoDeletion();
+                    // Fix for b/21666018: When undoing a delete in Fullscreen
+                    // mode, just flip
+                    // back to the filmstrip to force a refresh.
+                    if (mFilmstripController.inFullScreen()) {
+                        mFilmstripController.goToFilmstrip();
+                    }
                     hideUndoDeletionBar(true);
                 }
             });
@@ -2692,11 +2816,6 @@ public class CameraActivity extends QuickActivity
     }
 
     @Override
-    public void showErrorAndFinish(int messageId) {
-        CameraUtil.showError(this, messageId, R.string.feedback_description_camera_access, true);
-    }
-
-    @Override
     public void finishActivityWithIntentCompleted(Intent resultIntent) {
         finishActivityWithIntentResult(Activity.RESULT_OK, resultIntent);
     }
@@ -2812,7 +2931,7 @@ public class CameraActivity extends QuickActivity
                 .getCaptureSessionManager();
 
         if (sessionManager.hasErrorMessage(contentUri)) {
-            showProcessError(sessionManager.getErrorMessage(contentUri));
+            showProcessError(sessionManager.getErrorMessageId(contentUri));
         } else {
             filmstripBottomPanel.hideProgressError();
             CaptureSession session = sessionManager.getSession(contentUri);
@@ -2823,8 +2942,8 @@ public class CameraActivity extends QuickActivity
                 if (sessionProgress < 0) {
                     hideSessionProgress();
                 } else {
-                    CharSequence progressMessage = session.getProgressMessage();
-                    showSessionProgress(progressMessage);
+                    int progressMessageId = session.getProgressMessageId();
+                    showSessionProgress(progressMessageId);
                     updateSessionProgress(sessionProgress);
                 }
             } else {