Animations/success state for external confirm device credentials (2/2)

Bug: 20929186
Change-Id: I4489dd37f1148fb03315ec337a546eee04660cb5
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8728ab4..56faa51 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1407,7 +1407,7 @@
             android:theme="@style/Theme.ConfirmDeviceCredentials"/>
 
         <activity android:name="ConfirmLockPassword"
-            android:windowSoftInputMode="adjustResize"
+            android:windowSoftInputMode="stateHidden|adjustResize"
             android:theme="@style/Theme.ConfirmDeviceCredentials"/>
 
         <activity android:name=".fingerprint.FingerprintSettings" android:exported="false"/>
diff --git a/res/anim/confirm_credential_close_enter.xml b/res/anim/confirm_credential_close_enter.xml
new file mode 100644
index 0000000..fcc3241
--- /dev/null
+++ b/res/anim/confirm_credential_close_enter.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false"
+    android:zAdjustment="top">
+  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+      android:interpolator="@*android:interpolator/decelerate_quart"
+      android:fillEnabled="true"
+      android:fillBefore="false" android:fillAfter="true"
+      android:duration="200"/>
+  <translate android:fromYDelta="8%" android:toYDelta="0"
+      android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+      android:interpolator="@*android:interpolator/decelerate_quint"
+      android:duration="350"/>
+</set>
diff --git a/res/anim/confirm_credential_close_exit.xml b/res/anim/confirm_credential_close_exit.xml
new file mode 100644
index 0000000..5afd8b2
--- /dev/null
+++ b/res/anim/confirm_credential_close_exit.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:interpolator/linear_out_slow_in"
+    android:fromAlpha="1.0" android:toAlpha="0.0"
+    android:duration="350" />
diff --git a/res/anim/confirm_credential_open_enter.xml b/res/anim/confirm_credential_open_enter.xml
new file mode 100644
index 0000000..06f3af8
--- /dev/null
+++ b/res/anim/confirm_credential_open_enter.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:interpolator/linear_out_slow_in"
+    android:fromAlpha="0.0" android:toAlpha="1.0"
+    android:duration="160" />
diff --git a/res/anim/confirm_credential_open_exit.xml b/res/anim/confirm_credential_open_exit.xml
new file mode 100644
index 0000000..3f20a04
--- /dev/null
+++ b/res/anim/confirm_credential_open_exit.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1.0" android:toAlpha="1.0"
+    android:duration="160" />
diff --git a/res/drawable/ic_fingerprint_success.xml b/res/drawable/ic_fingerprint_success.xml
new file mode 100644
index 0000000..e800fae
--- /dev/null
+++ b/res/drawable/ic_fingerprint_success.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="40.0"
+        android:viewportHeight="40.0">
+    <path
+        android:pathData="M20.0,20.0m-20.0,0.0a20.0,20.0 0.0,1.0 1.0,40.0 0.0a20.0,20.0 0.0,1.0 1.0,-40.0 0.0"
+        android:fillColor="?android:attr/colorAccent"/>
+    <path
+        android:pathData="M11.2,21.41l1.63,-1.619999 4.17,4.169998 10.59,-10.589999 1.619999,1.63 -12.209999,12.209999z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 5e9d870..1d58779 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -225,6 +225,11 @@
         <item name="android:layout_height">wrap_content</item>
     </style>
 
+    <style name="ConfirmDeviceCredentialsAnimationStyle" parent="@*android:style/Animation.Material.Activity">
+        <item name="android:activityOpenEnterAnimation">@anim/confirm_credential_open_enter</item>
+        <item name="android:activityOpenExitAnimation">@anim/confirm_credential_open_exit</item>
+    </style>
+
     <style name="Transparent">
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowNoTitle">true</item>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 76fc04f..f508f76 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -199,7 +199,7 @@
         <item name="android:colorPrimaryDark">@*android:color/material_blue_grey_950</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
-        <item name="preferenceBackgroundColor">@color/confirm_device_credential_dark_background</item>
+        <item name="android:windowBackground">@color/confirm_device_credential_dark_background</item>
 
         <item name="confirmDeviceCredentialsSideMargin">32dp</item>
         <item name="confirmDeviceCredentialsTopMargin">32dp</item>
