CancellationSignalProvider.java

/*
 * Copyright 2020 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 androidx.biometric;

import android.os.Build;
import android.os.CancellationSignal;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;

/**
 * Creates and caches cancellation signal objects that are compatible with
 * {@link android.hardware.biometrics.BiometricPrompt} or
 * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
 */
@SuppressWarnings("deprecation")
class CancellationSignalProvider {
    /**
     * An injector for various class dependencies. Used for testing.
     */
    @VisibleForTesting
    interface Injector {
        /**
         * Returns a cancellation signal object that is compatible with
         * {@link android.hardware.biometrics.BiometricPrompt}.
         *
         * @return An instance of {@link android.os.CancellationSignal}.
         */
        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
        @NonNull
        android.os.CancellationSignal getBiometricCancellationSignal();

        /**
         * Returns a cancellation signal object that is compatible with
         * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
         *
         * @return An instance of {@link androidx.core.os.CancellationSignal}.
         */
        @NonNull
        androidx.core.os.CancellationSignal getFingerprintCancellationSignal();
    }

    /**
     * The injector for class dependencies used by this provider.
     */
    private final Injector mInjector;

    /**
     * A cancellation signal object that is compatible with
     * {@link android.hardware.biometrics.BiometricPrompt}.
     */
    @Nullable private android.os.CancellationSignal mBiometricCancellationSignal;

    /**
     * A cancellation signal object that is compatible with
     * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
     */
    @Nullable private androidx.core.os.CancellationSignal mFingerprintCancellationSignal;

    /**
     * Creates a new cancellation signal provider instance.
     */
    CancellationSignalProvider() {
        mInjector = new Injector() {
            @Override
            @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
            @NonNull
            public CancellationSignal getBiometricCancellationSignal() {
                return Api16Impl.create();
            }

            @Override
            @NonNull
            public androidx.core.os.CancellationSignal getFingerprintCancellationSignal() {
                return new androidx.core.os.CancellationSignal();
            }
        };
    }

    /**
     * Creates a new cancellation signal provider instance with the given injector.
     *
     * @param injector An injector for class and method dependencies.
     */
    @VisibleForTesting
    CancellationSignalProvider(Injector injector) {
        mInjector = injector;
    }

    /**
     * Provides a cancellation signal object that is compatible with
     * {@link android.hardware.biometrics.BiometricPrompt}.
     *
     * <p>Subsequent calls to this method for the same provider instance will return the same
     * cancellation signal, until {@link #cancel()} is invoked.
     *
     * @return A cancellation signal that can be passed to
     *  {@link android.hardware.biometrics.BiometricPrompt}.
     */
    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
    @NonNull
    android.os.CancellationSignal getBiometricCancellationSignal() {
        if (mBiometricCancellationSignal == null) {
            mBiometricCancellationSignal = mInjector.getBiometricCancellationSignal();
        }
        return mBiometricCancellationSignal;
    }

    /**
     * Provides a cancellation signal object that is compatible with
     * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
     *
     * <p>Subsequent calls to this method for the same provider instance will return the same
     * cancellation signal, until {@link #cancel()} is invoked.
     *
     * @return A cancellation signal that can be passed to
     *  {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}.
     */
    @NonNull
    androidx.core.os.CancellationSignal getFingerprintCancellationSignal() {
        if (mFingerprintCancellationSignal == null) {
            mFingerprintCancellationSignal = mInjector.getFingerprintCancellationSignal();
        }
        return mFingerprintCancellationSignal;
    }

    /**
     * Invokes cancel for all cached cancellation signal objects and clears any references to them.
     */
    void cancel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
                && mBiometricCancellationSignal != null) {
            Api16Impl.cancel(mBiometricCancellationSignal);
            mBiometricCancellationSignal = null;
        }
        if (mFingerprintCancellationSignal != null) {
            mFingerprintCancellationSignal.cancel();
            mFingerprintCancellationSignal = null;
        }
    }

    /**
     * Nested class to avoid verification errors for methods introduced in Android 4.1 (API 16).
     */
    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
    private static class Api16Impl {
        // Prevent instantiation.
        private Api16Impl() {}

        /**
         * Creates a new instance of the platform class {@link android.os.CancellationSignal}.
         *
         * @return An instance of {@link android.os.CancellationSignal}.
         */
        static android.os.CancellationSignal create() {
            return new android.os.CancellationSignal();
        }

        /**
         * Calls {@link android.os.CancellationSignal#cancel()} for the given cancellation signal.
         */
        static void cancel(android.os.CancellationSignal cancellationSignal) {
            cancellationSignal.cancel();
        }
    }
}