Snap for 12241618 from 4cffde0188 to 24Q4-release
Change-Id: I0972b0440315b2aeba62b629ec3cae971112a3d3
This commit is contained in:
@@ -49,3 +49,10 @@ flag {
|
||||
description: "Flag to gate support of injected preference icons containing raw data"
|
||||
bug: "351884562"
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "catalyst_legal_information"
|
||||
namespace: "android_settings"
|
||||
description: "This flag controls the About phone > Legal information page migration"
|
||||
bug: "323791114"
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
android:lineBreakWordStyle="phrase"
|
||||
android:maxLines="10"/>
|
||||
|
||||
<!-- Circular icons (32dp) will be ImageViews under this LinearLayout -->
|
||||
<LinearLayout
|
||||
<!-- Circular icons (32dp) will be ImageViews under this container -->
|
||||
<com.android.settings.notification.modes.CircularIconsView
|
||||
android:id="@+id/circles_container"
|
||||
android:importantForAccessibility="noHideDescendants"
|
||||
android:orientation="horizontal"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<Spinner
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:theme="@style/Widget.PopupWindow.Settings" />
|
||||
@@ -1554,7 +1554,7 @@
|
||||
<item>@*android:drawable/ic_zen_mode_type_theater</item> <!-- Film reel -->
|
||||
<item>@*android:drawable/ic_zen_mode_icon_book</item>
|
||||
<!-- Wellbeing -->
|
||||
<item>@*android:drawable/ic_zen_mode_type_unknown</item> <!-- Lotus flower -->
|
||||
<item>@*android:drawable/ic_zen_mode_icon_lotus_flower</item>
|
||||
<item>@*android:drawable/ic_zen_mode_type_immersive</item>
|
||||
<item>@*android:drawable/ic_zen_mode_icon_headphones</item>
|
||||
<item>@*android:drawable/ic_zen_mode_icon_tv</item>
|
||||
@@ -1565,10 +1565,10 @@
|
||||
<item>@*android:drawable/ic_zen_mode_icon_fork_and_knife</item>
|
||||
<item>@*android:drawable/ic_zen_mode_icon_shopping_cart</item>
|
||||
<item>@*android:drawable/ic_zen_mode_icon_child</item>
|
||||
<item>@*android:drawable/ic_zen_mode_icon_rabbit</item>
|
||||
<item>@*android:drawable/ic_zen_mode_icon_animal_paw</item>
|
||||
<!-- Generic / abstract -->
|
||||
<item>@*android:drawable/ic_zen_mode_type_managed</item> <!-- Account -->
|
||||
<item>@*android:drawable/ic_zen_mode_type_unknown</item> <!-- Star badge -->
|
||||
<item>@*android:drawable/ic_zen_mode_type_managed</item> <!-- Two people / Supervisor -->
|
||||
<item>@*android:drawable/ic_zen_mode_type_other</item> <!-- Star -->
|
||||
<item>@*android:drawable/ic_zen_mode_icon_heart</item>
|
||||
<item>@*android:drawable/ic_zen_mode_icon_house</item>
|
||||
@@ -1616,10 +1616,10 @@
|
||||
<item>Fork and knife</item>
|
||||
<item>Shopping cart</item>
|
||||
<item>Child</item>
|
||||
<item>Rabbit</item>
|
||||
<item>Animal paw</item>
|
||||
<!-- Generic / abstract -->
|
||||
<item>Supervisor</item>
|
||||
<item>Star badge</item>
|
||||
<item>Two people</item>
|
||||
<item>Star</item>
|
||||
<item>Heart</item>
|
||||
<item>House</item>
|
||||
|
||||
@@ -9535,6 +9535,23 @@
|
||||
<!-- Priority Modes: Blurb for modes that are not of a specific type (OTHER, UNKNOWN). [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_blurb_generic">Minimize interruptions by only allowing important people and apps to reach you</string>
|
||||
|
||||
<!-- Priority Modes: Inspirational text for a mode that activates during a fixed time schedule (e.g. 9:00-17:00). [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_schedule_time">Set a mode that follows a regular schedule</string>
|
||||
<!-- Priority Modes: Inspirational text for a mode that activates during calendar events. [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_schedule_calendar">Keep your device in sync with your day’s events</string>
|
||||
<!-- Priority Modes: Inspirational text for a mode of type BEDTIME. [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_bedtime">Wake up feeling like 100%</string>
|
||||
<!-- Priority Modes: Inspirational text for a mode of type DRIVING. [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_driving">Put safety first while on the road</string>
|
||||
<!-- Priority Modes: Inspirational text for a mode of type IMMERSIVE. [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_immersive">Gain focus to get in the zone</string>
|
||||
<!-- Priority Modes: Inspirational text for a mode of type THEATER. [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_theater">For moments when courtesy counts</string>
|
||||
<!-- Priority Modes: Inspirational text for a mode of type MANAGED. [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_managed">Guided usage to help you stay in good hands</string>
|
||||
<!-- Priority Modes: Inspirational text for modes that are not of a specific type (OTHER, UNKNOWN). [CHAR LIMIT=NONE] -->
|
||||
<string name="zen_mode_inspiration_generic">Take control of your attention</string>
|
||||
|
||||
<!-- Content description for help icon button [CHAR LIMIT=20] -->
|
||||
<string name="warning_button_text">Warning</string>
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"com.android.settings.fuelgauge.batteryusage.BatteryUsageBreakdownController"
|
||||
settings:isPreferenceVisible="false">
|
||||
|
||||
<com.android.settings.fuelgauge.batteryusage.SpinnerPreference
|
||||
<com.android.settingslib.widget.SettingsSpinnerPreference
|
||||
android:key="battery_usage_spinner"
|
||||
settings:isPreferenceVisible="false" />
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ package com.android.settings;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.ContentObserver;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
import android.util.FeatureFlagUtils;
|
||||
@@ -74,9 +76,6 @@ public class SettingsApplication extends Application {
|
||||
|
||||
// Set Spa environment.
|
||||
setSpaEnvironment();
|
||||
if (Flags.fingerprintV2Enrollment()) {
|
||||
mBiometricsEnvironment = new BiometricsEnvironment(this);
|
||||
}
|
||||
|
||||
if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
|
||||
&& FeatureFlagUtils.isEnabled(this,
|
||||
@@ -120,7 +119,20 @@ public class SettingsApplication extends Application {
|
||||
|
||||
@Nullable
|
||||
public BiometricsEnvironment getBiometricEnvironment() {
|
||||
return mBiometricsEnvironment;
|
||||
if (Flags.fingerprintV2Enrollment()) {
|
||||
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
||||
final FingerprintManager fpm = getSystemService(FingerprintManager.class);
|
||||
if (mBiometricsEnvironment == null) {
|
||||
mBiometricsEnvironment = new BiometricsEnvironment(this, fpm);
|
||||
}
|
||||
return mBiometricsEnvironment;
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.accessibility;
|
||||
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.provider.Settings;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
/**
|
||||
* Utility class for retrieving accessibility daltonizer related values in secure settings.
|
||||
*/
|
||||
public class DaltonizerPreferenceUtil {
|
||||
|
||||
/**
|
||||
* Return the daltonizer display mode stored in
|
||||
* {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER}.
|
||||
* By default it returns {@link DALTONIZER_CORRECT_DEUTERANOMALY}.
|
||||
*/
|
||||
public static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver) {
|
||||
final String daltonizerStringValue = Settings.Secure.getString(
|
||||
resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER);
|
||||
if (daltonizerStringValue == null) {
|
||||
return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
|
||||
}
|
||||
final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue);
|
||||
return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY
|
||||
: daltonizerIntValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the daltonizer enabled value in
|
||||
* {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED}.
|
||||
* By default it returns false.
|
||||
*/
|
||||
public static boolean isSecureAccessibilityDaltonizerEnabled(ContentResolver resolver) {
|
||||
return Settings.Secure.getInt(
|
||||
resolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
||||
OFF) == ON;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
@@ -36,8 +35,6 @@ import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.widget.SelectorWithWidgetPreference;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -70,17 +67,6 @@ public class DaltonizerRadioButtonPreferenceController extends BasePreferenceCon
|
||||
};
|
||||
}
|
||||
|
||||
protected static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver) {
|
||||
final String daltonizerStringValue = Settings.Secure.getString(
|
||||
resolver, DALTONIZER_TYPE_SETTINGS_KEY);
|
||||
if (daltonizerStringValue == null) {
|
||||
return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
|
||||
}
|
||||
final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue);
|
||||
return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY
|
||||
: daltonizerIntValue;
|
||||
}
|
||||
|
||||
private Map<String, Integer> getDaltonizerValueToKeyMap() {
|
||||
if (mAccessibilityDaltonizerKeyToValueMap.isEmpty()) {
|
||||
|
||||
@@ -123,7 +109,8 @@ public class DaltonizerRadioButtonPreferenceController extends BasePreferenceCon
|
||||
}
|
||||
|
||||
private int getAccessibilityDaltonizerValue() {
|
||||
final int daltonizerValue = getSecureAccessibilityDaltonizerValue(mContentResolver);
|
||||
final int daltonizerValue =
|
||||
DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue(mContentResolver);
|
||||
return daltonizerValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.android.settings.accessibility;
|
||||
|
||||
import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled;
|
||||
import static com.android.settings.accessibility.DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
@@ -158,14 +161,11 @@ public class DaltonizerSaturationSeekbarPreferenceController
|
||||
}
|
||||
|
||||
private boolean shouldSeekBarEnabled() {
|
||||
int enabled = Settings.Secure.getInt(
|
||||
mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0);
|
||||
int mode = Settings.Secure.getInt(
|
||||
mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, -1);
|
||||
boolean enabled = isSecureAccessibilityDaltonizerEnabled(mContentResolver);
|
||||
int mode = getSecureAccessibilityDaltonizerValue(mContentResolver);
|
||||
|
||||
// enabled == 0 is disabled and also default.
|
||||
// mode == 0 is gray scale where saturation level isn't applicable.
|
||||
// mode == -1 is disabled and also default.
|
||||
return enabled != 0 && mode != -1 && mode != 0;
|
||||
return enabled && mode != -1 && mode != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
|
||||
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
|
||||
import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
@@ -145,7 +146,8 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF
|
||||
|
||||
@Override
|
||||
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
|
||||
final boolean isEnabled = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
|
||||
final boolean isEnabled =
|
||||
isSecureAccessibilityDaltonizerEnabled(getContentResolver());
|
||||
if (enabled == isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.ServiceManager.ServiceNotFoundException
|
||||
import android.view.MotionEvent
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
@@ -29,33 +26,47 @@ import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.UserRepoImpl
|
||||
import com.android.settings.biometrics.fingerprint2.debug.data.repository.UdfpsEnrollDebugRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.debug.domain.interactor.DebugTouchEventInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.SensorInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
|
||||
import java.util.concurrent.Executors
|
||||
import kotlinx.coroutines.MainScope
|
||||
@@ -70,43 +81,53 @@ import kotlinx.coroutines.flow.flowOf
|
||||
* This code is instantiated within the [SettingsApplication], all repos should be private &
|
||||
* immutable and all interactors should public and immutable
|
||||
*/
|
||||
class BiometricsEnvironment(context: SettingsApplication) : ViewModelStoreOwner {
|
||||
|
||||
class BiometricsEnvironment(
|
||||
val context: SettingsApplication,
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
) : ViewModelStoreOwner {
|
||||
private val executorService = Executors.newSingleThreadExecutor()
|
||||
private val backgroundDispatcher = executorService.asCoroutineDispatcher()
|
||||
private val applicationScope = MainScope()
|
||||
private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context))
|
||||
private val fingerprintManager = try {
|
||||
if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
||||
context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager?
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (exception: ServiceNotFoundException){
|
||||
null
|
||||
}
|
||||
|
||||
private val userRepo = UserRepoImpl(context.userId)
|
||||
private val fingerprintSettingsRepository =
|
||||
FingerprintSettingsRepositoryImpl(
|
||||
context.resources.getInteger(
|
||||
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
|
||||
)
|
||||
)
|
||||
private val fingerprintEnrollmentRepository =
|
||||
FingerprintEnrollmentRepositoryImpl(fingerprintManager, userRepo, fingerprintSettingsRepository,
|
||||
backgroundDispatcher, applicationScope)
|
||||
private val fingerprintSensorRepository: FingerprintSensorRepository =
|
||||
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope)
|
||||
private val debuggingRepository: DebuggingRepository = DebuggingRepositoryImpl()
|
||||
private val udfpsDebugRepo = UdfpsEnrollDebugRepositoryImpl()
|
||||
|
||||
/** For now, interactors are public to those with access to the [BiometricsEnvironment] class */
|
||||
val fingerprintEnrollInteractor: FingerprintEnrollInteractor by lazy {
|
||||
FingerprintEnrollInteractorImpl(context, fingerprintManager, Settings)
|
||||
}
|
||||
fun createSensorPropertiesInteractor(): SensorInteractor =
|
||||
SensorInteractorImpl(fingerprintSensorRepository)
|
||||
|
||||
/** [FingerprintManagerInteractor] to be used to construct view models */
|
||||
val fingerprintManagerInteractor: FingerprintManagerInteractor by lazy {
|
||||
FingerprintManagerInteractorImpl(
|
||||
context,
|
||||
backgroundDispatcher,
|
||||
fingerprintManager,
|
||||
fingerprintSensorRepository,
|
||||
gateKeeperPasswordProvider,
|
||||
fingerprintEnrollInteractor,
|
||||
)
|
||||
}
|
||||
fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor =
|
||||
CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
|
||||
|
||||
fun createGenerateChallengeInteractor(): GenerateChallengeInteractor =
|
||||
GenerateChallengeInteractorImpl(fingerprintManager, context.userId, gateKeeperPasswordProvider)
|
||||
|
||||
fun createFingerprintEnrollInteractor(): EnrollFingerprintInteractor =
|
||||
EnrollFingerprintInteractorImpl(context.userId, fingerprintManager, Settings)
|
||||
|
||||
fun createFingerprintsEnrolledInteractor(): EnrolledFingerprintsInteractorImpl =
|
||||
EnrolledFingerprintsInteractorImpl(fingerprintManager, context.userId)
|
||||
|
||||
fun createAuthenticateInteractor(): AuthenitcateInteractor =
|
||||
AuthenticateInteractorImpl(fingerprintManager, context.userId)
|
||||
|
||||
fun createRemoveFingerprintInteractor(): RemoveFingerprintInteractor =
|
||||
RemoveFingerprintsInteractorImpl(fingerprintManager, context.userId)
|
||||
|
||||
fun createRenameFingerprintInteractor(): RenameFingerprintInteractor =
|
||||
RenameFingerprintsInteractorImpl(fingerprintManager, context.userId, backgroundDispatcher)
|
||||
|
||||
val accessibilityInteractor: AccessibilityInteractor by lazy {
|
||||
AccessibilityInteractorImpl(
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.data.repository
|
||||
|
||||
import android.hardware.biometrics.BiometricStateListener
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** Repository that contains information about fingerprint enrollments. */
|
||||
interface FingerprintEnrollmentRepository {
|
||||
/** The current enrollments of the user */
|
||||
val currentEnrollments: Flow<List<FingerprintData>?>
|
||||
|
||||
/** Indicates if a user can enroll another fingerprint */
|
||||
val canEnrollUser: Flow<Boolean>
|
||||
|
||||
fun maxFingerprintsEnrollable(): Int
|
||||
}
|
||||
|
||||
class FingerprintEnrollmentRepositoryImpl(
|
||||
fingerprintManager: FingerprintManager,
|
||||
userRepo: UserRepo,
|
||||
private val settingsRepository: FingerprintSettingsRepository,
|
||||
backgroundDispatcher: CoroutineDispatcher,
|
||||
applicationScope: CoroutineScope,
|
||||
) : FingerprintEnrollmentRepository {
|
||||
|
||||
private val enrollmentChangedFlow: Flow<Int?> =
|
||||
callbackFlow {
|
||||
val callback =
|
||||
object : BiometricStateListener() {
|
||||
override fun onEnrollmentsChanged(userId: Int, sensorId: Int, hasEnrollments: Boolean) {
|
||||
trySend(userId)
|
||||
}
|
||||
}
|
||||
withContext(backgroundDispatcher) {
|
||||
fingerprintManager.registerBiometricStateListener(callback)
|
||||
}
|
||||
awaitClose {
|
||||
// no way to unregister
|
||||
}
|
||||
}
|
||||
.stateIn(applicationScope, started = SharingStarted.Eagerly, initialValue = null)
|
||||
|
||||
override val currentEnrollments: Flow<List<FingerprintData>> =
|
||||
userRepo.currentUser
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { currentUser ->
|
||||
enrollmentChangedFlow.map { enrollmentChanged ->
|
||||
if (enrollmentChanged == null || enrollmentChanged == currentUser) {
|
||||
fingerprintManager
|
||||
.getEnrolledFingerprints(currentUser)
|
||||
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
|
||||
?.toList()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
.filterNotNull()
|
||||
.flowOn(backgroundDispatcher)
|
||||
|
||||
override val canEnrollUser: Flow<Boolean> =
|
||||
currentEnrollments.map {
|
||||
it?.size?.let { it < settingsRepository.maxEnrollableFingerprints() } ?: false
|
||||
}
|
||||
|
||||
override fun maxFingerprintsEnrollable(): Int {
|
||||
return settingsRepository.maxEnrollableFingerprints()
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -43,10 +45,13 @@ import kotlinx.coroutines.withContext
|
||||
interface FingerprintSensorRepository {
|
||||
/** Get the [FingerprintSensor] */
|
||||
val fingerprintSensor: Flow<FingerprintSensor>
|
||||
|
||||
/** Indicates if this device supports the side fingerprint sensor */
|
||||
val hasSideFps: Flow<Boolean>
|
||||
}
|
||||
|
||||
class FingerprintSensorRepositoryImpl(
|
||||
fingerprintManager: FingerprintManager?,
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
backgroundDispatcher: CoroutineDispatcher,
|
||||
activityScope: CoroutineScope,
|
||||
) : FingerprintSensorRepository {
|
||||
@@ -66,7 +71,7 @@ class FingerprintSensorRepositoryImpl(
|
||||
}
|
||||
}
|
||||
withContext(backgroundDispatcher) {
|
||||
fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
|
||||
fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
|
||||
}
|
||||
awaitClose {}
|
||||
}
|
||||
@@ -75,6 +80,9 @@ class FingerprintSensorRepositoryImpl(
|
||||
override val fingerprintSensor: Flow<FingerprintSensor> =
|
||||
fingerprintPropsInternal.transform { emit(it.toFingerprintSensor()) }
|
||||
|
||||
override val hasSideFps: Flow<Boolean> =
|
||||
fingerprintSensor.flatMapLatest { flow { emit(fingerprintManager.isPowerbuttonFps()) } }
|
||||
|
||||
companion object {
|
||||
|
||||
private val DEFAULT_PROPS =
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.data.repository
|
||||
|
||||
/**
|
||||
* Repository for storing metadata about fingerprint enrollments.
|
||||
*/
|
||||
interface FingerprintSettingsRepository {
|
||||
/**
|
||||
* Indicates the maximum number of fingerprints enrollable
|
||||
*/
|
||||
fun maxEnrollableFingerprints(): Int
|
||||
}
|
||||
|
||||
class FingerprintSettingsRepositoryImpl(private val maxFingerprintsEnrollable: Int) :
|
||||
FingerprintSettingsRepository {
|
||||
override fun maxEnrollableFingerprints() = maxFingerprintsEnrollable
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.data.repository
|
||||
|
||||
import android.graphics.Point
|
||||
import android.view.MotionEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.data.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* A repository responsible for indicating the current user.
|
||||
*/
|
||||
interface UserRepo {
|
||||
/**
|
||||
* This flow indicates the current user.
|
||||
*/
|
||||
val currentUser: Flow<Int>
|
||||
}
|
||||
|
||||
class UserRepoImpl(val currUser: Int): UserRepo {
|
||||
override val currentUser: Flow<Int> = flowOf(currUser)
|
||||
}
|
||||
@@ -97,6 +97,8 @@ class UdfpsEnrollDebugRepositoryImpl :
|
||||
}
|
||||
|
||||
override val fingerprintSensor: Flow<FingerprintSensor> = flowOf(sensorProps)
|
||||
override val hasSideFps: Flow<Boolean>
|
||||
get() = flowOf(false)
|
||||
|
||||
private fun pointToLeftOfSensor(sensorLocation: Rect): MotionEvent =
|
||||
MotionEvent.obtain(
|
||||
|
||||
@@ -26,4 +26,4 @@ class DebugTouchEventInteractorImpl(
|
||||
) : TouchEventInteractor {
|
||||
override val touchEvent: Flow<MotionEvent> =
|
||||
udfpsSimulatedTouchEventsRepository.touchExplorationDebug
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
class AuthenticateInteractorImpl(
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
private val userId: Int,
|
||||
) : AuthenitcateInteractor {
|
||||
|
||||
override suspend fun authenticate(): FingerprintAuthAttemptModel =
|
||||
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
|
||||
val authenticationCallback =
|
||||
object : FingerprintManager.AuthenticationCallback() {
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
if (c.isCompleted) {
|
||||
Log.d(TAG, "framework sent down onAuthError after finish")
|
||||
return
|
||||
}
|
||||
c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
if (c.isCompleted) {
|
||||
Log.d(TAG, "framework sent down onAuthError after finish")
|
||||
return
|
||||
}
|
||||
c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
|
||||
}
|
||||
}
|
||||
|
||||
val cancellationSignal = CancellationSignal()
|
||||
c.invokeOnCancellation { cancellationSignal.cancel() }
|
||||
fingerprintManager.authenticate(
|
||||
null,
|
||||
cancellationSignal,
|
||||
authenticationCallback,
|
||||
null,
|
||||
userId,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AuthenticateInteractor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class CanEnrollFingerprintsInteractorImpl(
|
||||
val fingerprintEnrollmentRepository: FingerprintEnrollmentRepository
|
||||
) : CanEnrollFingerprintsInteractor {
|
||||
override val canEnrollFingerprints: Flow<Boolean> = fingerprintEnrollmentRepository.canEnrollUser
|
||||
/** Indicates the maximum fingerprints enrollable for a given user */
|
||||
override fun maxFingerprintsEnrollable(): Int {
|
||||
return fingerprintEnrollmentRepository.maxFingerprintsEnrollable()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class EnrollFingerprintInteractorImpl(
|
||||
private val userId: Int,
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
private val fingerprintFlow: FingerprintFlow,
|
||||
) : EnrollFingerprintInteractor {
|
||||
private val enrollRequestOutstanding = MutableStateFlow(false)
|
||||
|
||||
override suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
fingerprintEnrollOptions: FingerprintEnrollOptions,
|
||||
): Flow<FingerEnrollState> = callbackFlow {
|
||||
// TODO (b/308456120) Improve this logic
|
||||
if (enrollRequestOutstanding.value) {
|
||||
Log.d(TAG, "Outstanding enroll request, waiting 150ms")
|
||||
delay(150)
|
||||
if (enrollRequestOutstanding.value) {
|
||||
Log.e(TAG, "Request still present, continuing")
|
||||
}
|
||||
}
|
||||
|
||||
enrollRequestOutstanding.update { true }
|
||||
|
||||
var streamEnded = false
|
||||
var totalSteps: Int? = null
|
||||
val enrollmentCallback =
|
||||
object : FingerprintManager.EnrollmentCallback() {
|
||||
override fun onEnrollmentProgress(remaining: Int) {
|
||||
// This is sort of an implementation detail, but unfortunately the API isn't
|
||||
// very expressive. If anything we should look at changing the FingerprintManager API.
|
||||
if (totalSteps == null) {
|
||||
totalSteps = remaining + 1
|
||||
}
|
||||
|
||||
trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error ->
|
||||
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
streamEnded = true
|
||||
enrollRequestOutstanding.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
|
||||
trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error
|
||||
->
|
||||
Log.d(TAG, "onEnrollmentHelp failed to send, due to $error")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
|
||||
trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error ->
|
||||
Log.d(TAG, "onEnrollmentError failed to send, due to $error")
|
||||
}
|
||||
Log.d(TAG, "onEnrollmentError($errMsgId)")
|
||||
streamEnded = true
|
||||
enrollRequestOutstanding.update { false }
|
||||
}
|
||||
|
||||
override fun onUdfpsPointerDown(sensorId: Int) {
|
||||
trySend(FingerEnrollState.PointerDown(sensorId)).onFailure { error ->
|
||||
Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUdfpsPointerUp(sensorId: Int) {
|
||||
trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error ->
|
||||
Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUdfpsOverlayShown() {
|
||||
trySend(FingerEnrollState.OverlayShown).onFailure { error ->
|
||||
Log.d(TAG, "OverlayShown failed to send, due to $error")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAcquired(isAcquiredGood: Boolean) {
|
||||
trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error ->
|
||||
Log.d(TAG, "Acquired failed to send, due to $error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cancellationSignal = CancellationSignal()
|
||||
|
||||
fingerprintManager.enroll(
|
||||
hardwareAuthToken,
|
||||
cancellationSignal,
|
||||
userId,
|
||||
enrollmentCallback,
|
||||
enrollReason.toOriginalReason(),
|
||||
fingerprintEnrollOptions,
|
||||
)
|
||||
awaitClose {
|
||||
// If the stream has not been ended, and the user has stopped collecting the flow
|
||||
// before it was over, send cancel.
|
||||
if (!streamEnded) {
|
||||
Log.e(TAG, "Cancel is sent from settings for enroll()")
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FingerprintEnrollStateRepository"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class EnrolledFingerprintsInteractorImpl(
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
userId: Int,
|
||||
) : EnrolledFingerprintsInteractor {
|
||||
override val enrolledFingerprints: Flow<List<FingerprintData>?> = flow {
|
||||
emit(
|
||||
fingerprintManager
|
||||
.getEnrolledFingerprints(userId)
|
||||
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
|
||||
?.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.CancellationSignal
|
||||
@@ -49,7 +48,7 @@ interface FingerprintEnrollInteractor {
|
||||
}
|
||||
|
||||
class FingerprintEnrollInteractorImpl(
|
||||
private val applicationContext: Context,
|
||||
private val userId: Int,
|
||||
private val fingerprintManager: FingerprintManager?,
|
||||
private val fingerprintFlow: FingerprintFlow,
|
||||
) : FingerprintEnrollInteractor {
|
||||
@@ -138,7 +137,7 @@ class FingerprintEnrollInteractorImpl(
|
||||
fingerprintManager?.enroll(
|
||||
hardwareAuthToken,
|
||||
cancellationSignal,
|
||||
applicationContext.userId,
|
||||
userId,
|
||||
enrollmentCallback,
|
||||
enrollReason.toOriginalReason(),
|
||||
fingerprintEnrollOptions,
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* 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.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
|
||||
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private const val TAG = "FingerprintManagerInteractor"
|
||||
|
||||
class FingerprintManagerInteractorImpl(
|
||||
applicationContext: Context,
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val fingerprintManager: FingerprintManager?,
|
||||
fingerprintSensorRepository: FingerprintSensorRepository,
|
||||
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
|
||||
private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor,
|
||||
) : FingerprintManagerInteractor {
|
||||
|
||||
private val maxFingerprints =
|
||||
applicationContext.resources.getInteger(
|
||||
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
|
||||
)
|
||||
private val applicationContext = applicationContext.applicationContext
|
||||
|
||||
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
|
||||
suspendCoroutine {
|
||||
val callback = GenerateChallengeCallback { _, userId, challenge ->
|
||||
val intent = Intent()
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
|
||||
val challengeToken =
|
||||
gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId)
|
||||
|
||||
gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false)
|
||||
val p = Pair(challenge, challengeToken)
|
||||
it.resume(p)
|
||||
}
|
||||
fingerprintManager?.generateChallenge(applicationContext.userId, callback)
|
||||
}
|
||||
|
||||
override val enrolledFingerprints: Flow<List<FingerprintData>?> = flow {
|
||||
emit(
|
||||
fingerprintManager?.getEnrolledFingerprints(applicationContext.userId)
|
||||
?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }?.toList()
|
||||
)
|
||||
}
|
||||
|
||||
override val canEnrollFingerprints: Flow<Boolean> = flow {
|
||||
emit(
|
||||
fingerprintManager?.getEnrolledFingerprints(applicationContext.userId)?.size ?: maxFingerprints < maxFingerprints
|
||||
)
|
||||
}
|
||||
|
||||
override val sensorPropertiesInternal = fingerprintSensorRepository.fingerprintSensor
|
||||
|
||||
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
|
||||
|
||||
override suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
fingerprintEnrollOptions: FingerprintEnrollOptions,
|
||||
): Flow<FingerEnrollState> =
|
||||
fingerprintEnrollStateRepository.enroll(
|
||||
hardwareAuthToken,
|
||||
enrollReason,
|
||||
fingerprintEnrollOptions,
|
||||
)
|
||||
|
||||
override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
|
||||
val callback =
|
||||
object : RemovalCallback() {
|
||||
override fun onRemovalError(
|
||||
fp: android.hardware.fingerprint.Fingerprint,
|
||||
errMsgId: Int,
|
||||
errString: CharSequence,
|
||||
) {
|
||||
it.resume(false)
|
||||
}
|
||||
|
||||
override fun onRemovalSucceeded(
|
||||
fp: android.hardware.fingerprint.Fingerprint?,
|
||||
remaining: Int,
|
||||
) {
|
||||
it.resume(true)
|
||||
}
|
||||
}
|
||||
fingerprintManager?.remove(
|
||||
android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
|
||||
applicationContext.userId,
|
||||
callback,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
|
||||
withContext(backgroundDispatcher) {
|
||||
fingerprintManager?.rename(fp.fingerId, applicationContext.userId, newName)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun hasSideFps(): Boolean? = suspendCancellableCoroutine {
|
||||
it.resume(fingerprintManager?.isPowerbuttonFps)
|
||||
}
|
||||
|
||||
override suspend fun authenticate(): FingerprintAuthAttemptModel =
|
||||
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
|
||||
val authenticationCallback =
|
||||
object : FingerprintManager.AuthenticationCallback() {
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
if (c.isCompleted) {
|
||||
Log.d(TAG, "framework sent down onAuthError after finish")
|
||||
return
|
||||
}
|
||||
c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
if (c.isCompleted) {
|
||||
Log.d(TAG, "framework sent down onAuthError after finish")
|
||||
return
|
||||
}
|
||||
c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
|
||||
}
|
||||
}
|
||||
|
||||
val cancellationSignal = CancellationSignal()
|
||||
c.invokeOnCancellation { cancellationSignal.cancel() }
|
||||
fingerprintManager?.authenticate(
|
||||
null,
|
||||
cancellationSignal,
|
||||
authenticationCallback,
|
||||
null,
|
||||
applicationContext.userId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,7 @@ import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintS
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Interactor that propagates the type of [FingerprintSensor] this device supports.
|
||||
*/
|
||||
/** Interactor that propagates the type of [FingerprintSensor] this device supports. */
|
||||
interface FingerprintSensorInteractor {
|
||||
/** Get the [FingerprintSensor] */
|
||||
val fingerprintSensor: Flow<FingerprintSensor>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Intent
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class GenerateChallengeInteractorImpl(
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
private val userId: Int,
|
||||
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
|
||||
) : GenerateChallengeInteractor {
|
||||
|
||||
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
|
||||
suspendCoroutine {
|
||||
val callback =
|
||||
FingerprintManager.GenerateChallengeCallback { _, userId, challenge ->
|
||||
val intent = Intent()
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
|
||||
val challengeToken =
|
||||
gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId)
|
||||
|
||||
gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false)
|
||||
val p = Pair(challenge, challengeToken)
|
||||
it.resume(p)
|
||||
}
|
||||
fingerprintManager.generateChallenge(userId, callback)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class RemoveFingerprintsInteractorImpl(
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
private val userId: Int,
|
||||
) : RemoveFingerprintInteractor {
|
||||
|
||||
override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
|
||||
val callback =
|
||||
object : RemovalCallback() {
|
||||
override fun onRemovalError(
|
||||
fp: android.hardware.fingerprint.Fingerprint,
|
||||
errMsgId: Int,
|
||||
errString: CharSequence,
|
||||
) {
|
||||
it.resume(false)
|
||||
}
|
||||
|
||||
override fun onRemovalSucceeded(
|
||||
fp: android.hardware.fingerprint.Fingerprint?,
|
||||
remaining: Int,
|
||||
) {
|
||||
it.resume(true)
|
||||
}
|
||||
}
|
||||
fingerprintManager.remove(
|
||||
android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
|
||||
userId,
|
||||
callback,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class RenameFingerprintsInteractorImpl(
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
private val userId: Int,
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
) : RenameFingerprintInteractor {
|
||||
|
||||
override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
|
||||
withContext(backgroundDispatcher) { fingerprintManager.rename(fp.fingerId, userId, newName) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class SensorInteractorImpl(private val repo: FingerprintSensorRepository) :
|
||||
SensorInteractor {
|
||||
override val sensorPropertiesInternal = repo.fingerprintSensor
|
||||
override val hasSideFps: Flow<Boolean> = repo.hasSideFps
|
||||
}
|
||||
@@ -24,4 +24,3 @@ interface TouchEventInteractor {
|
||||
/** A flow simulating user touches. */
|
||||
val touchEvent: Flow<MotionEvent>
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest
|
||||
package="com.android.settings.biometrics.fingerprint2.lib">
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
|
||||
/** Interactor responsible for coordinating authentication. */
|
||||
interface AuthenitcateInteractor {
|
||||
/** Runs the authenticate flow */
|
||||
suspend fun authenticate(): FingerprintAuthAttemptModel
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Returns whether or not a user can enroll a fingerprint */
|
||||
interface CanEnrollFingerprintsInteractor {
|
||||
/** Returns true if a user can enroll a fingerprint false otherwise. */
|
||||
val canEnrollFingerprints: Flow<Boolean>
|
||||
/** Indicates the maximum fingerprints enrollable for a given user */
|
||||
fun maxFingerprintsEnrollable(): Int
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Interactor that enrolls a fingerprint */
|
||||
interface EnrollFingerprintInteractor {
|
||||
/**
|
||||
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
|
||||
* enrollment. If successful data in the [fingerprintEnrollState] should be populated.
|
||||
*/
|
||||
suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
fingerprintEnrollOptions: FingerprintEnrollOptions,
|
||||
): Flow<FingerEnrollState>
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Interface to obtain the enrolled fingerprints */
|
||||
interface EnrolledFingerprintsInteractor {
|
||||
/** Returns the list of current fingerprints. */
|
||||
val enrolledFingerprints: Flow<List<FingerprintData>?>
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Interface to obtain the necessary data for FingerprintEnrollment/Settings
|
||||
*
|
||||
* Note that this interface should not have dependencies on heavyweight libraries such as the
|
||||
* framework, hidl/aidl, etc. This makes it much easier to test and create fakes for.
|
||||
*/
|
||||
interface FingerprintManagerInteractor {
|
||||
/** Returns the list of current fingerprints. */
|
||||
val enrolledFingerprints: Flow<List<FingerprintData>?>
|
||||
|
||||
/** Returns the max enrollable fingerprints, note during SUW this might be 1 */
|
||||
val maxEnrollableFingerprints: Flow<Int>
|
||||
|
||||
/** Returns true if a user can enroll a fingerprint false otherwise. */
|
||||
val canEnrollFingerprints: Flow<Boolean>
|
||||
|
||||
/** Retrieves the sensor properties of a device */
|
||||
val sensorPropertiesInternal: Flow<FingerprintSensor?>
|
||||
|
||||
/** Runs the authenticate flow */
|
||||
suspend fun authenticate(): FingerprintAuthAttemptModel
|
||||
|
||||
/**
|
||||
* Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
|
||||
* challenge and challenge token. This info can be used for secure operations such as enrollment
|
||||
*
|
||||
* @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
|
||||
* @return A [Pair] of the challenge and challenge token
|
||||
*/
|
||||
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
|
||||
|
||||
/**
|
||||
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
|
||||
* enrollment. If successful data in the [fingerprintEnrollState] should be populated.
|
||||
*/
|
||||
suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
fingerprintEnrollOptions: FingerprintEnrollOptions,
|
||||
): Flow<FingerEnrollState>
|
||||
|
||||
/**
|
||||
* Removes the given fingerprint, returning true if it was successfully removed and false
|
||||
* otherwise
|
||||
*/
|
||||
suspend fun removeFingerprint(fp: FingerprintData): Boolean
|
||||
|
||||
/** Renames the given fingerprint if one exists */
|
||||
suspend fun renameFingerprint(fp: FingerprintData, newName: String)
|
||||
|
||||
/** Indicates if the device has side fingerprint */
|
||||
suspend fun hasSideFps(): Boolean?
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
/** This interactor is responsible for generating a challenge. */
|
||||
interface GenerateChallengeInteractor {
|
||||
/**
|
||||
* Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
|
||||
* challenge and challenge token. This info can be used for secure operations such as enrollment
|
||||
*
|
||||
* @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
|
||||
* @return A [Pair] of the challenge and challenge token
|
||||
*/
|
||||
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
|
||||
/** Interactor in charge of removing a fingerprint */
|
||||
interface RemoveFingerprintInteractor {
|
||||
/**
|
||||
* Removes the given fingerprint, returning true if it was successfully removed and false
|
||||
* otherwise
|
||||
*/
|
||||
suspend fun removeFingerprint(fp: FingerprintData): Boolean
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
|
||||
/** Interactor that can rename a fingerprint. */
|
||||
interface RenameFingerprintInteractor {
|
||||
/** Renames the given fingerprint if one exists */
|
||||
suspend fun renameFingerprint(fp: FingerprintData, newName: String)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.fingerprint2.lib.domain.interactor
|
||||
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Interactor that has various information about a fingerprint sensor */
|
||||
interface SensorInteractor {
|
||||
/** Retrieves the sensor properties of the device */
|
||||
val sensorPropertiesInternal: Flow<FingerprintSensor?>
|
||||
/** Indicates if the device supports side fps */
|
||||
val hasSideFps: Flow<Boolean>
|
||||
}
|
||||
@@ -96,8 +96,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* View models below this line are not used by this class but must be initialized
|
||||
* in the activity view model store to be used by other view models.
|
||||
* View models below this line are not used by this class but must be initialized in the activity
|
||||
* view model store to be used by other view models.
|
||||
*/
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel by viewModels {
|
||||
FingerprintEnrollViewModel.Factory
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
|
||||
@@ -46,10 +46,10 @@ import kotlinx.coroutines.flow.update
|
||||
|
||||
/** View Model used by the rear fingerprint enrollment fragment. */
|
||||
class RFPSViewModel(
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
orientationInteractor: OrientationInteractor,
|
||||
private val fingerprintManager: FingerprintManagerInteractor,
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
orientationInteractor: OrientationInteractor,
|
||||
private val sensorInteractor: SensorInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _textViewIsVisible = MutableStateFlow(false)
|
||||
@@ -62,7 +62,7 @@ class RFPSViewModel(
|
||||
val shouldAnimateIcon = _shouldAnimateIcon
|
||||
|
||||
private var enrollFlow: Flow<FingerEnrollState?> =
|
||||
fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
|
||||
sensorInteractor.sensorPropertiesInternal.filterNotNull().combine(
|
||||
fingerprintEnrollViewModel.enrollFlow
|
||||
) { props, enroll ->
|
||||
if (props.sensorType == FingerprintSensorType.REAR) {
|
||||
@@ -181,7 +181,7 @@ class RFPSViewModel(
|
||||
provider[FingerprintEnrollEnrollingViewModel::class],
|
||||
provider[FingerprintNavigationViewModel::class],
|
||||
biometricEnvironment.orientationInteractor,
|
||||
biometricEnvironment.fingerprintManagerInteractor,
|
||||
biometricEnvironment.createSensorPropertiesInteractor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Orientatio
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
|
||||
@@ -76,17 +76,17 @@ class UdfpsViewModel(
|
||||
enrollStageInteractor: EnrollStageInteractor,
|
||||
orientationInteractor: OrientationInteractor,
|
||||
udfpsEnrollInteractor: UdfpsEnrollInteractor,
|
||||
fingerprintManager: FingerprintManagerInteractor,
|
||||
accessibilityInteractor: AccessibilityInteractor,
|
||||
sensorRepository: FingerprintSensorInteractor,
|
||||
touchEventInteractor: TouchEventInteractor,
|
||||
sensorInteractor: SensorInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
private val isSetupWizard = flowOf(false)
|
||||
private var shouldResetErollment = false
|
||||
|
||||
private var _enrollState: Flow<FingerEnrollState?> =
|
||||
fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
|
||||
sensorInteractor.sensorPropertiesInternal.filterNotNull().combine(
|
||||
fingerprintEnrollEnrollingViewModel.enrollFlow
|
||||
) { props, enroll ->
|
||||
if (props.sensorType.isUdfps()) {
|
||||
@@ -198,8 +198,7 @@ class UdfpsViewModel(
|
||||
.distinctUntilChanged()
|
||||
|
||||
private val _touchEvent: MutableStateFlow<MotionEvent?> = MutableStateFlow(null)
|
||||
val touchEvent =
|
||||
_touchEvent.asStateFlow().filterNotNull()
|
||||
val touchEvent = _touchEvent.asStateFlow().filterNotNull()
|
||||
|
||||
/** Determines the current [EnrollStageModel] enrollment is in */
|
||||
private val enrollStage: Flow<EnrollStageModel> =
|
||||
@@ -267,11 +266,7 @@ class UdfpsViewModel(
|
||||
backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
touchEventInteractor.touchEvent.collect {
|
||||
_touchEvent.update { it }
|
||||
}
|
||||
}
|
||||
viewModelScope.launch { touchEventInteractor.touchEvent.collect { _touchEvent.update { it } } }
|
||||
}
|
||||
|
||||
/** Indicates if we should show the lottie. */
|
||||
@@ -430,10 +425,10 @@ class UdfpsViewModel(
|
||||
biometricEnvironment.enrollStageInteractor,
|
||||
biometricEnvironment.orientationInteractor,
|
||||
biometricEnvironment.udfpsEnrollInteractor,
|
||||
biometricEnvironment.fingerprintManagerInteractor,
|
||||
biometricEnvironment.accessibilityInteractor,
|
||||
biometricEnvironment.sensorInteractor,
|
||||
biometricEnvironment.touchEventInteractor,
|
||||
biometricEnvironment.createSensorPropertiesInteractor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,27 +16,27 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Models the UI state for [FingerprintEnrollConfirmationV2Fragment] */
|
||||
class FingerprintEnrollConfirmationViewModel(
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
fingerprintInteractor: FingerprintManagerInteractor,
|
||||
private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
/**
|
||||
* Indicates if the add another button is possible. This should only be true when the user is able
|
||||
* to enroll more fingerprints.
|
||||
*/
|
||||
val isAddAnotherButtonVisible: Flow<Boolean> = fingerprintInteractor.canEnrollFingerprints
|
||||
val isAddAnotherButtonVisible: Flow<Boolean> =
|
||||
canEnrollFingerprintsInteractor.canEnrollFingerprints
|
||||
|
||||
/**
|
||||
* Indicates that the user has clicked the next button and is done with fingerprint enrollment.
|
||||
@@ -64,7 +64,7 @@ class FingerprintEnrollConfirmationViewModel(
|
||||
val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!)
|
||||
FingerprintEnrollConfirmationViewModel(
|
||||
provider[FingerprintNavigationViewModel::class],
|
||||
biometricEnvironment!!.fingerprintManagerInteractor,
|
||||
biometricEnvironment!!.createCanEnrollFingerprintsInteractor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
|
||||
@@ -44,20 +44,20 @@ import kotlinx.coroutines.launch
|
||||
|
||||
/** Models the UI state for fingerprint enroll education */
|
||||
class FingerprintEnrollFindSensorViewModel(
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
backgroundViewModel: BackgroundViewModel,
|
||||
fingerprintFlowViewModel: FingerprintFlowViewModel,
|
||||
accessibilityInteractor: AccessibilityInteractor,
|
||||
foldStateInteractor: FoldStateInteractor,
|
||||
orientationInteractor: OrientationInteractor,
|
||||
fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
backgroundViewModel: BackgroundViewModel,
|
||||
fingerprintFlowViewModel: FingerprintFlowViewModel,
|
||||
accessibilityInteractor: AccessibilityInteractor,
|
||||
foldStateInteractor: FoldStateInteractor,
|
||||
orientationInteractor: OrientationInteractor,
|
||||
sensorInteractor: SensorInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
/** Represents the stream of sensor type. */
|
||||
val sensorType: Flow<FingerprintSensorType> =
|
||||
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
|
||||
sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
|
||||
private val _isUdfps: Flow<Boolean> =
|
||||
sensorType.map {
|
||||
it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
|
||||
@@ -216,7 +216,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
biometricEnvironment.accessibilityInteractor,
|
||||
biometricEnvironment.foldStateInteractor,
|
||||
biometricEnvironment.orientationInteractor,
|
||||
biometricEnvironment.fingerprintManagerInteractor,
|
||||
biometricEnvironment.createSensorPropertiesInteractor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
@@ -30,13 +30,13 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** A view model for fingerprint enroll introduction. */
|
||||
class FingerprintEnrollIntroViewModel(
|
||||
val navigationViewModel: FingerprintNavigationViewModel,
|
||||
fingerprintFlowViewModel: FingerprintFlowViewModel,
|
||||
fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
val navigationViewModel: FingerprintNavigationViewModel,
|
||||
fingerprintFlowViewModel: FingerprintFlowViewModel,
|
||||
sensorInteractor: SensorInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
/** Represents a stream of [FingerprintSensor] */
|
||||
val sensor: Flow<FingerprintSensor?> = fingerprintManagerInteractor.sensorPropertiesInternal
|
||||
val sensor: Flow<FingerprintSensor?> = sensorInteractor.sensorPropertiesInternal
|
||||
|
||||
/** Represents a stream of [FingerprintFlow] */
|
||||
val fingerprintFlow: Flow<FingerprintFlow?> = fingerprintFlowViewModel.fingerprintFlow
|
||||
@@ -67,7 +67,7 @@ class FingerprintEnrollIntroViewModel(
|
||||
FingerprintEnrollIntroViewModel(
|
||||
provider[FingerprintNavigationViewModel::class],
|
||||
provider[FingerprintFlowViewModel::class],
|
||||
biometricEnvironment!!.fingerprintManagerInteractor,
|
||||
biometricEnvironment!!.createSensorPropertiesInteractor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
|
||||
@@ -42,9 +43,10 @@ import kotlinx.coroutines.flow.update
|
||||
|
||||
/** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
|
||||
class FingerprintEnrollViewModel(
|
||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
val navigationViewModel: FingerprintNavigationViewModel,
|
||||
gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
val navigationViewModel: FingerprintNavigationViewModel,
|
||||
private val sensorInteractor: SensorInteractor,
|
||||
private val fingerprintEnrollInteractor: EnrollFingerprintInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
/**
|
||||
@@ -67,7 +69,7 @@ class FingerprintEnrollViewModel(
|
||||
|
||||
/** Represents the stream of [FingerprintSensorType] */
|
||||
val sensorType: Flow<FingerprintSensorType?> =
|
||||
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
|
||||
sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
|
||||
|
||||
/**
|
||||
* A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
|
||||
@@ -90,7 +92,7 @@ class FingerprintEnrollViewModel(
|
||||
enrollReason != null &&
|
||||
enrollOptions != null
|
||||
) {
|
||||
fingerprintManagerInteractor
|
||||
fingerprintEnrollInteractor
|
||||
.enroll(hardwareAuthToken.token, enrollReason, enrollOptions)
|
||||
.collect { emit(it) }
|
||||
}
|
||||
@@ -137,9 +139,10 @@ class FingerprintEnrollViewModel(
|
||||
val biometricEnvironment = settingsApplication.biometricEnvironment
|
||||
val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!)
|
||||
FingerprintEnrollViewModel(
|
||||
biometricEnvironment!!.fingerprintManagerInteractor,
|
||||
provider[FingerprintGatekeeperViewModel::class],
|
||||
provider[FingerprintNavigationViewModel::class],
|
||||
biometricEnvironment!!.createSensorPropertiesInteractor(),
|
||||
biometricEnvironment!!.createFingerprintEnrollInteractor(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -50,7 +50,7 @@ sealed interface GatekeeperInfo {
|
||||
* in as a parameter to this class.
|
||||
*/
|
||||
class FingerprintGatekeeperViewModel(
|
||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor
|
||||
private val generateChallengeInteractor: GenerateChallengeInteractor
|
||||
) : ViewModel() {
|
||||
|
||||
private var _gatekeeperInfo: MutableStateFlow<GatekeeperInfo?> = MutableStateFlow(null)
|
||||
@@ -78,7 +78,7 @@ class FingerprintGatekeeperViewModel(
|
||||
_gatekeeperInfo.update { GatekeeperInfo.Invalid }
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
|
||||
val res = generateChallengeInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
|
||||
_gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) }
|
||||
if (shouldStartTimer) {
|
||||
startTimeout()
|
||||
@@ -119,7 +119,7 @@ class FingerprintGatekeeperViewModel(
|
||||
val settingsApplication =
|
||||
this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication
|
||||
val biometricEnvironment = settingsApplication.biometricEnvironment
|
||||
FingerprintGatekeeperViewModel(biometricEnvironment!!.fingerprintManagerInteractor)
|
||||
FingerprintGatekeeperViewModel(biometricEnvironment!!.createGenerateChallengeInteractor())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.android.settings.SettingsApplication
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Finish
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
|
||||
@@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.update
|
||||
* fragments/viewmodels that want to consume these events. It should provide no additional
|
||||
* functionality beyond what is available in [FingerprintNavigationStep].
|
||||
*/
|
||||
class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
|
||||
class FingerprintNavigationViewModel(sensorInteractor: SensorInteractor) :
|
||||
ViewModel() {
|
||||
|
||||
private val _flowInternal: MutableStateFlow<FingerprintFlow?> = MutableStateFlow(null)
|
||||
@@ -55,7 +55,7 @@ class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintMa
|
||||
combine(
|
||||
_flowInternal,
|
||||
_hasConfirmedDeviceCredential,
|
||||
fingerprintManagerInteractor.sensorPropertiesInternal,
|
||||
sensorInteractor.sensorPropertiesInternal,
|
||||
) { flow, hasConfirmed, sensorType ->
|
||||
if (flow == null || sensorType == null) {
|
||||
return@combine null
|
||||
@@ -144,7 +144,7 @@ class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintMa
|
||||
val settingsApplication =
|
||||
this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication
|
||||
val biometricEnvironment = settingsApplication.biometricEnvironment
|
||||
FingerprintNavigationViewModel(biometricEnvironment!!.fingerprintManagerInteractor)
|
||||
FingerprintNavigationViewModel(biometricEnvironment!!.createSensorPropertiesInteractor())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,19 +35,16 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.settings.R
|
||||
import com.android.settings.SettingsApplication
|
||||
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
|
||||
import com.android.settings.biometrics.BiometricEnrollBase
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
@@ -223,35 +220,24 @@ class FingerprintSettingsV2Fragment :
|
||||
val fingerprintSensorProvider =
|
||||
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
|
||||
val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
|
||||
val fingerprintEnrollStateRepository =
|
||||
FingerprintEnrollInteractorImpl(
|
||||
requireContext().applicationContext,
|
||||
fingerprintManager,
|
||||
Settings,
|
||||
)
|
||||
|
||||
val interactor =
|
||||
FingerprintManagerInteractorImpl(
|
||||
context.applicationContext,
|
||||
backgroundDispatcher,
|
||||
fingerprintManager,
|
||||
fingerprintSensorProvider,
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
|
||||
fingerprintEnrollStateRepository,
|
||||
)
|
||||
|
||||
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||
val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
|
||||
val application = requireActivity().application as SettingsApplication
|
||||
val environment =
|
||||
application.biometricEnvironment
|
||||
?: throw IllegalStateException("The biometric environment must be present")
|
||||
|
||||
navigationViewModel =
|
||||
ViewModelProvider(
|
||||
this,
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
userId,
|
||||
interactor,
|
||||
backgroundDispatcher,
|
||||
token,
|
||||
challenge,
|
||||
environment.createFingerprintsEnrolledInteractor(),
|
||||
environment.createGenerateChallengeInteractor(),
|
||||
),
|
||||
)[FingerprintSettingsNavigationViewModel::class.java]
|
||||
|
||||
@@ -260,9 +246,14 @@ class FingerprintSettingsV2Fragment :
|
||||
this,
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
userId,
|
||||
interactor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
environment.createCanEnrollFingerprintsInteractor(),
|
||||
environment.createSensorPropertiesInteractor(),
|
||||
environment.createAuthenticateInteractor(),
|
||||
environment.createRenameFingerprintInteractor(),
|
||||
environment.createRemoveFingerprintInteractor(),
|
||||
environment.createFingerprintsEnrolledInteractor(),
|
||||
),
|
||||
)[FingerprintSettingsViewModel::class.java]
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.settings.biometrics.BiometricEnrollBase
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -33,10 +34,11 @@ import kotlinx.coroutines.launch
|
||||
/** A Viewmodel that represents the navigation of the FingerprintSettings activity. */
|
||||
class FingerprintSettingsNavigationViewModel(
|
||||
private val userId: Int,
|
||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
tokenInit: ByteArray?,
|
||||
challengeInit: Long?,
|
||||
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
|
||||
private val generateChallengeInteractor: GenerateChallengeInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
private var token = tokenInit
|
||||
@@ -52,7 +54,7 @@ class FingerprintSettingsNavigationViewModel(
|
||||
_nextStep.update { LaunchConfirmDeviceCredential(userId) }
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
if (fingerprintManagerInteractor.enrolledFingerprints.last()?.isEmpty() == true) {
|
||||
if (enrolledFingerprintsInteractor.enrolledFingerprints.last()?.isEmpty() == true) {
|
||||
_nextStep.update { EnrollFirstFingerprint(userId, null, challenge, token) }
|
||||
} else {
|
||||
showSettingsHelper()
|
||||
@@ -148,13 +150,13 @@ class FingerprintSettingsNavigationViewModel(
|
||||
}
|
||||
|
||||
private suspend fun launchEnrollNextStep(gateKeeperPasswordHandle: Long?) {
|
||||
fingerprintManagerInteractor.enrolledFingerprints.collect {
|
||||
enrolledFingerprintsInteractor.enrolledFingerprints.collect {
|
||||
if (it?.isEmpty() == true) {
|
||||
_nextStep.update { EnrollFirstFingerprint(userId, gateKeeperPasswordHandle, null, null) }
|
||||
} else {
|
||||
viewModelScope.launch(backgroundDispatcher) {
|
||||
val challengePair =
|
||||
fingerprintManagerInteractor.generateChallenge(gateKeeperPasswordHandle!!)
|
||||
generateChallengeInteractor.generateChallenge(gateKeeperPasswordHandle!!)
|
||||
challenge = challengePair.first
|
||||
token = challengePair.second
|
||||
|
||||
@@ -174,10 +176,11 @@ class FingerprintSettingsNavigationViewModel(
|
||||
|
||||
class FingerprintSettingsNavigationModelFactory(
|
||||
private val userId: Int,
|
||||
private val interactor: FingerprintManagerInteractor,
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val token: ByteArray?,
|
||||
private val challenge: Long?,
|
||||
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
|
||||
private val generateChallengeInteractor: GenerateChallengeInteractor,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -185,10 +188,11 @@ class FingerprintSettingsNavigationViewModel(
|
||||
|
||||
return FingerprintSettingsNavigationViewModel(
|
||||
userId,
|
||||
interactor,
|
||||
backgroundDispatcher,
|
||||
token,
|
||||
challenge,
|
||||
enrolledFingerprintsInteractor,
|
||||
generateChallengeInteractor,
|
||||
)
|
||||
as T
|
||||
}
|
||||
|
||||
@@ -21,7 +21,12 @@ import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
@@ -49,9 +54,14 @@ private const val DEBUG = false
|
||||
/** Models the UI state for fingerprint settings. */
|
||||
class FingerprintSettingsViewModel(
|
||||
private val userId: Int,
|
||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
|
||||
private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
|
||||
private val sensorInteractor: SensorInteractor,
|
||||
private val authenticateInteractor: AuthenitcateInteractor,
|
||||
private val renameFingerprintInteractor: RenameFingerprintInteractor,
|
||||
private val removeFingerprintInteractor: RemoveFingerprintInteractor,
|
||||
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
|
||||
) : ViewModel() {
|
||||
private val _enrolledFingerprints: MutableStateFlow<List<FingerprintData>?> =
|
||||
MutableStateFlow(null)
|
||||
@@ -62,19 +72,18 @@ class FingerprintSettingsViewModel(
|
||||
|
||||
/** Represents the stream of the information of "Add Fingerprint" preference. */
|
||||
val addFingerprintPrefInfo: Flow<Pair<Boolean, Int>> =
|
||||
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
|
||||
emit(
|
||||
Pair(
|
||||
fingerprintManagerInteractor.canEnrollFingerprints.first(),
|
||||
fingerprintManagerInteractor.maxEnrollableFingerprints.first(),
|
||||
)
|
||||
)
|
||||
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(
|
||||
canEnrollFingerprintsInteractor.canEnrollFingerprints
|
||||
) { _, canEnrollFingerprints ->
|
||||
Pair(canEnrollFingerprints, canEnrollFingerprintsInteractor.maxFingerprintsEnrollable())
|
||||
}
|
||||
|
||||
/** Represents the stream of visibility of sfps preference. */
|
||||
val isSfpsPrefVisible: Flow<Boolean> =
|
||||
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
|
||||
emit(fingerprintManagerInteractor.hasSideFps() == true && !it.isNullOrEmpty())
|
||||
_enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(sensorInteractor.hasSideFps) {
|
||||
fingerprints,
|
||||
hasSideFps ->
|
||||
hasSideFps && !fingerprints.isNullOrEmpty()
|
||||
}
|
||||
|
||||
private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
|
||||
@@ -90,10 +99,10 @@ class FingerprintSettingsViewModel(
|
||||
private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
|
||||
private val _fingerprintSensorType: Flow<FingerprintSensorType> =
|
||||
fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
|
||||
sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
|
||||
|
||||
private val _sensorNullOrEmpty: Flow<Boolean> =
|
||||
fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
|
||||
sensorInteractor.sensorPropertiesInternal.map { it == null }
|
||||
|
||||
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptModel.Error?> =
|
||||
MutableStateFlow(null)
|
||||
@@ -172,7 +181,7 @@ class FingerprintSettingsViewModel(
|
||||
while (it && navigationViewModel.nextStep.value is ShowSettings) {
|
||||
Log.d(TAG, "canAuthenticate authing")
|
||||
attemptingAuth()
|
||||
when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
|
||||
when (val authAttempt = authenticateInteractor.authenticate()) {
|
||||
is FingerprintAuthAttemptModel.Success -> {
|
||||
onAuthSuccess(authAttempt)
|
||||
emit(authAttempt)
|
||||
@@ -243,7 +252,7 @@ class FingerprintSettingsViewModel(
|
||||
/** A request to delete a fingerprint */
|
||||
fun deleteFingerprint(fp: FingerprintData) {
|
||||
viewModelScope.launch(backgroundDispatcher) {
|
||||
if (fingerprintManagerInteractor.removeFingerprint(fp)) {
|
||||
if (removeFingerprintInteractor.removeFingerprint(fp)) {
|
||||
updateEnrolledFingerprints()
|
||||
}
|
||||
}
|
||||
@@ -252,7 +261,7 @@ class FingerprintSettingsViewModel(
|
||||
/** A request to rename a fingerprint */
|
||||
fun renameFingerprint(fp: FingerprintData, newName: String) {
|
||||
viewModelScope.launch {
|
||||
fingerprintManagerInteractor.renameFingerprint(fp, newName)
|
||||
renameFingerprintInteractor.renameFingerprint(fp, newName)
|
||||
updateEnrolledFingerprints()
|
||||
}
|
||||
}
|
||||
@@ -271,7 +280,7 @@ class FingerprintSettingsViewModel(
|
||||
}
|
||||
|
||||
private suspend fun updateEnrolledFingerprints() {
|
||||
_enrolledFingerprints.update { fingerprintManagerInteractor.enrolledFingerprints.first() }
|
||||
_enrolledFingerprints.update { enrolledFingerprintsInteractor.enrolledFingerprints.first() }
|
||||
}
|
||||
|
||||
/** Used to indicate whether the consumer of the view model is ready for authentication. */
|
||||
@@ -288,9 +297,14 @@ class FingerprintSettingsViewModel(
|
||||
|
||||
class FingerprintSettingsViewModelFactory(
|
||||
private val userId: Int,
|
||||
private val interactor: FingerprintManagerInteractor,
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
|
||||
private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
|
||||
private val sensorInteractor: SensorInteractor,
|
||||
private val authenticateInteractor: AuthenitcateInteractor,
|
||||
private val renameFingerprintInteractor: RenameFingerprintInteractor,
|
||||
private val removeFingerprintInteractor: RemoveFingerprintInteractor,
|
||||
private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -298,9 +312,14 @@ class FingerprintSettingsViewModel(
|
||||
|
||||
return FingerprintSettingsViewModel(
|
||||
userId,
|
||||
interactor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
canEnrollFingerprintsInteractor,
|
||||
sensorInteractor,
|
||||
authenticateInteractor,
|
||||
renameFingerprintInteractor,
|
||||
removeFingerprintInteractor,
|
||||
enrolledFingerprintsInteractor,
|
||||
)
|
||||
as T
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
@@ -46,9 +47,13 @@ import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||
import com.android.settingslib.core.lifecycle.events.OnCreate;
|
||||
import com.android.settingslib.core.lifecycle.events.OnDestroy;
|
||||
import com.android.settingslib.core.lifecycle.events.OnResume;
|
||||
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
|
||||
import com.android.settingslib.widget.FooterPreference;
|
||||
import com.android.settingslib.widget.SettingsSpinnerAdapter;
|
||||
import com.android.settingslib.widget.SettingsSpinnerPreference;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -58,7 +63,7 @@ import java.util.Set;
|
||||
|
||||
/** Controller for battery usage breakdown preference group. */
|
||||
public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
implements LifecycleObserver, OnResume, OnDestroy {
|
||||
implements LifecycleObserver, OnResume, OnDestroy, OnCreate, OnSaveInstanceState {
|
||||
private static final String TAG = "BatteryUsageBreakdownController";
|
||||
private static final String ROOT_PREFERENCE_KEY = "battery_usage_breakdown";
|
||||
private static final String FOOTER_PREFERENCE_KEY = "battery_usage_footer";
|
||||
@@ -67,6 +72,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
private static final String PACKAGE_NAME_NONE = "none";
|
||||
private static final String SLOT_TIMESTAMP = "slot_timestamp";
|
||||
private static final String ANOMALY_KEY = "anomaly_key";
|
||||
private static final String KEY_SPINNER_POSITION = "spinner_position";
|
||||
private static final List<BatteryDiffEntry> EMPTY_ENTRY_LIST = new ArrayList<>();
|
||||
|
||||
private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
|
||||
@@ -78,12 +84,12 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
|
||||
@VisibleForTesting final Map<String, Preference> mPreferenceCache = new ArrayMap<>();
|
||||
|
||||
private int mSpinnerPosition;
|
||||
private String mSlotInformation;
|
||||
private SettingsSpinnerPreference mSpinnerPreference;
|
||||
private SettingsSpinnerAdapter<CharSequence> mSpinnerAdapter;
|
||||
|
||||
@VisibleForTesting Context mPrefContext;
|
||||
@VisibleForTesting PreferenceCategory mRootPreference;
|
||||
@VisibleForTesting SpinnerPreference mSpinnerPreference;
|
||||
@VisibleForTesting PreferenceGroup mAppListPreferenceGroup;
|
||||
@VisibleForTesting FooterPreference mFooterPreference;
|
||||
@VisibleForTesting BatteryDiffData mBatteryDiffData;
|
||||
@@ -92,6 +98,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
@VisibleForTesting String mPercentLessThanThresholdContentDescription;
|
||||
@VisibleForTesting boolean mIsHighlightSlot;
|
||||
@VisibleForTesting int mAnomalyKeyNumber;
|
||||
@VisibleForTesting int mSpinnerPosition;
|
||||
@VisibleForTesting String mAnomalyEntryKey;
|
||||
@VisibleForTesting String mAnomalyHintString;
|
||||
@VisibleForTesting String mAnomalyHintPrefKey;
|
||||
@@ -110,6 +117,15 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) {
|
||||
return;
|
||||
}
|
||||
mSpinnerPosition = savedInstanceState.getInt(KEY_SPINNER_POSITION, mSpinnerPosition);
|
||||
Log.d(TAG, "onCreate() spinnerPosition=" + mSpinnerPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
final int currentUiMode =
|
||||
@@ -140,6 +156,15 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) {
|
||||
return;
|
||||
}
|
||||
savedInstanceState.putInt(KEY_SPINNER_POSITION, mSpinnerPosition);
|
||||
Log.d(TAG, "onSaveInstanceState() spinnerPosition=" + mSpinnerPosition);
|
||||
}
|
||||
|
||||
private boolean isAnomalyBatteryDiffEntry(BatteryDiffEntry entry) {
|
||||
return mIsHighlightSlot
|
||||
&& mAnomalyEntryKey != null
|
||||
@@ -218,11 +243,14 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
formatPercentage);
|
||||
|
||||
mAppListPreferenceGroup.setOrderingAsAdded(false);
|
||||
mSpinnerPreference.initializeSpinner(
|
||||
mSpinnerAdapter = new SettingsSpinnerAdapter<>(mPrefContext);
|
||||
mSpinnerAdapter.addAll(
|
||||
new String[] {
|
||||
mPrefContext.getString(R.string.battery_usage_spinner_view_by_apps),
|
||||
mPrefContext.getString(R.string.battery_usage_spinner_view_by_systems)
|
||||
},
|
||||
});
|
||||
mSpinnerPreference.setAdapter(mSpinnerAdapter);
|
||||
mSpinnerPreference.setOnItemSelectedListener(
|
||||
new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(
|
||||
@@ -244,6 +272,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
mSpinnerPreference.setSelection(mSpinnerPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* 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.fuelgauge.batteryusage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.widget.SettingsSpinnerAdapter;
|
||||
|
||||
/** A preference which contains a spinner. */
|
||||
public class SpinnerPreference extends Preference {
|
||||
private static final String TAG = "SpinnerPreference";
|
||||
|
||||
private AdapterView.OnItemSelectedListener mOnItemSelectedListener;
|
||||
|
||||
@VisibleForTesting Spinner mSpinner;
|
||||
@VisibleForTesting String[] mItems;
|
||||
@VisibleForTesting int mSavedSpinnerPosition;
|
||||
|
||||
public SpinnerPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setLayoutResource(R.layout.preference_spinner);
|
||||
}
|
||||
|
||||
void initializeSpinner(
|
||||
String[] items, AdapterView.OnItemSelectedListener onItemSelectedListener) {
|
||||
mItems = items;
|
||||
mOnItemSelectedListener = onItemSelectedListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder view) {
|
||||
if (mSpinner != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSpinner = (Spinner) view.findViewById(R.id.spinner);
|
||||
mSpinner.setAdapter(new SpinnerAdapter(getContext(), mItems));
|
||||
mSpinner.setSelection(mSavedSpinnerPosition);
|
||||
mSpinner.setLongClickable(false);
|
||||
if (mOnItemSelectedListener != null) {
|
||||
mSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable onSaveInstanceState() {
|
||||
if (mSpinner == null) {
|
||||
return super.onSaveInstanceState();
|
||||
}
|
||||
Log.d(TAG, "onSaveInstanceState() spinnerPosition=" + mSpinner.getSelectedItemPosition());
|
||||
return new SavedState(super.onSaveInstanceState(), mSpinner.getSelectedItemPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Parcelable state) {
|
||||
if (state == null || state == BaseSavedState.EMPTY_STATE) {
|
||||
super.onRestoreInstanceState(state);
|
||||
return;
|
||||
}
|
||||
if (!(state instanceof SavedState)) {
|
||||
// To avoid the IllegalArgumentException, return the BaseSavedState.EMPTY_STATE.
|
||||
super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE);
|
||||
return;
|
||||
}
|
||||
SavedState savedState = (SavedState) state;
|
||||
super.onRestoreInstanceState(savedState.getSuperState());
|
||||
mSavedSpinnerPosition = savedState.getSpinnerPosition();
|
||||
if (mOnItemSelectedListener != null) {
|
||||
mOnItemSelectedListener.onItemSelected(
|
||||
/* parent= */ null,
|
||||
/* view= */ null,
|
||||
savedState.getSpinnerPosition(),
|
||||
/* id= */ 0);
|
||||
}
|
||||
Log.d(TAG, "onRestoreInstanceState() spinnerPosition=" + savedState.getSpinnerPosition());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class SavedState extends BaseSavedState {
|
||||
private int mSpinnerPosition;
|
||||
|
||||
SavedState(Parcelable superState, int spinnerPosition) {
|
||||
super(superState);
|
||||
mSpinnerPosition = spinnerPosition;
|
||||
}
|
||||
|
||||
int getSpinnerPosition() {
|
||||
return mSpinnerPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpinnerAdapter extends SettingsSpinnerAdapter<CharSequence> {
|
||||
private final String[] mItems;
|
||||
|
||||
SpinnerAdapter(Context context, String[] items) {
|
||||
super(context);
|
||||
mItems = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mItems.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getItem(int position) {
|
||||
return mItems[position];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,13 +61,6 @@ abstract class AbstractZenModeHeaderController extends AbstractZenModePreference
|
||||
LayoutPreference preference = checkNotNull(screen.findPreference(getPreferenceKey()));
|
||||
preference.setSelectable(false);
|
||||
|
||||
if (mHeaderController == null) {
|
||||
mHeaderController = EntityHeaderController.newInstance(
|
||||
mFragment.getActivity(),
|
||||
mFragment,
|
||||
preference.findViewById(R.id.entity_header));
|
||||
}
|
||||
|
||||
ImageView iconView = checkNotNull(preference.findViewById(R.id.entity_header_icon));
|
||||
ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
|
||||
if (layoutParams.width != iconSizePx || layoutParams.height != iconSizePx) {
|
||||
@@ -75,6 +68,14 @@ abstract class AbstractZenModeHeaderController extends AbstractZenModePreference
|
||||
layoutParams.height = iconSizePx;
|
||||
iconView.setLayoutParams(layoutParams);
|
||||
}
|
||||
|
||||
if (mHeaderController == null) {
|
||||
mHeaderController = EntityHeaderController.newInstance(
|
||||
mFragment.getActivity(),
|
||||
mFragment,
|
||||
preference.findViewById(R.id.entity_header));
|
||||
mHeaderController.done(false); // Make the space for the (unused) name go away.
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateIcon(Preference preference, @NonNull ZenMode zenMode,
|
||||
|
||||
@@ -16,236 +16,72 @@
|
||||
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
|
||||
import com.google.common.base.Equivalence;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class CircularIconsPreference extends RestrictedPreference {
|
||||
|
||||
private static final float DISABLED_ITEM_ALPHA = 0.3f;
|
||||
|
||||
record LoadedIcons(ImmutableList<Drawable> icons, int extraItems) {
|
||||
static final LoadedIcons EMPTY = new LoadedIcons(ImmutableList.of(), 0);
|
||||
}
|
||||
|
||||
private Executor mUiExecutor;
|
||||
|
||||
// Chronologically, fields will be set top-to-bottom.
|
||||
@Nullable private CircularIconSet<?> mIconSet;
|
||||
@Nullable private ListenableFuture<List<Drawable>> mPendingLoadIconsFuture;
|
||||
@Nullable private LoadedIcons mLoadedIcons;
|
||||
private CircularIconSet<?> mIconSet = CircularIconSet.EMPTY;
|
||||
|
||||
public CircularIconsPreference(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
public CircularIconsPreference(Context context, Executor uiExecutor) {
|
||||
this(context);
|
||||
mUiExecutor = uiExecutor;
|
||||
init();
|
||||
}
|
||||
|
||||
public CircularIconsPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init(context);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mUiExecutor = context.getMainExecutor();
|
||||
private void init() {
|
||||
setLayoutResource(R.layout.preference_circular_icons);
|
||||
}
|
||||
|
||||
<T> void displayIcons(CircularIconSet<T> iconSet) {
|
||||
displayIcons(iconSet, null);
|
||||
<T> void setIcons(CircularIconSet<T> iconSet) {
|
||||
setIcons(iconSet, null);
|
||||
}
|
||||
|
||||
<T> void displayIcons(CircularIconSet<T> iconSet, @Nullable Equivalence<T> itemEquivalence) {
|
||||
if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) {
|
||||
<T> void setIcons(CircularIconSet<T> iconSet, @Nullable Equivalence<T> itemEquivalence) {
|
||||
if (mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mIconSet = iconSet;
|
||||
|
||||
mLoadedIcons = null;
|
||||
if (mPendingLoadIconsFuture != null) {
|
||||
mPendingLoadIconsFuture.cancel(true);
|
||||
mPendingLoadIconsFuture = null;
|
||||
}
|
||||
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
CircularIconsView iconContainer = checkNotNull(
|
||||
(CircularIconsView) holder.findViewById(R.id.circles_container));
|
||||
|
||||
LinearLayout iconContainer = checkNotNull(
|
||||
(LinearLayout) holder.findViewById(R.id.circles_container));
|
||||
bindIconContainer(iconContainer);
|
||||
}
|
||||
|
||||
private void bindIconContainer(LinearLayout container) {
|
||||
if (mLoadedIcons != null) {
|
||||
// We have the icons ready to display already, show them.
|
||||
setDrawables(container, mLoadedIcons);
|
||||
} else if (mIconSet != null) {
|
||||
// We know what icons we want, but haven't yet loaded them.
|
||||
if (mIconSet.size() == 0) {
|
||||
container.setVisibility(View.GONE);
|
||||
mLoadedIcons = LoadedIcons.EMPTY;
|
||||
return;
|
||||
}
|
||||
container.setVisibility(View.VISIBLE);
|
||||
if (container.getMeasuredWidth() != 0) {
|
||||
startLoadingIcons(container, mIconSet);
|
||||
} else {
|
||||
container.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startLoadingIcons(LinearLayout container, CircularIconSet<?> iconSet) {
|
||||
Resources res = getContext().getResources();
|
||||
int availableSpace = container.getMeasuredWidth();
|
||||
int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
|
||||
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
|
||||
int numIconsThatFit = availableSpace / iconHorizontalSpace;
|
||||
|
||||
List<ListenableFuture<Drawable>> iconFutures;
|
||||
int extraItems;
|
||||
if (iconSet.size() > numIconsThatFit) {
|
||||
// Reserve one space for the (+xx) textview.
|
||||
int numIconsToShow = numIconsThatFit - 1;
|
||||
if (numIconsToShow < 0) {
|
||||
numIconsToShow = 0;
|
||||
}
|
||||
iconFutures = iconSet.getIcons(numIconsToShow);
|
||||
extraItems = iconSet.size() - numIconsToShow;
|
||||
} else {
|
||||
// Fit exactly or with remaining space.
|
||||
iconFutures = iconSet.getIcons();
|
||||
extraItems = 0;
|
||||
}
|
||||
|
||||
// Display icons when all are ready (more consistent than randomly loading).
|
||||
mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
|
||||
FutureUtil.whenDone(
|
||||
mPendingLoadIconsFuture,
|
||||
icons -> {
|
||||
mLoadedIcons = new LoadedIcons(ImmutableList.copyOf(icons), extraItems);
|
||||
notifyChanged(); // So that view is rebound and icons actually shown.
|
||||
},
|
||||
mUiExecutor);
|
||||
}
|
||||
|
||||
private void setDrawables(LinearLayout container, LoadedIcons loadedIcons) {
|
||||
// Rearrange child views until we have <numImages> ImageViews...
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
int numImages = loadedIcons.icons.size();
|
||||
int numImageViews = getChildCount(container, ImageView.class);
|
||||
if (numImages > numImageViews) {
|
||||
for (int i = 0; i < numImages - numImageViews; i++) {
|
||||
ImageView imageView = (ImageView) inflater.inflate(
|
||||
R.layout.preference_circular_icons_item, container, false);
|
||||
container.addView(imageView, 0);
|
||||
}
|
||||
} else if (numImageViews > numImages) {
|
||||
for (int i = 0; i < numImageViews - numImages; i++) {
|
||||
container.removeViewAt(0);
|
||||
}
|
||||
}
|
||||
// ... plus 0/1 TextViews at the end.
|
||||
if (loadedIcons.extraItems > 0 && !(getLastChild(container) instanceof TextView)) {
|
||||
TextView plusView = (TextView) inflater.inflate(
|
||||
R.layout.preference_circular_icons_plus_item, container, false);
|
||||
container.addView(plusView);
|
||||
} else if (loadedIcons.extraItems == 0 && (getLastChild(container) instanceof TextView)) {
|
||||
container.removeViewAt(container.getChildCount() - 1);
|
||||
}
|
||||
|
||||
// Show images (and +n if needed).
|
||||
for (int i = 0; i < numImages; i++) {
|
||||
ImageView imageView = (ImageView) container.getChildAt(i);
|
||||
imageView.setImageDrawable(loadedIcons.icons.get(i));
|
||||
}
|
||||
if (loadedIcons.extraItems > 0) {
|
||||
TextView textView = (TextView) checkNotNull(getLastChild(container));
|
||||
textView.setText(getContext().getString(R.string.zen_mode_plus_n_items,
|
||||
loadedIcons.extraItems));
|
||||
}
|
||||
|
||||
// Apply enabled/disabled style.
|
||||
for (int i = 0; i < container.getChildCount(); i++) {
|
||||
View child = container.getChildAt(i);
|
||||
child.setAlpha(isEnabled() ? 1.0f : DISABLED_ITEM_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getChildCount(ViewGroup parent, Class<? extends View> childClass) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < parent.getChildCount(); i++) {
|
||||
if (childClass.isInstance(parent.getChildAt(i))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static View getLastChild(ViewGroup parent) {
|
||||
if (parent.getChildCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
return parent.getChildAt(parent.getChildCount() - 1);
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@Nullable
|
||||
LoadedIcons getLoadedIcons() {
|
||||
return mLoadedIcons;
|
||||
iconContainer.setVisibility(mIconSet != null && mIconSet.size() == 0 ? GONE : VISIBLE);
|
||||
iconContainer.setEnabled(isEnabled());
|
||||
iconContainer.setIcons(mIconSet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.notification.modes;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class CircularIconsView extends LinearLayout {
|
||||
|
||||
private static final float DISABLED_ITEM_ALPHA = 0.3f;
|
||||
|
||||
record Icons(ImmutableList<Drawable> icons, int extraItems) { }
|
||||
|
||||
private Executor mUiExecutor;
|
||||
private int mNumberOfCirclesThatFit;
|
||||
|
||||
// Chronologically, fields will be set top-to-bottom.
|
||||
@Nullable private CircularIconSet<?> mIconSet;
|
||||
@Nullable private ListenableFuture<List<Drawable>> mPendingLoadIconsFuture;
|
||||
@Nullable private Icons mDisplayedIcons;
|
||||
|
||||
public CircularIconsView(Context context) {
|
||||
super(context);
|
||||
setUiExecutor(context.getMainExecutor());
|
||||
}
|
||||
|
||||
public CircularIconsView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setUiExecutor(context.getMainExecutor());
|
||||
}
|
||||
|
||||
public CircularIconsView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setUiExecutor(context.getMainExecutor());
|
||||
}
|
||||
|
||||
public CircularIconsView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
setUiExecutor(context.getMainExecutor());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setUiExecutor(Executor uiExecutor) {
|
||||
mUiExecutor = uiExecutor;
|
||||
}
|
||||
|
||||
<T> void setIcons(CircularIconSet<T> iconSet) {
|
||||
if (mIconSet != null && mIconSet.equals(iconSet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mIconSet = checkNotNull(iconSet);
|
||||
cancelPendingTasks();
|
||||
if (getMeasuredWidth() != 0) {
|
||||
startLoadingIcons(iconSet);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelPendingTasks() {
|
||||
mDisplayedIcons = null;
|
||||
if (mPendingLoadIconsFuture != null) {
|
||||
mPendingLoadIconsFuture.cancel(true);
|
||||
mPendingLoadIconsFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
int numFitting = getNumberOfCirclesThatFit();
|
||||
if (mNumberOfCirclesThatFit != numFitting) {
|
||||
// View has been measured for the first time OR its dimensions have changed since then.
|
||||
// Keep track, because we want to reload stuff if more (or less) items fit.
|
||||
mNumberOfCirclesThatFit = numFitting;
|
||||
|
||||
if (mIconSet != null) {
|
||||
cancelPendingTasks();
|
||||
startLoadingIcons(mIconSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getNumberOfCirclesThatFit() {
|
||||
Resources res = getContext().getResources();
|
||||
int availableSpace = getMeasuredWidth();
|
||||
int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
|
||||
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
|
||||
return availableSpace / iconHorizontalSpace;
|
||||
}
|
||||
|
||||
private void startLoadingIcons(CircularIconSet<?> iconSet) {
|
||||
int numCirclesThatFit = getNumberOfCirclesThatFit();
|
||||
|
||||
List<ListenableFuture<Drawable>> iconFutures;
|
||||
int extraItems;
|
||||
if (iconSet.size() > numCirclesThatFit) {
|
||||
// Reserve one space for the (+xx) textview.
|
||||
int numIconsToShow = numCirclesThatFit - 1;
|
||||
if (numIconsToShow < 0) {
|
||||
numIconsToShow = 0;
|
||||
}
|
||||
iconFutures = iconSet.getIcons(numIconsToShow);
|
||||
extraItems = iconSet.size() - numIconsToShow;
|
||||
} else {
|
||||
// Fit exactly or with remaining space.
|
||||
iconFutures = iconSet.getIcons();
|
||||
extraItems = 0;
|
||||
}
|
||||
|
||||
// Display icons when all are ready (more consistent than randomly loading).
|
||||
mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
|
||||
FutureUtil.whenDone(
|
||||
mPendingLoadIconsFuture,
|
||||
icons -> setDrawables(new Icons(ImmutableList.copyOf(icons), extraItems)),
|
||||
mUiExecutor);
|
||||
}
|
||||
|
||||
private void setDrawables(Icons icons) {
|
||||
mDisplayedIcons = icons;
|
||||
|
||||
// Rearrange child views until we have <numImages> ImageViews...
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
int numImages = icons.icons.size();
|
||||
int numImageViews = getChildCount(ImageView.class);
|
||||
if (numImages > numImageViews) {
|
||||
for (int i = 0; i < numImages - numImageViews; i++) {
|
||||
ImageView imageView = (ImageView) inflater.inflate(
|
||||
R.layout.preference_circular_icons_item, this, false);
|
||||
addView(imageView, 0);
|
||||
}
|
||||
} else if (numImageViews > numImages) {
|
||||
for (int i = 0; i < numImageViews - numImages; i++) {
|
||||
removeViewAt(0);
|
||||
}
|
||||
}
|
||||
// ... plus 0/1 TextViews at the end.
|
||||
if (icons.extraItems > 0 && !(getLastChild() instanceof TextView)) {
|
||||
TextView plusView = (TextView) inflater.inflate(
|
||||
R.layout.preference_circular_icons_plus_item, this, false);
|
||||
this.addView(plusView);
|
||||
} else if (icons.extraItems == 0 && (getLastChild() instanceof TextView)) {
|
||||
removeViewAt(getChildCount() - 1);
|
||||
}
|
||||
|
||||
// Show images (and +n if needed).
|
||||
for (int i = 0; i < numImages; i++) {
|
||||
ImageView imageView = (ImageView) getChildAt(i);
|
||||
imageView.setImageDrawable(icons.icons.get(i));
|
||||
}
|
||||
if (icons.extraItems > 0) {
|
||||
TextView textView = (TextView) checkNotNull(getLastChild());
|
||||
textView.setText(getContext().getString(R.string.zen_mode_plus_n_items,
|
||||
icons.extraItems));
|
||||
}
|
||||
|
||||
applyEnabledDisabledAppearance(isEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
applyEnabledDisabledAppearance(isEnabled());
|
||||
}
|
||||
|
||||
private void applyEnabledDisabledAppearance(boolean enabled) {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
child.setAlpha(enabled ? 1.0f : DISABLED_ITEM_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
private int getChildCount(Class<? extends View> childClass) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
if (childClass.isInstance(getChildAt(i))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private View getLastChild() {
|
||||
if (getChildCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
return getChildAt(getChildCount() - 1);
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@Nullable
|
||||
Icons getDisplayedIcons() {
|
||||
return mDisplayedIcons;
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,7 @@ class IconUtil {
|
||||
@Px int innerSizePx = res.getDimensionPixelSize(R.dimen.zen_mode_header_inner_icon_size);
|
||||
|
||||
Drawable base = composeIcons(
|
||||
context.getResources(),
|
||||
background,
|
||||
Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer),
|
||||
@@ -89,6 +90,7 @@ class IconUtil {
|
||||
innerSizePx);
|
||||
|
||||
Drawable selected = composeIcons(
|
||||
context.getResources(),
|
||||
background,
|
||||
Utils.getColorAttr(context, com.android.internal.R.attr.materialColorPrimary),
|
||||
outerSizePx,
|
||||
@@ -111,6 +113,7 @@ class IconUtil {
|
||||
*/
|
||||
static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) {
|
||||
return composeIconCircle(
|
||||
context.getResources(),
|
||||
Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer),
|
||||
context.getResources().getDimensionPixelSize(
|
||||
@@ -129,6 +132,7 @@ class IconUtil {
|
||||
*/
|
||||
static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) {
|
||||
return composeIconCircle(
|
||||
context.getResources(),
|
||||
context.getColorStateList(R.color.modes_icon_selectable_background),
|
||||
context.getResources().getDimensionPixelSize(
|
||||
R.dimen.zen_mode_icon_list_item_circle_diameter),
|
||||
@@ -146,6 +150,7 @@ class IconUtil {
|
||||
static Drawable makeCircularIconPreferenceItem(@NonNull Context context,
|
||||
@DrawableRes int iconResId) {
|
||||
return composeIconCircle(
|
||||
context.getResources(),
|
||||
Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer),
|
||||
context.getResources().getDimensionPixelSize(
|
||||
@@ -166,6 +171,7 @@ class IconUtil {
|
||||
Resources res = context.getResources();
|
||||
if (Strings.isNullOrEmpty(displayName)) {
|
||||
return composeIconCircle(
|
||||
context.getResources(),
|
||||
Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorTertiaryContainer),
|
||||
res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter),
|
||||
@@ -204,17 +210,17 @@ class IconUtil {
|
||||
return new BitmapDrawable(context.getResources(), bitmap);
|
||||
}
|
||||
|
||||
private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
|
||||
Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
|
||||
return composeIcons(new ShapeDrawable(new OvalShape()), circleColor, circleDiameterPx, icon,
|
||||
iconColor, iconSizePx);
|
||||
private static Drawable composeIconCircle(Resources res, ColorStateList circleColor,
|
||||
@Px int circleDiameterPx, Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
|
||||
return composeIcons(res, new ShapeDrawable(new OvalShape()), circleColor, circleDiameterPx,
|
||||
icon, iconColor, iconSizePx);
|
||||
}
|
||||
|
||||
private static Drawable composeIcons(Drawable outer, ColorStateList outerColor,
|
||||
private static Drawable composeIcons(Resources res, Drawable outer, ColorStateList outerColor,
|
||||
@Px int outerSizePx, Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
|
||||
Drawable background = checkNotNull(outer.getConstantState()).newDrawable().mutate();
|
||||
Drawable background = checkNotNull(outer.getConstantState()).newDrawable(res).mutate();
|
||||
background.setTintList(outerColor);
|
||||
Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable().mutate();
|
||||
Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable(res).mutate();
|
||||
foreground.setTintList(iconColor);
|
||||
|
||||
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
|
||||
|
||||
@@ -21,9 +21,13 @@ import static android.app.AutomaticZenRule.TYPE_DRIVING;
|
||||
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
|
||||
import static android.app.AutomaticZenRule.TYPE_MANAGED;
|
||||
import static android.app.AutomaticZenRule.TYPE_OTHER;
|
||||
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
|
||||
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
|
||||
import static android.app.AutomaticZenRule.TYPE_THEATER;
|
||||
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
|
||||
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActionBar;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
@@ -39,6 +43,7 @@ import android.widget.Toolbar;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
@@ -124,6 +129,11 @@ public class SetupInterstitialActivity extends FragmentActivity {
|
||||
title.setText(mode.getName());
|
||||
}
|
||||
|
||||
TextView subtitle = findViewById(R.id.mode_name_subtitle);
|
||||
if (subtitle != null) {
|
||||
subtitle.setText(getSubtitle(mode));
|
||||
}
|
||||
|
||||
ImageView img = findViewById(R.id.image);
|
||||
if (img != null) {
|
||||
setImage(img, mode);
|
||||
@@ -135,6 +145,27 @@ public class SetupInterstitialActivity extends FragmentActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
@SuppressLint("SwitchIntDef")
|
||||
private static int getSubtitle(ZenMode mode) {
|
||||
if (mode.isSystemOwned()) {
|
||||
return switch (mode.getType()) {
|
||||
case TYPE_SCHEDULE_TIME -> R.string.zen_mode_inspiration_schedule_time;
|
||||
case TYPE_SCHEDULE_CALENDAR -> R.string.zen_mode_inspiration_schedule_calendar;
|
||||
default -> R.string.zen_mode_inspiration_generic; // Custom Manual
|
||||
};
|
||||
} else {
|
||||
return switch (mode.getType()) {
|
||||
case TYPE_BEDTIME -> R.string.zen_mode_inspiration_bedtime;
|
||||
case TYPE_DRIVING -> R.string.zen_mode_inspiration_driving;
|
||||
case TYPE_IMMERSIVE -> R.string.zen_mode_inspiration_immersive;
|
||||
case TYPE_THEATER -> R.string.zen_mode_inspiration_theater;
|
||||
case TYPE_MANAGED -> R.string.zen_mode_inspiration_managed;
|
||||
default -> R.string.zen_mode_inspiration_generic; // Including OTHER, UNKNOWN.
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void setImage(@NonNull ImageView img, @NonNull ZenMode mode) {
|
||||
int drawableRes = switch (mode.getType()) {
|
||||
case TYPE_BEDTIME -> R.drawable.modes_interstitial_bedtime;
|
||||
@@ -142,7 +173,9 @@ public class SetupInterstitialActivity extends FragmentActivity {
|
||||
case TYPE_IMMERSIVE -> R.drawable.modes_interstitial_immersive;
|
||||
case TYPE_THEATER -> R.drawable.modes_interstitial_theater;
|
||||
case TYPE_MANAGED -> R.drawable.modes_interstitial_managed;
|
||||
case TYPE_OTHER -> R.drawable.modes_interstitial_other;
|
||||
case TYPE_OTHER, TYPE_SCHEDULE_CALENDAR, TYPE_SCHEDULE_TIME ->
|
||||
R.drawable.modes_interstitial_other;
|
||||
case TYPE_UNKNOWN -> R.drawable.modes_interstitial_unknown;
|
||||
default -> R.drawable.modes_interstitial_unknown;
|
||||
};
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
|
||||
|
||||
if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
|
||||
mPreference.setSummary(R.string.zen_mode_apps_none_apps);
|
||||
mPreference.displayIcons(CircularIconSet.EMPTY);
|
||||
mPreference.setIcons(CircularIconSet.EMPTY);
|
||||
if (mAppSession != null) {
|
||||
mAppSession.deactivateSession();
|
||||
}
|
||||
@@ -151,7 +151,7 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
|
||||
|
||||
ImmutableList<AppEntry> apps = getAppsBypassingDndSortedByName(allApps);
|
||||
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
|
||||
mPreference.displayIcons(new CircularIconSet<>(apps,
|
||||
mPreference.setIcons(new CircularIconSet<>(apps,
|
||||
app -> mAppIconRetriever.apply(app.info)),
|
||||
APP_ENTRY_EQUIVALENCE);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
|
||||
|
||||
preference.setEnabled(zenMode.isEnabled());
|
||||
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
|
||||
((CircularIconsPreference) preference).displayIcons(getSoundIcons(zenMode.getPolicy()));
|
||||
((CircularIconsPreference) preference).setIcons(getSoundIcons(zenMode.getPolicy()));
|
||||
}
|
||||
|
||||
private CircularIconSet<Integer> getSoundIcons(ZenPolicy policy) {
|
||||
|
||||
@@ -96,7 +96,7 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon
|
||||
|
||||
preference.setEnabled(zenMode.isEnabled());
|
||||
preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode.getPolicy()));
|
||||
((CircularIconsPreference) preference).displayIcons(getPeopleIcons(zenMode.getPolicy()),
|
||||
((CircularIconsPreference) preference).setIcons(getPeopleIcons(zenMode.getPolicy()),
|
||||
PEOPLE_ITEM_EQUIVALENCE);
|
||||
}
|
||||
|
||||
|
||||
@@ -397,7 +397,7 @@ public class WifiHotspotRepository {
|
||||
* @return {@code true} if Wi-Fi Hotspot 5 GHz Band is available
|
||||
*/
|
||||
public boolean is5gAvailable() {
|
||||
if (!mBand5g.isUsableChannelsReady && is5GHzBandSupported()) {
|
||||
if (!mBand5g.isChannelsReady && is5GHzBandSupported()) {
|
||||
isChannelAvailable(mBand5g);
|
||||
}
|
||||
return mBand5g.isAvailable();
|
||||
@@ -439,7 +439,7 @@ public class WifiHotspotRepository {
|
||||
* @return {@code true} if Wi-Fi Hotspot 6 GHz Band is available
|
||||
*/
|
||||
public boolean is6gAvailable() {
|
||||
if (!mBand6g.isUsableChannelsReady && is6GHzBandSupported()) {
|
||||
if (!mBand6g.isChannelsReady && is6GHzBandSupported()) {
|
||||
isChannelAvailable(mBand6g);
|
||||
}
|
||||
return mBand6g.isAvailable();
|
||||
@@ -475,19 +475,19 @@ public class WifiHotspotRepository {
|
||||
List<WifiAvailableChannel> channels =
|
||||
mWifiManager.getAllowedChannels(sapBand.band, OP_MODE_SAP);
|
||||
log("isChannelAvailable(), band:" + sapBand.band + ", channels:" + channels);
|
||||
sapBand.hasUsableChannels = (channels != null && channels.size() > 0);
|
||||
sapBand.isUsableChannelsUnsupported = false;
|
||||
sapBand.hasChannels = (channels != null && channels.size() > 0);
|
||||
sapBand.isChannelsUnsupported = false;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Querying usable SAP channels failed, band:" + sapBand.band);
|
||||
sapBand.hasUsableChannels = false;
|
||||
sapBand.isUsableChannelsUnsupported = true;
|
||||
Log.e(TAG, "Querying SAP channels failed, band:" + sapBand.band);
|
||||
sapBand.hasChannels = false;
|
||||
sapBand.isChannelsUnsupported = true;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// This is expected on some hardware.
|
||||
Log.e(TAG, "Querying usable SAP channels is unsupported, band:" + sapBand.band);
|
||||
sapBand.hasUsableChannels = false;
|
||||
sapBand.isUsableChannelsUnsupported = true;
|
||||
Log.e(TAG, "Querying SAP channels is unsupported, band:" + sapBand.band);
|
||||
sapBand.hasChannels = false;
|
||||
sapBand.isChannelsUnsupported = true;
|
||||
}
|
||||
sapBand.isUsableChannelsReady = true;
|
||||
sapBand.isChannelsReady = true;
|
||||
log("isChannelAvailable(), " + sapBand);
|
||||
return sapBand.isAvailable();
|
||||
}
|
||||
@@ -531,8 +531,8 @@ public class WifiHotspotRepository {
|
||||
}
|
||||
|
||||
protected void purgeRefreshData() {
|
||||
mBand5g.isUsableChannelsReady = false;
|
||||
mBand6g.isUsableChannelsReady = false;
|
||||
mBand5g.isChannelsReady = false;
|
||||
mBand6g.isChannelsReady = false;
|
||||
}
|
||||
|
||||
protected void startAutoRefresh() {
|
||||
@@ -615,15 +615,15 @@ public class WifiHotspotRepository {
|
||||
|
||||
@VisibleForTesting
|
||||
void updateCapabilityChanged() {
|
||||
if (mBand5g.isUsableChannelsUnsupported) {
|
||||
if (mBand5g.isChannelsUnsupported) {
|
||||
update5gAvailable();
|
||||
log("updateCapabilityChanged(), " + mBand5g);
|
||||
}
|
||||
if (mBand6g.isUsableChannelsUnsupported) {
|
||||
if (mBand6g.isChannelsUnsupported) {
|
||||
update6gAvailable();
|
||||
log("updateCapabilityChanged(), " + mBand6g);
|
||||
}
|
||||
if (mBand5g.isUsableChannelsUnsupported || mBand6g.isUsableChannelsUnsupported) {
|
||||
if (mBand5g.isChannelsUnsupported || mBand6g.isChannelsUnsupported) {
|
||||
updateSpeedType();
|
||||
}
|
||||
}
|
||||
@@ -676,9 +676,9 @@ public class WifiHotspotRepository {
|
||||
@VisibleForTesting
|
||||
static class SapBand {
|
||||
public int band;
|
||||
public boolean isUsableChannelsReady;
|
||||
public boolean hasUsableChannels;
|
||||
public boolean isUsableChannelsUnsupported;
|
||||
public boolean isChannelsReady;
|
||||
public boolean hasChannels;
|
||||
public boolean isChannelsUnsupported;
|
||||
public boolean hasCapability;
|
||||
|
||||
SapBand(int band) {
|
||||
@@ -689,7 +689,7 @@ public class WifiHotspotRepository {
|
||||
* Return whether SoftAp band is available or not.
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
return isUsableChannelsUnsupported ? hasCapability : hasUsableChannels;
|
||||
return isChannelsUnsupported ? hasCapability : hasChannels;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -697,10 +697,10 @@ public class WifiHotspotRepository {
|
||||
public String toString() {
|
||||
return "SapBand{"
|
||||
+ "band:" + band
|
||||
+ ",isUsableChannelsReady:" + isUsableChannelsReady
|
||||
+ ",hasUsableChannels:" + hasUsableChannels
|
||||
+ ",isUsableChannelsUnsupported:" + isUsableChannelsUnsupported
|
||||
+ ",hasChannelsCapability:" + hasCapability
|
||||
+ ",isChannelsReady:" + isChannelsReady
|
||||
+ ",hasChannels:" + hasChannels
|
||||
+ ",isChannelsUnsupported:" + isChannelsUnsupported
|
||||
+ ",hasCapability:" + hasCapability
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,6 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
private ContentResolver mContentResolver;
|
||||
private DaltonizerSaturationSeekbarPreferenceController mController;
|
||||
|
||||
private int mOriginalSaturationLevel = -1;
|
||||
|
||||
private PreferenceScreen mScreen;
|
||||
private LifecycleOwner mLifecycleOwner;
|
||||
private Lifecycle mLifecycle;
|
||||
@@ -73,10 +71,6 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
public void setup() {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
mContentResolver = context.getContentResolver();
|
||||
mOriginalSaturationLevel = Settings.Secure.getInt(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
|
||||
7);
|
||||
|
||||
mPreference = new SeekBarPreference(context);
|
||||
mPreference.setKey(ToggleDaltonizerPreferenceFragment.KEY_SATURATION);
|
||||
@@ -92,10 +86,18 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
Settings.Secure.putInt(
|
||||
Settings.Secure.putString(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
|
||||
null);
|
||||
Settings.Secure.putString(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
||||
null);
|
||||
Settings.Secure.putString(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
|
||||
mOriginalSaturationLevel);
|
||||
null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,6 +113,22 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
|
||||
public void getAvailabilityStatus_defaultSettings_unavailable() {
|
||||
// By default enabled == false.
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
|
||||
public void getAvailabilityStatus_enabledDefaultDisplayMode_available() {
|
||||
setDaltonizerEnabled(1);
|
||||
|
||||
// By default display mode is deuteranomaly.
|
||||
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
|
||||
public void getAvailabilityStatus_flagEnabledProtanEnabled_available() {
|
||||
@@ -306,10 +324,7 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
mLifecycle.addObserver(mController);
|
||||
mLifecycle.handleLifecycleEvent(ON_RESUME);
|
||||
|
||||
Settings.Secure.putInt(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
||||
1);
|
||||
setDaltonizerEnabled(1);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
assertThat(mPreference.isEnabled()).isTrue();
|
||||
@@ -324,10 +339,7 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
mLifecycle.addObserver(mController);
|
||||
mLifecycle.handleLifecycleEvent(ON_RESUME);
|
||||
|
||||
Settings.Secure.putInt(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
||||
0);
|
||||
setDaltonizerEnabled(0);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
assertThat(mPreference.isEnabled()).isFalse();
|
||||
@@ -342,10 +354,7 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
mLifecycle.addObserver(mController);
|
||||
mLifecycle.handleLifecycleEvent(ON_RESUME);
|
||||
|
||||
Settings.Secure.putInt(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
|
||||
0);
|
||||
setDaltonizerDisplay(0);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
assertThat(mPreference.isEnabled()).isFalse();
|
||||
@@ -361,23 +370,28 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest {
|
||||
mLifecycle.handleLifecycleEvent(ON_STOP);
|
||||
|
||||
// enabled.
|
||||
Settings.Secure.putInt(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
||||
1);
|
||||
setDaltonizerEnabled(1);
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
|
||||
assertThat(mPreference.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
private void setDaltonizerMode(int enabled, int mode) {
|
||||
setDaltonizerEnabled(enabled);
|
||||
setDaltonizerDisplay(mode);
|
||||
}
|
||||
|
||||
private void setDaltonizerEnabled(int enabled) {
|
||||
Settings.Secure.putInt(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
|
||||
enabled);
|
||||
Settings.Secure.putInt(
|
||||
}
|
||||
|
||||
private void setDaltonizerDisplay(int mode) {
|
||||
Settings.Secure.putString(
|
||||
mContentResolver,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
|
||||
mode);
|
||||
Integer.toString(mode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
@@ -57,6 +58,7 @@ public final class BatteryUsageBreakdownControllerTest {
|
||||
private static final String PREF_KEY = "pref_key";
|
||||
private static final String PREF_KEY2 = "pref_key2";
|
||||
private static final String PREF_SUMMARY = "fake preference summary";
|
||||
private static final String KEY_SPINNER_POSITION = "spinner_position";
|
||||
private static final long TIME_LESS_THAN_HALF_MINUTE = DateUtils.MINUTE_IN_MILLIS / 2 - 1;
|
||||
|
||||
@Mock private InstrumentedPreferenceFragment mFragment;
|
||||
@@ -148,6 +150,15 @@ public final class BatteryUsageBreakdownControllerTest {
|
||||
verify(mAppListPreferenceGroup).removeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSaveInstanceState_returnExpectedResult() {
|
||||
mBatteryUsageBreakdownController.mSpinnerPosition = 1;
|
||||
final Bundle savedInstanceState = new Bundle();
|
||||
mBatteryUsageBreakdownController.onSaveInstanceState(savedInstanceState);
|
||||
|
||||
assertThat(savedInstanceState.getInt(KEY_SPINNER_POSITION)).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAllPreferences_addAllPreferences() {
|
||||
final String appLabel = "fake app label";
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.fuelgauge.batteryusage;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class SpinnerPreferenceTest {
|
||||
|
||||
private Context mContext;
|
||||
private SpinnerPreference mSpinnerPreference;
|
||||
|
||||
@Mock private Spinner mMockSpinner;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
mSpinnerPreference = new SpinnerPreference(mContext, /* attrs= */ null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_returnExpectedResult() {
|
||||
assertThat(mSpinnerPreference.getLayoutResource()).isEqualTo(R.layout.preference_spinner);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeSpinner_returnExpectedResult() {
|
||||
final String[] items = new String[] {"item1", "item2"};
|
||||
mSpinnerPreference.initializeSpinner(items, null);
|
||||
assertThat(mSpinnerPreference.mItems).isEqualTo(items);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSaveInstanceState_returnExpectedResult() {
|
||||
doReturn(1).when(mMockSpinner).getSelectedItemPosition();
|
||||
mSpinnerPreference.mSpinner = mMockSpinner;
|
||||
SpinnerPreference.SavedState savedState =
|
||||
(SpinnerPreference.SavedState) mSpinnerPreference.onSaveInstanceState();
|
||||
assertThat(savedState.getSpinnerPosition()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onRestoreInstanceState_returnExpectedResult() {
|
||||
SpinnerPreference.SavedState savedState =
|
||||
new SpinnerPreference.SavedState(Preference.BaseSavedState.EMPTY_STATE, 2);
|
||||
mSpinnerPreference.onRestoreInstanceState(savedState);
|
||||
assertThat(mSpinnerPreference.mSavedSpinnerPosition).isEqualTo(2);
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,7 @@ public class CircularIconsPreferenceTest {
|
||||
|
||||
private Context mContext;
|
||||
private CircularIconsPreference mPreference;
|
||||
private PreferenceViewHolder mViewHolder;
|
||||
private ViewGroup mContainer;
|
||||
private CircularIconsView mContainer;
|
||||
|
||||
private int mOneIconWidth;
|
||||
|
||||
@@ -73,179 +72,211 @@ public class CircularIconsPreferenceTest {
|
||||
mContext = RuntimeEnvironment.application;
|
||||
CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
|
||||
mPreference = new TestableCircularIconsPreference(mContext);
|
||||
// Tests should call bindAndMeasureViewHolder() so that icons can be added.
|
||||
// Tests should call bindAndLayoutViewHolder() so that icons can be added.
|
||||
|
||||
Resources res = mContext.getResources();
|
||||
mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
|
||||
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
|
||||
}
|
||||
|
||||
private void bindAndMeasureViewHolder(int viewWidth) {
|
||||
private void bindAndLayoutViewHolder(int viewWidth) {
|
||||
bindViewHolder();
|
||||
measureViewHolder(viewWidth);
|
||||
layoutViewHolder(viewWidth);
|
||||
}
|
||||
|
||||
private void bindViewHolder() {
|
||||
View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
|
||||
null);
|
||||
mContainer = checkNotNull(preferenceView.findViewById(R.id.circles_container));
|
||||
mViewHolder = PreferenceViewHolder.createInstanceForTests(preferenceView);
|
||||
mPreference.onBindViewHolder(mViewHolder);
|
||||
mContainer.setUiExecutor(MoreExecutors.directExecutor());
|
||||
PreferenceViewHolder viewHolder = PreferenceViewHolder.createInstanceForTests(
|
||||
preferenceView);
|
||||
mPreference.onBindViewHolder(viewHolder);
|
||||
}
|
||||
|
||||
private void measureViewHolder(int viewWidth) {
|
||||
private void layoutViewHolder(int viewWidth) {
|
||||
checkState(mContainer != null, "Call bindViewHolder() first!");
|
||||
mContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
|
||||
mContainer.getViewTreeObserver().dispatchOnGlobalLayout();
|
||||
mContainer.layout(0, 0, viewWidth, 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_loadsIcons() {
|
||||
public void setIcons_loadsIcons() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
mPreference.displayIcons(iconSet);
|
||||
bindAndLayoutViewHolder(VIEW_WIDTH);
|
||||
mPreference.setIcons(iconSet);
|
||||
|
||||
assertThat(getIcons(mContainer)).hasSize(2);
|
||||
assertThat(((ColorDrawable) getIcons(mContainer).get(0)).getColor()).isEqualTo(1);
|
||||
assertThat(((ColorDrawable) getIcons(mContainer).get(1)).getColor()).isEqualTo(2);
|
||||
assertThat(getDrawables(mContainer)).hasSize(2);
|
||||
assertThat(((ColorDrawable) getDrawables(mContainer).get(0)).getColor()).isEqualTo(1);
|
||||
assertThat(((ColorDrawable) getDrawables(mContainer).get(1)).getColor()).isEqualTo(2);
|
||||
assertThat(getPlusText(mContainer)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_noIcons_hidesRow() {
|
||||
public void setIcons_noIcons_hidesRow() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
mPreference.displayIcons(iconSet);
|
||||
bindAndLayoutViewHolder(VIEW_WIDTH);
|
||||
mPreference.setIcons(iconSet);
|
||||
|
||||
assertThat(mContainer.getVisibility()).isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
|
||||
public void setIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
|
||||
int width = 300;
|
||||
int fittingCircles = width / mOneIconWidth;
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(
|
||||
IntStream.range(0, fittingCircles).boxed().toList(),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(width);
|
||||
mPreference.displayIcons(iconSet);
|
||||
bindAndLayoutViewHolder(width);
|
||||
mPreference.setIcons(iconSet);
|
||||
|
||||
assertThat(getIcons(mContainer)).hasSize(fittingCircles);
|
||||
assertThat(getIcons(mContainer)).containsExactlyElementsIn(
|
||||
assertThat(getDrawables(mContainer)).hasSize(fittingCircles);
|
||||
assertThat(getDrawables(mContainer)).containsExactlyElementsIn(
|
||||
Futures.allAsList(iconSet.getIcons()).get()).inOrder();
|
||||
assertThat(getPlusText(mContainer)).isNull();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
|
||||
public void setIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
|
||||
int width = 300;
|
||||
int fittingCircles = width / mOneIconWidth;
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(
|
||||
IntStream.range(0, fittingCircles + 5).boxed().toList(),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(width);
|
||||
mPreference.displayIcons(iconSet);
|
||||
bindAndLayoutViewHolder(width);
|
||||
mPreference.setIcons(iconSet);
|
||||
|
||||
// N-1 icons, plus (+6) text.
|
||||
assertThat(getIcons(mContainer)).hasSize(fittingCircles - 1);
|
||||
assertThat(getIcons(mContainer)).containsExactlyElementsIn(
|
||||
assertThat(getDrawables(mContainer)).hasSize(fittingCircles - 1);
|
||||
assertThat(getDrawables(mContainer)).containsExactlyElementsIn(
|
||||
Futures.allAsList(iconSet.getIcons(fittingCircles - 1)).get())
|
||||
.inOrder();
|
||||
assertThat(getPlusText(mContainer)).isEqualTo("+6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_teenyTinySpace_showsPlusIcon_noCrash() {
|
||||
public void setIcons_teenyTinySpace_showsPlusIcon_noCrash() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(1);
|
||||
mPreference.displayIcons(iconSet);
|
||||
bindAndLayoutViewHolder(1);
|
||||
mPreference.setIcons(iconSet);
|
||||
|
||||
assertThat(getIcons(mContainer)).isEmpty();
|
||||
assertThat(getDrawables(mContainer)).isEmpty();
|
||||
assertThat(getPlusText(mContainer)).isEqualTo("+2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_beforeBind_loadsIconsOnBindAndMeasure() {
|
||||
public void setIcons_beforeBind_loadsIconsOnBindAndMeasure() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new);
|
||||
|
||||
mPreference.displayIcons(iconSet);
|
||||
assertThat(mPreference.getLoadedIcons()).isNull(); // Hold...
|
||||
mPreference.setIcons(iconSet);
|
||||
assertThat(mContainer).isNull(); // Hold...
|
||||
|
||||
bindViewHolder();
|
||||
assertThat(mPreference.getLoadedIcons()).isNull(); // Hooooold...
|
||||
assertThat(getDrawables(mContainer)).hasSize(0); // Hooooold...
|
||||
|
||||
measureViewHolder(VIEW_WIDTH);
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(3);
|
||||
assertThat(getIcons(mContainer)).hasSize(3);
|
||||
layoutViewHolder(VIEW_WIDTH);
|
||||
assertThat(getDrawables(mContainer)).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_beforeMeasure_loadsIconsOnMeasure() {
|
||||
public void setIcons_beforeMeasure_loadsIconsOnMeasure() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new);
|
||||
bindViewHolder();
|
||||
|
||||
mPreference.displayIcons(iconSet);
|
||||
assertThat(mPreference.getLoadedIcons()).isNull();
|
||||
mPreference.setIcons(iconSet);
|
||||
assertThat(getDrawables(mContainer)).hasSize(0);
|
||||
|
||||
measureViewHolder(VIEW_WIDTH);
|
||||
assertThat(getIcons(mContainer)).hasSize(3);
|
||||
layoutViewHolder(VIEW_WIDTH);
|
||||
assertThat(getDrawables(mContainer)).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_calledAgain_reloadsIcons() {
|
||||
public void setIcons_calledAgain_reloadsIcons() {
|
||||
CircularIconSet<Integer> threeIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new);
|
||||
CircularIconSet<Integer> twoIcons = new CircularIconSet<>(ImmutableList.of(1, 2),
|
||||
ColorDrawable::new);
|
||||
CircularIconSet<Integer> fourIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4),
|
||||
ColorDrawable::new);
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
bindAndLayoutViewHolder(VIEW_WIDTH);
|
||||
|
||||
mPreference.displayIcons(threeIcons);
|
||||
assertThat(mPreference.getLoadedIcons()).isNotNull();
|
||||
assertThat(getIcons(mContainer)).hasSize(3);
|
||||
mPreference.setIcons(threeIcons);
|
||||
assertThat(getDrawables(mContainer)).hasSize(3);
|
||||
|
||||
mPreference.displayIcons(twoIcons);
|
||||
assertThat(mPreference.getLoadedIcons()).isNotNull();
|
||||
assertThat(getIcons(mContainer)).hasSize(2);
|
||||
mPreference.setIcons(twoIcons);
|
||||
assertThat(getDrawables(mContainer)).hasSize(2);
|
||||
|
||||
mPreference.displayIcons(fourIcons);
|
||||
assertThat(mPreference.getLoadedIcons()).isNotNull();
|
||||
assertThat(getIcons(mContainer)).hasSize(4);
|
||||
mPreference.setIcons(fourIcons);
|
||||
assertThat(getDrawables(mContainer)).hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_sameSet_doesNotReloadIcons() {
|
||||
public void setIcons_sameSet_doesNotReloadIcons() {
|
||||
CircularIconSet<Integer> one = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new);
|
||||
CircularIconSet<Integer> same = Mockito.spy(new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new));
|
||||
when(same.getIcons()).thenThrow(new RuntimeException("Shouldn't be called!"));
|
||||
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
bindAndLayoutViewHolder(VIEW_WIDTH);
|
||||
|
||||
mPreference.displayIcons(one);
|
||||
mPreference.setIcons(one);
|
||||
|
||||
mPreference.displayIcons(same); // if no exception, wasn't called.
|
||||
mPreference.setIcons(same); // if no exception, wasn't called.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sizeChanged_reloadsIconsIfDifferentFit() {
|
||||
CircularIconSet<Integer> largeIconSet = new CircularIconSet<>(
|
||||
IntStream.range(0, 100).boxed().toList(),
|
||||
ColorDrawable::new);
|
||||
mPreference.setIcons(largeIconSet);
|
||||
|
||||
// Base space -> some icons
|
||||
int firstWidth = 600;
|
||||
int firstFittingCircles = firstWidth / mOneIconWidth;
|
||||
bindAndLayoutViewHolder(firstWidth);
|
||||
|
||||
assertThat(getDrawables(mContainer)).hasSize(firstFittingCircles - 1);
|
||||
assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (firstFittingCircles - 1)));
|
||||
|
||||
// More space -> more icons
|
||||
int secondWidth = 1000;
|
||||
int secondFittingCircles = secondWidth / mOneIconWidth;
|
||||
assertThat(secondFittingCircles).isGreaterThan(firstFittingCircles);
|
||||
bindAndLayoutViewHolder(secondWidth);
|
||||
|
||||
assertThat(getDrawables(mContainer)).hasSize(secondFittingCircles - 1);
|
||||
assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (secondFittingCircles - 1)));
|
||||
|
||||
// Less space -> fewer icons
|
||||
int thirdWidth = 600;
|
||||
int thirdFittingCircles = thirdWidth / mOneIconWidth;
|
||||
bindAndLayoutViewHolder(thirdWidth);
|
||||
|
||||
assertThat(getDrawables(mContainer)).hasSize(thirdFittingCircles - 1);
|
||||
assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (thirdFittingCircles - 1)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void onBindViewHolder_withDifferentView_reloadsIconsCorrectly() {
|
||||
View preferenceViewOne = LayoutInflater.from(mContext).inflate(
|
||||
mPreference.getLayoutResource(), null);
|
||||
ViewGroup containerOne = preferenceViewOne.findViewById(R.id.circles_container);
|
||||
CircularIconsView containerOne = preferenceViewOne.findViewById(R.id.circles_container);
|
||||
containerOne.setUiExecutor(MoreExecutors.directExecutor());
|
||||
PreferenceViewHolder viewHolderOne = PreferenceViewHolder.createInstanceForTests(
|
||||
preferenceViewOne);
|
||||
containerOne.measure(makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
|
||||
@@ -253,7 +284,8 @@ public class CircularIconsPreferenceTest {
|
||||
|
||||
View preferenceViewTwo = LayoutInflater.from(mContext).inflate(
|
||||
mPreference.getLayoutResource(), null);
|
||||
ViewGroup containerTwo = preferenceViewTwo.findViewById(R.id.circles_container);
|
||||
CircularIconsView containerTwo = preferenceViewTwo.findViewById(R.id.circles_container);
|
||||
containerTwo.setUiExecutor(MoreExecutors.directExecutor());
|
||||
PreferenceViewHolder viewHolderTwo = PreferenceViewHolder.createInstanceForTests(
|
||||
preferenceViewTwo);
|
||||
containerTwo.measure(makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
|
||||
@@ -265,25 +297,25 @@ public class CircularIconsPreferenceTest {
|
||||
ColorDrawable::new);
|
||||
|
||||
mPreference.onBindViewHolder(viewHolderOne);
|
||||
mPreference.displayIcons(iconSetOne);
|
||||
assertThat(getIcons(containerOne)).hasSize(3);
|
||||
mPreference.setIcons(iconSetOne);
|
||||
assertThat(getDrawables(containerOne)).hasSize(3);
|
||||
|
||||
mPreference.onBindViewHolder(viewHolderTwo);
|
||||
assertThat(getIcons(containerTwo)).hasSize(3);
|
||||
assertThat(getDrawables(containerTwo)).hasSize(3);
|
||||
|
||||
mPreference.displayIcons(iconSetTwo);
|
||||
mPreference.setIcons(iconSetTwo);
|
||||
|
||||
// The second view is updated and the first view is unaffected.
|
||||
assertThat(getIcons(containerTwo)).hasSize(2);
|
||||
assertThat(getIcons(containerOne)).hasSize(3);
|
||||
assertThat(getDrawables(containerTwo)).hasSize(2);
|
||||
assertThat(getDrawables(containerOne)).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_afterDisplayIcons_showsEnabledOrDisabledImages() {
|
||||
public void setEnabled_afterSetIcons_showsEnabledOrDisabledImages() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
|
||||
ColorDrawable::new);
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
mPreference.displayIcons(iconSet);
|
||||
bindAndLayoutViewHolder(VIEW_WIDTH);
|
||||
mPreference.setIcons(iconSet);
|
||||
assertThat(getViews(mContainer)).hasSize(2);
|
||||
|
||||
mPreference.setEnabled(false);
|
||||
@@ -294,13 +326,13 @@ public class CircularIconsPreferenceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEnabled_beforeDisplayIcons_showsEnabledOrDisabledImages() {
|
||||
public void setEnabled_beforeSetIcons_showsEnabledOrDisabledImages() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
|
||||
ColorDrawable::new);
|
||||
|
||||
mPreference.setEnabled(false);
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
mPreference.displayIcons(iconSet);
|
||||
bindAndLayoutViewHolder(VIEW_WIDTH);
|
||||
mPreference.setIcons(iconSet);
|
||||
|
||||
assertThat(getViews(mContainer)).hasSize(2);
|
||||
assertThat(getViews(mContainer).get(0).getAlpha()).isLessThan(1f);
|
||||
@@ -314,7 +346,7 @@ public class CircularIconsPreferenceTest {
|
||||
return views;
|
||||
}
|
||||
|
||||
private static List<Drawable> getIcons(ViewGroup container) {
|
||||
private static List<Drawable> getDrawables(ViewGroup container) {
|
||||
ArrayList<Drawable> drawables = new ArrayList<>();
|
||||
for (int i = 0; i < container.getChildCount(); i++) {
|
||||
if (container.getChildAt(i) instanceof ImageView imageView) {
|
||||
|
||||
@@ -20,14 +20,12 @@ import android.content.Context;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
class TestableCircularIconsPreference extends CircularIconsPreference {
|
||||
|
||||
private PreferenceViewHolder mLastViewHolder;
|
||||
|
||||
TestableCircularIconsPreference(Context context) {
|
||||
super(context, MoreExecutors.directExecutor());
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.settings.notification.modes;
|
||||
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
|
||||
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -52,6 +53,7 @@ import android.view.View;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
@@ -82,6 +84,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
|
||||
private ZenModeAppsLinkPreferenceController mController;
|
||||
private CircularIconsPreference mPreference;
|
||||
private CircularIconsView mIconsView;
|
||||
|
||||
private Context mContext;
|
||||
@Mock
|
||||
@@ -114,6 +117,8 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
// Ensure the preference view is bound & measured (needed to add child ImageViews).
|
||||
View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
|
||||
null);
|
||||
mIconsView = checkNotNull(preferenceView.findViewById(R.id.circles_container));
|
||||
mIconsView.setUiExecutor(MoreExecutors.directExecutor());
|
||||
preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
|
||||
PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
|
||||
@@ -273,7 +278,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
appEntries.add(createAppEntry("test2", mContext.getUserId()));
|
||||
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(2);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -313,7 +318,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
|
||||
mController.updateState(mPreference, zenModeWithNone);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
|
||||
verifyNoMoreInteractions(mApplicationsState);
|
||||
verifyNoMoreInteractions(mSession);
|
||||
|
||||
@@ -322,7 +327,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
verify(mApplicationsState).newSession(any(), any());
|
||||
verify(mSession).rebuild(any(), any(), anyBoolean());
|
||||
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -343,11 +348,11 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
verify(mApplicationsState).newSession(any(), any());
|
||||
verify(mSession).rebuild(any(), any(), anyBoolean());
|
||||
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
|
||||
|
||||
mController.updateState(mPreference, zenModeWithNone);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
|
||||
verify(mSession).deactivateSession();
|
||||
verifyNoMoreInteractions(mSession);
|
||||
verifyNoMoreInteractions(mApplicationsState);
|
||||
@@ -356,7 +361,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
// updateState()) is ignored.
|
||||
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -95,7 +95,7 @@ public final class ZenModeOtherLinkPreferenceControllerTest {
|
||||
|
||||
mController.updateState(pref, mode);
|
||||
|
||||
verify(pref).displayIcons(argThat(iconSet -> iconSet.size() == 3));
|
||||
verify(pref).setIcons(argThat(iconSet -> iconSet.size() == 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,7 +107,7 @@ public final class ZenModeOtherLinkPreferenceControllerTest {
|
||||
|
||||
mController.updateState(pref, mode);
|
||||
|
||||
verify(pref).displayIcons(argThat(iconSet ->
|
||||
verify(pref).setIcons(argThat(iconSet ->
|
||||
iconSet.size() == ZenModeSummaryHelper.OTHER_SOUND_CATEGORIES.size()));
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
|
||||
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
|
||||
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -50,6 +51,7 @@ import android.view.View;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.notification.modes.ZenHelperBackend.Contact;
|
||||
import com.android.settingslib.notification.ConversationIconFactory;
|
||||
import com.android.settingslib.notification.modes.TestModeBuilder;
|
||||
@@ -76,6 +78,7 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
|
||||
|
||||
private ZenModePeopleLinkPreferenceController mController;
|
||||
private CircularIconsPreference mPreference;
|
||||
private CircularIconsView mIconsView;
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
@@ -94,6 +97,8 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
|
||||
// Ensure the preference view is bound & measured (needed to add icons).
|
||||
View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
|
||||
null);
|
||||
mIconsView = checkNotNull(preferenceView.findViewById(R.id.circles_container));
|
||||
mIconsView.setUiExecutor(MoreExecutors.directExecutor());
|
||||
preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
|
||||
PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
|
||||
@@ -142,9 +147,9 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
|
||||
|
||||
mController.updateState(mPreference, mode);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons()).isNotNull();
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(2);
|
||||
assertThat(mPreference.getLoadedIcons().icons().stream()
|
||||
assertThat(mIconsView.getDisplayedIcons()).isNotNull();
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(2);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons().stream()
|
||||
.map(ColorDrawable.class::cast)
|
||||
.map(d -> d.getColor()).toList())
|
||||
.containsExactly(2, 3).inOrder();
|
||||
@@ -162,9 +167,9 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
|
||||
|
||||
mController.updateState(mPreference, mode);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons()).isNotNull();
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(4);
|
||||
assertThat(mPreference.getLoadedIcons().icons().stream()
|
||||
assertThat(mIconsView.getDisplayedIcons()).isNotNull();
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(4);
|
||||
assertThat(mIconsView.getDisplayedIcons().icons().stream()
|
||||
.map(ColorDrawable.class::cast)
|
||||
.map(d -> d.getColor()).toList())
|
||||
.containsExactly(1, 2, 3, 4).inOrder();
|
||||
@@ -182,8 +187,8 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
|
||||
|
||||
mController.updateState(mPreference, mode);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons()).isNotNull();
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
|
||||
assertThat(mIconsView.getDisplayedIcons()).isNotNull();
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
|
||||
verify(mHelperBackend, never()).getContactPhoto(any());
|
||||
}
|
||||
|
||||
@@ -201,8 +206,8 @@ public final class ZenModePeopleLinkPreferenceControllerTest {
|
||||
|
||||
mController.updateState(mPreference, mode);
|
||||
|
||||
assertThat(mPreference.getLoadedIcons()).isNotNull();
|
||||
assertThat(mPreference.getLoadedIcons().icons()).hasSize(3);
|
||||
assertThat(mIconsView.getDisplayedIcons()).isNotNull();
|
||||
assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(3);
|
||||
verify(mConversationIconFactory, times(3)).getConversationDrawable((ShortcutInfo) any(),
|
||||
any(), anyInt(), anyBoolean());
|
||||
}
|
||||
|
||||
@@ -162,7 +162,4 @@ public class ZenModeAddBypassingAppsPreferenceControllerTest {
|
||||
assertThat(pref.getKey()).isEqualTo(
|
||||
ZenModeAddBypassingAppsPreferenceController.KEY_NO_APPS);
|
||||
}
|
||||
|
||||
// TODO(b/331624810): Add tests to verify updateAppList() when the filter is
|
||||
// ApplicationsState.FILTER_ENABLED_NOT_QUIET
|
||||
}
|
||||
|
||||
@@ -111,9 +111,10 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
|
||||
|
||||
var fingerprintEnrollViewModel =
|
||||
FingerprintEnrollViewModel(
|
||||
fingerprintManagerInteractor,
|
||||
gatekeeperViewModel,
|
||||
navigationViewModel,
|
||||
fingerprintManagerInteractor,
|
||||
fingerprintManagerInteractor,
|
||||
)
|
||||
|
||||
var fingerprintEnrollEnrollingViewModel =
|
||||
|
||||
@@ -22,7 +22,14 @@ import android.hardware.biometrics.SensorProperties
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
@@ -35,7 +42,15 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
|
||||
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
class FakeFingerprintManagerInteractor :
|
||||
AuthenitcateInteractor,
|
||||
CanEnrollFingerprintsInteractor,
|
||||
EnrolledFingerprintsInteractor,
|
||||
EnrollFingerprintInteractor,
|
||||
GenerateChallengeInteractor,
|
||||
RemoveFingerprintInteractor,
|
||||
RenameFingerprintInteractor,
|
||||
SensorInteractor {
|
||||
|
||||
var enrollableFingerprints: Int = 5
|
||||
var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
|
||||
@@ -67,19 +82,22 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
|
||||
emit(enrolledFingerprintsInternal)
|
||||
}
|
||||
|
||||
override val canEnrollFingerprints: Flow<Boolean> = flow {
|
||||
emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
|
||||
}
|
||||
|
||||
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
|
||||
override fun maxFingerprintsEnrollable(): Int {
|
||||
return enrollableFingerprints
|
||||
}
|
||||
|
||||
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
|
||||
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
|
||||
override val hasSideFps: Flow<Boolean> =
|
||||
flowOf(sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON)
|
||||
|
||||
override suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
fingerprintEnrollOptions: FingerprintEnrollOptions
|
||||
fingerprintEnrollOptions: FingerprintEnrollOptions,
|
||||
): Flow<FingerEnrollState> = flowOf(*enrollStateViewModel.toTypedArray())
|
||||
|
||||
override suspend fun removeFingerprint(fp: FingerprintData): Boolean {
|
||||
@@ -92,7 +110,4 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun hasSideFps(): Boolean {
|
||||
return sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.settings.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.hardware.biometrics.ComponentInfoInternal
|
||||
import android.hardware.biometrics.SensorLocationInternal
|
||||
@@ -30,23 +29,37 @@ import android.hardware.fingerprint.FingerprintSensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import android.os.CancellationSignal
|
||||
import android.os.Handler
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.UserRepo
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Default
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -75,13 +88,28 @@ import org.mockito.stubbing.OngoingStubbing
|
||||
class FingerprintManagerInteractorTest {
|
||||
|
||||
@JvmField @Rule var rule = MockitoJUnit.rule()
|
||||
private lateinit var underTest: FingerprintManagerInteractor
|
||||
private var context: Context = ApplicationProvider.getApplicationContext()
|
||||
private lateinit var enrolledFingerprintsInteractorUnderTest: EnrolledFingerprintsInteractor
|
||||
private lateinit var generateChallengeInteractorUnderTest: GenerateChallengeInteractor
|
||||
private lateinit var removeFingerprintsInteractorUnderTest: RemoveFingerprintInteractor
|
||||
private lateinit var renameFingerprintsInteractorUnderTest: RenameFingerprintInteractor
|
||||
private lateinit var authenticateInteractorImplUnderTest: AuthenticateInteractorImpl
|
||||
private lateinit var canEnrollFingerprintsInteractorUnderTest: CanEnrollFingerprintsInteractor
|
||||
private lateinit var enrollInteractorUnderTest: EnrollFingerprintInteractor
|
||||
|
||||
private val userId = 0
|
||||
private var backgroundDispatcher = StandardTestDispatcher()
|
||||
@Mock private lateinit var fingerprintManager: FingerprintManager
|
||||
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
|
||||
|
||||
private var testScope = TestScope(backgroundDispatcher)
|
||||
private var backgroundScope = testScope.backgroundScope
|
||||
private val flow: FingerprintFlow = Default
|
||||
private val maxFingerprints = 5
|
||||
private val currUser = MutableStateFlow(0)
|
||||
private val userRepo =
|
||||
object : UserRepo {
|
||||
override val currentUser: Flow<Int> = currUser
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -89,7 +117,7 @@ class FingerprintManagerInteractorTest {
|
||||
FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
5 /* maxEnrollmentsPerUser */,
|
||||
maxFingerprints,
|
||||
listOf<ComponentInfoInternal>(),
|
||||
FingerprintSensorProperties.TYPE_POWER_BUTTON,
|
||||
false /* halControlsIllumination */,
|
||||
@@ -97,20 +125,37 @@ class FingerprintManagerInteractorTest {
|
||||
listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
|
||||
)
|
||||
.toFingerprintSensor()
|
||||
|
||||
val fingerprintSensorRepository =
|
||||
object : FingerprintSensorRepository {
|
||||
override val fingerprintSensor: Flow<FingerprintSensor> = flowOf(sensor)
|
||||
override val hasSideFps: Flow<Boolean> = flowOf(false)
|
||||
}
|
||||
|
||||
underTest =
|
||||
FingerprintManagerInteractorImpl(
|
||||
context,
|
||||
backgroundDispatcher,
|
||||
val settingsRepository = FingerprintSettingsRepositoryImpl(maxFingerprints)
|
||||
val fingerprintEnrollmentRepository =
|
||||
FingerprintEnrollmentRepositoryImpl(
|
||||
fingerprintManager,
|
||||
fingerprintSensorRepository,
|
||||
gateKeeperPasswordProvider,
|
||||
FingerprintEnrollInteractorImpl(context, fingerprintManager, Default),
|
||||
userRepo,
|
||||
settingsRepository,
|
||||
backgroundDispatcher,
|
||||
backgroundScope,
|
||||
)
|
||||
|
||||
enrolledFingerprintsInteractorUnderTest =
|
||||
EnrolledFingerprintsInteractorImpl(fingerprintManager, userId)
|
||||
generateChallengeInteractorUnderTest =
|
||||
GenerateChallengeInteractorImpl(fingerprintManager, userId, gateKeeperPasswordProvider)
|
||||
removeFingerprintsInteractorUnderTest =
|
||||
RemoveFingerprintsInteractorImpl(fingerprintManager, userId)
|
||||
renameFingerprintsInteractorUnderTest =
|
||||
RenameFingerprintsInteractorImpl(fingerprintManager, userId, backgroundDispatcher)
|
||||
authenticateInteractorImplUnderTest = AuthenticateInteractorImpl(fingerprintManager, userId)
|
||||
|
||||
canEnrollFingerprintsInteractorUnderTest =
|
||||
CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
|
||||
|
||||
enrollInteractorUnderTest = EnrollFingerprintInteractorImpl(userId, fingerprintManager, flow)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -119,7 +164,8 @@ class FingerprintManagerInteractorTest {
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
|
||||
|
||||
val emptyFingerprintList: List<Fingerprint> = emptyList()
|
||||
assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
|
||||
assertThat(enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last())
|
||||
.isEqualTo(emptyFingerprintList)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -129,7 +175,7 @@ class FingerprintManagerInteractorTest {
|
||||
val fingerprintList: List<Fingerprint> = listOf(expected)
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
|
||||
|
||||
val list = underTest.enrolledFingerprints.last()
|
||||
val list = enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last()
|
||||
assertThat(list!!.size).isEqualTo(fingerprintList.size)
|
||||
val actual = list[0]
|
||||
assertThat(actual.name).isEqualTo(expected.name)
|
||||
@@ -138,24 +184,51 @@ class FingerprintManagerInteractorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCanEnrollFingerprint() =
|
||||
fun testCanEnrollFingerprintSucceeds() =
|
||||
testScope.runTest {
|
||||
val fingerprintList1: List<Fingerprint> =
|
||||
val fingerprintList: List<Fingerprint> =
|
||||
listOf(
|
||||
Fingerprint("Finger 1,", 2, 3L),
|
||||
Fingerprint("Finger 2,", 3, 3L),
|
||||
Fingerprint("Finger 3,", 4, 3L),
|
||||
Fingerprint("Finger 1", 2, 3L),
|
||||
Fingerprint("Finger 2", 3, 3L),
|
||||
Fingerprint("Finger 3", 4, 3L),
|
||||
)
|
||||
val fingerprintList2: List<Fingerprint> =
|
||||
fingerprintList1.plus(
|
||||
listOf(Fingerprint("Finger 4,", 5, 3L), Fingerprint("Finger 5,", 6, 3L))
|
||||
)
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt()))
|
||||
.thenReturn(fingerprintList1)
|
||||
.thenReturn(fingerprintList2)
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
|
||||
|
||||
assertThat(underTest.canEnrollFingerprints.last()).isTrue()
|
||||
assertThat(underTest.canEnrollFingerprints.last()).isFalse()
|
||||
var result: Boolean? = null
|
||||
val job =
|
||||
testScope.launch {
|
||||
canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it }
|
||||
}
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCanEnrollFingerprintFails() =
|
||||
testScope.runTest {
|
||||
val fingerprintList: List<Fingerprint> =
|
||||
listOf(
|
||||
Fingerprint("Finger 1", 2, 3L),
|
||||
Fingerprint("Finger 2", 3, 3L),
|
||||
Fingerprint("Finger 3", 4, 3L),
|
||||
Fingerprint("Finger 4", 5, 3L),
|
||||
Fingerprint("Finger 5", 6, 3L),
|
||||
)
|
||||
whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
|
||||
|
||||
var result: Boolean? = null
|
||||
val job =
|
||||
testScope.launch {
|
||||
canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it }
|
||||
}
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -178,7 +251,8 @@ class FingerprintManagerInteractorTest {
|
||||
argumentCaptor()
|
||||
|
||||
var result: Pair<Long, ByteArray?>? = null
|
||||
val job = testScope.launch { result = underTest.generateChallenge(1L) }
|
||||
val job =
|
||||
testScope.launch { result = generateChallengeInteractorUnderTest.generateChallenge(1L) }
|
||||
runCurrent()
|
||||
|
||||
verify(fingerprintManager).generateChallenge(anyInt(), capture(generateChallengeCallback))
|
||||
@@ -201,7 +275,10 @@ class FingerprintManagerInteractorTest {
|
||||
|
||||
var result: Boolean? = null
|
||||
val job =
|
||||
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
||||
testScope.launch {
|
||||
result =
|
||||
removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove)
|
||||
}
|
||||
runCurrent()
|
||||
|
||||
verify(fingerprintManager)
|
||||
@@ -224,7 +301,10 @@ class FingerprintManagerInteractorTest {
|
||||
|
||||
var result: Boolean? = null
|
||||
val job =
|
||||
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
|
||||
testScope.launch {
|
||||
result =
|
||||
removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove)
|
||||
}
|
||||
runCurrent()
|
||||
|
||||
verify(fingerprintManager)
|
||||
@@ -246,7 +326,7 @@ class FingerprintManagerInteractorTest {
|
||||
testScope.runTest {
|
||||
val fingerprintToRename = FingerprintData("Finger 2", 1, 2L)
|
||||
|
||||
underTest.renameFingerprint(fingerprintToRename, "Woo")
|
||||
renameFingerprintsInteractorUnderTest.renameFingerprint(fingerprintToRename, "Woo")
|
||||
|
||||
verify(fingerprintManager).rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
|
||||
}
|
||||
@@ -257,7 +337,7 @@ class FingerprintManagerInteractorTest {
|
||||
val fingerprint = Fingerprint("Woooo", 100, 101L)
|
||||
|
||||
var result: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { result = underTest.authenticate() }
|
||||
val job = launch { result = authenticateInteractorImplUnderTest.authenticate() }
|
||||
|
||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
|
||||
|
||||
@@ -284,7 +364,7 @@ class FingerprintManagerInteractorTest {
|
||||
fun testAuth_lockout() =
|
||||
testScope.runTest {
|
||||
var result: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { result = underTest.authenticate() }
|
||||
val job = launch { result = authenticateInteractorImplUnderTest.authenticate() }
|
||||
|
||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
|
||||
|
||||
@@ -314,7 +394,7 @@ class FingerprintManagerInteractorTest {
|
||||
val token = byteArrayOf(5, 3, 2)
|
||||
var result: FingerEnrollState? = null
|
||||
val job = launch {
|
||||
underTest
|
||||
enrollInteractorUnderTest
|
||||
.enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
|
||||
.collect { result = it }
|
||||
}
|
||||
@@ -343,7 +423,7 @@ class FingerprintManagerInteractorTest {
|
||||
val token = byteArrayOf(5, 3, 2)
|
||||
var result: FingerEnrollState? = null
|
||||
val job = launch {
|
||||
underTest
|
||||
enrollInteractorUnderTest
|
||||
.enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
|
||||
.collect { result = it }
|
||||
}
|
||||
@@ -372,7 +452,7 @@ class FingerprintManagerInteractorTest {
|
||||
val token = byteArrayOf(5, 3, 2)
|
||||
var result: FingerEnrollState? = null
|
||||
val job = launch {
|
||||
underTest
|
||||
enrollInteractorUnderTest
|
||||
.enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
|
||||
.collect { result = it }
|
||||
}
|
||||
|
||||
@@ -99,9 +99,10 @@ class FingerprintEnrollFindSensorViewModelV2Test {
|
||||
backgroundViewModel.inForeground()
|
||||
enrollViewModel =
|
||||
FingerprintEnrollViewModel(
|
||||
fakeFingerprintManagerInteractor,
|
||||
gatekeeperViewModel,
|
||||
navigationViewModel,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
accessibilityInteractor =
|
||||
object : AccessibilityInteractor {
|
||||
|
||||
@@ -49,8 +49,7 @@ class RFPSIconTouchViewModelTest {
|
||||
fun setup() {
|
||||
Dispatchers.setMain(backgroundDispatcher)
|
||||
testScope = TestScope(backgroundDispatcher)
|
||||
rfpsIconTouchViewModel =
|
||||
RFPSIconTouchViewModel()
|
||||
rfpsIconTouchViewModel = RFPSIconTouchViewModel()
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -88,9 +88,10 @@ class FingerprintEnrollEnrollingViewModelTest {
|
||||
backgroundViewModel.inForeground()
|
||||
val fingerprintEnrollViewModel =
|
||||
FingerprintEnrollViewModel(
|
||||
fakeFingerprintManagerInteractor,
|
||||
gateKeeperViewModel,
|
||||
navigationViewModel,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
enrollEnrollingViewModel =
|
||||
FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel)
|
||||
|
||||
@@ -67,10 +67,11 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
underTest =
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
null,
|
||||
null,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
.create(FingerprintSettingsNavigationViewModel::class.java)
|
||||
}
|
||||
@@ -272,10 +273,11 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
underTest =
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
token,
|
||||
challenge,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
.create(FingerprintSettingsNavigationViewModel::class.java)
|
||||
|
||||
@@ -299,10 +301,11 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
underTest =
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
token,
|
||||
challenge,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
.create(FingerprintSettingsNavigationViewModel::class.java)
|
||||
|
||||
@@ -331,10 +334,11 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
underTest =
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
token,
|
||||
challenge,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
.create(FingerprintSettingsNavigationViewModel::class.java)
|
||||
|
||||
|
||||
@@ -73,19 +73,25 @@ class FingerprintSettingsViewModelTest {
|
||||
navigationViewModel =
|
||||
FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
null,
|
||||
null,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
.create(FingerprintSettingsNavigationViewModel::class.java)
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
}
|
||||
@@ -114,14 +120,7 @@ class FingerprintSettingsViewModelTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
recreateSettingsViewModel()
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
@@ -156,14 +155,7 @@ class FingerprintSettingsViewModelTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
recreateSettingsViewModel()
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
@@ -198,14 +190,7 @@ class FingerprintSettingsViewModelTest {
|
||||
val success = FingerprintAuthAttemptModel.Success(1)
|
||||
fakeFingerprintManagerInteractor.authenticateAttempt = success
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
recreateSettingsViewModel()
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
|
||||
@@ -225,14 +210,7 @@ class FingerprintSettingsViewModelTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(fingerprintToDelete)
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
recreateSettingsViewModel()
|
||||
|
||||
var dialog: PreferenceViewModel? = null
|
||||
val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
|
||||
@@ -261,14 +239,7 @@ class FingerprintSettingsViewModelTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(fingerprintToRename)
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
recreateSettingsViewModel()
|
||||
|
||||
var dialog: PreferenceViewModel? = null
|
||||
val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
|
||||
@@ -299,14 +270,7 @@ class FingerprintSettingsViewModelTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(fingerprintToDelete)
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
recreateSettingsViewModel()
|
||||
|
||||
var dialog: PreferenceViewModel? = null
|
||||
val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
|
||||
@@ -390,6 +354,22 @@ class FingerprintSettingsViewModelTest {
|
||||
assertThat(authAttempt).isEqualTo(null)
|
||||
}
|
||||
|
||||
private fun recreateSettingsViewModel() {
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
fakeFingerprintManagerInteractor,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
}
|
||||
|
||||
private fun setupAuth(): MutableList<FingerprintData> {
|
||||
fakeFingerprintManagerInteractor.sensorProp =
|
||||
FingerprintSensorPropertiesInternal(
|
||||
@@ -409,14 +389,7 @@ class FingerprintSettingsViewModelTest {
|
||||
val success = FingerprintAuthAttemptModel.Success(1)
|
||||
fakeFingerprintManagerInteractor.authenticateAttempt = success
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
defaultUserId,
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
recreateSettingsViewModel()
|
||||
|
||||
return fingerprints
|
||||
}
|
||||
|
||||
@@ -118,12 +118,12 @@ public class WifiHotspotRepositoryTest {
|
||||
mRepository.mSecurityType = mSecurityType;
|
||||
mRepository.mSpeedType = mSpeedType;
|
||||
mRepository.mIsDualBand = true;
|
||||
mRepository.mBand5g.isUsableChannelsReady = true;
|
||||
mRepository.mBand5g.isUsableChannelsUnsupported = false;
|
||||
mRepository.mBand5g.hasUsableChannels = true;
|
||||
mRepository.mBand6g.isUsableChannelsReady = true;
|
||||
mRepository.mBand6g.isUsableChannelsUnsupported = false;
|
||||
mRepository.mBand6g.hasUsableChannels = true;
|
||||
mRepository.mBand5g.isChannelsReady = true;
|
||||
mRepository.mBand5g.isChannelsUnsupported = false;
|
||||
mRepository.mBand5g.hasChannels = true;
|
||||
mRepository.mBand6g.isChannelsReady = true;
|
||||
mRepository.mBand6g.isChannelsUnsupported = false;
|
||||
mRepository.mBand6g.hasChannels = true;
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -382,7 +382,7 @@ public class WifiHotspotRepositoryTest {
|
||||
@Test
|
||||
public void updateSpeedType_singleBand5gPreferredBut5gUnavailable_get2gSpeedType() {
|
||||
mRepository.mIsDualBand = false;
|
||||
mRepository.mBand5g.hasUsableChannels = false;
|
||||
mRepository.mBand5g.hasChannels = false;
|
||||
SoftApConfiguration config = new SoftApConfiguration.Builder()
|
||||
.setBand(WIFI_5GHZ_BAND_PREFERRED).build();
|
||||
when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
|
||||
@@ -407,7 +407,7 @@ public class WifiHotspotRepositoryTest {
|
||||
@Test
|
||||
public void updateSpeedType_singleBand6gPreferredBut6gUnavailable_get5gSpeedType() {
|
||||
mRepository.mIsDualBand = false;
|
||||
mRepository.mBand6g.hasUsableChannels = false;
|
||||
mRepository.mBand6g.hasChannels = false;
|
||||
SoftApConfiguration config = new SoftApConfiguration.Builder()
|
||||
.setBand(WIFI_6GHZ_BAND_PREFERRED).build();
|
||||
when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
|
||||
@@ -420,8 +420,8 @@ public class WifiHotspotRepositoryTest {
|
||||
@Test
|
||||
public void updateSpeedType_singleBand6gPreferredBut5gAnd6gUnavailable_get2gSpeedType() {
|
||||
mRepository.mIsDualBand = false;
|
||||
mRepository.mBand5g.hasUsableChannels = false;
|
||||
mRepository.mBand6g.hasUsableChannels = false;
|
||||
mRepository.mBand5g.hasChannels = false;
|
||||
mRepository.mBand6g.hasChannels = false;
|
||||
SoftApConfiguration config = new SoftApConfiguration.Builder()
|
||||
.setBand(WIFI_6GHZ_BAND_PREFERRED).build();
|
||||
when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
|
||||
@@ -446,7 +446,7 @@ public class WifiHotspotRepositoryTest {
|
||||
@Test
|
||||
public void updateSpeedType_dualBand2gAnd5gBut5gUnavailable_get2gSpeedType() {
|
||||
mRepository.mIsDualBand = true;
|
||||
mRepository.mBand5g.hasUsableChannels = false;
|
||||
mRepository.mBand5g.hasChannels = false;
|
||||
SoftApConfiguration config = new SoftApConfiguration.Builder()
|
||||
.setBand(WIFI_5GHZ_BAND_PREFERRED).build();
|
||||
when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
|
||||
@@ -562,19 +562,19 @@ public class WifiHotspotRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void is5gAvailable_hasUsableChannels_returnTrue() {
|
||||
public void is5gAvailable_hasChannels_returnTrue() {
|
||||
mRepository.mIs5gBandSupported = true;
|
||||
// Reset m5gBand to trigger an update
|
||||
mRepository.mBand5g.isUsableChannelsReady = false;
|
||||
mRepository.mBand5g.isChannelsReady = false;
|
||||
|
||||
assertThat(mRepository.is5gAvailable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void is5gAvailable_noUsableChannels_returnFalse() {
|
||||
public void is5gAvailable_noChannels_returnFalse() {
|
||||
mRepository.mIs5gBandSupported = true;
|
||||
// Reset m5gBand to trigger an update
|
||||
mRepository.mBand5g.isUsableChannelsReady = false;
|
||||
mRepository.mBand5g.isChannelsReady = false;
|
||||
when(mWifiManager.getAllowedChannels(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS, OP_MODE_SAP))
|
||||
.thenReturn(null);
|
||||
|
||||
@@ -585,7 +585,7 @@ public class WifiHotspotRepositoryTest {
|
||||
@UiThreadTest
|
||||
public void get5gAvailable_shouldNotReturnNull() {
|
||||
// Reset m5gBand to trigger an update
|
||||
mRepository.mBand5g.isUsableChannelsReady = false;
|
||||
mRepository.mBand5g.isChannelsReady = false;
|
||||
|
||||
assertThat(mRepository.get5gAvailable()).isNotNull();
|
||||
}
|
||||
@@ -606,19 +606,19 @@ public class WifiHotspotRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void is6gAvailable_hasUsableChannels_returnTrue() {
|
||||
public void is6gAvailable_hasChannels_returnTrue() {
|
||||
mRepository.mIs6gBandSupported = true;
|
||||
// Reset m6gBand to trigger an update
|
||||
mRepository.mBand6g.isUsableChannelsReady = false;
|
||||
mRepository.mBand6g.isChannelsReady = false;
|
||||
|
||||
assertThat(mRepository.is6gAvailable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void is6gAvailable_noUsableChannels_returnFalse() {
|
||||
public void is6gAvailable_noChannels_returnFalse() {
|
||||
mRepository.mIs6gBandSupported = true;
|
||||
// Reset m6gBand to trigger an update
|
||||
mRepository.mBand6g.isUsableChannelsReady = false;
|
||||
mRepository.mBand6g.isChannelsReady = false;
|
||||
when(mWifiManager.getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP))
|
||||
.thenReturn(null);
|
||||
|
||||
@@ -658,33 +658,33 @@ public class WifiHotspotRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isChannelAvailable_throwIllegalArgumentException_hasUsableChannelsFalse() {
|
||||
public void isChannelAvailable_throwIllegalArgumentException_hasChannelsFalse() {
|
||||
doThrow(IllegalArgumentException.class).when(mWifiManager)
|
||||
.getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP);
|
||||
|
||||
mRepository.isChannelAvailable(mRepository.mBand6g);
|
||||
|
||||
assertThat(mRepository.mBand6g.hasUsableChannels).isFalse();
|
||||
assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isTrue();
|
||||
assertThat(mRepository.mBand6g.hasChannels).isFalse();
|
||||
assertThat(mRepository.mBand6g.isChannelsUnsupported).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isChannelAvailable_throwUnsupportedOperationException_hasUsableChannelsFalse() {
|
||||
public void isChannelAvailable_throwUnsupportedOperationException_hasChannelsFalse() {
|
||||
doThrow(UnsupportedOperationException.class).when(mWifiManager)
|
||||
.getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP);
|
||||
|
||||
mRepository.isChannelAvailable(mRepository.mBand6g);
|
||||
|
||||
assertThat(mRepository.mBand6g.hasUsableChannels).isFalse();
|
||||
assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isTrue();
|
||||
assertThat(mRepository.mBand6g.hasChannels).isFalse();
|
||||
assertThat(mRepository.mBand6g.isChannelsUnsupported).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isChannelAvailable_noExceptionAndHasUsableChannels_hasUsableChannelsTrue() {
|
||||
public void isChannelAvailable_noExceptionAndHasChannels_hasChannelsTrue() {
|
||||
mRepository.isChannelAvailable(mRepository.mBand6g);
|
||||
|
||||
assertThat(mRepository.mBand6g.hasUsableChannels).isTrue();
|
||||
assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isFalse();
|
||||
assertThat(mRepository.mBand6g.hasChannels).isTrue();
|
||||
assertThat(mRepository.mBand6g.isChannelsUnsupported).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -744,9 +744,9 @@ public class WifiHotspotRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateCapabilityChanged_band5gUsableChannelsUnsupported_update5gAvailable() {
|
||||
public void updateCapabilityChanged_band5gChannelsUnsupported_update5gAvailable() {
|
||||
mRepository = spy(new WifiHotspotRepository(mContext, mWifiManager, mTetheringManager));
|
||||
mRepository.mBand5g.isUsableChannelsUnsupported = true;
|
||||
mRepository.mBand5g.isChannelsUnsupported = true;
|
||||
|
||||
mRepository.updateCapabilityChanged();
|
||||
|
||||
@@ -755,9 +755,9 @@ public class WifiHotspotRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateCapabilityChanged_band6gUsableChannelsUnsupported_update5gAvailable() {
|
||||
public void updateCapabilityChanged_band6gChannelsUnsupported_update5gAvailable() {
|
||||
mRepository = spy(new WifiHotspotRepository(mContext, mWifiManager, mTetheringManager));
|
||||
mRepository.mBand6g.isUsableChannelsUnsupported = true;
|
||||
mRepository.mBand6g.isChannelsUnsupported = true;
|
||||
|
||||
mRepository.updateCapabilityChanged();
|
||||
|
||||
@@ -766,18 +766,18 @@ public class WifiHotspotRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_isUsableChannelsUnsupportedFalse_returnHasUsableChannels() {
|
||||
mRepository.mBand6g.isUsableChannelsUnsupported = false;
|
||||
mRepository.mBand6g.hasUsableChannels = false;
|
||||
public void isAvailable_isChannelsUnsupportedFalse_returnHasChannels() {
|
||||
mRepository.mBand6g.isChannelsUnsupported = false;
|
||||
mRepository.mBand6g.hasChannels = false;
|
||||
mRepository.mBand6g.hasCapability = true;
|
||||
|
||||
assertThat(mRepository.mBand6g.isAvailable()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_isUsableChannelsUnsupportedTrue_returnHasCapability() {
|
||||
mRepository.mBand6g.isUsableChannelsUnsupported = true;
|
||||
mRepository.mBand6g.hasUsableChannels = false;
|
||||
public void isAvailable_isChannelsUnsupportedTrue_returnHasCapability() {
|
||||
mRepository.mBand6g.isChannelsUnsupported = true;
|
||||
mRepository.mBand6g.hasChannels = false;
|
||||
mRepository.mBand6g.hasCapability = true;
|
||||
|
||||
assertThat(mRepository.mBand6g.isAvailable()).isTrue();
|
||||
|
||||
Reference in New Issue
Block a user