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

Commit aae4a15a authored by Kevin Chyn's avatar Kevin Chyn
Browse files

Add FingerprintDialog API and related plumbing

This commit adds the plumbing which sends the signals from the API
to the component in SysUI. The dialog will be implemented in another
CL in this topic.

Bug: 67497360

Test: Modify Settings to use the new API
Test: FingerprintDialogImpl is able to notify FingerprintDialog clients
      of user events
Test: System dialog is dismissed when client application is killed
      unexpectedly
Test: Open FP settings, lock device, authenticate, authenticate. Repeat

Change-Id: Id28ec9691646bed765dc069cceb4678d9f6db92e
parent 43137e8e
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -147,9 +147,10 @@ java_library {
        "core/java/android/hardware/display/IDisplayManager.aidl",
        "core/java/android/hardware/display/IDisplayManagerCallback.aidl",
        "core/java/android/hardware/display/IVirtualDisplayCallback.aidl",
        "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl",
        "core/java/android/hardware/fingerprint/IFingerprintDialogReceiver.aidl",
        "core/java/android/hardware/fingerprint/IFingerprintService.aidl",
        "core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl",
        "core/java/android/hardware/fingerprint/IFingerprintClientActiveCallback.aidl",
        "core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl",
        "core/java/android/hardware/hdmi/IHdmiControlCallback.aidl",
        "core/java/android/hardware/hdmi/IHdmiControlService.aidl",
+14 −0
Original line number Diff line number Diff line
@@ -16339,6 +16339,20 @@ package android.hardware.display {
package android.hardware.fingerprint {
  public class FingerprintDialog {
    method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback);
    method public void authenticate(android.os.CancellationSignal, java.util.concurrent.Executor, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback);
  }
  public static class FingerprintDialog.Builder {
    ctor public FingerprintDialog.Builder();
    method public android.hardware.fingerprint.FingerprintDialog build(android.content.Context);
    method public android.hardware.fingerprint.FingerprintDialog.Builder setDescription(java.lang.CharSequence);
    method public android.hardware.fingerprint.FingerprintDialog.Builder setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener);
    method public android.hardware.fingerprint.FingerprintDialog.Builder setSubtitle(java.lang.CharSequence);
    method public android.hardware.fingerprint.FingerprintDialog.Builder setTitle(java.lang.CharSequence);
  }
  public class FingerprintManager {
    method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, int, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, android.os.Handler);
    method public boolean hasEnrolledFingerprints();
+293 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 android.hardware.fingerprint;

import static android.Manifest.permission.USE_FINGERPRINT;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.DialogInterface;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.IFingerprintDialogReceiver;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.text.TextUtils;

import java.util.concurrent.Executor;

/**
 * A class that manages a system-provided fingerprint dialog.
 */
public class FingerprintDialog {

    /**
     * @hide
     */
    public static final String KEY_TITLE = "title";
    /**
     * @hide
     */
    public static final String KEY_SUBTITLE = "subtitle";
    /**
     * @hide
     */
    public static final String KEY_DESCRIPTION = "description";
    /**
     * @hide
     */
    public static final String KEY_POSITIVE_TEXT = "positive_text";
    /**
     * @hide
     */
    public static final String KEY_NEGATIVE_TEXT = "negative_text";

    /**
     * Error/help message will show for this amount of time.
     * For error messages, the dialog will also be dismissed after this amount of time.
     * Error messages will be propagated back to the application via AuthenticationCallback
     * after this amount of time.
     * @hide
     */
    public static final int HIDE_DIALOG_DELAY = 3000; // ms
    /**
     * @hide
     */
    public static final int DISMISSED_REASON_POSITIVE = 1;

    /**
     * @hide
     */
    public static final int DISMISSED_REASON_NEGATIVE = 2;

    /**
     * @hide
     */
    public static final int DISMISSED_REASON_USER_CANCEL = 3;

    private static class ButtonInfo {
        Executor executor;
        DialogInterface.OnClickListener listener;
        ButtonInfo(Executor ex, DialogInterface.OnClickListener l) {
            executor = ex;
            listener = l;
        }
    }

    /**
     * A builder that collects arguments, to be shown on the system-provided fingerprint dialog.
     **/
    public static class Builder {
        private final Bundle bundle;
        private ButtonInfo positiveButtonInfo;
        private ButtonInfo negativeButtonInfo;

        /**
         * Creates a builder for a fingerprint dialog.
         */
        public Builder() {
            bundle = new Bundle();
        }

        /**
         * Required: Set the title to display.
         * @param title
         * @return
         */
        public Builder setTitle(@NonNull CharSequence title) {
            bundle.putCharSequence(KEY_TITLE, title);
            return this;
        }

        /**
         * Optional: Set the subtitle to display.
         * @param subtitle
         * @return
         */
        public Builder setSubtitle(@NonNull CharSequence subtitle) {
            bundle.putCharSequence(KEY_SUBTITLE, subtitle);
            return this;
        }

        /**
         * Optional: Set the description to display.
         * @param description
         * @return
         */
        public Builder setDescription(@NonNull CharSequence description) {
            bundle.putCharSequence(KEY_DESCRIPTION, description);
            return this;
        }

        /**
         * Optional: Set the text for the positive button. If not set, the positive button
         * will not show.
         * @param text
         * @return
         * @hide
         */
        public Builder setPositiveButton(@NonNull CharSequence text,
                @NonNull @CallbackExecutor Executor executor,
                @NonNull DialogInterface.OnClickListener listener) {
            if (TextUtils.isEmpty(text)) {
                throw new IllegalArgumentException("Text must be set and non-empty");
            }
            if (executor == null) {
                throw new IllegalArgumentException("Executor must not be null");
            }
            if (listener == null) {
                throw new IllegalArgumentException("Listener must not be null");
            }
            bundle.putCharSequence(KEY_POSITIVE_TEXT, text);
            positiveButtonInfo = new ButtonInfo(executor, listener);
            return this;
        }

        /**
         * Required: Set the text for the negative button.
         * @param text
         * @return
         */
        public Builder setNegativeButton(@NonNull CharSequence text,
                @NonNull @CallbackExecutor Executor executor,
                @NonNull DialogInterface.OnClickListener listener) {
            if (TextUtils.isEmpty(text)) {
                throw new IllegalArgumentException("Text must be set and non-empty");
            }
            if (executor == null) {
                throw new IllegalArgumentException("Executor must not be null");
            }
            if (listener == null) {
                throw new IllegalArgumentException("Listener must not be null");
            }
            bundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
            negativeButtonInfo = new ButtonInfo(executor, listener);
            return this;
        }

        /**
         * Creates a {@link FingerprintDialog} with the arguments supplied to this builder.
         * @param context
         * @return a {@link FingerprintDialog}
         * @throws IllegalArgumentException if any of the required fields are not set.
         */
        public FingerprintDialog build(Context context) {
            final CharSequence title = bundle.getCharSequence(KEY_TITLE);
            final CharSequence negative = bundle.getCharSequence(KEY_NEGATIVE_TEXT);

            if (TextUtils.isEmpty(title)) {
                throw new IllegalArgumentException("Title must be set and non-empty");
            } else if (TextUtils.isEmpty(negative)) {
                throw new IllegalArgumentException("Negative text must be set and non-empty");
            }
            return new FingerprintDialog(context, bundle, positiveButtonInfo, negativeButtonInfo);
        }
    }

    private FingerprintManager mFingerprintManager;
    private Bundle mBundle;
    private ButtonInfo mPositiveButtonInfo;
    private ButtonInfo mNegativeButtonInfo;

    IFingerprintDialogReceiver mDialogReceiver = new IFingerprintDialogReceiver.Stub() {
        @Override
        public void onDialogDismissed(int reason) {
            // Check the reason and invoke OnClickListener(s) if necessary
            if (reason == DISMISSED_REASON_POSITIVE) {
                mPositiveButtonInfo.executor.execute(() -> {
                    mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE);
                });
            } else if (reason == DISMISSED_REASON_NEGATIVE) {
                mNegativeButtonInfo.executor.execute(() -> {
                    mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
                });
            }
        }
    };

    private FingerprintDialog(Context context, Bundle bundle,
            ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) {
        mBundle = bundle;
        mPositiveButtonInfo = positiveButtonInfo;
        mNegativeButtonInfo = negativeButtonInfo;
        mFingerprintManager = context.getSystemService(FingerprintManager.class);
    }

    /**
     * This call warms up the fingerprint hardware, displays a system-provided dialog,
     * and starts scanning for a fingerprint. It terminates when
     * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when
     * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called,
     * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user
     * dismisses the system-provided dialog, at which point the crypto object becomes invalid.
     * This operation can be canceled by using the provided cancel object. The application will
     * receive authentication errors through {@link AuthenticationCallback}, and button events
     * through the corresponding callback set in
     * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
     * It is safe to reuse the {@link FingerprintDialog} object, and calling
     * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)}
     * while an existing authentication attempt is occurring will stop the previous client and
     * start a new authentication. The interrupted client will receive a cancelled notification
     * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
     *
     * @throws IllegalArgumentException if any of the arguments are null
     *
     * @param crypto object associated with the call
     * @param cancel an object that can be used to cancel authentication
     * @param executor an executor to handle callback events
     * @param callback an object to receive authentication events
     */
    @RequiresPermission(USE_FINGERPRINT)
    public void authenticate(@NonNull FingerprintManager.CryptoObject crypto,
            @NonNull CancellationSignal cancel,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull FingerprintManager.AuthenticationCallback callback) {
        mFingerprintManager.authenticate(crypto, cancel, mBundle, executor, mDialogReceiver,
                callback);
    }

    /**
     * This call warms up the fingerprint hardware, displays a system-provided dialog,
     * and starts scanning for a fingerprint. It terminates when
     * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when
     * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called,
     * when {@link AuthenticationCallback#onAuthenticationFailed()} is called or when the user
     * dismisses the system-provided dialog. This operation can be canceled by using the provided
     * cancel object. The application will receive authentication errors through
     * {@link AuthenticationCallback}, and button events through the corresponding callback set in
     * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
     * It is safe to reuse the {@link FingerprintDialog} object, and calling
     * {@link FingerprintDialog#authenticate(CancellationSignal, Executor, AuthenticationCallback)}
     * while an existing authentication attempt is occurring will stop the previous client and
     * start a new authentication. The interrupted client will receive a cancelled notification
     * through {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
     *
     * @throws IllegalArgumentException if any of the arguments are null
     *
     * @param cancel an object that can be used to cancel authentication
     * @param executor an executor to handle callback events
     * @param callback an object to receive authentication events
     */
    @RequiresPermission(USE_FINGERPRINT)
    public void authenticate(@NonNull CancellationSignal cancel,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull FingerprintManager.AuthenticationCallback callback) {
        mFingerprintManager.authenticate(cancel, mBundle, executor, mDialogReceiver, callback);
    }
}
+204 −58

File changed.

Preview size limit exceeded, changes collapsed.

+28 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 android.hardware.fingerprint;

import android.hardware.fingerprint.Fingerprint;
import android.os.Bundle;
import android.os.UserHandle;

/**
 * Communication channel from the FingerprintDialog (SysUI) back to AuthenticationClient.
 * @hide
 */
oneway interface IFingerprintDialogReceiver {
    void onDialogDismissed(int reason);
}
Loading