@@ -208,6 +208,8 @@
         <item name="@*android:regularColor">@color/lock_pattern_view_regular_color_dark</item>
         <item name="@*android:successColor">@color/lock_pattern_view_regular_color_dark</item>
         <item name="@*android:errorColor">@color/lock_pattern_view_error_color</item>
+
+        <item name="android:windowAnimationStyle">@style/ConfirmDeviceCredentialsAnimationStyle</item>
     </style>
 
     <style name="Theme.FingerprintEnroll" parent="@*android:style/Theme.Material.Settings.NoActionBar">
diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java b/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java
index fe3de97..f56c315 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java
@@ -16,17 +16,23 @@
 
 package com.android.settings;
 
+import android.app.Fragment;
 import android.app.KeyguardManager;
 import android.os.Bundle;
 import android.view.MenuItem;
 import android.view.WindowManager;
 
-public class ConfirmDeviceCredentialBaseActivity extends SettingsActivity {
+public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivity {
+
+    private boolean mRestoring;
+    private boolean mDark;
+    private boolean mEnterAnimationPending;
 
     @Override
     protected void onCreate(Bundle savedState) {
         if (getIntent().getBooleanExtra(ConfirmDeviceCredentialBaseFragment.DARK_THEME, false)) {
             setTheme(R.style.Theme_ConfirmDeviceCredentialsDark);
+            mDark = true;
         }
         super.onCreate(savedState);
         boolean deviceLocked = getSystemService(KeyguardManager.class).isKeyguardLocked();
@@ -41,6 +47,7 @@
             getActionBar().setDisplayHomeAsUpEnabled(true);
             getActionBar().setHomeButtonEnabled(true);
         }
+        mRestoring = savedState != null;
     }
 
     @Override
@@ -51,4 +58,37 @@
         }
         return super.onOptionsItemSelected(item);
     }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (!isChangingConfigurations() && !mRestoring && mDark) {
+            prepareEnterAnimation();
+            mEnterAnimationPending = true;
+        }
+    }
+
+    private ConfirmDeviceCredentialBaseFragment getFragment() {
+        Fragment fragment = getFragmentManager().findFragmentById(R.id.main_content);
+        if (fragment != null && fragment instanceof ConfirmDeviceCredentialBaseFragment) {
+            return (ConfirmDeviceCredentialBaseFragment) fragment;
+        }
+        return null;
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        if (mEnterAnimationPending) {
+            startEnterAnimation();
+        }
+    }
+
+    public void prepareEnterAnimation() {
+        getFragment().prepareEnterAnimation();
+    }
+
+    public void startEnterAnimation() {
+        getFragment().startEnterAnimation();
+    }
 }
diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
index 414aa01..a8b5f2d 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
@@ -45,6 +45,8 @@
 
     private FingerprintUiHelper mFingerprintHelper;
     private boolean mAllowFpAuthentication;
+    protected Button mCancelButton;
+    protected ImageView mFingerprintIcon;
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -56,14 +58,15 @@
     @Override
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
+        mCancelButton = (Button) view.findViewById(R.id.cancelButton);
+        mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon);
         mFingerprintHelper = new FingerprintUiHelper(
-                (ImageView) view.findViewById(R.id.fingerprintIcon),
+                mFingerprintIcon,
                 (TextView) view.findViewById(R.id.errorText), this);
         boolean showCancelButton = getActivity().getIntent().getBooleanExtra(
                 SHOW_CANCEL_BUTTON, false);
