Merge "Prevent NullPointerException of PhotoModule in quick resume-pause." into ub-camera-glacier
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 12edaf5..d05248a 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -1321,6 +1321,7 @@
         = new CameraExceptionHandler.CameraExceptionCallback() {
                 @Override
                 public void onCameraError(int errorCode) {
+                    // Not a fatal error. only do Log.e().
                     Log.e(TAG, "Camera error callback. error=" + errorCode);
                 }
                 @Override
@@ -1334,14 +1335,19 @@
                     onFatalError();
                 }
                 private void onFatalError() {
+                    if (mCameraFatalError) {
+                        return;
+                    }
                     mCameraFatalError = true;
+
                     // If the activity receives exception during onPause, just exit the app.
                     if (mPaused && !isFinishing()) {
-                        Log.v(TAG, "Fatal error during onPause, call Activity.finish()");
+                        Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
                         finish();
+                    } else {
+                        CameraUtil.showErrorAndFinish(CameraActivity.this,
+                                R.string.cannot_connect_camera);
                     }
-                    CameraUtil.showErrorAndFinish(CameraActivity.this,
-                            R.string.cannot_connect_camera);
                 }
             };
 
@@ -1456,7 +1462,7 @@
         mPanoramaViewHelper = new PanoramaViewHelper(this);
         mPanoramaViewHelper.onCreate();
         // Set up the camera preview first so the preview shows up ASAP.
-        mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder);
+        mDataAdapter = new CameraDataAdapter(mAppContext, mMainHandler, R.color.photo_placeholder);
         mDataAdapter.setLocalDataListener(mLocalDataListener);
 
         mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
@@ -1692,9 +1698,10 @@
         if (mCameraFatalError && !isFinishing()) {
             Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
             finish();
+        } else {
+            // Close the camera and wait for the operation done.
+            mCameraController.closeCamera(true);
         }
-        // Close the camera and wait for the operation done.
-        mCameraController.closeCamera(true);
     }
 
     @Override
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index d410bc6..e8b05aa 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -1206,6 +1206,12 @@
         } catch (InterruptedException e) {
             throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
         }
+        if (mCamera != null) {
+            // If the camera is already open, do nothing.
+            Log.d(TAG, "Camera already open, not re-opening.");
+            mCameraOpenCloseLock.release();
+            return;
+        }
         mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(),
                 new OpenCallback() {
                     @Override
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index ce53dc2..a99fcb4 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -596,6 +596,7 @@
         mMeteringAreaSupported =
                 mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
         readVideoPreferences();
+        updateDesiredPreviewSize();
         resizeForPreviewAspectRatio();
         initializeFocusManager();
         // TODO: Having focus overlay manager caching the parameters is prone to error,
@@ -765,17 +766,28 @@
         }
         mProfile = CamcorderProfile.get(mCameraId, quality);
         mPreferenceRead = true;
+    }
+
+    /**
+     * Calculates and sets local class variables for Desired Preview sizes.
+     * This function should be called after every change in preview camera
+     * resolution and/or before the preview starts. Note that these values still
+     * need to be pushed to the CameraSettings to actually change the preview
+     * resolution.  Does nothing when camera pointer is null.
+     */
+    private void updateDesiredPreviewSize() {
         if (mCameraDevice == null) {
             return;
         }
+
         mCameraSettings = mCameraDevice.getSettings();
         Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
                 mCameraSettings, mCameraCapabilities, mProfile, mUI.getPreviewScreenSize());
         mDesiredPreviewWidth = desiredPreviewSize.x;
         mDesiredPreviewHeight = desiredPreviewSize.y;
         mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
-        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
-                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
+        Log.v(TAG, "Updated DesiredPreview=" + mDesiredPreviewWidth + "x"
+                + mDesiredPreviewHeight);
     }
 
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@@ -787,7 +799,9 @@
      * com.android.camera.cameradevice.CameraCapabilities#getPreferredPreviewSizeForVideo()}
      * but also considers the current preview area size on screen and make sure
      * the final preview size will not be smaller than 1/2 of the current
