Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8cbb488b authored by Kevin Chyn's avatar Kevin Chyn
Browse files

13/n: persist device credential across configuration changes

1) AuthContainerView can be started in either `biometric` or `credential`
   views. This is to potentially support an API where only credential
   is allowed/requested.
2) When onSaveState, AuthContainerView saves both the states of
   `biometric` and `credential` visibility. In the case of configuration
   change, AuthController is responsibile for checking the visibility
   and creating a AuthContainerView with the correct initial view.
3) When AuthCredentialView is the initial view, it owns the panel
   expansion.
4) Added landscape layout

Bug: 140127687

Test: atest com.android.systemui.biometrics
Test: BiometricPromptDemo, use pattern, rotate device. auth, cancel,
      demo logs are correct.

Change-Id: I1f501cf13b924353f251a69757fdb9d7e0bf1d21
parent ff168dc4
Loading
Loading
Loading
Loading
+113 −0
Original line number Diff line number Diff line
<!--
  ~ Copyright (C) 2019 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.
  -->

<com.android.systemui.biometrics.AuthCredentialView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:elevation="@dimen/biometric_dialog_elevation">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical">

        <Space
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <ImageView
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:background="@drawable/auth_dialog_lock"/>

        <TextView
            android:id="@+id/title"
            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="24dp"
            android:layout_marginTop="12dp"
            android:textSize="20sp"
            android:gravity="center"
            android:textColor="?android:attr/textColorPrimary"/>

        <TextView
            android:id="@+id/subtitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="24dp"
            android:layout_marginTop="8dp"
            android:textSize="16sp"
            android:gravity="center"
            android:textColor="?android:attr/textColorPrimary"/>

        <TextView
            android:id="@+id/description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="24dp"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:textSize="16sp"
            android:textColor="?android:attr/textColorPrimary"/>

        <Space
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TextView
            android:id="@+id/error"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="24dp"
            android:textSize="16sp"
            android:gravity="center"
            android:textColor="?android:attr/colorError"/>

        <Space
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="1"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical"
        android:gravity="center">

        <com.android.internal.widget.LockPatternView
            android:id="@+id/lockPattern"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:layout_gravity="center"
            android:clipChildren="false"
            android:clipToPadding="false"
            style="@style/LockPatternStyleBiometricPrompt"/>

    </LinearLayout>

</com.android.systemui.biometrics.AuthCredentialView>
 No newline at end of file
+0 −1
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