-        Button cancelButton = (Button) view.findViewById(R.id.cancelButton);
-        cancelButton.setVisibility(showCancelButton ? View.VISIBLE : View.GONE);
-        cancelButton.setOnClickListener(new View.OnClickListener() {
+        mCancelButton.setVisibility(showCancelButton ? View.VISIBLE : View.GONE);
+        mCancelButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 getActivity().finish();
@@ -100,4 +103,10 @@
     @Override
     public void onFingerprintIconVisibilityChanged(boolean visible) {
     }
+
+    public void prepareEnterAnimation() {
+    }
+
+    public void startEnterAnimation() {
+    }
 }
diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java
index db0b44a..7761807 100644
--- a/src/com/android/settings/ConfirmLockPassword.java
+++ b/src/com/android/settings/ConfirmLockPassword.java
@@ -19,9 +19,12 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.TextViewInputDisabler;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
 
 import android.app.Fragment;
 import android.app.admin.DevicePolicyManager;
@@ -39,11 +42,14 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
+import java.util.ArrayList;
+
 public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
 
     public static class InternalActivity extends ConfirmLockPassword {
@@ -89,6 +95,9 @@
         private boolean mIsAlpha;
         private InputMethodManager mImm;
         private boolean mUsingFingerprint = false;
+        private AppearAnimationUtils mAppearAnimationUtils;
+        private DisappearAnimationUtils mDisappearAnimationUtils;
+        private boolean mBlockImm;
 
         // required constructor for fragments
         public ConfirmLockPasswordFragment() {
@@ -144,6 +153,14 @@
             int currentType = mPasswordEntry.getInputType();
             mPasswordEntry.setInputType(mIsAlpha ? currentType
                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
+            mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
+                    220, 2f /* translationScale */, 1f /* delayScale*/,
+                    AnimationUtils.loadInterpolator(getContext(),
+                            android.R.interpolator.linear_out_slow_in));
+            mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
+                    110, 1f /* translationScale */,
+                    0.5f /* delayScale */, AnimationUtils.loadInterpolator(
+                            getContext(), android.R.interpolator.fast_out_linear_in));
             return view;
         }
 
@@ -163,6 +180,43 @@
         }
 
         @Override
+        public void prepareEnterAnimation() {
+            super.prepareEnterAnimation();
+            mHeaderTextView.setAlpha(0f);
+            mDetailsTextView.setAlpha(0f);
+            mCancelButton.setAlpha(0f);
+            mPasswordEntry.setAlpha(0f);
+            mFingerprintIcon.setAlpha(0f);
+            mBlockImm = true;
+        }
+
+        private View[] getActiveViews() {
+            ArrayList<View> result = new ArrayList<>();
+            result.add(mHeaderTextView);
+            result.add(mDetailsTextView);
+            if (mCancelButton.getVisibility() == View.VISIBLE) {
+                result.add(mCancelButton);
+            }
+            result.add(mPasswordEntry);
+            if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
+                result.add(mFingerprintIcon);
+            }
+            return result.toArray(new View[] {});
+        }
+
+        @Override
+        public void startEnterAnimation() {
+            super.startEnterAnimation();
+            mAppearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
+                @Override
+                public void run() {
+                    mBlockImm = false;
+                    resetState();
+                }
+            });
+        }
+
+        @Override
         public void onPause() {
             super.onPause();
             if (mCountdownTimer != null) {
@@ -199,9 +253,7 @@
 
         @Override
         protected void authenticationSucceeded() {
-            Intent intent = new Intent();
-            getActivity().setResult(RESULT_OK, intent);
-            getActivity().finish();
+            startDisappearAnimation(new Intent());
         }
 
         @Override
@@ -210,6 +262,7 @@
         }
 
         private void resetState() {
+            if (mBlockImm) return;
             mPasswordEntry.setEnabled(true);
             mPasswordEntryInputDisabler.setInputEnabled(true);
             if (shouldAutoShowSoftKeyboard()) {
@@ -222,7 +275,7 @@
         }
 
         public void onWindowFocusChanged(boolean hasFocus) {
-            if (!hasFocus) {
+            if (!hasFocus || mBlockImm) {
                 return;
             }
             // Post to let window focus logic to finish to allow soft input show/hide properly.
@@ -312,11 +365,28 @@
                     });
         }
 
+        private void startDisappearAnimation(final Intent intent) {
+            if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
+                mDisappearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
+                    @Override
+                    public void run() {
+                        getActivity().setResult(RESULT_OK, intent);
+                        getActivity().finish();
+                        getActivity().overridePendingTransition(
+                                R.anim.confirm_credential_close_enter,
+                                R.anim.confirm_credential_close_exit);
+                    }
+                });
+            } else {
+                getActivity().setResult(RESULT_OK, intent);
+                getActivity().finish();
+            }
+        }
+
         private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs) {
             mPasswordEntryInputDisabler.setInputEnabled(true);
             if (matched) {
-                getActivity().setResult(RESULT_OK, intent);
-                getActivity().finish();
+                startDisappearAnimation(intent);
             } else {
                 if (timeoutMs > 0) {
                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
index 90a39f9..1737635 100644
--- a/src/com/android/settings/ConfirmLockPattern.java
+++ b/src/com/android/settings/ConfirmLockPattern.java
@@ -22,10 +22,21 @@
 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternView.Cell;
+import com.android.settingslib.animation.AppearAnimationCreator;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.Fragment;
 import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.os.CountDownTimer;
 import android.os.SystemClock;
 import android.os.AsyncTask;
@@ -33,11 +44,16 @@
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.view.MenuItem;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 import android.widget.TextView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -70,7 +86,8 @@
         return false;
     }
 
-    public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment {
+    public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
+            implements AppearAnimationCreator<Object> {
 
         // how long we wait to clear a wrong pattern
         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
@@ -93,6 +110,9 @@
         private CharSequence mHeaderText;
         private CharSequence mDetailsText;
 
+        private AppearAnimationUtils mAppearAnimationUtils;
+        private DisappearAnimationUtils mDisappearAnimationUtils;
+
         // required constructor for fragments
         public ConfirmLockPatternFragment() {
 
@@ -144,6 +164,20 @@
                     getActivity().finish();
                 }
             }
+            mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
+                    AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
+                    1.3f /* delayScale */, AnimationUtils.loadInterpolator(
+                    getContext(), android.R.interpolator.linear_out_slow_in));
+            mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
+                    125, 4f /* translationScale */,
+                    0.3f /* delayScale */, AnimationUtils.loadInterpolator(
+                    getContext(), android.R.interpolator.fast_out_linear_in),
+                    new AppearAnimationUtils.RowTranslationScaler() {
+                        @Override
+                        public float getRowTranslationScale(int row, int numRows) {
+                            return (float)(numRows - row) / numRows;
+                        }
+                    });
             return view;
         }
 