-     * on screen preview area in terms of their short sides.</p>
+     * on screen preview area in terms of their short sides.  This function has
+     * highest priority of WYSIWYG, 1:1 matching as its best match, even if
+     * there's a larger preview that meets the condition above. </p>
      *
      * @return The preferred preview size or {@code null} if the camera is not
      *         opened yet.
@@ -817,6 +831,19 @@
                 it.remove();
             }
         }
+
+        // Take highest priority for WYSIWYG when the preview exactly matches
+        // video frame size.  The variable sizes is assumed to be filtered
+        // for sizes beyond the UI size.
+        for (Size size : sizes) {
+            if (size.width() == profile.videoFrameWidth
+                    && size.height() == profile.videoFrameHeight) {
+                Log.v(TAG, "Selected =" + size.width() + "x" + size.height()
+                           + " on WYSIWYG Priority");
+                return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
+            }
+        }
+
         Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
                 (double) profile.videoFrameWidth / profile.videoFrameHeight);
         return new Point(optimalSize.width(), optimalSize.height());
@@ -1589,6 +1616,9 @@
     private void setCameraParameters() {
         SettingsManager settingsManager = mActivity.getSettingsManager();
 
+        // Update Desired Preview size in case video camera resolution has changed.
+        updateDesiredPreviewSize();
+
         mCameraSettings.setPreviewSize(new Size(mDesiredPreviewWidth, mDesiredPreviewHeight));
         // This is required for Samsung SGH-I337 and probably other Samsung S4 versions
         if (Build.BRAND.toLowerCase().contains("samsung")) {
@@ -1623,7 +1653,7 @@
         // here we determine the picture size based on the preview size.
         List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
         Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
-                (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
+                mDesiredPreviewWidth, mDesiredPreviewHeight);
         Size original = new Size(mCameraSettings.getCurrentPhotoSize());
         if (!original.equals(optimalSize)) {
             mCameraSettings.setPhotoSize(optimalSize);
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index 721b213..19ae6f6 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Handler;
 import android.view.View;
 
 import com.android.camera.Storage;
@@ -40,6 +41,7 @@
     private static final int DEFAULT_DECODE_SIZE = 1600;
 
     private final Context mContext;
+    private final Handler mCallbackHandler;
 
     private LocalDataList mImages;
 
@@ -53,8 +55,9 @@
 
     private LocalData mLocalDataToDelete;
 
-    public CameraDataAdapter(Context context, int placeholderResource) {
+    public CameraDataAdapter(Context context, Handler callbackHandler, int placeholderResource) {
         mContext = context;
+        mCallbackHandler = callbackHandler;
         mImages = new LocalDataList();
         mPlaceHolderResourceId = placeholderResource;
     }
@@ -144,10 +147,17 @@
     }
 
     @Override
-    public void setListener(Listener listener) {
+    public void setListener(final Listener listener) {
         mListener = listener;
         if (mImages.size() != 0) {
-            mListener.onDataLoaded();
+            mCallbackHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (listener != null) {
+                        listener.onDataLoaded();
+                    }
+                }
+            });
         }
     }
 
@@ -160,8 +170,8 @@
     }
 
     @Override
-    public void removeData(int dataID) {
-        LocalData d = mImages.remove(dataID);
+    public void removeData(final int dataID) {
+        final LocalData d = mImages.remove(dataID);
         if (d == null) {
             return;
         }
@@ -169,7 +179,14 @@
         // Delete previously removed data first.
         executeDeletion();
         mLocalDataToDelete = d;
-        mListener.onDataRemoved(dataID, d);
+        mCallbackHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mListener != null) {
+                    mListener.onDataRemoved(dataID, d);
+                }
+            }
+        });
     }
 
     @Override
@@ -231,12 +248,19 @@
             return;
         }
 
-        LocalData data = mImages.get(pos);
+        final LocalData data = mImages.get(pos);
         LocalData refreshedData = data.refresh(mContext);
 
         // Refresh failed. Probably removed already.
-        if (refreshedData == null && mListener != null) {
-            mListener.onDataRemoved(pos, data);
+        if (refreshedData == null) {
+            mCallbackHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mListener != null) {
+                        mListener.onDataRemoved(pos, data);
+                    }
+                }
+            });
             return;
         }
         updateData(pos, refreshedData);
