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

Commit 10ed79ba authored by Yuanjia Hsu's avatar Yuanjia Hsu Committed by Android (Google) Code Review
Browse files

Merge "Implement “Set up Face or Fingerprint Unlock first” page"

parents 7acbaf2d 06466b03
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -2524,6 +2524,8 @@
            <meta-data android:name="com.android.settings.icon_tintable" android:value="true" />
        </activity-alias>

        <activity android:name=".biometrics.activeunlock.ActiveUnlockRequireBiometricSetup" android:exported="false"/>

        <!-- Note this must not be exported since it returns the password in the intent -->
        <activity android:name=".password.ConfirmLockPattern$InternalActivity"
            android:exported="false"
+24 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2023 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.google.android.setupdesign.GlifLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/setup_wizard_layout"
    style="?attr/fingerprint_layout_theme"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.google.android.setupdesign.GlifLayout>
+7 −1
Original line number Diff line number Diff line
@@ -495,8 +495,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
    @Override
    public void finish() {
        if (mGkPwHandle != null) {
            // When launched as InternalActivity, the mGkPwHandle was gotten from intent extra
            // instead of requesting from the user. Do not remove the mGkPwHandle in service side
            // for this case because the caller activity may still need it and will be responsible
            // for removing it.
            if (!(this instanceof InternalActivity)) {
                BiometricUtils.removeGatekeeperPasswordHandle(this, mGkPwHandle);
            }
        }
        super.finish();
    }

+147 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.
 */

package com.android.settings.biometrics.activeunlock;

import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL;
import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED;

import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;

import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;

import androidx.annotation.VisibleForTesting;

import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollActivity;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.combination.CombinedBiometricStatusUtils;

import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;

/**
 * Activity which instructs the user to set up face or fingerprint unlock before setting the watch
 * unlock.
 */
public class ActiveUnlockRequireBiometricSetup extends BiometricEnrollBase {
    private static final String TAG = "ActiveUnlockRequireBiometricSetup";

    @VisibleForTesting
    static final int BIOMETRIC_ENROLL_REQUEST = 1001;
    private long mGkPwHandle;
    private boolean mNextClicked;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activeunlock_require_biometric_setup);

        mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
        Log.i(TAG, "mUserId = " + mUserId);
        mGkPwHandle = getIntent().getLongExtra(EXTRA_KEY_GK_PW_HANDLE, 0L);

        final PackageManager pm = getApplicationContext().getPackageManager();
        boolean hasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
        boolean hasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
        if (hasFeatureFace && hasFeatureFingerprint) {
            setHeaderText(
                    R.string.security_settings_activeunlock_require_face_fingerprint_setup_title);
            setDescriptionText(
                    R.string.security_settings_activeunlock_require_face_fingerprint_setup_message);
        } else if (hasFeatureFingerprint) {
            setHeaderText(R.string.security_settings_activeunlock_require_fingerprint_setup_title);
            setDescriptionText(
                    R.string.security_settings_activeunlock_require_fingerprint_setup_message);
        } else if (hasFeatureFace) {
            setHeaderText(R.string.security_settings_activeunlock_require_face_setup_title);
            setDescriptionText(
                    R.string.security_settings_activeunlock_require_face_setup_message);
        }

        mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
        mFooterBarMixin.setSecondaryButton(
                new FooterButton.Builder(this)
                        .setText(R.string.cancel)
                        .setListener(this::onCancelClick)
                        .setButtonType(FooterButton.ButtonType.CANCEL)
                        .setTheme(R.style.SudGlifButton_Secondary)
                        .build()
        );

        mFooterBarMixin.setPrimaryButton(
                new FooterButton.Builder(this)
                        .setText(R.string.security_settings_activeunlock_biometric_setup)
                        .setListener(this::onNextButtonClick)
                        .setButtonType(FooterButton.ButtonType.NEXT)
                        .setTheme(R.style.SudGlifButton_Primary)
                        .build()
        );
    }

    @Override
    public void onBackPressed() {
        finish();
    }

    private void onCancelClick(View view) {
        finish();
    }

    @Override
    protected boolean shouldFinishWhenBackgrounded() {
        return super.shouldFinishWhenBackgrounded() && !mNextClicked;
    }

    @Override
    protected void onNextButtonClick(View view) {
        mNextClicked = true;
        Intent intent = new Intent(this, BiometricEnrollActivity.InternalActivity.class);
        intent.setAction(ACTION_BIOMETRIC_ENROLL);
        intent.putExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, BIOMETRIC_STRONG);
        intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
        intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
        startActivityForResult(intent, BIOMETRIC_ENROLL_REQUEST);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == BIOMETRIC_ENROLL_REQUEST && resultCode != RESULT_CANCELED) {
            CombinedBiometricStatusUtils combinedBiometricStatusUtils =
                    new CombinedBiometricStatusUtils(this, mUserId);
            if (combinedBiometricStatusUtils.hasEnrolled()) {
                // TODO(b/264813444): launch active unlock setting page in GmsCore without double
                //  authentication.
            }
        }
        mNextClicked = false;
        finish();
    }

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.ACTIVE_UNLOCK_REQUIRE_BIOMETRIC_SETUP;
    }
}
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.
 */