@@ -187,6 +221,51 @@
             }
         }
 
+        @Override
+        public void prepareEnterAnimation() {
+            super.prepareEnterAnimation();
+            mHeaderTextView.setAlpha(0f);
+            mCancelButton.setAlpha(0f);
+            mLockPatternView.setAlpha(0f);
+            mDetailsTextView.setAlpha(0f);
+            mFingerprintIcon.setAlpha(0f);
+        }
+
+        private Object[][] getActiveViews() {
+            ArrayList<ArrayList<Object>> result = new ArrayList<>();
+            result.add(new ArrayList<Object>(Collections.singletonList(mHeaderTextView)));
+            result.add(new ArrayList<Object>(Collections.singletonList(mDetailsTextView)));
+            if (mCancelButton.getVisibility() == View.VISIBLE) {
+                result.add(new ArrayList<Object>(Collections.singletonList(mCancelButton)));
+            }
+            LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
+            for (int i = 0; i < cellStates.length; i++) {
+                ArrayList<Object> row = new ArrayList<>();
+                for (int j = 0; j < cellStates[i].length; j++) {
+                    row.add(cellStates[i][j]);
+                }
+                result.add(row);
+            }
+            if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
+                result.add(new ArrayList<Object>(Collections.singletonList(mFingerprintIcon)));
+            }
+            Object[][] resultArr = new Object[result.size()][cellStates[0].length];
+            for (int i = 0; i < result.size(); i++) {
+                ArrayList<Object> row = result.get(i);
+                for (int j = 0; j < row.size(); j++) {
+                    resultArr[i][j] = row.get(j);
+                }
+            }
+            return resultArr;
+        }
+
+        @Override
+        public void startEnterAnimation() {
+            super.startEnterAnimation();
+            mLockPatternView.setAlpha(1f);
+            mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
+        }
+
         private void updateStage(Stage stage) {
             switch (stage) {
                 case NeedToUnlock:
@@ -242,9 +321,27 @@
 
         @Override
         protected void authenticationSucceeded() {
-            Intent intent = new Intent();
-            getActivity().setResult(Activity.RESULT_OK, intent);
-            getActivity().finish();
+            startDisappearAnimation(new Intent());
+        }
+
+        private void startDisappearAnimation(final Intent intent) {
+            if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
+                mLockPatternView.clearPattern();
+                mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                getActivity().setResult(RESULT_OK, intent);
+                                getActivity().finish();
+                                getActivity().overridePendingTransition(
+                                        R.anim.confirm_credential_close_enter,
+                                        R.anim.confirm_credential_close_exit);
+                            }
+                        }, this);
+            } else {
+                getActivity().setResult(RESULT_OK, intent);
+                getActivity().finish();
+            }
         }
 
         @Override