@@ -245,22 +269,27 @@
     @Override
     public void updateData(final int pos, LocalData data) {
         mImages.set(pos, data);
-        if (mListener != null) {
-            mListener.onDataUpdated(new UpdateReporter() {
-                @Override
-                public boolean isDataRemoved(int dataID) {
-                    return false;
-                }
+        mCallbackHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mListener != null) {
+                    mListener.onDataUpdated(new UpdateReporter() {
+                        @Override
+                        public boolean isDataRemoved(int dataID) {
+                            return false;
+                        }
 
-                @Override
-                public boolean isDataUpdated(int dataID) {
-                    return (dataID == pos);
+                        @Override
+                        public boolean isDataUpdated(int dataID) {
+                            return (dataID == pos);
+                        }
+                    });
                 }
-            });
-        }
+            }
+        });
     }
 
-    private void insertData(LocalData data) {
+    private void insertData(final LocalData data) {
         // Since this function is mostly for adding the newest data,
         // a simple linear search should yield the best performance over a
         // binary search.
@@ -271,9 +300,15 @@
             ;
         }
         mImages.add(pos, data);
-        if (mListener != null) {
-            mListener.onDataInserted(pos, data);
-        }
+        final int fpos = pos;
+        mCallbackHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mListener != null) {
+                    mListener.onDataInserted(fpos, data);
+                }
+            }
+        });
     }
 
     /** Update all the data */
@@ -282,9 +317,14 @@
             return;
         }
         mImages = list;
-        if (mListener != null) {
-            mListener.onDataLoaded();
-        }
+        mCallbackHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mListener != null) {
+                    mListener.onDataLoaded();
+                }
+            }
+        });
     }
 
     @Override
@@ -321,7 +361,7 @@
         return getTotalNumber();
     }
 