<com.android.systemui.biometrics.AuthCredentialView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/contents"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
+59 −16
Original line number Diff line number Diff line
@@ -107,11 +107,25 @@ public class AuthContainerView extends LinearLayout
        String mOpPackageName;
        int mModalityMask;
        boolean mSkipIntro;
        @Builder.InitialView int mInitialView;
    }

    public static class Builder {
        Config mConfig;

        /**
         * Start the prompt with biometric UI. May flow to credential view if
         * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} is set to true.
         */
        public static final int INITIAL_VIEW_BIOMETRIC = 1;
        /**
         * Start the prompt with credential UI
         */
        public static final int INITIAL_VIEW_CREDENTIAL = 2;
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({INITIAL_VIEW_BIOMETRIC, INITIAL_VIEW_CREDENTIAL})
        @interface InitialView {}

        public Builder(Context context) {
            mConfig = new Config();
            mConfig.mContext = context;
@@ -147,6 +161,11 @@ public class AuthContainerView extends LinearLayout
            return this;
        }

        public Builder setInitialView(@InitialView int initialView) {
            mConfig.mInitialView = initialView;
            return this;
        }

        public AuthContainerView build(int modalityMask) {
            mConfig.mModalityMask = modalityMask;
            return new AuthContainerView(mConfig);
@@ -175,7 +194,7 @@ public class AuthContainerView extends LinearLayout
                    break;
                case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
                    mConfig.mCallback.onDeviceCredentialPressed();
                    showCredentialView();
                    addCredentialView(false /* animatePanel */);
                    break;
                default:
                    Log.e(TAG, "Unhandled action: " + action);
@@ -226,6 +245,7 @@ public class AuthContainerView extends LinearLayout
            return;
        }

        mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
        mBackgroundView = mFrameLayout.findViewById(R.id.background);

        UserManager userManager = mContext.getSystemService(UserManager.class);
@@ -239,15 +259,6 @@ public class AuthContainerView extends LinearLayout
            mBackgroundView.setImageDrawable(image);
        }

        mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
        mBiometricView.setPanelController(mPanelController);
        mBiometricView.setBiometricPromptBundle(config.mBiometricPromptBundle);
        mBiometricView.setCallback(mBiometricCallback);
        mBiometricView.setBackgroundView(mBackgroundView);
        mBiometricView.setUserId(mConfig.mUserId);

        mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
        mBiometricScrollView.addView(mBiometricView);
        addView(mFrameLayout);

        setOnKeyListener((v, keyCode, event) -> {
@@ -264,13 +275,30 @@ public class AuthContainerView extends LinearLayout
        requestFocus();
    }

    private void showCredentialView() {
    private void addBiometricView() {
        mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
        mBiometricView.setPanelController(mPanelController);
        mBiometricView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
        mBiometricView.setCallback(mBiometricCallback);
        mBiometricView.setBackgroundView(mBackgroundView);
        mBiometricView.setUserId(mConfig.mUserId);
        mBiometricScrollView.addView(mBiometricView);
    }

    /**
     * Adds the credential view. When going from biometric to credential view, the biometric
     * view starts the panel expansion animation. If the credential view is being shown first,
     * it should own the panel expansion.
     * @param animatePanel if the credential view needs to own the panel expansion animation
     */
    private void addCredentialView(boolean animatePanel) {
        final LayoutInflater factory = LayoutInflater.from(mContext);
        mCredentialView = (AuthCredentialView) factory.inflate(
                R.layout.auth_credential_view, null, false);
        mCredentialView.setUser(mConfig.mUserId);
        mCredentialView.setCallback(mCredentialCallback);
        mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
        mCredentialView.setPanelController(mPanelController, animatePanel);
        mFrameLayout.addView(mCredentialView);
    }

@@ -285,6 +313,21 @@ public class AuthContainerView extends LinearLayout
        super.onAttachedToWindow();
        mWakefulnessLifecycle.addObserver(this);

        Log.v(TAG, "Initial view: " + mConfig.mInitialView);

        switch (mConfig.mInitialView) {
            case Builder.INITIAL_VIEW_BIOMETRIC:
                addBiometricView();
                break;
            case Builder.INITIAL_VIEW_CREDENTIAL:
                addCredentialView(true /* animatePanel */);
                break;
            default:
                Log.e(TAG, "Initial view not supported: " + mConfig.mInitialView);
                break;
        }


        if (mConfig.mSkipIntro) {
            mContainerState = STATE_SHOWING;
        } else {
@@ -373,6 +416,11 @@ public class AuthContainerView extends LinearLayout
    @Override
    public void onSaveState(@NonNull Bundle outState) {
        outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
        // In the case where biometric and credential are both allowed, we can assume that
        // biometric isn't showing if credential is showing since biometric is shown first.
        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
                mBiometricView != null && mCredentialView == null);
        outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
        mBiometricView.onSaveState(outState);
    }

@@ -441,11 +489,6 @@ public class AuthContainerView extends LinearLayout
        });
    }

    private boolean isDeviceCredentialAllowed() {
        return mConfig.mBiometricPromptBundle.getBoolean(
                BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
    }

    private void sendPendingCallbackIfNotNull() {
        Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
        if (mPendingCallbackReason != null) {
+21 −6
Original line number Diff line number Diff line
@@ -218,7 +218,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
            Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
            skipAnimation = true;
        }
        showDialog(args, skipAnimation, null /* savedState */);
        showDialog(args, skipAnimation, null /* savedState */,
                AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC);
    }

    @Override
@@ -253,7 +254,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
        mCurrentDialog.dismissFromSystemServer();
    }

    private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
    private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState,
            @AuthContainerView.Builder.InitialView int initialView) {
        mCurrentDialogArgs = args;
        final int type = args.argi1;
        final Bundle biometricPromptBundle = (Bundle) args.arg1;
@@ -268,7 +270,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
                userId,
                type,
                opPackageName,
                skipAnimation);
                skipAnimation,
                initialView);

        if (newDialog == null) {
            Log.e(TAG, "Unsupported type: " + type);
@@ -280,7 +283,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
                    + " savedState: " + savedState
                    + " mCurrentDialog: " + mCurrentDialog
                    + " newDialog: " + newDialog
                    + " type: " + type);
                    + " type: " + type
                    + " initialView: " + initialView);
        }

        if (mCurrentDialog != null) {
@@ -320,13 +324,23 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
            // to send its pending callback immediately.
            if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
                    != AuthContainerView.STATE_ANIMATING_OUT) {
                showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
                final boolean credentialShowing =
                        savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);

                // We can assume if credential is showing, then biometric doesn't need to be shown,
                // since credential is always after biometric.
                final int initialView = credentialShowing
                        ? AuthContainerView.Builder.INITIAL_VIEW_CREDENTIAL
                        : AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC;

                showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState, initialView);
            }
        }
    }

    protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
            int userId, int type, String opPackageName, boolean skipIntro) {
            int userId, int type, String opPackageName, boolean skipIntro,
            @AuthContainerView.Builder.InitialView int initialView) {
        return new AuthContainerView.Builder(mContext)
                .setCallback(this)
                .setBiometricPromptBundle(biometricPromptBundle)
@@ -334,6 +348,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
                .setUserId(userId)
                .setOpPackageName(opPackageName)
                .setSkipIntro(skipIntro)
                .setInitialView(initialView)
                .build(type);
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ public class AuthCredentialView extends LinearLayout {
    private Callback mCallback;
    private ErrorTimer mErrorTimer;
    private Bundle mBiometricPromptBundle;
    private AuthPanelController mPanelController;
    private boolean mShouldAnimatePanel;

    private TextView mTitleView;
    private TextView mSubtitleView;
@@ -213,6 +215,11 @@ public class AuthCredentialView extends LinearLayout {
        mBiometricPromptBundle = bundle;
    }

    void setPanelController(AuthPanelController panelController, boolean animatePanel) {
        mPanelController = panelController;
        mShouldAnimatePanel = animatePanel;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
@@ -258,4 +265,17 @@ public class AuthCredentialView extends LinearLayout {
        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (mShouldAnimatePanel) {
            // Credential view is always full screen.
            mPanelController.setUseFullScreen(true);
            mPanelController.updateForContentDimensions(mPanelController.getContainerWidth(),
                    mPanelController.getContainerHeight(), 0 /* animateDurationMs */);
            mShouldAnimatePanel = false;
        }
    }

}
Loading