@@ -357,8 +454,7 @@
                     boolean matched, Intent intent, int timeoutMs) {
                 mLockPatternView.setEnabled(true);
                 if (matched) {
-                    getActivity().setResult(Activity.RESULT_OK, intent);
-                    getActivity().finish();
+                    startDisappearAnimation(intent);
                 } else {
                     if (timeoutMs > 0) {
                         long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
@@ -394,5 +490,52 @@
                 }
             }.start();
         }
+
+        @Override
+        public void createAnimation(Object obj, long delay,
+                long duration, float translationY, final boolean appearing,
+                Interpolator interpolator,
+                final Runnable finishListener) {
+            if (obj instanceof LockPatternView.CellState) {
+                final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
+                if (appearing) {
+                    animatedCell.scale = 0.0f;
+                    animatedCell.alpha = 1.0f;
+                }
+                animatedCell.translateY = appearing ? translationY : 0;
+                ValueAnimator animator = ValueAnimator.ofFloat(animatedCell.translateY,
+                        appearing ? 0 : translationY);
+                animator.setInterpolator(interpolator);
+                animator.setDuration(duration);
+                animator.setStartDelay(delay);
+                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animation) {
+                        float animatedFraction = animation.getAnimatedFraction();
+                        if (appearing) {
+                            animatedCell.scale = animatedFraction;
+                        } else {
+                            animatedCell.alpha = 1 - animatedFraction;
+                        }
+                        animatedCell.translateY = (float) animation.getAnimatedValue();
+                        mLockPatternView.invalidate();
+                    }
+                });
+                if (finishListener != null) {
+                    animator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            finishListener.run();
+                        }
+                    });
+                }
+
+                animator.start();
+                mLockPatternView.invalidate();
+            } else {
+                mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
+                        appearing, interpolator, finishListener);
+            }
+        }
     }
 }
diff --git a/src/com/android/settings/fingerprint/FingerprintUiHelper.java b/src/com/android/settings/fingerprint/FingerprintUiHelper.java
index 6ae76aa..23a135e 100644
--- a/src/com/android/settings/fingerprint/FingerprintUiHelper.java
+++ b/src/com/android/settings/fingerprint/FingerprintUiHelper.java
@@ -18,7 +18,6 @@
 
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.CancellationSignal;
-import android.os.Vibrator;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -31,8 +30,6 @@
 public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
 
     private static final long ERROR_TIMEOUT = 1300;
-    private static final long[] FP_ERROR_VIBRATE_PATTERN = new long[] {0, 30, 100, 30};
-    private static final long[] FP_SUCCESS_VIBRATE_PATTERN = new long[] {0, 30};
 
     private ImageView mIcon;
     private TextView mErrorTextView;
@@ -92,7 +89,7 @@
 
     @Override
     public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
-        vibrateFingerprintSuccess();
+        mIcon.setImageResource(R.drawable.ic_fingerprint_success);
         mCallback.onAuthenticated();
     }
 
@@ -101,21 +98,12 @@
             return;
         }
 
-        vibrateFingerprintError();
         mIcon.setImageResource(R.drawable.ic_fingerprint_error);
         mErrorTextView.setText(error);
         mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
         mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT);
     }
 
-    private void vibrateFingerprintError() {
-        mIcon.getContext().getSystemService(Vibrator.class).vibrate(FP_ERROR_VIBRATE_PATTERN, -1);
-    }
-
-    private void vibrateFingerprintSuccess() {
-        mIcon.getContext().getSystemService(Vibrator.class).vibrate(FP_SUCCESS_VIBRATE_PATTERN, -1);
-    }
-
     private Runnable mResetErrorTextRunnable = new Runnable() {
         @Override
         public void run() {