-    private class LoadNewPhotosTask extends AsyncTask<ContentResolver, Void, List<LocalData>> {
+    private class LoadNewPhotosTask extends AsyncTask<ContentResolver, Void, Void> {
 
         private final long mMinPhotoId;
 
@@ -332,34 +372,29 @@
         /**
          * Loads any new photos added to our storage directory since our last query.
          * @param contentResolvers {@link android.content.ContentResolver} to load data.
-         * @return An {@link java.util.ArrayList} containing any new data.
          */
         @Override
-        protected List<LocalData> doInBackground(ContentResolver... contentResolvers) {
+        protected Void doInBackground(ContentResolver... contentResolvers) {
             if (mMinPhotoId != LocalMediaData.QUERY_ALL_MEDIA_ID) {
                 final ContentResolver cr = contentResolvers[0];
-                return LocalMediaData.PhotoData.query(cr, LocalMediaData.PhotoData.CONTENT_URI,
+                List<LocalData> newPhotoData = LocalMediaData.PhotoData.query(cr, LocalMediaData.PhotoData.CONTENT_URI,
                         mMinPhotoId);
-            }
-            return new ArrayList<LocalData>(0);
-        }
-
-        @Override
-        protected void onPostExecute(List<LocalData> newPhotoData) {
-            if (!newPhotoData.isEmpty()) {
-                LocalData newestPhoto = newPhotoData.get(0);
-                // We may overlap with another load task or a query task, in which case we want
-                // to be sure we never decrement the oldest seen id.
-                mLastPhotoId = Math.max(mLastPhotoId, newestPhoto.getContentId());
-            }
-            // We may add data that is already present, but if we do, it will be deduped in addData.
-            // addData does not dedupe session items, so we ignore them here
-            for (LocalData localData : newPhotoData) {
-                Uri sessionUri = Storage.getSessionUriFromContentUri(localData.getUri());
-                if (sessionUri == null) {
-                    addData(localData);
+                if (!newPhotoData.isEmpty()) {
+                    LocalData newestPhoto = newPhotoData.get(0);
+                    // We may overlap with another load task or a query task, in which case we want
+                    // to be sure we never decrement the oldest seen id.
+                    mLastPhotoId = Math.max(mLastPhotoId, newestPhoto.getContentId());
+                }
+                // We may add data that is already present, but if we do, it will be deduped in addData.
+                // addData does not dedupe session items, so we ignore them here
+                for (LocalData localData : newPhotoData) {
+                    Uri sessionUri = Storage.getSessionUriFromContentUri(localData.getUri());
+                    if (sessionUri == null) {
+                        addData(localData);
+                    }
                 }
             }
+            return null;
         }
     }
 
diff --git a/src/com/android/camera/one/v2/ImageCaptureManager.java b/src/com/android/camera/one/v2/ImageCaptureManager.java
index a815345..0687071 100644
--- a/src/com/android/camera/one/v2/ImageCaptureManager.java
+++ b/src/com/android/camera/one/v2/ImageCaptureManager.java
@@ -55,7 +55,7 @@
  * This also manages the lifecycle of {@link Image}s within the application as
  * they are passed in from the lower-level camera2 API.
  */
-@TargetApi(Build.VERSION_CODES.L)
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback implements
         ImageReader.OnImageAvailableListener {
     /**
diff --git a/src/com/android/camera/one/v2/OneCameraZslImpl.java b/src/com/android/camera/one/v2/OneCameraZslImpl.java
index aa03b88..63f6a90 100644
--- a/src/com/android/camera/one/v2/OneCameraZslImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraZslImpl.java
@@ -75,7 +75,7 @@
  * shutter lag.<br>
  * TODO: Determine what the maximum number of full YUV capture frames is.
  */
-@TargetApi(Build.VERSION_CODES.L)
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class OneCameraZslImpl extends AbstractOneCamera {
     private static final Tag TAG = new Tag("OneCameraZslImpl2");
 
diff --git a/src/com/android/camera/settings/AppUpgrader.java b/src/com/android/camera/settings/AppUpgrader.java
index b3bb0db..9ff3dfa 100644
--- a/src/com/android/camera/settings/AppUpgrader.java
+++ b/src/com/android/camera/settings/AppUpgrader.java
@@ -43,6 +43,7 @@
     private static final String OLD_CAMERA_PREFERENCES_PREFIX = "_preferences_";
     private static final String OLD_MODULE_PREFERENCES_PREFIX = "_preferences_module_";
     private static final String OLD_GLOBAL_PREFERENCES_FILENAME = "_preferences_camera";
+    private static final String OLD_KEY_UPGRADE_VERSION = "pref_strict_upgrade_version";
 
     /**
      * With this version everyone was forced to choose their location settings
@@ -75,6 +76,12 @@
     private static final int CAMERA_SETTINGS_SELECTED_MODULE_INDEX = 5;
 
     /**
+     * With this version internal storage is changed to use only Strings, and
+     * a type conversion process should execute.
+     */
+    private static final int CAMERA_SETTINGS_STRINGS_UPGRADE = 5;
+
+    /**
      * Increment this value whenever new AOSP UpgradeSteps need to be executed.
      */
     public static final int APP_UPGRADE_VERSION = 6;
@@ -88,28 +95,35 @@
 
     @Override
     protected int getLastVersion(SettingsManager settingsManager) {
-        // Prior appwide versions were stored in the default preferences. If
-        // current
-        // state indicates this is still the case, port the version and then
-        // process
-        // all other known app settings to the new SettingsManager string
-        // scheme.
-        try {
-            return super.getLastVersion(settingsManager);
-        } catch (ClassCastException e) {
-            // We infer that a ClassCastException here means we have pre-String
-            // settings that need to be upgraded, so we hack in a full upgrade
-            // here.
-            upgradeTypesToStrings(settingsManager);
-            // Retrieve version as default now that we're sure it is converted
-            return super.getLastVersion(settingsManager);
+        // Prior upgrade versions were stored in the default preferences as int
+        // and String. We create a new version location for migration to String.
+        // If we don't have a version persisted in the new location, check for
+        // the prior value from the old location. We expect the old value to be
+        // processed during {@link #upgradeTypesToStrings}.
+        SharedPreferences defaultPreferences = settingsManager.getDefaultPreferences();
+        if (defaultPreferences.contains(OLD_KEY_UPGRADE_VERSION)) {
+            Map<String, ?> allPrefs = defaultPreferences.getAll();
+            Object oldVersion = allPrefs.get(OLD_KEY_UPGRADE_VERSION);
+            defaultPreferences.edit().remove(OLD_KEY_UPGRADE_VERSION).apply();
+            if (oldVersion instanceof Integer) {
+                return (Integer) oldVersion;
+            } else if (oldVersion instanceof String) {
+                return SettingsManager.convertToInt((String) oldVersion);
+            }
         }
+        return super.getLastVersion(settingsManager);
     }
 
     @Override
     public void upgrade(SettingsManager settingsManager, int lastVersion, int currentVersion) {
         Context context = mAppController.getAndroidContext();
 
+        // Do strings upgrade first before 'earlier' upgrades, since they assume
+        // valid storage of values.
+        if (lastVersion < CAMERA_SETTINGS_STRINGS_UPGRADE) {
+            upgradeTypesToStrings(settingsManager);
+        }
+
         if (lastVersion < FORCE_LOCATION_CHOICE_VERSION) {
             forceLocationChoice(settingsManager);
         }
@@ -153,11 +167,6 @@
         SharedPreferences oldGlobalPreferences =
                 settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME);
 
-        // Strict upgrade version: Integer -> String, from default.
-        int strictUpgradeVersion = removeInteger(defaultPreferences, Keys.KEY_UPGRADE_VERSION);
-        settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_UPGRADE_VERSION,
-                strictUpgradeVersion);
-
         // Location: boolean -> String, from default.
         if (defaultPreferences.contains(Keys.KEY_RECORD_LOCATION)) {
             boolean location = removeBoolean(defaultPreferences, Keys.KEY_RECORD_LOCATION);
diff --git a/src/com/android/camera/settings/Keys.java b/src/com/android/camera/settings/Keys.java
index ad81782..35d0aca 100644
--- a/src/com/android/camera/settings/Keys.java
+++ b/src/com/android/camera/settings/Keys.java
@@ -62,7 +62,7 @@
             "pref_release_dialog_last_shown_version";
     public static final String KEY_FLASH_SUPPORTED_BACK_CAMERA =
             "pref_flash_supported_back_camera";
-    public static final String KEY_UPGRADE_VERSION = "pref_strict_upgrade_version";
+    public static final String KEY_UPGRADE_VERSION = "pref_upgrade_version";
     public static final String KEY_REQUEST_RETURN_HDR_PLUS = "pref_request_return_hdr_plus";
     public static final String KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING =
             "pref_should_show_refocus_viewer_cling";
diff --git a/src/com/android/camera/settings/SettingsManager.java b/src/com/android/camera/settings/SettingsManager.java
index 297bacb..6d90832 100644
--- a/src/com/android/camera/settings/SettingsManager.java
+++ b/src/com/android/camera/settings/SettingsManager.java
@@ -320,7 +320,13 @@
      */
     public String getString(String scope, String key, String defaultValue) {
         SharedPreferences preferences = getPreferencesFromScope(scope);
-        return preferences.getString(key, defaultValue);
+        try {
+            return preferences.getString(key, defaultValue);
+        } catch (ClassCastException e) {
+            Log.w(TAG, "existing preference with invalid type, removing and returning default", e);
+            preferences.edit().remove(key).apply();
+            return defaultValue;
+        }
     }
 
     /**
@@ -332,13 +338,13 @@
     }
 
     /**
-     * Retrieve a setting's value as an Integer, manually specifiying
+     * Retrieve a setting's value as an Integer, manually specifying
      * a default value.
      */
     public Integer getInteger(String scope, String key, Integer defaultValue) {
         String defaultValueString = Integer.toString(defaultValue);
         String value = getString(scope, key, defaultValueString);
-        return Integer.parseInt(value);
+        return convertToInt(value);
     }
 
     /**
@@ -356,7 +362,7 @@
     public boolean getBoolean(String scope, String key, boolean defaultValue) {
         String defaultValueString = defaultValue ? "1" : "0";
         String value = getString(scope, key, defaultValueString);
-        return (Integer.parseInt(value) != 0);
+        return convertToBoolean(value);
     }
 
     /**
@@ -517,6 +523,29 @@
     }
 
     /**
+     * Package private conversion method to turn String storage format into
+     * ints.
+     *
+     * @param value String to be converted to int
+     * @return int value of stored String
+     */
+    static int convertToInt(String value) {
+        return Integer.parseInt(value);
+    }
+
+    /**
+     * Package private conversion method to turn String storage format into
+     * booleans.
+     *
+     * @param value String to be converted to boolean
+     * @return boolean value of stored String
+     */
+    static boolean convertToBoolean(String value) {
+        return Integer.parseInt(value) != 0;
+    }
+
+
+    /**
      * Package private conversion method to turn booleans into preferred
      * String storage format.
      *
diff --git a/src/com/android/camera/ui/BottomBar.java b/src/com/android/camera/ui/BottomBar.java
index c481a24..d5fdbf6 100644
--- a/src/com/android/camera/ui/BottomBar.java
+++ b/src/com/android/camera/ui/BottomBar.java
@@ -77,9 +77,7 @@
     private final float mCircleRadius;
     private CaptureLayoutHelper mCaptureLayoutHelper = null;
 
-    // for Android L, these backgrounds are RippleDrawables (ISA LayerDrawable)
-    // pre-L, they're plain old LayerDrawables
-    private final LayerDrawable[] mShutterButtonBackgrounds;
+    private final Drawable.ConstantState[] mShutterButtonBackgroundConstantStates;
     // a reference to the shutter background's first contained drawable
     // if it's an animated circle drawable (for video mode)
     private AnimatedCircleDrawable mAnimatedCircleDrawable;
@@ -102,25 +100,11 @@
         TypedArray ar = context.getResources()
                 .obtainTypedArray(R.array.shutter_button_backgrounds);
         int len = ar.length();
-
-        mShutterButtonBackgrounds = new LayerDrawable[len];
+        mShutterButtonBackgroundConstantStates = new Drawable.ConstantState[len];
         for (int i = 0; i < len; i++) {
             int drawableId = ar.getResourceId(i, -1);
-            LayerDrawable shutterBackground = mShutterButtonBackgrounds[i] =
-                    (LayerDrawable) context.getResources().getDrawable(drawableId).mutate();
-
-            // the background for video has a circle_item drawable placeholder
-            // that gets replaced by an AnimatedCircleDrawable for the cool
-            // shrink-down-to-a-circle effect
-            // all other modes need not do this replace
-            Drawable d = shutterBackground.findDrawableByLayerId(R.id.circle_item);
-            if (d != null) {
-                Drawable animatedCircleDrawable =
-                        new AnimatedCircleDrawable((int) mCircleRadius);
-                animatedCircleDrawable.setLevel(DRAWABLE_MAX_LEVEL);
-                shutterBackground
-                        .setDrawableByLayerId(R.id.circle_item, animatedCircleDrawable);
-            }
+            mShutterButtonBackgroundConstantStates[i] =
+                    context.getResources().getDrawable(drawableId).getConstantState();
         }
         ar.recycle();
     }
@@ -382,13 +366,33 @@
         }
     }
 
+    private LayerDrawable applyCircleDrawableToShutterBackground(LayerDrawable shutterBackground) {
+        // the background for video has a circle_item drawable placeholder
+        // that gets replaced by an AnimatedCircleDrawable for the cool
+        // shrink-down-to-a-circle effect
+        // all other modes need not do this replace
+        Drawable d = shutterBackground.findDrawableByLayerId(R.id.circle_item);
+        if (d != null) {
+            Drawable animatedCircleDrawable =
+                    new AnimatedCircleDrawable((int) mCircleRadius);
+            animatedCircleDrawable.setLevel(DRAWABLE_MAX_LEVEL);
+            shutterBackground
+                    .setDrawableByLayerId(R.id.circle_item, animatedCircleDrawable);
+        }
+
+        return shutterBackground;
+    }
+
+    private LayerDrawable newDrawableFromConstantState(Drawable.ConstantState constantState) {
+        return (LayerDrawable) constantState.newDrawable(getContext().getResources());
+    }
+
     private void setupShutterBackgroundForModeIndex(int index) {
-        LayerDrawable shutterBackground = mShutterButtonBackgrounds[index];
+        LayerDrawable shutterBackground = applyCircleDrawableToShutterBackground(
+                newDrawableFromConstantState(mShutterButtonBackgroundConstantStates[index]));
         mShutterButton.setBackground(shutterBackground);
-        // FIXME this is crashing NPE on getConstantState()
-        // reverting for now
-        // b/559979
-        //mCancelButton.setBackground(shutterBackground.getConstantState().newDrawable());
+        mCancelButton.setBackground(applyCircleDrawableToShutterBackground(
+                newDrawableFromConstantState(mShutterButtonBackgroundConstantStates[index])));
 
         Drawable d = shutterBackground.getDrawable(0);
         mAnimatedCircleDrawable = null;
diff --git a/src/com/android/camera/util/ApiHelper.java b/src/com/android/camera/util/ApiHelper.java
index efdee85..8e25464 100644
--- a/src/com/android/camera/util/ApiHelper.java
+++ b/src/com/android/camera/util/ApiHelper.java
@@ -86,7 +86,7 @@
     }
 
     public static boolean isLOrHigher() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.L
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                 || "L".equals(Build.VERSION.CODENAME);
     }
 }
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index a55aa84..658e73b 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -301,6 +301,7 @@
         // appearing, so check to ensure that the activity is not shutting down
         // before attempting to attach a dialog to the window manager.
         if (!activity.isFinishing()) {
+            Log.e(TAG, "Show fatal error dialog");
             new AlertDialog.Builder(activity)
                     .setCancelable(false)
                     .setTitle(R.string.camera_error_title)
@@ -562,12 +563,24 @@
                 }
             }
         }
+
         return optimalSizeIndex;
     }
 
-    /** Returns the largest picture size which matches the given aspect ratio. */
+    /**
+     * Returns the largest picture size which matches the given aspect ratio,
+     * except for the special WYSIWYG case where the picture size exactly matches
+     * the target size.
+     *
+     * @param sizes        a list of candidate sizes, available for use
+     * @param targetWidth  the ideal width of the video snapshot
+     * @param targetHeight the ideal height of the video snapshot
+     * @return the Optimal Video Snapshot Picture Size
+     */
     public static com.android.ex.camera2.portability.Size getOptimalVideoSnapshotPictureSize(
-            List<com.android.ex.camera2.portability.Size> sizes, double targetRatio) {
+            List<com.android.ex.camera2.portability.Size> sizes, int targetWidth,
+            int targetHeight) {
+
         // Use a very small tolerance because we want an exact match.
         final double ASPECT_TOLERANCE = 0.001;
         if (sizes == null) {
@@ -576,7 +589,17 @@
 
         com.android.ex.camera2.portability.Size optimalSize = null;
 
+        //  WYSIWYG Override
+        //  We assume that physical display constraints have already been
+        //  imposed on the variables sizes
+        for (com.android.ex.camera2.portability.Size size : sizes) {
+            if (size.height() == targetHeight && size.width() == targetWidth) {
+                return size;
+            }
+        }
+
         // Try to find a size matches aspect ratio and has the largest width
+        final double targetRatio = (double) targetWidth / targetHeight;
         for (com.android.ex.camera2.portability.Size size : sizes) {
             double ratio = (double) size.width() / size.height();
             if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
diff --git a/src/com/android/camera/widget/FilmstripView.java b/src/com/android/camera/widget/FilmstripView.java
index 7bf675e..0f7442e 100644
--- a/src/com/android/camera/widget/FilmstripView.java
+++ b/src/com/android/camera/widget/FilmstripView.java
@@ -959,7 +959,7 @@
         }
     }
 
-    @TargetApi(Build.VERSION_CODES.L)
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     private void setMaxElevation(View v) {
         v.setElevation(Float.MAX_VALUE);
     }