package com.android.settings.biometrics.activeunlock;

import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED;
import static com.android.settings.biometrics.activeunlock.ActiveUnlockRequireBiometricSetup.BIOMETRIC_ENROLL_REQUEST;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.robolectric.RuntimeEnvironment.application;

import android.app.settings.SettingsEnums;
import android.content.ComponentName;

import com.android.settings.R;
import com.android.settings.biometrics.BiometricEnrollActivity;

import com.google.android.setupcompat.PartnerCustomizationLayout;
import com.google.android.setupcompat.template.FooterBarMixin;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowActivity;

@RunWith(RobolectricTestRunner.class)
public class ActiveUnlockRequireBiometricSetupTest {

    private ActiveUnlockRequireBiometricSetup mActivity;
    private PartnerCustomizationLayout mLayout;

    @Before
    public void setUp() {
        mActivity = Robolectric.buildActivity(
                ActiveUnlockRequireBiometricSetup.class).setup().get();
        mLayout = mActivity.findViewById(R.id.setup_wizard_layout);
    }

    @Test
    public void onBackPressed_shouldFinish() {
        mActivity.onBackPressed();

        assertThat(mActivity.isFinishing()).isTrue();
    }

    @Test
    public void clickCancel_shouldFinish() {
        mLayout.getMixin(FooterBarMixin.class).getSecondaryButtonView().performClick();

        assertThat(mActivity.isFinishing()).isTrue();
    }

    @Test
    public void clickNext_shouldLaunchBiometricSetup() {
        final ComponentName expectedComponent = new ComponentName(application,
                BiometricEnrollActivity.InternalActivity.class);

        mLayout.getMixin(FooterBarMixin.class).getPrimaryButtonView().performClick();

        ShadowActivity.IntentForResult startedActivity = Shadows.shadowOf(
                mActivity).getNextStartedActivityForResult();
        assertWithMessage("Next activity").that(startedActivity).isNotNull();
        assertThat(startedActivity.intent.getComponent()).isEqualTo(expectedComponent);
    }

    @Test
    public void onActivityResult_shouldFinish() {
        mActivity.onActivityResult(BIOMETRIC_ENROLL_REQUEST, RESULT_FINISHED, null);

        assertThat(mActivity.isFinishing()).isTrue();
    }

    @Test
    public void getMetricsCategory_returnsCorrectCategory() {
        assertThat(mActivity.getMetricsCategory()).isEqualTo(
                SettingsEnums.ACTIVE_UNLOCK_REQUIRE_BIOMETRIC_SETUP);
    }
}