Merge "Quick fix: fix bug when using binary search," into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c64187f..998ecc5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -645,6 +645,7 @@
<activity android:name="Settings$FaceSettingsActivity"
android:label="@string/security_settings_face_preference_title"
android:exported="true"
+ android:theme="@style/Theme.Settings.NoActionBar"
android:icon="@drawable/ic_face_header">
<intent-filter>
<action android:name="android.settings.FACE_SETTINGS" />
@@ -659,6 +660,7 @@
<activity android:name="Settings$FaceSettingsInternalActivity"
android:label="@string/security_settings_face_preference_title"
android:exported="false"
+ android:theme="@style/Theme.Settings.NoActionBar"
android:icon="@drawable/ic_face_header"
android:taskAffinity="com.android.settings.root">
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index d4960c2..d0a8afe 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -9,6 +9,7 @@
"settings_connecteddevice_flag_declarations.aconfig",
"settings_globalintl_flag_declarations.aconfig",
"settings_apn_flag_declarations.aconfig",
+ "settings_onboarding_experience_flag_declarations.aconfig"
],
}
diff --git a/aconfig/settings_onboarding_experience_flag_declarations.aconfig b/aconfig/settings_onboarding_experience_flag_declarations.aconfig
new file mode 100644
index 0000000..6fb5377
--- /dev/null
+++ b/aconfig/settings_onboarding_experience_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.settings.flags"
+
+flag {
+ name: "enable_sound_backup"
+ namespace: "onboarding_experience"
+ description: "Feature flag for B&R sound settings."
+ bug: "278975761"
+}
diff --git a/res-product/values/strings.xml b/res-product/values/strings.xml
index 63ab6ac..0e1713c 100644
--- a/res-product/values/strings.xml
+++ b/res-product/values/strings.xml
@@ -738,4 +738,11 @@
<string name="daltonizer_feature_summary" product="default">Adjust how colors display on your phone</string>
<!-- The daltonizer feature summary display as a subtext as an item in a list. -->
<string name="daltonizer_feature_summary" product="tablet">Adjust how colors display on your tablet</string>
+
+ <!-- Output device type for the phone speaker that is available for spatializer effect. [CHAR LIMIT=NONE]-->
+ <string name="spatial_audio_speaker" product="default">Phone speakers</string>
+ <!-- Output device type for the phone speaker that is available for spatializer effect. [CHAR LIMIT=NONE]-->
+ <string name="spatial_audio_speaker" product="tablet">Tablet speakers</string>
+ <!-- Output device type for the phone speaker that is available for spatializer effect. [CHAR LIMIT=NONE]-->
+ <string name="spatial_audio_speaker" product="device">Device speakers</string>
</resources>
diff --git a/res/drawable/battery_hints_chip_bg.xml b/res/drawable/battery_hints_chip_bg.xml
new file mode 100644
index 0000000..e7d1d0f
--- /dev/null
+++ b/res/drawable/battery_hints_chip_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/settingslib_dialog_background" />
+ <corners android:radius="@dimen/battery_hints_chip_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/battery_hints_chip_bg_ripple.xml b/res/drawable/battery_hints_chip_bg_ripple.xml
new file mode 100644
index 0000000..a8bd0b37
--- /dev/null
+++ b/res/drawable/battery_hints_chip_bg_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:drawable="@drawable/battery_hints_chip_bg"/>
+</ripple>
\ No newline at end of file
diff --git a/res/layout/anomaly_app_item_preference.xml b/res/layout/anomaly_app_item_preference.xml
new file mode 100644
index 0000000..0a19849
--- /dev/null
+++ b/res/layout/anomaly_app_item_preference.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <include layout="@layout/preference_app"/>
+
+ <LinearLayout
+ android:id="@+id/warning_chip"
+ android:visibility="gone"
+ android:clickable="false"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <Space
+ android:layout_width="@dimen/secondary_app_icon_size"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="8dp"
+ android:layout_marginStart="16dp"
+ android:background="@drawable/battery_hints_chip_bg_ripple">
+
+ <ImageView
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_gravity="center_vertical|start"
+ android:contentDescription="@string/battery_hints_warning_icon_a11y"
+ android:src="@drawable/ic_battery_tips_warning_icon" />
+
+ <TextView
+ android:id="@+id/warning_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="8dp"
+ android:layout_gravity="center_vertical|start"
+ android:textAlignment="viewStart"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"/>
+ </LinearLayout>
+
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 1723d17..f454954 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1420,20 +1420,49 @@
<item>color_battery_anomaly_yellow_selector</item>
</string-array>
- <!-- The following 3 arrays are for power anomaly tips card. Please keep them the same size. -->
- <string-array name="power_anomaly_titles">
- <item>Turn on adaptive brightness to extend battery life</item>
- <item>Reduce screen timeout to extend battery life</item>
+ <!-- The following 4 arrays are for power anomaly tips card. Please keep them the same size. -->
+ <string-array name="power_anomaly_title_ids" translatable="false">
+ <item>battery_tips_settings_summary_brightness</item>
+ <item>battery_tips_settings_summary_screen_timeout</item>
+ <item>battery_tips_apps_summary_always_high</item>
+ <item>battery_tips_apps_summary_higher_than_usual</item>
+ <item>battery_tips_apps_summary_always_high_in_background</item>
+ <item>battery_tips_apps_summary_higher_than_usual_in_background</item>
+ <item>battery_tips_apps_summary_always_high_in_foreground</item>
+ <item>battery_tips_apps_summary_higher_than_usual_in_foreground</item>
</string-array>
<string-array name="power_anomaly_main_btn_strings" translatable="false">
<item>@string/battery_tips_card_action_button</item>
<item>@string/battery_tips_card_action_button</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
+ <item>@string/battery_tips_card_action_button_check</item>
</string-array>
<string-array name="power_anomaly_dismiss_btn_strings" translatable="false">
<item>@string/battery_tips_card_dismiss_button</item>
<item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ <item>@string/battery_tips_card_dismiss_button</item>
+ </string-array>
+
+ <string-array name="power_anomaly_hint_messages" translatable="false">
+ <item></item>
+ <item></item>
+ <item>@string/battery_app_item_hint</item>
+ <item>@string/battery_app_item_hint</item>
+ <item>@string/battery_app_item_hint_in_bg</item>
+ <item>@string/battery_app_item_hint_in_bg</item>
+ <item>@string/battery_app_item_hint_in_fg</item>
+ <item>@string/battery_app_item_hint_in_fg</item>
</string-array>
<!-- A list of not supporting Terms of Address. [DO NOT TRANSLATE] -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a8ad434..205e2a3 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -403,6 +403,7 @@
<!-- Battery tips card view component -->
<dimen name="battery_tips_card_corner_radius_small">4dp</dimen>
<dimen name="battery_tips_card_corner_radius_normal">24dp</dimen>
+ <dimen name="battery_hints_chip_corner_radius">8dp</dimen>
<!-- Dimensions for Dream settings cards -->
<dimen name="dream_item_min_column_width">174dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e4b68d0..89f3bd4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7387,7 +7387,7 @@
<string name="vibrate_when_ringing_option_ramping_ringer">Vibrate first then ring gradually</string>
<!-- Sound: Title for the option enabling spatializer effect. [CHAR LIMIT=30] -->
- <string name="spatial_audio_title">Spatial audio</string>
+ <string name="spatial_audio_title">Spatial Audio</string>
<!-- Sound: Other sounds: Title for the option enabling touch sounds for dial pad tones. [CHAR LIMIT=30] -->
<string name="dial_pad_tones_title">Dial pad tones</string>
@@ -7434,9 +7434,6 @@
<!-- Setting summary for controlling how caption text display in real time [CHAR LIMIT=NONE]-->
<string name="live_caption_summary">Automatically caption media</string>
- <!-- Output device type for the phone speaker that is available for spatializer effect. [CHAR LIMIT=NONE]-->
- <string name="spatial_audio_speaker">Phone speaker</string>
-
<!-- Output device type for the wired headphones that is available for spatializer effect. [CHAR LIMIT=NONE]-->
<string name="spatial_audio_wired_headphones">Wired headphones</string>
@@ -9821,12 +9818,51 @@
<!-- Label of action button in battery tips card [CHAR LIMIT=50] -->
<string name="battery_tips_card_action_button">View Settings</string>
+ <!-- Label of action button in battery tips card [CHAR LIMIT=50] -->
+ <string name="battery_tips_card_action_button_check">Check</string>
+
<!-- Label of dismiss button in battery tips card [CHAR LIMIT=50] -->
<string name="battery_tips_card_dismiss_button">Got it</string>
<!-- Feedback card message in battery tips card [CHAR LIMIT=NONE] -->
<string name="battery_tips_card_feedback_info">Is this message helpful?</string>
+ <!-- Content description for battery hints warning icon of app anomaly [CHAR LIMIT=NONE] -->
+ <string name="battery_hints_warning_icon_a11y">Battery tips warning icon</string>
+
+ <!-- Summary of settings anomaly for adaptive brightness [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_settings_summary_brightness">Turn on adaptive brightness to extend battery life</string>
+
+ <!-- Summary of settings anomaly for screen timeout [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_settings_summary_screen_timeout">Reduce screen timeout to extend battery life</string>
+
+ <!-- Summary of apps anomaly for always high [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_always_high"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery</string>
+
+ <!-- Summary of apps anomaly for higher than usual [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_higher_than_usual"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery than usual</string>
+
+ <!-- Summary of apps anomaly for always high in background [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_always_high_in_background"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery while in the background</string>
+
+ <!-- Summary of apps anomaly for higher than usual in background [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_higher_than_usual_in_background"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery than usual while in the background</string>
+
+ <!-- Summary of apps anomaly for always high in foreground [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_always_high_in_foreground"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery while in the foreground</string>
+
+ <!-- Summary of apps anomaly for higher than usual in foreground [CHAR LIMIT=NONE] -->
+ <string name="battery_tips_apps_summary_higher_than_usual_in_foreground"><xliff:g id="app_label" example="Pokemon Go">%1$s</xliff:g> used more battery than usual while in the foreground</string>
+
+ <!-- Label of hint for apps anomaly in battery usage [CHAR LIMIT=NONE] -->
+ <string name="battery_app_item_hint">High battery usage</string>
+
+ <!-- Label of hint for apps background anomaly in battery usage [CHAR LIMIT=NONE] -->
+ <string name="battery_app_item_hint_in_bg">High battery usage in the background</string>
+
+ <!-- Label of hint for apps foreground anomaly in battery usage [CHAR LIMIT=NONE] -->
+ <string name="battery_app_item_hint_in_fg">High battery usage in the foreground</string>
+
<!-- Filter title for battery unrestricted[CHAR_LIMIT=50]-->
<string name="filter_battery_unrestricted_title">Unrestricted</string>
diff --git a/res/xml/data_usage_list.xml b/res/xml/data_usage_list.xml
index 28f09c6..791fc86 100644
--- a/res/xml/data_usage_list.xml
+++ b/res/xml/data_usage_list.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="usage_amount"
@@ -32,6 +33,7 @@
<PreferenceCategory
android:key="apps_group"
- android:layout="@layout/preference_category_no_label" />
+ android:layout="@layout/preference_category_no_label"
+ settings:controller="com.android.settings.datausage.DataUsageListAppsController" />
</PreferenceScreen>
diff --git a/src/com/android/settings/accessibility/HearingAidUtils.java b/src/com/android/settings/accessibility/HearingAidUtils.java
index 4734c55..edf2d9f 100644
--- a/src/com/android/settings/accessibility/HearingAidUtils.java
+++ b/src/com/android/settings/accessibility/HearingAidUtils.java
@@ -42,9 +42,10 @@
*/
public static void launchHearingAidPairingDialog(FragmentManager fragmentManager,
@NonNull CachedBluetoothDevice device, int launchPage) {
- // No need to show the pair another ear dialog if the device supports and enables CSIP.
+ // No need to show the pair another ear dialog if the device supports CSIP.
// CSIP will pair other devices in the same set automatically.
- if (isCsipSupportedAndEnabled(device)) {
+ if (device.getProfiles().stream().anyMatch(
+ profile -> profile instanceof CsipSetCoordinatorProfile)) {
return;
}
if (device.isConnectedAshaHearingAidDevice()
@@ -63,10 +64,4 @@
HearingAidPairingDialogFragment.newInstance(device.getAddress(), launchPage)
.show(fragmentManager, HearingAidPairingDialogFragment.TAG);
}
-
- private static boolean isCsipSupportedAndEnabled(@NonNull CachedBluetoothDevice device) {
- return device.getProfiles().stream().anyMatch(
- profile -> (profile instanceof CsipSetCoordinatorProfile)
- && (profile.isEnabled(device.getDevice())));
- }
}
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
index 182e5ae..98d56cc 100644
--- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -159,6 +159,16 @@
return CONDITIONALLY_UNAVAILABLE;
}
+ // If we are in work profile mode and there is no user then we
+ // should hide for now. We use CONDITIONALLY_UNAVAILABLE
+ // because it is possible for the user to be set later.
+ if (mIsWorkProfile) {
+ UserHandle workProfile = getWorkProfileUserHandle();
+ if (workProfile == null) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ }
+
return AVAILABLE;
}
@@ -186,12 +196,17 @@
fragment.getSettingsLifecycle().addObserver(this);
mFragmentManager = fragmentManager;
mIsWorkProfile = isWorkProfile;
+
setDelegate(delegate);
verifyReceivedIntent(launchIntent);
// Recreate the content observers because the user might have changed.
mSettingsContentObserver.unregister();
mSettingsContentObserver.register();
+
+ // When we set the mIsWorkProfile above we should try and force a refresh
+ // so we can get the correct data.
+ delegate.forceDelegateRefresh();
}
/**
@@ -302,10 +317,15 @@
null);
}
- private Set<ComponentName> buildComponentNameSet(List<CredentialProviderInfo> providers) {
+ private Set<ComponentName> buildComponentNameSet(
+ List<CredentialProviderInfo> providers, boolean removeNonPrimary) {
Set<ComponentName> output = new HashSet<>();
for (CredentialProviderInfo cpi : providers) {
+ if (removeNonPrimary && !cpi.isPrimary()) {
+ continue;
+ }
+
output.add(cpi.getComponentName());
}
@@ -321,13 +341,16 @@
List<CredentialProviderInfo> newProviders =
mCredentialManager.getCredentialProviderServices(
getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
- Set<ComponentName> newComponents = buildComponentNameSet(newProviders);
+ Set<ComponentName> newComponents = buildComponentNameSet(newProviders, false);
+ Set<ComponentName> newPrimaryComponents = buildComponentNameSet(newProviders, true);
// Get the list of old components
- Set<ComponentName> oldComponents = buildComponentNameSet(mServices);
+ Set<ComponentName> oldComponents = buildComponentNameSet(mServices, false);
+ Set<ComponentName> oldPrimaryComponents = buildComponentNameSet(mServices, true);
// If the sets are equal then don't update the UI.
- if (oldComponents.equals(newComponents)) {
+ if (oldComponents.equals(newComponents)
+ && oldPrimaryComponents.equals(newPrimaryComponents)) {
return;
}
@@ -698,12 +721,22 @@
protected int getUser() {
if (mIsWorkProfile) {
- UserHandle workProfile = Utils.getManagedProfile(UserManager.get(mContext));
- return workProfile.getIdentifier();
+ UserHandle workProfile = getWorkProfileUserHandle();
+ if (workProfile != null) {
+ return workProfile.getIdentifier();
+ }
}
return UserHandle.myUserId();
}
+ private @Nullable UserHandle getWorkProfileUserHandle() {
+ if (mIsWorkProfile) {
+ return Utils.getManagedProfile(UserManager.get(mContext));
+ }
+
+ return null;
+ }
+
/** Called when the dialog button is clicked. */
private static interface DialogHost {
void onDialogClick(int whichButton);
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
index 1f57198..2c8ee8f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
@@ -27,12 +27,18 @@
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.toOriginalReason
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.channels.awaitClose
+import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -67,6 +73,16 @@
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
/**
+ * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
+ * enrollment. Returning the [FingerEnrollStateViewModel] that represents this fingerprint
+ * enrollment state.
+ */
+ suspend fun enroll(
+ hardwareAuthToken: ByteArray?,
+ enrollReason: EnrollReason,
+ ): Flow<FingerEnrollStateViewModel>
+
+ /**
* Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise
*/
@@ -133,6 +149,51 @@
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
+ override suspend fun enroll(
+ hardwareAuthToken: ByteArray?,
+ enrollReason: EnrollReason,
+ ): Flow<FingerEnrollStateViewModel> = callbackFlow {
+ var streamEnded = false
+ val enrollmentCallback =
+ object : FingerprintManager.EnrollmentCallback() {
+ override fun onEnrollmentProgress(remaining: Int) {
+ trySend(FingerEnrollStateViewModel.EnrollProgress(remaining)).onFailure { error ->
+ Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
+ }
+ if (remaining == 0) {
+ streamEnded = true
+ }
+ }
+
+ override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
+ trySend(FingerEnrollStateViewModel.EnrollHelp(helpMsgId, helpString.toString()))
+ .onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") }
+ }
+
+ override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
+ trySend(FingerEnrollStateViewModel.EnrollError(errMsgId, errString.toString()))
+ .onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") }
+ streamEnded = true
+ }
+ }
+
+ val cancellationSignal = CancellationSignal()
+ fingerprintManager.enroll(
+ hardwareAuthToken,
+ cancellationSignal,
+ applicationContext.userId,
+ enrollmentCallback,
+ enrollReason.toOriginalReason()
+ )
+ awaitClose {
+ // If the stream has not been ended, and the user has stopped collecting the flow
+ // before it was over, send cancel.
+ if (!streamEnded) {
+ cancellationSignal.cancel()
+ }
+ }
+ }
+
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
val callback =
object : RemovalCallback() {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
index f6d20ae..d43aeba 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -43,12 +43,12 @@
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollmentIntroV2Fragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
@@ -70,7 +70,7 @@
* children fragments.
*/
class FingerprintEnrollmentV2Activity : FragmentActivity() {
- private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
+ private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
private val coroutineDispatcher = Dispatchers.Default
@@ -170,18 +170,18 @@
navigationViewModel =
ViewModelProvider(
this,
- FingerprintEnrollmentNavigationViewModel.FingerprintEnrollmentNavigationViewModelFactory(
+ FingerprintEnrollNavigationViewModel.FingerprintEnrollNavigationViewModelFactory(
backgroundDispatcher,
interactor,
gatekeeperViewModel,
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
)
- )[FingerprintEnrollmentNavigationViewModel::class.java]
+ )[FingerprintEnrollNavigationViewModel::class.java]
// Initialize FingerprintViewModel
ViewModelProvider(
this,
- FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor)
+ FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor, backgroundDispatcher)
)[FingerprintEnrollViewModel::class.java]
// Initialize scroll view model
@@ -198,7 +198,7 @@
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
Enrollment -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
- Intro -> FingerprintEnrollmentIntroV2Fragment::class.java as Class<Fragment>
+ Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
else -> null
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt
index df4cf72..b12491f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollConfirmationV2Fragment.kt
@@ -19,7 +19,7 @@
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/**
* A fragment to indicate that fingerprint enrollment has been completed.
@@ -33,7 +33,7 @@
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+ ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
}
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
index 915aa1f..3f615ce 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollEnrollingV2Fragment.kt
@@ -19,7 +19,7 @@
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/** A fragment that is responsible for enrolling a users fingerprint. */
class FingerprintEnrollEnrollingV2Fragment : Fragment() {
@@ -28,7 +28,7 @@
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+ ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
}
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
index e9e1db2..beb84e9 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
@@ -20,7 +20,7 @@
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/**
* A fragment that is used to educate the user about the fingerprint sensor on this device.
@@ -36,7 +36,7 @@
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+ ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
}
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollmentIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
similarity index 97%
rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollmentIntroV2Fragment.kt
rename to src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
index f2f925b..03c7a5f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollmentIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -33,8 +33,8 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
@@ -72,10 +72,10 @@
* 2. How the data will be stored
* 3. How the user can access and remove their data
*/
-class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
+class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
private lateinit var footerBarMixin: FooterBarMixin
private lateinit var textModel: TextModel
- private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
+ private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
@@ -83,7 +83,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navigationViewModel =
- ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+ ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
fingerprintEnrollViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
fingerprintScrollViewModel =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/EnrollReason.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/EnrollReason.kt
new file mode 100644
index 0000000..87deeb3
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/EnrollReason.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.ui.enrollment.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+
+/**
+ * The reason for enrollment. Represents [FingerprintManager.EnrollReason]
+ */
+enum class EnrollReason {
+ /** The enroll happens on education screen. */
+ FindSensor,
+ /** The enroll happens on enrolling screen. */
+ EnrollEnrolling
+}
+
+/** Convert EnrollReason to original [FingerprintManager.EnrollReason]. */
+fun EnrollReason.toOriginalReason(): Int {
+ return when (this) {
+ EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
+ EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerEnrollStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerEnrollStateViewModel.kt
new file mode 100644
index 0000000..73343bd
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerEnrollStateViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.ui.enrollment.viewmodel
+
+import android.annotation.StringRes
+
+/**
+ * Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more
+ * information
+ */
+sealed class FingerEnrollStateViewModel {
+ /** Represents enrollment step progress. */
+ data class EnrollProgress(
+ val remainingSteps: Int,
+ ) : FingerEnrollStateViewModel()
+ /** Represents that recoverable error has been encountered during enrollment. */
+ data class EnrollHelp(
+ @StringRes val helpMsgId: Int,
+ val helpString: String,
+ ) : FingerEnrollStateViewModel()
+ /** Represents that an unrecoverable error has been encountered and the operation is complete. */
+ data class EnrollError(
+ @StringRes val errMsgId: Int,
+ val errString: String,
+ ) : FingerEnrollStateViewModel()
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
index 31fa03d..cb1beb9 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import androidx.lifecycle.ViewModel
@@ -21,13 +20,24 @@
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.toSensorType
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.flow.update
-/** Represents all of the fingerprint information needed for fingerprint enrollment. */
-class FingerprintEnrollViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
- ViewModel() {
+private const val TAG = "FingerprintEnrollViewModel"
+
+/** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
+class FingerprintEnrollViewModel(
+ private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+ backgroundDispatcher: CoroutineDispatcher,
+) : ViewModel() {
/** Represents the stream of [FingerprintSensorType] */
val sensorType: Flow<FingerprintSensorType> =
@@ -35,14 +45,55 @@
it.sensorType.toSensorType()
}
- class FingerprintEnrollViewModelFactory(val interactor: FingerprintManagerInteractor) :
- ViewModelProvider.Factory {
+ private var _enrollReason: MutableStateFlow<EnrollReason> =
+ MutableStateFlow(EnrollReason.FindSensor)
+ private var _hardwareAuthToken: MutableStateFlow<ByteArray?> = MutableStateFlow(null)
+ private var _consumerShouldEnroll: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ /**
+ * A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
+ * an enrollment process
+ */
+ val enrollFlow: Flow<FingerEnrollStateViewModel> =
+ combine(_consumerShouldEnroll, _hardwareAuthToken, _enrollReason) {
+ consumerShouldEnroll,
+ hardwareAuthToken,
+ enrollReason ->
+ Triple(consumerShouldEnroll, hardwareAuthToken, enrollReason)
+ }
+ .transformLatest {
+ // transformLatest() instead of transform() is used here for cancelling previous enroll()
+ // whenever |consumerShouldEnroll| is changed. Otherwise the latest value will be suspended
+ // since enroll() is an infinite callback flow.
+ (consumerShouldEnroll, hardwareAuthToken, enrollReason) ->
+ if (consumerShouldEnroll && hardwareAuthToken != null) {
+ fingerprintManagerInteractor.enroll(hardwareAuthToken, enrollReason).collect { emit(it) }
+ }
+ }
+ .flowOn(backgroundDispatcher)
+
+ /** Used to indicate the consumer of the view model is ready for an enrollment. */
+ fun startEnroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) {
+ _enrollReason.update { enrollReason }
+ _hardwareAuthToken.update { hardwareAuthToken }
+ // Update _consumerShouldEnroll after updating the other values.
+ _consumerShouldEnroll.update { true }
+ }
+
+ /** Used to indicate to stop the enrollment. */
+ fun stopEnroll() {
+ _consumerShouldEnroll.update { false }
+ }
+
+ class FingerprintEnrollViewModelFactory(
+ val interactor: FingerprintManagerInteractor,
+ val backgroundDispatcher: CoroutineDispatcher
+ ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
): T {
- return FingerprintEnrollViewModel(interactor) as T
+ return FingerprintEnrollViewModel(interactor, backgroundDispatcher) as T
}
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllmentNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
similarity index 95%
rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllmentNavigationViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
index 6a8a8c4..dafe545 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllmentNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
@@ -29,7 +29,7 @@
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-const val TAG = "FingerprintEnrollmentNavigationViewModel"
+private const val TAG = "FingerprintEnrollNavigationViewModel"
/** Interface to validate a gatekeeper hat */
interface Validator {
@@ -54,7 +54,7 @@
* This class is responsible for sending a [NavigationStep] which indicates where the user is in the
* Fingerprint Enrollment flow
*/
-class FingerprintEnrollmentNavigationViewModel(
+class FingerprintEnrollNavigationViewModel(
private val dispatcher: CoroutineDispatcher,
private val validator: Validator,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
@@ -131,7 +131,7 @@
}
}
- class FingerprintEnrollmentNavigationViewModelFactory(
+ class FingerprintEnrollNavigationViewModelFactory(
private val backgroundDispatcher: CoroutineDispatcher,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
@@ -143,7 +143,7 @@
modelClass: Class<T>,
): T {
- return FingerprintEnrollmentNavigationViewModel(
+ return FingerprintEnrollNavigationViewModel(
backgroundDispatcher,
object : Validator {
override fun validateGateKeeper(challenge: Long?): Boolean {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
index 5486e7a..fa4463a 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
@@ -29,6 +29,8 @@
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+private const val TAG = "FingerprintGatekeeperViewModel"
+
sealed interface GatekeeperInfo {
object Invalid : GatekeeperInfo
object Timeout : GatekeeperInfo
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsDataSyncController.java b/src/com/android/settings/bluetooth/BluetoothDetailsDataSyncController.java
index 5969ada..a8cd85f 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsDataSyncController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsDataSyncController.java
@@ -64,6 +64,7 @@
mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class);
mCompanionDeviceManager.getAllAssociations().stream().filter(
+ a -> a.getDeviceMacAddress() != null).filter(
a -> Objects.equal(mCachedDevice.getAddress(),
a.getDeviceMacAddress().toString().toUpperCase())).max(
Comparator.comparingLong(AssociationInfo::getTimeApprovedMs)).ifPresent(
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java b/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java
index 188b4ad..562a469 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java
@@ -31,6 +31,8 @@
import com.google.common.annotations.VisibleForTesting;
+import java.util.Set;
+
/**
* This class handles button preference logic to display for hearing aid device.
*/
@@ -91,7 +93,11 @@
}
private boolean getButtonPreferenceVisibility(CachedBluetoothDevice cachedDevice) {
- return isBinauralMode(cachedDevice) && isOnlyOneSideConnected(cachedDevice);
+ // The device is not connected yet. Don't show the button.
+ if (!cachedDevice.isConnectedHearingAidDevice()) {
+ return false;
+ }
+ return isBinauralMode(cachedDevice) && !isOtherSideConnected(cachedDevice);
}
private void launchPairingDetail() {
@@ -106,16 +112,25 @@
return cachedDevice.getDeviceMode() == HearingAidInfo.DeviceMode.MODE_BINAURAL;
}
- private boolean isOnlyOneSideConnected(CachedBluetoothDevice cachedDevice) {
- if (!cachedDevice.isConnectedAshaHearingAidDevice()) {
- return false;
+ private boolean isOtherSideConnected(CachedBluetoothDevice cachedDevice) {
+ // Check sub device for ASHA hearing aid
+ if (cachedDevice.isConnectedAshaHearingAidDevice()) {
+ final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+ if (subDevice != null && subDevice.isConnectedAshaHearingAidDevice()) {
+ return true;
+ }
}
- final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
- if (subDevice != null && subDevice.isConnectedAshaHearingAidDevice()) {
- return false;
+ // Check member device for LE audio hearing aid
+ if (cachedDevice.isConnectedLeAudioHearingAidDevice()) {
+ final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+ for (CachedBluetoothDevice memberDevice : memberDevices) {
+ if (memberDevice.isConnectedLeAudioHearingAidDevice()) {
+ return true;
+ }
+ }
}
- return true;
+ return false;
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 555868b..f473f2c 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -69,7 +69,9 @@
private static final String ENABLE_DUAL_MODE_AUDIO =
"persist.bluetooth.enable_dual_mode_audio";
private static final String CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT = "le_audio_enabled_by_default";
- private static final boolean LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE = true;
+ private static final boolean LE_AUDIO_TOGGLE_VISIBLE_DEFAULT_VALUE = true;
+ private static final String LE_AUDIO_TOGGLE_VISIBLE_PROPERTY =
+ "persist.bluetooth.leaudio.toggle_visible";
private LocalBluetoothManager mManager;
private LocalBluetoothProfileManager mProfileManager;
@@ -465,15 +467,13 @@
private void updateLeAudioConfig() {
mIsLeContactSharingEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
SettingsUIDeviceConfig.BT_LE_AUDIO_CONTACT_SHARING_ENABLED, true);
- boolean isLeDeviceDetailEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED,
- LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE);
+ boolean isLeAudioToggleVisible = SystemProperties.getBoolean(
+ LE_AUDIO_TOGGLE_VISIBLE_PROPERTY, LE_AUDIO_TOGGLE_VISIBLE_DEFAULT_VALUE);
boolean isLeEnabledByDefault = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT, false);
- mIsLeAudioToggleEnabled = isLeDeviceDetailEnabled || isLeEnabledByDefault;
+ mIsLeAudioToggleEnabled = isLeAudioToggleVisible || isLeEnabledByDefault;
Log.d(TAG, "BT_LE_AUDIO_CONTACT_SHARING_ENABLED:" + mIsLeContactSharingEnabled
- + ", BT_LE_AUDIO_DEVICE_DETAIL_ENABLED:" + isLeDeviceDetailEnabled
+ + ", LE_AUDIO_TOGGLE_VISIBLE_PROPERTY:" + isLeAudioToggleVisible
+ ", CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT:" + isLeEnabledByDefault);
}
diff --git a/src/com/android/settings/core/SettingsUIDeviceConfig.java b/src/com/android/settings/core/SettingsUIDeviceConfig.java
index 2401ff5..94074df 100644
--- a/src/com/android/settings/core/SettingsUIDeviceConfig.java
+++ b/src/com/android/settings/core/SettingsUIDeviceConfig.java
@@ -42,19 +42,4 @@
* {@code true} whether or not event_log for generic actions is enabled. Default is true.
*/
public static final String GENERIC_EVENT_LOGGING_ENABLED = "event_logging_enabled";
- /**
- * {@code true} whether to show LE Audio toggle in device detail page. Default is false.
- */
- public static final String BT_LE_AUDIO_DEVICE_DETAIL_ENABLED =
- "bt_le_audio_device_detail_enabled";
- /**
- * {@code true} if press and hold nav handle to search is enabled.
- */
- public static final String PRESS_HOLD_NAV_HANDLE_TO_SEARCH =
- "press_hold_nav_handle_to_search";
- /**
- * {@code true} if long press home button to search is enabled.
- */
- public static final String LONG_PRESS_HOME_BUTTON_TO_SEARCH =
- "long_press_home_button_to_search";
}
diff --git a/src/com/android/settings/datausage/AppDataUsagePreference.java b/src/com/android/settings/datausage/AppDataUsagePreference.java
index 2805819..d8c7392 100644
--- a/src/com/android/settings/datausage/AppDataUsagePreference.java
+++ b/src/com/android/settings/datausage/AppDataUsagePreference.java
@@ -38,6 +38,7 @@
public AppDataUsagePreference(Context context, AppItem item, int percent,
UidDetailProvider provider) {
super(context);
+ setKey("app_data_usage_" + item.key);
mItem = item;
mPercent = percent;
diff --git a/src/com/android/settings/datausage/ChartDataUsagePreference.java b/src/com/android/settings/datausage/ChartDataUsagePreference.java
index 12fb03b..e2a103e 100644
--- a/src/com/android/settings/datausage/ChartDataUsagePreference.java
+++ b/src/com/android/settings/datausage/ChartDataUsagePreference.java
@@ -56,8 +56,6 @@
private long mStart;
private long mEnd;
private NetworkCycleChartData mNetworkCycleChartData;
- private int mSecondaryColor;
- private int mSeriesColor;
public ChartDataUsagePreference(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -310,10 +308,4 @@
mEnd = data.getEndTime();
notifyChanged();
}
-
- public void setColors(int seriesColor, int secondaryColor) {
- mSeriesColor = seriesColor;
- mSecondaryColor = secondaryColor;
- notifyChanged();
- }
}
diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java
index b030219..39287c19 100644
--- a/src/com/android/settings/datausage/DataUsageList.java
+++ b/src/com/android/settings/datausage/DataUsageList.java
@@ -15,19 +15,15 @@
package com.android.settings.datausage;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
-import android.app.usage.NetworkStats;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkPolicy;
import android.net.NetworkTemplate;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
-import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.EventLog;
import android.util.Log;
@@ -46,25 +42,18 @@
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.Loader;
import androidx.preference.Preference;
-import androidx.preference.PreferenceGroup;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.datausage.CycleAdapter.SpinnerInterface;
-import com.android.settings.datausage.lib.AppDataUsageRepository;
import com.android.settings.network.MobileDataEnabledListener;
import com.android.settings.network.MobileNetworkRepository;
-import com.android.settings.network.ProxySubscriptionManager;
import com.android.settings.widget.LoadingViewController;
-import com.android.settingslib.AppItem;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import com.android.settingslib.net.NetworkCycleChartData;
import com.android.settingslib.net.NetworkCycleChartDataLoader;
-import com.android.settingslib.net.NetworkStatsSummaryLoader;
-import com.android.settingslib.net.UidDetailProvider;
import com.android.settingslib.utils.ThreadUtils;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -85,14 +74,11 @@
private static final String KEY_USAGE_AMOUNT = "usage_amount";
private static final String KEY_CHART_DATA = "chart_data";
- private static final String KEY_APPS_GROUP = "apps_group";
private static final String KEY_TEMPLATE = "template";
private static final String KEY_APP = "app";
@VisibleForTesting
static final int LOADER_CHART_DATA = 2;
- @VisibleForTesting
- static final int LOADER_SUMMARY = 3;
@VisibleForTesting
MobileDataEnabledListener mDataStateListener;
@@ -113,18 +99,15 @@
@Nullable
private List<NetworkCycleChartData> mCycleData;
- // Caches the cycles for startAppDataUsage usage, which need be cleared when resumed.
- private ArrayList<Long> mCycles;
// Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle,
// which need be cleared when resumed.
private CycleAdapter.CycleItem mLastDisplayedCycle;
- private UidDetailProvider mUidDetailProvider;
private CycleAdapter mCycleAdapter;
private Preference mUsageAmount;
- private PreferenceGroup mApps;
private View mHeader;
private MobileNetworkRepository mMobileNetworkRepository;
private SubscriptionInfoEntity mSubscriptionInfoEntity;
+ private DataUsageListAppsController mDataUsageListAppsController;
@Override
public int getMetricsCategory() {
@@ -148,14 +131,19 @@
return;
}
- mUidDetailProvider = new UidDetailProvider(activity);
mUsageAmount = findPreference(KEY_USAGE_AMOUNT);
mChart = findPreference(KEY_CHART_DATA);
- mApps = findPreference(KEY_APPS_GROUP);
processArgument();
+ if (mTemplate == null) {
+ Log.e(TAG, "No template; leaving");
+ finish();
+ return;
+ }
updateSubscriptionInfoEntity();
mDataStateListener = new MobileDataEnabledListener(activity, this);
+ mDataUsageListAppsController = use(DataUsageListAppsController.class);
+ mDataUsageListAppsController.init(mTemplate);
}
@Override
@@ -216,7 +204,6 @@
super.onResume();
mLoadingViewController.showLoadingViewDelayed();
mDataStateListener.start(mSubId);
- mCycles = null;
mLastDisplayedCycle = null;
// kick off loader for network history
@@ -224,8 +211,6 @@
// network history when showing app detail.
getLoaderManager().restartLoader(LOADER_CHART_DATA,
buildArgs(mTemplate), mNetworkCycleDataCallbacks);
-
- updateBody();
}
@Override
@@ -234,16 +219,6 @@
mDataStateListener.stop();
getLoaderManager().destroyLoader(LOADER_CHART_DATA);
- getLoaderManager().destroyLoader(LOADER_SUMMARY);
- }
-
- @Override
- public void onDestroy() {
- if (mUidDetailProvider != null) {
- mUidDetailProvider.clearCache();
- mUidDetailProvider = null;
- }
- super.onDestroy();
}
@Override
@@ -295,33 +270,6 @@
updatePolicy();
}
- /**
- * Update body content based on current tab. Loads network cycle data from system, and
- * binds them to visible controls.
- */
- private void updateBody() {
- if (!isAdded()) return;
-
- final Context context = getActivity();
-
- // detail mode can change visible menus, invalidate
- getActivity().invalidateOptionsMenu();
-
- int seriesColor = context.getColor(R.color.sim_noitification);
- if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- final SubscriptionInfo sir = ProxySubscriptionManager.getInstance(context)
- .getActiveSubscriptionInfo(mSubId);
-
- if (sir != null) {
- seriesColor = sir.getIconTint();
- }
- }
-
- final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor),
- Color.blue(seriesColor));
- mChart.setColors(seriesColor, secondaryColor);
- }
-
private Bundle buildArgs(NetworkTemplate template) {
final Bundle args = new Bundle();
args.putParcelable(KEY_TEMPLATE, template);
@@ -352,6 +300,7 @@
if (mCycleData != null) {
mCycleAdapter.updateCycleList(mCycleData);
}
+ mDataUsageListAppsController.setCycleData(mCycleData);
updateSelectedCycle();
}
@@ -402,67 +351,18 @@
if (LOGD) Log.d(TAG, "updateDetailData()");
// kick off loader for detailed stats
- getLoaderManager().restartLoader(LOADER_SUMMARY, null /* args */,
- mNetworkStatsDetailCallbacks);
+ mDataUsageListAppsController.update(
+ mSubscriptionInfoEntity == null ? null : mSubscriptionInfoEntity.carrierId,
+ mChart.getInspectStart(),
+ mChart.getInspectEnd()
+ );
final long totalBytes = mCycleData != null && !mCycleData.isEmpty()
- ? mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getTotalUsage() : 0;
+ ? mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getTotalUsage() : 0;
final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(getActivity(), totalBytes);
mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase));
}
- /**
- * Bind the given buckets.
- */
- private void bindStats(List<AppDataUsageRepository.Bucket> buckets) {
- mApps.removeAll();
- AppDataUsageRepository repository = new AppDataUsageRepository(
- requireContext(),
- ActivityManager.getCurrentUser(),
- mSubscriptionInfoEntity == null ? null : mSubscriptionInfoEntity.carrierId,
- appItem -> mUidDetailProvider.getUidDetail(appItem.key, true).packageName
- );
- for (var itemPercentPair : repository.getAppPercent(buckets)) {
- final AppDataUsagePreference preference = new AppDataUsagePreference(getContext(),
- itemPercentPair.getFirst(), itemPercentPair.getSecond(), mUidDetailProvider);
- preference.setOnPreferenceClickListener(p -> {
- AppDataUsagePreference pref = (AppDataUsagePreference) p;
- startAppDataUsage(pref.getItem());
- return true;
- });
- mApps.addPreference(preference);
- }
- }
-
- @VisibleForTesting
- void startAppDataUsage(AppItem item) {
- if (mCycleData == null) {
- return;
- }
- final Bundle args = new Bundle();
- args.putParcelable(AppDataUsage.ARG_APP_ITEM, item);
- args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate);
- if (mCycles == null) {
- mCycles = new ArrayList<>();
- for (NetworkCycleChartData data : mCycleData) {
- if (mCycles.isEmpty()) {
- mCycles.add(data.getEndTime());
- }
- mCycles.add(data.getStartTime());
- }
- }
- args.putSerializable(AppDataUsage.ARG_NETWORK_CYCLES, mCycles);
- args.putLong(AppDataUsage.ARG_SELECTED_CYCLE,
- mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getEndTime());
-
- new SubSettingLauncher(getContext())
- .setDestination(AppDataUsage.class.getName())
- .setTitleRes(R.string.data_usage_app_summary_title)
- .setArguments(args)
- .setSourceMetricsCategory(getMetricsCategory())
- .launch();
- }
-
private final OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
@@ -502,44 +402,6 @@
}
};
- private final LoaderCallbacks<NetworkStats> mNetworkStatsDetailCallbacks =
- new LoaderCallbacks<>() {
- @Override
- @NonNull
- public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
- return new NetworkStatsSummaryLoader.Builder(getContext())
- .setStartTime(mChart.getInspectStart())
- .setEndTime(mChart.getInspectEnd())
- .setNetworkTemplate(mTemplate)
- .build();
- }
-
- @Override
- public void onLoadFinished(
- @NonNull Loader<NetworkStats> loader, NetworkStats data) {
- bindStats(AppDataUsageRepository.Companion.convertToBuckets(data));
- updateEmptyVisible();
- }
-
- @Override
- public void onLoaderReset(@NonNull Loader<NetworkStats> loader) {
- mApps.removeAll();
- updateEmptyVisible();
- }
-
- private void updateEmptyVisible() {
- if ((mApps.getPreferenceCount() != 0)
- != (getPreferenceScreen().getPreferenceCount() != 0)) {
- if (mApps.getPreferenceCount() != 0) {
- getPreferenceScreen().addPreference(mUsageAmount);
- getPreferenceScreen().addPreference(mApps);
- } else {
- getPreferenceScreen().removeAll();
- }
- }
- }
- };
-
private static boolean isGuestUser(Context context) {
if (context == null) return false;
final UserManager userManager = context.getSystemService(UserManager.class);
diff --git a/src/com/android/settings/datausage/DataUsageListAppsController.kt b/src/com/android/settings/datausage/DataUsageListAppsController.kt
new file mode 100644
index 0000000..cc55e1a
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageListAppsController.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.datausage
+
+import android.app.ActivityManager
+import android.content.Context
+import android.net.NetworkTemplate
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.core.SubSettingLauncher
+import com.android.settings.datausage.lib.AppDataUsageRepository
+import com.android.settingslib.AppItem
+import com.android.settingslib.net.NetworkCycleChartData
+import com.android.settingslib.net.UidDetailProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class DataUsageListAppsController(context: Context, preferenceKey: String) :
+ BasePreferenceController(context, preferenceKey) {
+
+ private val uidDetailProvider = UidDetailProvider(context)
+ private lateinit var template: NetworkTemplate
+ private lateinit var repository: AppDataUsageRepository
+ private lateinit var preference: PreferenceGroup
+ private lateinit var lifecycleScope: LifecycleCoroutineScope
+
+ private var cycleData: List<NetworkCycleChartData>? = null
+
+ fun init(template: NetworkTemplate) {
+ this.template = template
+ repository = AppDataUsageRepository(
+ context = mContext,
+ currentUserId = ActivityManager.getCurrentUser(),
+ template = template,
+ ) { appItem: AppItem -> uidDetailProvider.getUidDetail(appItem.key, true).packageName }
+ }
+
+ override fun getAvailabilityStatus() = AVAILABLE
+
+ override fun displayPreference(screen: PreferenceScreen) {
+ super.displayPreference(screen)
+ preference = screen.findPreference(preferenceKey)!!
+ }
+
+ override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+ lifecycleScope = viewLifecycleOwner.lifecycleScope
+ }
+
+ fun setCycleData(cycleData: List<NetworkCycleChartData>?) {
+ this.cycleData = cycleData
+ }
+
+ fun update(carrierId: Int?, startTime: Long, endTime: Long) = lifecycleScope.launch {
+ val apps = withContext(Dispatchers.Default) {
+ repository.getAppPercent(carrierId, startTime, endTime).map { (appItem, percent) ->
+ AppDataUsagePreference(mContext, appItem, percent, uidDetailProvider).apply {
+ setOnPreferenceClickListener {
+ startAppDataUsage(appItem, endTime)
+ true
+ }
+ }
+ }
+ }
+ preference.removeAll()
+ for (app in apps) {
+ preference.addPreference(app)
+ }
+ }
+
+ @VisibleForTesting
+ fun startAppDataUsage(item: AppItem, endTime: Long) {
+ val cycleData = cycleData ?: return
+ val args = Bundle().apply {
+ putParcelable(AppDataUsage.ARG_APP_ITEM, item)
+ putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, template)
+ val cycles = ArrayList<Long>().apply {
+ for (data in cycleData) {
+ if (isEmpty()) add(data.endTime)
+ add(data.startTime)
+ }
+ }
+ putSerializable(AppDataUsage.ARG_NETWORK_CYCLES, cycles)
+ putLong(AppDataUsage.ARG_SELECTED_CYCLE, endTime)
+ }
+ SubSettingLauncher(mContext).apply {
+ setDestination(AppDataUsage::class.java.name)
+ setTitleRes(R.string.data_usage_app_summary_title)
+ setArguments(args)
+ setSourceMetricsCategory(metricsCategory)
+ }.launch()
+ }
+}
diff --git a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
index 3813af5..074a555 100644
--- a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
+++ b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
@@ -17,11 +17,15 @@
package com.android.settings.datausage.lib
import android.app.usage.NetworkStats
+import android.app.usage.NetworkStatsManager
import android.content.Context
import android.net.NetworkPolicyManager
+import android.net.NetworkTemplate
import android.os.Process
import android.os.UserHandle
+import android.util.Log
import android.util.SparseArray
+import androidx.annotation.VisibleForTesting
import com.android.settings.R
import com.android.settingslib.AppItem
import com.android.settingslib.net.UidDetailProvider
@@ -30,15 +34,18 @@
class AppDataUsageRepository(
private val context: Context,
private val currentUserId: Int,
- private val carrierId: Int?,
- private val getPackageName: (AppItem) -> String,
+ private val template: NetworkTemplate,
+ private val getPackageName: (AppItem) -> String?,
) {
- data class Bucket(
- val uid: Int,
- val bytes: Long,
- )
+ private val networkStatsManager = context.getSystemService(NetworkStatsManager::class.java)!!
- fun getAppPercent(buckets: List<Bucket>): List<Pair<AppItem, Int>> {
+ fun getAppPercent(carrierId: Int?, startTime: Long, endTime: Long): List<Pair<AppItem, Int>> {
+ val networkStats = querySummary(startTime, endTime) ?: return emptyList()
+ return getAppPercent(carrierId, convertToBuckets(networkStats))
+ }
+
+ @VisibleForTesting
+ fun getAppPercent(carrierId: Int?, buckets: List<Bucket>): List<Pair<AppItem, Int>> {
val items = ArrayList<AppItem>()
val knownItems = SparseArray<AppItem>()
val profiles = context.userManager.userProfiles
@@ -61,7 +68,7 @@
item.restricted = true
}
- val filteredItems = filterItems(items).sorted()
+ val filteredItems = filterItems(carrierId, items).sorted()
val largest: Long = filteredItems.maxOfOrNull { it.total } ?: 0
return filteredItems.map { item ->
val percentTotal = if (largest > 0) (item.total * 100 / largest).toInt() else 0
@@ -69,7 +76,14 @@
}
}
- private fun filterItems(items: List<AppItem>): List<AppItem> {
+ private fun querySummary(startTime: Long, endTime: Long): NetworkStats? = try {
+ networkStatsManager.querySummary(template, startTime, endTime)
+ } catch (e: RuntimeException) {
+ Log.e(TAG, "Exception querying network detail.", e)
+ null
+ }
+
+ private fun filterItems(carrierId: Int?, items: List<AppItem>): List<AppItem> {
// When there is no specified SubscriptionInfo, Wi-Fi data usage will be displayed.
// In this case, the carrier service package also needs to be hidden.
if (carrierId != null && carrierId !in context.resources.getIntArray(
@@ -178,7 +192,15 @@
}
companion object {
- fun convertToBuckets(stats: NetworkStats): List<Bucket> {
+ private const val TAG = "AppDataUsageRepository"
+
+ @VisibleForTesting
+ data class Bucket(
+ val uid: Int,
+ val bytes: Long,
+ )
+
+ private fun convertToBuckets(stats: NetworkStats): List<Bucket> {
val buckets = mutableListOf<Bucket>()
stats.use {
val bucket = NetworkStats.Bucket()
diff --git a/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java
index 298ced0..980bdaa 100644
--- a/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
+import android.os.SystemProperties;
import android.provider.DeviceConfig;
import androidx.annotation.VisibleForTesting;
@@ -27,7 +28,6 @@
import androidx.preference.SwitchPreference;
import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settingslib.development.DeveloperOptionsPreferenceController;
/**
@@ -40,9 +40,12 @@
private static final String PREFERENCE_KEY = "bluetooth_show_leaudio_device_details";
private static final String CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT = "le_audio_enabled_by_default";
- private static final boolean LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE = true;
+ private static final boolean LE_AUDIO_TOGGLE_VISIBLE_DEFAULT_VALUE = true;
static int sLeAudioSupportedStateCache = BluetoothStatusCodes.ERROR_UNKNOWN;
+ static final String LE_AUDIO_TOGGLE_VISIBLE_PROPERTY =
+ "persist.bluetooth.leaudio.toggle_visible";
+
@VisibleForTesting
BluetoothAdapter mBluetoothAdapter;
@@ -73,10 +76,7 @@
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean isEnabled = (Boolean) newValue;
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED,
- isEnabled ? "true" : "false", LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE);
+ SystemProperties.set(LE_AUDIO_TOGGLE_VISIBLE_PROPERTY, Boolean.toString(isEnabled));
return true;
}
@@ -86,25 +86,13 @@
return;
}
- final boolean leAudioDeviceDetailEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED,
- LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE);
+ final boolean isLeAudioToggleVisible = SystemProperties.getBoolean(
+ LE_AUDIO_TOGGLE_VISIBLE_PROPERTY, LE_AUDIO_TOGGLE_VISIBLE_DEFAULT_VALUE);
final boolean leAudioEnabledByDefault = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_BLUETOOTH, CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT, false);
mPreference.setEnabled(!leAudioEnabledByDefault);
- ((SwitchPreference) mPreference).setChecked(leAudioDeviceDetailEnabled
+ ((SwitchPreference) mPreference).setChecked(isLeAudioToggleVisible
|| leAudioEnabledByDefault);
}
-
- @Override
- protected void onDeveloperOptionsSwitchDisabled() {
- super.onDeveloperOptionsSwitchDisabled();
- // Reset the toggle to null when the developer option is disabled
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED, "null",
- LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE);
- }
}
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
index ed8cc62..9e970d2 100644
--- a/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetector.java
@@ -23,8 +23,6 @@
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
-import java.util.concurrent.TimeUnit;
-
/**
* Detect whether the battery is too low
*/
@@ -46,9 +44,7 @@
@Override
public BatteryTip detect() {
- final boolean lowBattery = mBatteryInfo.batteryLevel <= mWarningLevel
- || (mBatteryInfo.discharging && mBatteryInfo.remainingTimeUs != 0
- && mBatteryInfo.remainingTimeUs < TimeUnit.HOURS.toMicros(mPolicy.lowBatteryHour));
+ final boolean lowBattery = mBatteryInfo.batteryLevel <= mWarningLevel;
final boolean lowBatteryEnabled = mPolicy.lowBatteryEnabled && !mIsPowerSaveMode;
final boolean dischargingLowBatteryState =
mPolicy.testLowBatteryTip || (mBatteryInfo.discharging && lowBattery);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java b/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java
new file mode 100644
index 0000000..2f139ec
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/AnomalyAppItemPreference.java
@@ -0,0 +1,60 @@
+/*
+ * 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.text.TextUtils;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+class AnomalyAppItemPreference extends PowerGaugePreference {
+
+ private static final String TAG = "AnomalyAppItemPreference";
+
+ private CharSequence mAnomalyHintText;
+
+ AnomalyAppItemPreference(Context context) {
+ super(context, /* attrs */ null);
+ setLayoutResource(R.layout.anomaly_app_item_preference);
+ }
+
+ void setAnomalyHint(CharSequence anomalyHintText) {
+ if (!TextUtils.equals(mAnomalyHintText, anomalyHintText)) {
+ mAnomalyHintText = anomalyHintText;
+ notifyChanged();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder viewHolder) {
+ super.onBindViewHolder(viewHolder);
+ final LinearLayout warningChipView =
+ (LinearLayout) viewHolder.findViewById(R.id.warning_chip);
+
+ if (!TextUtils.isEmpty(mAnomalyHintText)) {
+ ((TextView) warningChipView.findViewById(R.id.warning_info)).setText(mAnomalyHintText);
+ warningChipView.setVisibility(View.VISIBLE);
+ } else {
+ warningChipView.setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java b/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java
new file mode 100644
index 0000000..d535490
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java
@@ -0,0 +1,231 @@
+/*
+ * 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.Bundle;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.core.SubSettingLauncher;
+
+import java.util.function.Function;
+
+final class AnomalyEventWrapper {
+ private static final String TAG = "AnomalyEventWrapper";
+
+ private final Context mContext;
+ private final PowerAnomalyEvent mPowerAnomalyEvent;
+
+ private final int mCardStyleId;
+ private final int mResourceIndex;
+
+ private SubSettingLauncher mSubSettingLauncher = null;
+ private Pair<Integer, Integer> mHighlightSlotPair = null;
+ private BatteryDiffEntry mRelatedBatteryDiffEntry = null;
+
+ AnomalyEventWrapper(Context context, PowerAnomalyEvent powerAnomalyEvent) {
+ mContext = context;
+ mPowerAnomalyEvent = powerAnomalyEvent;
+ // Set basic battery tips card info
+ mCardStyleId = mPowerAnomalyEvent.getType().getNumber();
+ mResourceIndex = mPowerAnomalyEvent.getKey().getNumber();
+ }
+
+ private <T> T getInfo(Function<WarningBannerInfo, T> warningBannerInfoSupplier,
+ Function<WarningItemInfo, T> warningItemInfoSupplier) {
+ if (warningBannerInfoSupplier != null && mPowerAnomalyEvent.hasWarningBannerInfo()) {
+ return warningBannerInfoSupplier.apply(mPowerAnomalyEvent.getWarningBannerInfo());
+ } else if (warningItemInfoSupplier != null && mPowerAnomalyEvent.hasWarningItemInfo()) {
+ return warningItemInfoSupplier.apply(mPowerAnomalyEvent.getWarningItemInfo());
+ }
+ return null;
+ }
+
+ private int getResourceId(int resourceId, int resourceIndex, String defType) {
+ final String key = getStringFromArrayResource(resourceId, resourceIndex);
+ return TextUtils.isEmpty(key) ? 0
+ : mContext.getResources().getIdentifier(key, defType, mContext.getPackageName());
+ }
+
+ private String getString(Function<WarningBannerInfo, String> warningBannerInfoSupplier,
+ Function<WarningItemInfo, String> warningItemInfoSupplier,
+ int resourceId, int resourceIndex) {
+ final String string = getInfo(warningBannerInfoSupplier, warningItemInfoSupplier);
+ return (!TextUtils.isEmpty(string) || resourceId <= 0) ? string
+ : getStringFromArrayResource(resourceId, resourceIndex);
+ }
+
+ private String getStringFromArrayResource(int resourceId, int resourceIndex) {
+ if (resourceId <= 0 || resourceIndex < 0) {
+ return null;
+ }
+ final String[] stringArray = mContext.getResources().getStringArray(resourceId);
+ return (resourceIndex >= 0 && resourceIndex < stringArray.length)
+ ? stringArray[resourceIndex] : null;
+ }
+
+ void setRelatedBatteryDiffEntry(BatteryDiffEntry batteryDiffEntry) {
+ mRelatedBatteryDiffEntry = batteryDiffEntry;
+ }
+
+ String getEventId() {
+ return mPowerAnomalyEvent.hasEventId() ? mPowerAnomalyEvent.getEventId() : null;
+ }
+
+ int getIconResId() {
+ return getResourceId(R.array.battery_tips_card_icons, mCardStyleId, "drawable");
+ }
+
+ int getColorResId() {
+ return getResourceId(R.array.battery_tips_card_colors, mCardStyleId, "color");
+ }
+
+ String getTitleString() {
+ final String protoTitleString = getInfo(WarningBannerInfo::getTitleString,
+ WarningItemInfo::getTitleString);
+ if (!TextUtils.isEmpty(protoTitleString)) {
+ return protoTitleString;
+ }
+ final int titleFormatResId = getResourceId(R.array.power_anomaly_title_ids,
+ mResourceIndex, "string");
+ if (mPowerAnomalyEvent.hasWarningBannerInfo()) {
+ return mContext.getString(titleFormatResId);
+ } else if (mPowerAnomalyEvent.hasWarningItemInfo() && mRelatedBatteryDiffEntry != null) {
+ final String appLabel = mRelatedBatteryDiffEntry.getAppLabel();
+ return mContext.getString(titleFormatResId, appLabel);
+ }
+ return null;
+ }
+
+ String getMainBtnString() {
+ return getString(WarningBannerInfo::getMainButtonString,
+ WarningItemInfo::getMainButtonString,
+ R.array.power_anomaly_main_btn_strings, mResourceIndex);
+ }
+
+ String getDismissBtnString() {
+ return getString(WarningBannerInfo::getCancelButtonString,
+ WarningItemInfo::getCancelButtonString,
+ R.array.power_anomaly_dismiss_btn_strings, mResourceIndex);
+ }
+
+ String getAnomalyHintString() {
+ return getStringFromArrayResource(R.array.power_anomaly_hint_messages, mResourceIndex);
+ }
+
+ String getDismissRecordKey() {
+ return mPowerAnomalyEvent.getDismissRecordKey();
+ }
+
+ boolean hasAnomalyEntryKey() {
+ return getAnomalyEntryKey() != null;
+ }
+
+ String getAnomalyEntryKey() {
+ return mPowerAnomalyEvent.hasWarningItemInfo()
+ && mPowerAnomalyEvent.getWarningItemInfo().hasItemKey()
+ ? mPowerAnomalyEvent.getWarningItemInfo().getItemKey() : null;
+ }
+
+ boolean hasSubSettingLauncher() {
+ if (mSubSettingLauncher == null) {
+ mSubSettingLauncher = getSubSettingLauncher();
+ }
+ return mSubSettingLauncher != null;
+ }
+
+ SubSettingLauncher getSubSettingLauncher() {
+ if (mSubSettingLauncher != null) {
+ return mSubSettingLauncher;
+ }
+ final String destinationClassName = getInfo(
+ WarningBannerInfo::getMainButtonDestination, null);
+ if (!TextUtils.isEmpty(destinationClassName)) {
+ final Integer sourceMetricsCategory = getInfo(
+ WarningBannerInfo::getMainButtonSourceMetricsCategory, null);
+ final String preferenceHighlightKey = getInfo(
+ WarningBannerInfo::getMainButtonSourceHighlightKey, null);
+ Bundle arguments = Bundle.EMPTY;
+ if (!TextUtils.isEmpty(preferenceHighlightKey)) {
+ arguments = new Bundle(1);
+ arguments.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
+ preferenceHighlightKey);
+ }
+ mSubSettingLauncher = new SubSettingLauncher(mContext)
+ .setDestination(destinationClassName)
+ .setSourceMetricsCategory(sourceMetricsCategory)
+ .setArguments(arguments);
+ }
+ return mSubSettingLauncher;
+ }
+
+ boolean hasHighlightSlotPair(BatteryLevelData batteryLevelData) {
+ if (mHighlightSlotPair == null) {
+ mHighlightSlotPair = getHighlightSlotPair(batteryLevelData);
+ }
+ return mHighlightSlotPair != null;
+ }
+
+ Pair<Integer, Integer> getHighlightSlotPair(BatteryLevelData batteryLevelData) {
+ if (mHighlightSlotPair != null) {
+ return mHighlightSlotPair;
+ }
+ if (!mPowerAnomalyEvent.hasWarningItemInfo()) {
+ return null;
+ }
+ final WarningItemInfo warningItemInfo = mPowerAnomalyEvent.getWarningItemInfo();
+ final Long startTimestamp = warningItemInfo.hasStartTimestamp()
+ ? warningItemInfo.getStartTimestamp() : null;
+ final Long endTimestamp = warningItemInfo.hasEndTimestamp()
+ ? warningItemInfo.getEndTimestamp() : null;
+ if (startTimestamp != null && endTimestamp != null) {
+ mHighlightSlotPair = batteryLevelData
+ .getIndexByTimestamps(startTimestamp, endTimestamp);
+ if (mHighlightSlotPair.first == BatteryChartViewModel.SELECTED_INDEX_INVALID
+ || mHighlightSlotPair.second == BatteryChartViewModel.SELECTED_INDEX_INVALID) {
+ // Drop invalid mHighlightSlotPair index
+ mHighlightSlotPair = null;
+ }
+ }
+ return mHighlightSlotPair;
+ }
+
+ boolean updateTipsCardPreference(BatteryTipsCardPreference preference) {
+ final String titleString = getTitleString();
+ if (TextUtils.isEmpty(titleString)) {
+ return false;
+ }
+ preference.setTitle(titleString);
+ preference.setIconResourceId(getIconResId());
+ preference.setMainButtonStrokeColorResourceId(getColorResId());
+ preference.setMainButtonLabel(getMainBtnString());
+ preference.setDismissButtonLabel(getDismissBtnString());
+ return true;
+ }
+
+ boolean launchSubSetting() {
+ if (!hasSubSettingLauncher()) {
+ return false;
+ }
+ // Navigate to sub setting page
+ mSubSettingLauncher.launch();
+ return true;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 75b7c08..1ae3bef 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -221,14 +221,20 @@
refreshUi();
}
+ boolean isHighlightSlotFocused() {
+ return (mDailyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
+ && mDailyHighlightSlotIndex == mDailyChartIndex
+ && mHourlyHighlightSlotIndex != BatteryChartViewModel.SELECTED_INDEX_INVALID
+ && mHourlyHighlightSlotIndex == mHourlyChartIndex);
+ }
+
void onHighlightSlotIndexUpdate(int dailyHighlightSlotIndex, int hourlyHighlightSlotIndex) {
- if (mDailyHighlightSlotIndex == dailyHighlightSlotIndex
- && mHourlyHighlightSlotIndex == hourlyHighlightSlotIndex) {
- return;
- }
mDailyHighlightSlotIndex = dailyHighlightSlotIndex;
mHourlyHighlightSlotIndex = hourlyHighlightSlotIndex;
refreshUi();
+ if (mOnSelectedIndexUpdatedListener != null) {
+ mOnSelectedIndexUpdatedListener.onSelectedIndexUpdated();
+ }
}
void selectHighlightSlotIndex() {
@@ -405,7 +411,7 @@
final String slotInformation = getSlotInformation();
return slotInformation == null
? mPrefContext.getString(
- R.string.battery_usage_breakdown_title_since_last_full_charge)
+ R.string.battery_usage_breakdown_title_since_last_full_charge)
: mPrefContext.getString(
R.string.battery_usage_breakdown_title_for_slot, slotInformation);
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
index e5cff20..2c799fa 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
@@ -120,12 +120,10 @@
public void onClick(View view) {
final int viewId = view.getId();
if (viewId == R.id.main_button || viewId == R.id.tips_card) {
- setVisible(false);
if (mOnConfirmListener != null) {
mOnConfirmListener.onConfirm();
}
} else if (viewId == R.id.dismiss_button) {
- setVisible(false);
if (mOnRejectListener != null) {
mOnRejectListener.onReject();
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
index b3a3508..44b2421 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
@@ -18,21 +18,15 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.os.Bundle;
import android.text.TextUtils;
import androidx.preference.PreferenceScreen;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.settings.R;
-import com.android.settings.SettingsActivity;
import com.android.settings.core.BasePreferenceController;
-import com.android.settings.core.SubSettingLauncher;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import java.util.function.Function;
-
/** Controls the update for battery tips card */
public class BatteryTipsController extends BasePreferenceController {
@@ -59,6 +53,10 @@
@VisibleForTesting
BatteryTipsCardPreference mCardPreference;
+ @VisibleForTesting
+ AnomalyEventWrapper mAnomalyEventWrapper = null;
+ @VisibleForTesting
+ Boolean mIsAcceptable = false;
public BatteryTipsController(Context context) {
super(context, ROOT_PREFERENCE_KEY);
@@ -85,132 +83,56 @@
mOnAnomalyRejectListener = listener;
}
- private <T> T getInfo(PowerAnomalyEvent powerAnomalyEvent,
- Function<WarningBannerInfo, T> warningBannerInfoSupplier,
- Function<WarningItemInfo, T> warningItemInfoSupplier) {
- if (warningBannerInfoSupplier != null && powerAnomalyEvent.hasWarningBannerInfo()) {
- return warningBannerInfoSupplier.apply(powerAnomalyEvent.getWarningBannerInfo());
- } else if (warningItemInfoSupplier != null && powerAnomalyEvent.hasWarningItemInfo()) {
- return warningItemInfoSupplier.apply(powerAnomalyEvent.getWarningItemInfo());
+ void acceptTipsCard() {
+ if (mAnomalyEventWrapper == null || !mIsAcceptable) {
+ return;
}
- return null;
- }
-
- private String getStringFromResource(int resourceId, int resourceIndex) {
- if (resourceId < 0) {
- return null;
+ // For anomaly events with same record key, dismissed until next time full charged.
+ final String dismissRecordKey = mAnomalyEventWrapper.getDismissRecordKey();
+ if (!TextUtils.isEmpty(dismissRecordKey)) {
+ DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, dismissRecordKey);
}
- final String[] stringArray = mContext.getResources().getStringArray(resourceId);
- return (resourceIndex >= 0 && resourceIndex < stringArray.length)
- ? stringArray[resourceIndex] : null;
+ mCardPreference.setVisible(false);
+ mMetricsFeatureProvider.action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT,
+ mAnomalyEventWrapper.getEventId());
}
- private int getResourceId(int resourceId, int resourceIndex, String defType) {
- final String key = getStringFromResource(resourceId, resourceIndex);
- return TextUtils.isEmpty(key) ? 0
- : mContext.getResources().getIdentifier(key, defType, mContext.getPackageName());
- }
-
- private String getString(PowerAnomalyEvent powerAnomalyEvent,
- Function<WarningBannerInfo, String> warningBannerInfoSupplier,
- Function<WarningItemInfo, String> warningItemInfoSupplier,
- int resourceId, int resourceIndex) {
- String string =
- getInfo(powerAnomalyEvent, warningBannerInfoSupplier, warningItemInfoSupplier);
- return (!TextUtils.isEmpty(string) || resourceId < 0) ? string
- : getStringFromResource(resourceId, resourceIndex);
- }
-
- /** Generate a key string of current anomaly to record as dismissed in sharedPreferences. */
- public static String getDismissRecordKey(PowerAnomalyEvent event) {
- if (!event.hasKey()) {
- return null;
- }
- switch (event.getKey()){
- case KEY_APP:
- return event.hasWarningItemInfo()
- && event.getWarningItemInfo().hasDismissRecordKey()
- ? event.getWarningItemInfo().getDismissRecordKey() : null;
- default:
- return event.getKey().name();
- }
- }
-
- void handleBatteryTipsCardUpdated(PowerAnomalyEvent powerAnomalyEvent) {
- if (powerAnomalyEvent == null) {
+ void handleBatteryTipsCardUpdated(
+ AnomalyEventWrapper anomalyEventWrapper, boolean isAcceptable) {
+ mAnomalyEventWrapper = anomalyEventWrapper;
+ mIsAcceptable = isAcceptable;
+ if (mAnomalyEventWrapper == null) {
mCardPreference.setVisible(false);
return;
}
- // Get card icon and color styles
- final int cardStyleId = powerAnomalyEvent.getType().getNumber();
- final int iconResId = getResourceId(
- R.array.battery_tips_card_icons, cardStyleId, "drawable");
- final int colorResId = getResourceId(
- R.array.battery_tips_card_colors, cardStyleId, "color");
-
// Get card preference strings and navigate fragment info
- final String eventId = powerAnomalyEvent.hasEventId()
- ? powerAnomalyEvent.getEventId() : null;
- final PowerAnomalyKey powerAnomalyKey = powerAnomalyEvent.hasKey()
- ? powerAnomalyEvent.getKey() : null;
- final int resourceIndex = powerAnomalyKey != null ? powerAnomalyKey.getNumber() : -1;
+ final String eventId = mAnomalyEventWrapper.getEventId();
- final String titleString = getString(powerAnomalyEvent, WarningBannerInfo::getTitleString,
- WarningItemInfo::getTitleString, R.array.power_anomaly_titles, resourceIndex);
- if (titleString.isEmpty()) {
+ // Update card & buttons preference
+ if (!mAnomalyEventWrapper.updateTipsCardPreference(mCardPreference)) {
mCardPreference.setVisible(false);
return;
}
- final String mainBtnString = getString(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonString, WarningItemInfo::getMainButtonString,
- R.array.power_anomaly_main_btn_strings, resourceIndex);
- final String dismissBtnString = getString(powerAnomalyEvent,
- WarningBannerInfo::getCancelButtonString, WarningItemInfo::getCancelButtonString,
- R.array.power_anomaly_dismiss_btn_strings, resourceIndex);
-
- final String destinationClassName = getInfo(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonDestination, null);
- final Integer sourceMetricsCategory = getInfo(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonSourceMetricsCategory, null);
- final String preferenceHighlightKey = getInfo(powerAnomalyEvent,
- WarningBannerInfo::getMainButtonSourceHighlightKey, null);
-
- // Update card preference and main button fragment launcher
- mCardPreference.setTitle(titleString);
- mCardPreference.setIconResourceId(iconResId);
- mCardPreference.setMainButtonStrokeColorResourceId(colorResId);
- mCardPreference.setMainButtonLabel(mainBtnString);
- mCardPreference.setDismissButtonLabel(dismissBtnString);
-
// Set battery tips card listener
mCardPreference.setOnConfirmListener(() -> {
+ mCardPreference.setVisible(false);
if (mOnAnomalyConfirmListener != null) {
mOnAnomalyConfirmListener.onAnomalyConfirm();
- } else if (!TextUtils.isEmpty(destinationClassName)) {
- // Navigate to sub setting page
- Bundle arguments = Bundle.EMPTY;
- if (!TextUtils.isEmpty(preferenceHighlightKey)) {
- arguments = new Bundle(1);
- arguments.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
- preferenceHighlightKey);
- }
- new SubSettingLauncher(mContext)
- .setDestination(destinationClassName)
- .setSourceMetricsCategory(sourceMetricsCategory)
- .setArguments(arguments)
- .launch();
+ } else if (mAnomalyEventWrapper.launchSubSetting()) {
+ mMetricsFeatureProvider.action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, eventId);
}
- mMetricsFeatureProvider.action(
- mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, eventId);
});
mCardPreference.setOnRejectListener(() -> {
+ mCardPreference.setVisible(false);
if (mOnAnomalyRejectListener != null) {
mOnAnomalyRejectListener.onAnomalyReject();
}
// For anomaly events with same record key, dismissed until next time full charged.
- final String dismissRecordKey = getDismissRecordKey(powerAnomalyEvent);
+ final String dismissRecordKey = mAnomalyEventWrapper.getDismissRecordKey();
if (!TextUtils.isEmpty(dismissRecordKey)) {
DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, dismissRecordKey);
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
index d51485a..4db4d3b 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
@@ -53,6 +53,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
/** Controller for battery usage breakdown preference group. */
@@ -93,6 +94,14 @@
BatteryDiffData mBatteryDiffData;
@VisibleForTesting
String mPercentLessThanThresholdText;
+ @VisibleForTesting
+ boolean mIsHighlightSlot;
+ @VisibleForTesting
+ String mAnomalyEventId;
+ @VisibleForTesting
+ String mAnomalyEntryKey;
+ @VisibleForTesting
+ String mAnomalyHintString;
public BatteryUsageBreakdownController(
Context context, Lifecycle lifecycle, SettingsActivity activity,
@@ -137,6 +146,12 @@
return false;
}
+ private String getActionKey(String packageName) {
+ final String actionKey = TextUtils.isEmpty(packageName)
+ ? PACKAGE_NAME_NONE : packageName;
+ return mAnomalyEventId == null ? actionKey : actionKey + "|" + mAnomalyEventId;
+ }
+
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (!(preference instanceof PowerGaugePreference)) {
@@ -151,7 +166,7 @@
? SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM
: SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM,
/* pageId */ SettingsEnums.OPEN_BATTERY_USAGE,
- TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName,
+ getActionKey(packageName),
(int) Math.round(diffEntry.getPercentage()));
Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s",
diffEntry.getAppLabel(), diffEntry.getKey(), packageName));
@@ -211,9 +226,23 @@
* used when showing the footer.
*/
void handleBatteryUsageUpdated(
- BatteryDiffData slotUsageData, String slotTimestamp, boolean isAllUsageDataEmpty) {
+ BatteryDiffData slotUsageData, String slotTimestamp,
+ boolean isAllUsageDataEmpty, boolean isHighlightSlot,
+ Optional<AnomalyEventWrapper> optionalAnomalyEventWrapper) {
mBatteryDiffData = slotUsageData;
mSlotTimestamp = slotTimestamp;
+ mIsHighlightSlot = isHighlightSlot;
+
+ if (optionalAnomalyEventWrapper != null) {
+ final AnomalyEventWrapper anomalyEventWrapper =
+ optionalAnomalyEventWrapper.orElse(null);
+ mAnomalyEventId = anomalyEventWrapper != null
+ ? anomalyEventWrapper.getEventId() : null;
+ mAnomalyEntryKey = anomalyEventWrapper != null
+ ? anomalyEventWrapper.getAnomalyEntryKey() : null;
+ mAnomalyHintString = anomalyEventWrapper != null
+ ? anomalyEventWrapper.getAnomalyHintString() : null;
+ }
showCategoryTitle(slotTimestamp);
showSpinnerAndAppList();
@@ -278,15 +307,15 @@
continue;
}
final String prefKey = entry.getKey();
- PowerGaugePreference pref = mAppListPreferenceGroup.findPreference(prefKey);
+ AnomalyAppItemPreference pref = mAppListPreferenceGroup.findPreference(prefKey);
if (pref != null) {
isAdded = true;
} else {
- pref = (PowerGaugePreference) mPreferenceCache.get(prefKey);
+ pref = (AnomalyAppItemPreference) mPreferenceCache.get(prefKey);
}
- // Creates new innstance if cached preference is not found.
+ // Creates new instance if cached preference is not found.
if (pref == null) {
- pref = new PowerGaugePreference(mPrefContext);
+ pref = new AnomalyAppItemPreference(mPrefContext);
pref.setKey(prefKey);
mPreferenceCache.put(prefKey, pref);
}
@@ -294,6 +323,10 @@
pref.setTitle(appLabel);
pref.setOrder(prefIndex);
pref.setSingleLineTitle(true);
+ // Updates App item preference style
+ pref.setAnomalyHint(mIsHighlightSlot && mAnomalyEntryKey != null
+ && mAnomalyEntryKey.equals(entry.getKey())
+ ? mAnomalyHintString : null);
// Sets the BatteryDiffEntry to preference for launching detailed page.
pref.setBatteryDiffEntry(entry);
pref.setSelectable(entry.validForRestriction());
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index 1cbf2a3..fd0f866 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -116,8 +116,16 @@
final Handler handler = new Handler(Looper.getMainLooper());
final BatteryLevelData batteryLevelData = DataProcessManager.getBatteryLevelData(
context, handler, /*isFromPeriodJob=*/ true,
- batteryDiffDataMap -> DatabaseUtils.sendBatteryUsageSlotData(context,
- ConvertUtils.convertToBatteryUsageSlotList(batteryDiffDataMap)));
+ batteryDiffDataMap -> {
+ DatabaseUtils.sendBatteryUsageSlotData(context,
+ ConvertUtils.convertToBatteryUsageSlotList(batteryDiffDataMap));
+ if (batteryDiffDataMap.values().stream().anyMatch(data ->
+ (!data.getAppDiffEntryList().isEmpty()
+ || !data.getSystemDiffEntryList().isEmpty()))) {
+ FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider()
+ .detectSettingsAnomaly(context, /* displayDrain= */ 0);
+ }
+ });
if (batteryLevelData == null) {
Log.d(TAG, "preprocessBatteryUsageSlots() no new battery usage data.");
return;
@@ -139,8 +147,6 @@
// No app usage data or battery diff data at this time.
loadAppUsageData(context);
preprocessBatteryUsageSlots(context);
- FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider()
- .detectSettingsAnomaly(context, /* displayDrain= */ 0);
}
Log.d(TAG, String.format(
"loadUsageDataSafely() in %d/ms", System.currentTimeMillis() - start));
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 283b742..fb83302 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -52,6 +52,7 @@
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.function.Predicate;
/** Advanced power usage. */
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
@@ -92,9 +93,9 @@
@VisibleForTesting
BatteryUsageBreakdownController mBatteryUsageBreakdownController;
@VisibleForTesting
- PowerAnomalyEvent mPowerAnomalyEvent;
- @VisibleForTesting
Optional<BatteryLevelData> mBatteryLevelData;
+ @VisibleForTesting
+ Optional<AnomalyEventWrapper> mHighlightEventWrapper;
@Override
public void onCreate(Bundle icicle) {
@@ -188,7 +189,7 @@
mIsChartDataLoaded = true;
mBatteryLevelData = null;
mBatteryUsageMap = null;
- mPowerAnomalyEvent = null;
+ mHighlightEventWrapper = null;
restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle,
mBatteryLevelDataLoaderCallbacks);
}
@@ -239,8 +240,13 @@
mScreenOnTimeController.handleSceenOnTimeUpdated(
slotUsageData.getScreenOnTime(), slotInformation);
}
+ // Hide card tips if the related highlight slot was clicked.
+ if (isAppsAnomalyEventFocused()) {
+ mBatteryTipsController.acceptTipsCard();
+ }
mBatteryUsageBreakdownController.handleBatteryUsageUpdated(
- slotUsageData, slotInformation, isBatteryUsageMapNullOrEmpty());
+ slotUsageData, slotInformation, isBatteryUsageMapNullOrEmpty(),
+ isAppsAnomalyEventFocused(), mHighlightEventWrapper);
Log.d(TAG, String.format("Battery usage list shows in %d millis",
System.currentTimeMillis() - mResumeTimestamp));
}
@@ -261,49 +267,95 @@
return;
}
Log.d(TAG, "anomalyEventList = " + anomalyEventList);
- final PowerAnomalyEvent displayEvent =
- getHighestScoreAnomalyEvent(getContext(), anomalyEventList);
- onDisplayAnomalyEventUpdated(displayEvent);
+
+ final Set<String> dismissedPowerAnomalyKeys =
+ DatabaseUtils.getDismissedPowerAnomalyKeys(getContext());
+ Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys);
+
+ // Choose an app anomaly event with highest score to show highlight slot
+ final PowerAnomalyEvent highlightEvent =
+ getAnomalyEvent(anomalyEventList, PowerAnomalyEvent::hasWarningItemInfo);
+ // Choose an event never dismissed to show as card.
+ // If the slot is already highlighted, the tips card should be the corresponding app
+ // or settings anomaly event.
+ final PowerAnomalyEvent tipsCardEvent =
+ getAnomalyEvent(anomalyEventList,
+ event -> !dismissedPowerAnomalyKeys.contains(event.getDismissRecordKey())
+ && (event.equals(highlightEvent) || !event.hasWarningItemInfo()));
+ onDisplayAnomalyEventUpdated(tipsCardEvent, highlightEvent);
}
@VisibleForTesting
- void onDisplayAnomalyEventUpdated(PowerAnomalyEvent event) {
- mPowerAnomalyEvent = event;
+ void onDisplayAnomalyEventUpdated(
+ PowerAnomalyEvent tipsCardEvent, PowerAnomalyEvent highlightEvent) {
if (mBatteryTipsController == null
|| mBatteryChartPreferenceController == null
|| mBatteryUsageBreakdownController == null) {
return;
}
+ final boolean isSameAnomalyEvent = (tipsCardEvent == highlightEvent);
// Update battery tips card preference & behaviour
mBatteryTipsController.setOnAnomalyConfirmListener(null);
mBatteryTipsController.setOnAnomalyRejectListener(null);
- mBatteryTipsController.handleBatteryTipsCardUpdated(mPowerAnomalyEvent);
+ final AnomalyEventWrapper tipsCardEventWrapper = (tipsCardEvent == null) ? null :
+ new AnomalyEventWrapper(getContext(), tipsCardEvent);
+ if (tipsCardEventWrapper != null) {
+ tipsCardEventWrapper.setRelatedBatteryDiffEntry(
+ findRelatedBatteryDiffEntry(tipsCardEventWrapper));
+ }
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ tipsCardEventWrapper, isSameAnomalyEvent);
// Update highlight slot effect in battery chart view
Pair<Integer, Integer> highlightSlotIndexPair = Pair.create(
BatteryChartViewModel.SELECTED_INDEX_INVALID,
BatteryChartViewModel.SELECTED_INDEX_INVALID);
- if (mPowerAnomalyEvent != null && mPowerAnomalyEvent.hasWarningItemInfo()) {
- final WarningItemInfo warningItemInfo = mPowerAnomalyEvent.getWarningItemInfo();
- final Long startTimestamp = warningItemInfo.hasStartTimestamp()
- ? warningItemInfo.getStartTimestamp() : null;
- final Long endTimestamp = warningItemInfo.hasEndTimestamp()
- ? warningItemInfo.getEndTimestamp() : null;
- if (startTimestamp != null && endTimestamp != null) {
- highlightSlotIndexPair = mBatteryLevelData.map(levelData ->
- levelData.getIndexByTimestamps(startTimestamp, endTimestamp))
- .orElse(highlightSlotIndexPair);
- mBatteryTipsController.setOnAnomalyConfirmListener(
- mBatteryChartPreferenceController::selectHighlightSlotIndex);
- mBatteryTipsController.setOnAnomalyRejectListener(
- () -> onDisplayAnomalyEventUpdated(null));
+ mHighlightEventWrapper = Optional.ofNullable(isSameAnomalyEvent ? tipsCardEventWrapper :
+ ((highlightEvent != null)
+ ? new AnomalyEventWrapper(getContext(), highlightEvent) : null));
+ if (mBatteryLevelData != null && mBatteryLevelData.isPresent()
+ && mHighlightEventWrapper.isPresent()
+ && mHighlightEventWrapper.get().hasHighlightSlotPair(mBatteryLevelData.get())) {
+ highlightSlotIndexPair = mHighlightEventWrapper.get()
+ .getHighlightSlotPair(mBatteryLevelData.get());
+ if (isSameAnomalyEvent) {
+ // For main button, focus on highlight slot when clicked
+ mBatteryTipsController.setOnAnomalyConfirmListener(() -> {
+ mBatteryChartPreferenceController.selectHighlightSlotIndex();
+ mBatteryTipsController.acceptTipsCard();
+ });
}
}
mBatteryChartPreferenceController.onHighlightSlotIndexUpdate(
highlightSlotIndexPair.first, highlightSlotIndexPair.second);
}
+ @VisibleForTesting
+ BatteryDiffEntry findRelatedBatteryDiffEntry(AnomalyEventWrapper eventWrapper) {
+ if (eventWrapper == null
+ || mBatteryLevelData == null || mBatteryLevelData.isEmpty()
+ || !eventWrapper.hasHighlightSlotPair(mBatteryLevelData.get())
+ || !eventWrapper.hasAnomalyEntryKey()
+ || mBatteryUsageMap == null) {
+ return null;
+ }
+ final Pair<Integer, Integer> highlightSlotIndexPair =
+ eventWrapper.getHighlightSlotPair(mBatteryLevelData.get());
+ final BatteryDiffData relatedDiffData = mBatteryUsageMap
+ .get(highlightSlotIndexPair.first).get(highlightSlotIndexPair.second);
+ final String anomalyEntryKey = eventWrapper.getAnomalyEntryKey();
+ if (relatedDiffData == null || anomalyEntryKey == null) {
+ return null;
+ }
+ for (BatteryDiffEntry entry : relatedDiffData.getAppDiffEntryList()) {
+ if (anomalyEntryKey.equals(entry.getKey())) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
private void setBatteryChartPreferenceController() {
if (mHistPref != null && mBatteryChartPreferenceController != null) {
mHistPref.setChartPreferenceController(mBatteryChartPreferenceController);
@@ -318,6 +370,11 @@
&& allBatteryDiffData.getSystemDiffEntryList().isEmpty());
}
+ private boolean isAppsAnomalyEventFocused() {
+ return mBatteryChartPreferenceController != null
+ && mBatteryChartPreferenceController.isHighlightSlotFocused();
+ }
+
private void logScreenUsageTime() {
final BatteryDiffData allBatteryDiffData = getAllBatteryDiffData(mBatteryUsageMap);
if (allBatteryDiffData == null) {
@@ -338,25 +395,22 @@
}
@VisibleForTesting
- static PowerAnomalyEvent getHighestScoreAnomalyEvent(
- Context context, PowerAnomalyEventList anomalyEventList) {
+ static PowerAnomalyEvent getAnomalyEvent(
+ PowerAnomalyEventList anomalyEventList, Predicate<PowerAnomalyEvent> predicate) {
if (anomalyEventList == null || anomalyEventList.getPowerAnomalyEventsCount() == 0) {
return null;
}
- final Set<String> dismissedPowerAnomalyKeys =
- DatabaseUtils.getDismissedPowerAnomalyKeys(context);
- Log.d(TAG, "dismissedPowerAnomalyKeys = " + dismissedPowerAnomalyKeys);
- final PowerAnomalyEvent highestScoreEvent = anomalyEventList.getPowerAnomalyEventsList()
+ final PowerAnomalyEvent filterAnomalyEvent = anomalyEventList.getPowerAnomalyEventsList()
.stream()
- .filter(event -> !dismissedPowerAnomalyKeys.contains(
- BatteryTipsController.getDismissRecordKey(event)))
+ .filter(predicate)
.max(Comparator.comparing(PowerAnomalyEvent::getScore))
.orElse(null);
- Log.d(TAG, "highestScoreAnomalyEvent = " + highestScoreEvent);
- return highestScoreEvent;
+ Log.d(TAG, "filterAnomalyEvent = " + filterAnomalyEvent);
+ return filterAnomalyEvent;
}
+
private static BatteryDiffData getAllBatteryDiffData(
Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
return batteryUsageMap == null ? null : batteryUsageMap
diff --git a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
index 99df215..caa9c35 100644
--- a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
+++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
@@ -18,6 +18,7 @@
WarningBannerInfo warning_banner_info = 6;
WarningItemInfo warning_item_info = 7;
}
+ optional string dismiss_record_key = 8;
}
// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
@@ -32,11 +33,16 @@
// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
// The enum value will be used to decide pre-defined title and button labels.
//
-// Next id: 3
+// Next id: 8
enum PowerAnomalyKey{
KEY_BRIGHTNESS = 0;
KEY_SCREEN_TIMEOUT = 1;
- KEY_APP = 2;
+ KEY_APP_TOTAL_ALWAYS_HIGH = 2;
+ KEY_APP_TOTAL_HIGHER_THAN_USUAL = 3;
+ KEY_APP_BACKGROUND_ALWAYS_HIGH = 4;
+ KEY_APP_BACKGROUND_HIGHER_THAN_USUAL = 5;
+ KEY_APP_FOREGROUND_ALWAYS_HIGH = 6;
+ KEY_APP_FOREGROUND_HIGHER_THAN_USUAL = 7;
}
message WarningBannerInfo {
@@ -60,6 +66,5 @@
optional string description_string = 5;
optional string main_button_string = 6;
optional string cancel_button_string = 7;
- optional string dismiss_record_key = 8;
- optional string item_key = 9;
+ optional string item_key = 8;
}
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 0cd12fe..9974ba2 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -18,7 +18,6 @@
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
-
import static com.android.internal.util.CollectionUtils.emptyIfNull;
import android.annotation.Nullable;
@@ -56,6 +55,8 @@
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -66,6 +67,9 @@
static final String SUB_ID = "sub_id";
@VisibleForTesting
static final String KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME = "unique_subscription_displayName";
+ private static final String REGEX_DISPLAY_NAME_PREFIXES = "^";
+ private static final String REGEX_DISPLAY_NAME_SUFFIXES = "\\s[0-9]+";
+
private static List<SubscriptionInfo> sAvailableResultsForTesting;
private static List<SubscriptionInfo> sActiveResultsForTesting;
@@ -281,8 +285,8 @@
String displayName = i.getDisplayName().toString();
info.originalName =
TextUtils.equals(displayName, PROFILE_GENERIC_DISPLAY_NAME)
- ? context.getResources().getString(R.string.sim_card)
- : displayName.trim();
+ ? context.getResources().getString(R.string.sim_card)
+ : displayName.trim();
return info;
});
@@ -298,12 +302,17 @@
// If a display name is duplicate, append the final 4 digits of the phone number.
// Creates a mapping of Subscription id to original display name + phone number display name
final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> {
+ int infoSubId = info.subscriptionInfo.getSubscriptionId();
String cachedDisplayName = getDisplayNameFromSharedPreference(
- context, info.subscriptionInfo.getSubscriptionId());
- if (!TextUtils.isEmpty(cachedDisplayName)) {
- Log.d(TAG, "use cached display name : " + cachedDisplayName);
+ context, infoSubId);
+ if (isValidCachedDisplayName(cachedDisplayName, info.originalName.toString())) {
+ Log.d(TAG, "use cached display name : for subId : " + infoSubId
+ + "cached display name : " + cachedDisplayName);
info.uniqueName = cachedDisplayName;
return info;
+ } else {
+ Log.d(TAG, "remove cached display name : " + infoSubId);
+ removeItemFromDisplayNameSharedPreference(context, infoSubId);
}
if (duplicateOriginalNames.contains(info.originalName)) {
@@ -320,9 +329,8 @@
} else {
info.uniqueName = info.originalName + " " + lastFourDigits;
Log.d(TAG, "Cache display name [" + info.uniqueName + "] for sub id "
- + info.subscriptionInfo.getSubscriptionId());
- saveDisplayNameToSharedPreference(
- context, info.subscriptionInfo.getSubscriptionId(), info.uniqueName);
+ + infoSubId);
+ saveDisplayNameToSharedPreference(context, infoSubId, info.uniqueName);
}
} else {
info.uniqueName = info.originalName;
@@ -404,10 +412,27 @@
.apply();
}
+ private static void removeItemFromDisplayNameSharedPreference(Context context, int subId) {
+ getDisplayNameSharedPreferenceEditor(context)
+ .remove(SUB_ID + subId)
+ .commit();
+ }
+
private static String getDisplayNameFromSharedPreference(Context context, int subid) {
return getDisplayNameSharedPreferences(context).getString(SUB_ID + subid, "");
}
+ @VisibleForTesting
+ static boolean isValidCachedDisplayName(String cachedDisplayName, String originalName) {
+ if (TextUtils.isEmpty(cachedDisplayName) || TextUtils.isEmpty(originalName)) {
+ return false;
+ }
+ String regex = REGEX_DISPLAY_NAME_PREFIXES + originalName + REGEX_DISPLAY_NAME_SUFFIXES;
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(cachedDisplayName);
+ return matcher.matches();
+ }
+
public static String getDisplayName(SubscriptionInfo info) {
final CharSequence name = info.getDisplayName();
if (name != null) {
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
new file mode 100644
index 0000000..756d90f
--- /dev/null
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.network.apn
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringArrayResource
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settings.R
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
+import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import java.util.Base64
+
+const val URI_TYPE = "uriType"
+const val URI = "uri"
+const val SUB_ID = "subId"
+const val MVNO_TYPE = "mvnoType"
+const val MVNO_MATCH_DATA = "mvnoMatchData"
+const val EDIT_URL = "editUrl"
+
+object ApnEditPageProvider : SettingsPageProvider {
+
+ override val name = "Apn"
+ const val TAG = "ApnPageProvider"
+
+ override val parameter = listOf(
+ navArgument(URI_TYPE) { type = NavType.StringType },
+ navArgument(URI) { type = NavType.StringType },
+ navArgument(SUB_ID) { type = NavType.IntType },
+ navArgument(MVNO_TYPE) { type = NavType.StringType },
+ navArgument(MVNO_MATCH_DATA) { type = NavType.StringType },
+ )
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ val apnDataInit = ApnData()
+ val apnDataCur = remember {
+ mutableStateOf(apnDataInit)
+ }
+ ApnPage(apnDataCur)
+ }
+
+ fun getRoute(
+ uriType: String,
+ uri: Uri,
+ subId: Int,
+ mMvnoType: String,
+ mMvnoMatchData: String
+ ): String = "${name}/$uriType/${
+ Base64.getUrlEncoder().encodeToString(uri.toString().toByteArray())
+ }/$subId/$mMvnoType/$mMvnoMatchData"
+}
+
+@Composable
+fun ApnPage(apnDataCur: MutableState<ApnData>) {
+ var apnData by apnDataCur
+ val context = LocalContext.current
+ val authTypeOptions = stringArrayResource(R.array.apn_auth_entries).toList()
+ val apnProtocolOptions = stringArrayResource(R.array.apn_protocol_entries).toList()
+ val mvnoTypeOptions = stringArrayResource(R.array.mvno_type_entries).toList()
+
+ RegularScaffold(
+ title = stringResource(id = R.string.apn_edit),
+ ) {
+ Column() {
+ SettingsOutlinedTextField(
+ apnData.name,
+ stringResource(R.string.apn_name),
+ enabled = apnData.nameEnabled
+ ) { apnData = apnData.copy(name = it) }
+ SettingsOutlinedTextField(
+ apnData.apn,
+ stringResource(R.string.apn_apn),
+ enabled = apnData.apnEnabled
+ ) { apnData = apnData.copy(apn = it) }
+ SettingsOutlinedTextField(
+ apnData.proxy,
+ stringResource(R.string.apn_http_proxy),
+ enabled = apnData.proxyEnabled
+ ) { apnData = apnData.copy(proxy = it) }
+ SettingsOutlinedTextField(
+ apnData.port,
+ stringResource(R.string.apn_http_port),
+ enabled = apnData.portEnabled
+ ) { apnData = apnData.copy(port = it) }
+ SettingsOutlinedTextField(
+ apnData.userName,
+ stringResource(R.string.apn_user),
+ enabled = apnData.userNameEnabled
+ ) { apnData = apnData.copy(userName = it) }
+ // TODO: password
+ SettingsOutlinedTextField(
+ apnData.server,
+ stringResource(R.string.apn_server),
+ enabled = apnData.serverEnabled
+ ) { apnData = apnData.copy(server = it) }
+ SettingsOutlinedTextField(
+ apnData.mmsc,
+ stringResource(R.string.apn_mmsc),
+ enabled = apnData.mmscEnabled
+ ) { apnData = apnData.copy(mmsc = it) }
+ SettingsOutlinedTextField(
+ apnData.mmsProxy,
+ stringResource(R.string.apn_mms_proxy),
+ enabled = apnData.mmsProxyEnabled
+ ) { apnData = apnData.copy(mmsProxy = it) }
+ SettingsOutlinedTextField(
+ apnData.mmsPort,
+ stringResource(R.string.apn_mms_port),
+ enabled = apnData.mmsPortEnabled
+ ) { apnData = apnData.copy(mmsPort = it) }
+ SettingsOutlinedTextField(
+ apnData.mcc,
+ stringResource(R.string.apn_mcc),
+ enabled = apnData.mccEnabled
+ ) { apnData = apnData.copy(mcc = it) }
+ SettingsOutlinedTextField(
+ apnData.mnc,
+ stringResource(R.string.apn_mnc),
+ enabled = apnData.mncEnabled
+ ) { apnData = apnData.copy(mnc = it) }
+ SettingsExposedDropdownMenuBox(
+ label = stringResource(R.string.apn_auth_type),
+ options = authTypeOptions,
+ selectedOptionText =
+ authTypeOptions.getOrElse(apnData.authType) { "" },
+ enabled = apnData.authTypeEnabled,
+ ) { apnData = apnData.copy(authType = authTypeOptions.indexOf(it)) }
+ SettingsOutlinedTextField(
+ apnData.apnType,
+ stringResource(R.string.apn_type),
+ enabled = apnData.apnTypeEnabled
+ ) { apnData = apnData.copy(apn = it) } // TODO: updateApnType
+ SettingsExposedDropdownMenuBox(
+ stringResource(R.string.apn_protocol),
+ apnProtocolOptions,
+ apnData.apnProtocol,
+ apnData.apnProtocolEnabled
+ ) { apnData = apnData.copy(apnProtocol = it) }
+ SettingsExposedDropdownMenuBox(
+ stringResource(R.string.apn_roaming_protocol),
+ apnProtocolOptions,
+ apnData.apnRoaming,
+ apnData.apnRoamingEnabled
+ ) { apnData = apnData.copy(apnRoaming = it) }
+ SwitchPreference(
+ object : SwitchPreferenceModel {
+ override val title = context.resources.getString(R.string.carrier_enabled)
+ override val changeable =
+ stateOf(apnData.apnEnableEnabled)
+ override val checked =
+ stateOf(apnData.apnEnable)
+ override val onCheckedChange = { newChecked: Boolean ->
+ apnData = apnData.copy(apnEnable = newChecked)
+ }
+ }
+ )
+ SettingsExposedDropdownMenuBox(
+ stringResource(R.string.mvno_type),
+ mvnoTypeOptions,
+ apnData.mvnoType,
+ apnData.mvnoTypeEnabled
+ ) {
+ apnData = apnData.copy(mvnoType = it)
+ } // TODO: mvnoDescription
+ SettingsOutlinedTextField(
+ apnData.mvnoValue,
+ stringResource(R.string.mvno_match_data),
+ enabled = apnData.mvnoValueEnabled
+ ) { apnData = apnData.copy(mvnoValue = it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/network/apn/ApnPreference.java b/src/com/android/settings/network/apn/ApnPreference.java
index f277db0..07d371a 100755
--- a/src/com/android/settings/network/apn/ApnPreference.java
+++ b/src/com/android/settings/network/apn/ApnPreference.java
@@ -16,6 +16,8 @@
package com.android.settings.network.apn;
+import static com.android.settings.network.apn.ApnEditPageProviderKt.EDIT_URL;
+
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
@@ -34,15 +36,21 @@
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
+import com.android.settings.flags.Flags;
+import com.android.settings.spa.SpaActivity;
/**
* Preference of APN UI entry
*/
-public class ApnPreference extends Preference implements CompoundButton.OnCheckedChangeListener,
- View.OnClickListener {
- private static final String TAG = "ApnPreference";
-
+public class ApnPreference extends Preference
+ implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
+ private static final String TAG = "ApnPreference";
+ private static String sSelectedKey = null;
+ private static CompoundButton sCurrentChecked = null;
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private boolean mProtectFromCheckedChange = false;
+ private boolean mSelectable = true;
+ private boolean mHideDetails = false;
/**
* Constructor of Preference
@@ -65,12 +73,6 @@
this(context, null);
}
- private static String sSelectedKey = null;
- private static CompoundButton sCurrentChecked = null;
- private boolean mProtectFromCheckedChange = false;
- private boolean mSelectable = true;
- private boolean mHideDetails = false;
-
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
@@ -147,25 +149,32 @@
}
if (mHideDetails) {
- Toast.makeText(context, context.getString(
- R.string.cannot_change_apn_toast), Toast.LENGTH_LONG).show();
+ Toast.makeText(context, context.getString(R.string.cannot_change_apn_toast),
+ Toast.LENGTH_LONG).show();
return;
}
- final Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos);
- final Intent editIntent = new Intent(Intent.ACTION_EDIT, url);
- editIntent.putExtra(ApnSettings.SUB_ID, mSubId);
- editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- context.startActivity(editIntent);
- }
- public void setSelectable(boolean selectable) {
- mSelectable = selectable;
+ final Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos);
+
+ if (Flags.newApnPageEnabled()) {
+ String route = ApnEditPageProvider.INSTANCE.getRoute(EDIT_URL, url, mSubId, "_", "_");
+ SpaActivity.startSpaActivity(context, route);
+ } else {
+ final Intent editIntent = new Intent(Intent.ACTION_EDIT, url);
+ editIntent.putExtra(ApnSettings.SUB_ID, mSubId);
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ context.startActivity(editIntent);
+ }
}
public boolean getSelectable() {
return mSelectable;
}
+ public void setSelectable(boolean selectable) {
+ mSelectable = selectable;
+ }
+
public void setSubId(int subId) {
mSubId = subId;
}
diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt
new file mode 100644
index 0000000..8a2d613
--- /dev/null
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.network.apn
+
+import android.provider.Telephony
+import android.telephony.TelephonyManager
+
+data class ApnData(
+ val name: String = "",
+ val apn: String = "",
+ val proxy: String = "",
+ val port: String = "",
+ val userName: String = "",
+ val passWord: String = "",
+ val server: String = "",
+ val mmsc: String = "",
+ val mmsProxy: String = "",
+ val mmsPort: String = "",
+ val mcc: String = "",
+ val mnc: String = "",
+ val authType: Int = -1,
+ val apnType: String = "",
+ val apnProtocol: String = "",
+ val apnRoaming: String = "",
+ val apnEnable: Boolean = true,
+ val bearer: Int = 0,
+ val mvnoType: String = "",
+ var mvnoValue: String = "",
+ val bearerBitmask: Int = 0,
+ val edited: Int = Telephony.Carriers.USER_EDITED,
+ val userEditable: Int = 1,
+ val carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID
+) {
+ var nameEnabled = true
+ var apnEnabled = true
+ var proxyEnabled = true
+ var portEnabled = true
+ var userNameEnabled = true
+ var passWordEnabled = true
+ var serverEnabled = true
+ var mmscEnabled = true
+ var mmsProxyEnabled = true
+ var mmsPortEnabled = true
+ var mccEnabled = true
+ var mncEnabled = true
+ var authTypeEnabled = true
+ var apnTypeEnabled = true
+ var apnProtocolEnabled = true
+ var apnRoamingEnabled = true
+ var apnEnableEnabled = true
+ var bearerEnabled = true
+ var mvnoTypeEnabled = true
+ var mvnoValueEnabled = false
+}
\ No newline at end of file
diff --git a/src/com/android/settings/panel/PanelSlicesAdapter.java b/src/com/android/settings/panel/PanelSlicesAdapter.java
index 57d8aa4..a2360d8 100644
--- a/src/com/android/settings/panel/PanelSlicesAdapter.java
+++ b/src/com/android/settings/panel/PanelSlicesAdapter.java
@@ -61,15 +61,12 @@
private final List<LiveData<Slice>> mSliceLiveData;
private final int mMetricsCategory;
private final PanelFragment mPanelFragment;
- private final String mSliceClickActionLabel;
public PanelSlicesAdapter(
PanelFragment fragment, Map<Uri, LiveData<Slice>> sliceLiveData, int metricsCategory) {
mPanelFragment = fragment;
mSliceLiveData = new ArrayList<>(sliceLiveData.values());
mMetricsCategory = metricsCategory;
- mSliceClickActionLabel = mPanelFragment.getContext().getString(
- R.string.accessibility_action_label_panel_slice);
}
@NonNull
@@ -77,7 +74,7 @@
public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
final Context context = viewGroup.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
- View view;
+ final View view;
if (viewType == PanelContent.VIEW_TYPE_SLIDER) {
view = inflater.inflate(R.layout.panel_slice_slider_row, viewGroup, false);
} else {
@@ -189,7 +186,6 @@
return;
}
sliceView.setTag(ROW_VIEW_TAG, new Object());
-
sliceView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
@@ -208,15 +204,17 @@
* Update the action label for TalkBack to be more specific
* @param view the RowView within the Slice
*/
- private void setActionLabel(View view) {
+ @VisibleForTesting void setActionLabel(View view) {
view.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
+
AccessibilityNodeInfo.AccessibilityAction customClick =
- new AccessibilityNodeInfo.AccessibilityAction(
- ACTION_CLICK, mSliceClickActionLabel);
+ new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, host
+ .getResources()
+ .getString(R.string.accessibility_action_label_panel_slice));
info.addAction(customClick);
}
});
diff --git a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
index 9e1d0d5..f72bcd9 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
@@ -18,7 +18,7 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.util.FeatureFlagUtils;
+import android.os.Flags;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
@@ -56,13 +56,8 @@
new BaseSearchIndexProvider(R.xml.private_space_settings) {
@Override
protected boolean isPageSearchEnabled(Context context) {
- // Temporary workaround for hiding PS Settings until the trunk stable feature
- // flag is available.
- // TODO(b/295516544): Remove this workaround when trunk stable feature flag is
- // available.
return SafetyCenterManagerWrapper.get().isEnabled(context)
- && FeatureFlagUtils.isEnabled(context,
- FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS);
+ && Flags.allowPrivateProfile();
}
@Override
diff --git a/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java b/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java
index b07c623..4910a7b 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceSafetySource.java
@@ -20,11 +20,11 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
+import android.os.Flags;
import android.os.UserManager;
import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceStatus;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.settings.R;
@@ -35,7 +35,7 @@
/** Private Space safety source for the Safety Center */
public final class PrivateSpaceSafetySource {
public static final String SAFETY_SOURCE_ID = "AndroidPrivateSpace";
- private static final String TAG = "PrivateSpaceSafetySource";
+ private static final String TAG = "PrivateSpaceSafetySrc";
private PrivateSpaceSafetySource() {}
@@ -54,10 +54,7 @@
return;
}
- // Temporary workaround to help prevent the PS Settings showing up in droidfood builds.
- // TODO(b/295516544): remove this when the trunk stable feature flag for PS is available.
- if (!FeatureFlagUtils.isEnabled(context,
- FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS)) {
+ if (!Flags.allowPrivateProfile()) {
// Setting null safetySourceData so that an old entry gets cleared out and this way
// provide a response since SC always expects one on rescan.
SafetyCenterManagerWrapper.get().setSafetySourceData(
diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
index 4e085c1..6960fc6 100644
--- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java
+++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
@@ -46,11 +46,11 @@
private static final int REQUEST_CODE_SCREEN_LOCK = 1;
private static final int REQUEST_CODE_SCREEN_LOCK_SETTINGS = 2;
- private LockScreenSafetySource() {
- }
+ private LockScreenSafetySource() {}
/** Sets lock screen safety data for Safety Center. */
- public static void setSafetySourceData(Context context,
+ public static void setSafetySourceData(
+ Context context,
ScreenLockPreferenceDetailsUtils screenLockPreferenceDetailsUtils,
SafetyEvent safetyEvent) {
if (!SafetyCenterManagerWrapper.get().isEnabled(context)) {
@@ -63,59 +63,61 @@
}
if (!screenLockPreferenceDetailsUtils.isAvailable()) {
- SafetyCenterManagerWrapper.get().setSafetySourceData(
- context,
- SAFETY_SOURCE_ID,
- /* safetySourceData= */ null,
- safetyEvent
- );
+ SafetyCenterManagerWrapper.get()
+ .setSafetySourceData(
+ context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent);
return;
}
final int userId = UserHandle.myUserId();
- final RestrictedLockUtils.EnforcedAdmin admin = RestrictedLockUtilsInternal
- .checkIfPasswordQualityIsSet(context, userId);
- final PendingIntent pendingIntent = createPendingIntent(context,
- screenLockPreferenceDetailsUtils.getLaunchChooseLockGenericFragmentIntent(
- SettingsEnums.SAFETY_CENTER), REQUEST_CODE_SCREEN_LOCK);
- final IconAction gearMenuIconAction = createGearMenuIconAction(context,
- screenLockPreferenceDetailsUtils);
- final boolean enabled =
+ final RestrictedLockUtils.EnforcedAdmin admin =
+ RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet(context, userId);
+ final PendingIntent pendingIntent =
+ createPendingIntent(
+ context,
+ screenLockPreferenceDetailsUtils.getLaunchChooseLockGenericFragmentIntent(
+ SettingsEnums.SAFETY_CENTER),
+ REQUEST_CODE_SCREEN_LOCK);
+ final IconAction gearMenuIconAction =
+ createGearMenuIconAction(context, screenLockPreferenceDetailsUtils);
+ final boolean lockScreenAllowedByAdmin =
!screenLockPreferenceDetailsUtils.isPasswordQualityManaged(userId, admin);
final boolean isLockPatternSecure = screenLockPreferenceDetailsUtils.isLockPatternSecure();
- final int severityLevel = enabled
- ? isLockPatternSecure
- ? SafetySourceData.SEVERITY_LEVEL_INFORMATION
- : SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION
- : SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED;
+ final int severityLevel =
+ lockScreenAllowedByAdmin
+ ? isLockPatternSecure
+ ? SafetySourceData.SEVERITY_LEVEL_INFORMATION
+ : SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION
+ : SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED;
-
- final SafetySourceStatus status = new SafetySourceStatus.Builder(
- context.getString(R.string.unlock_set_unlock_launch_picker_title),
- screenLockPreferenceDetailsUtils.getSummary(UserHandle.myUserId()),
- severityLevel)
- .setPendingIntent(pendingIntent)
- .setEnabled(enabled)
- .setIconAction(gearMenuIconAction).build();
+ final SafetySourceStatus status =
+ new SafetySourceStatus.Builder(
+ context.getString(R.string.unlock_set_unlock_launch_picker_title),
+ lockScreenAllowedByAdmin
+ ? screenLockPreferenceDetailsUtils.getSummary(
+ UserHandle.myUserId())
+ : context.getString(R.string.disabled_by_policy_title),
+ severityLevel)
+ .setPendingIntent(lockScreenAllowedByAdmin ? pendingIntent : null)
+ .setEnabled(lockScreenAllowedByAdmin)
+ .setIconAction(lockScreenAllowedByAdmin ? gearMenuIconAction : null)
+ .build();
final SafetySourceData.Builder safetySourceDataBuilder =
new SafetySourceData.Builder().setStatus(status);
- if (enabled && !isLockPatternSecure) {
+ if (lockScreenAllowedByAdmin && !isLockPatternSecure) {
safetySourceDataBuilder.addIssue(createNoScreenLockIssue(context, pendingIntent));
}
final SafetySourceData safetySourceData = safetySourceDataBuilder.build();
- SafetyCenterManagerWrapper.get().setSafetySourceData(
- context,
- SAFETY_SOURCE_ID,
- safetySourceData,
- safetyEvent
- );
+ SafetyCenterManagerWrapper.get()
+ .setSafetySourceData(context, SAFETY_SOURCE_ID, safetySourceData, safetyEvent);
}
/** Notifies Safety Center of a change in lock screen settings. */
public static void onLockScreenChange(Context context) {
setSafetySourceData(
- context, new ScreenLockPreferenceDetailsUtils(context),
+ context,
+ new ScreenLockPreferenceDetailsUtils(context),
new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build());
// Also send refreshed safety center data for biometrics, since changing lockscreen settings
@@ -123,45 +125,45 @@
BiometricsSafetySource.onBiometricsChanged(context);
}
- private static IconAction createGearMenuIconAction(Context context,
- ScreenLockPreferenceDetailsUtils screenLockPreferenceDetailsUtils) {
- return screenLockPreferenceDetailsUtils.shouldShowGearMenu() ? new IconAction(
- IconAction.ICON_TYPE_GEAR,
- createPendingIntent(context,
- screenLockPreferenceDetailsUtils.getLaunchScreenLockSettingsIntent(
- SettingsEnums.SAFETY_CENTER),
- REQUEST_CODE_SCREEN_LOCK_SETTINGS))
+ private static IconAction createGearMenuIconAction(
+ Context context, ScreenLockPreferenceDetailsUtils screenLockPreferenceDetailsUtils) {
+ return screenLockPreferenceDetailsUtils.shouldShowGearMenu()
+ ? new IconAction(
+ IconAction.ICON_TYPE_GEAR,
+ createPendingIntent(
+ context,
+ screenLockPreferenceDetailsUtils.getLaunchScreenLockSettingsIntent(
+ SettingsEnums.SAFETY_CENTER),
+ REQUEST_CODE_SCREEN_LOCK_SETTINGS))
: null;
}
- private static PendingIntent createPendingIntent(Context context, Intent intent,
- int requestCode) {
- return PendingIntent
- .getActivity(
- context,
- requestCode,
- intent,
- PendingIntent.FLAG_IMMUTABLE);
+ private static PendingIntent createPendingIntent(
+ Context context, Intent intent, int requestCode) {
+ return PendingIntent.getActivity(
+ context, requestCode, intent, PendingIntent.FLAG_IMMUTABLE);
}
- private static SafetySourceIssue createNoScreenLockIssue(Context context,
- PendingIntent pendingIntent) {
- final SafetySourceIssue.Action action = new SafetySourceIssue.Action.Builder(
- SET_SCREEN_LOCK_ACTION_ID,
- context.getString(R.string.no_screen_lock_issue_action_label),
- pendingIntent).build();
+ private static SafetySourceIssue createNoScreenLockIssue(
+ Context context, PendingIntent pendingIntent) {
+ final SafetySourceIssue.Action action =
+ new SafetySourceIssue.Action.Builder(
+ SET_SCREEN_LOCK_ACTION_ID,
+ context.getString(R.string.no_screen_lock_issue_action_label),
+ pendingIntent)
+ .build();
// Custom notification deliberately has zero actions
final SafetySourceIssue.Notification customNotification =
- new SafetySourceIssue.Notification.Builder(
- context.getString(R.string.no_screen_lock_issue_notification_title),
- context.getString(R.string.no_screen_lock_issue_notification_text))
- .build();
+ new SafetySourceIssue.Notification.Builder(
+ context.getString(R.string.no_screen_lock_issue_notification_title),
+ context.getString(R.string.no_screen_lock_issue_notification_text))
+ .build();
return new SafetySourceIssue.Builder(
- NO_SCREEN_LOCK_ISSUE_ID,
- context.getString(R.string.no_screen_lock_issue_title),
- context.getString(R.string.no_screen_lock_issue_summary),
- SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
- NO_SCREEN_LOCK_ISSUE_TYPE_ID)
+ NO_SCREEN_LOCK_ISSUE_ID,
+ context.getString(R.string.no_screen_lock_issue_title),
+ context.getString(R.string.no_screen_lock_issue_summary),
+ SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
+ NO_SCREEN_LOCK_ISSUE_TYPE_ID)
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
.addAction(action)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_MANUAL)
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index f08a2de..40cc9a2 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.util.FeatureFlagUtils
+import com.android.settings.network.apn.ApnEditPageProvider
import com.android.settings.spa.about.AboutPhonePageProvider
import com.android.settings.spa.app.AllAppListPageProvider
import com.android.settings.spa.app.AppsMainPageProvider
@@ -34,8 +35,8 @@
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
-import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider
+import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.storage.StorageAppListPageProvider
import com.android.settings.spa.core.instrumentation.SpaLogProvider
import com.android.settings.spa.development.UsageStatsPageProvider
@@ -95,6 +96,7 @@
AboutPhonePageProvider,
StorageAppListPageProvider.Apps,
StorageAppListPageProvider.Games,
+ ApnEditPageProvider,
) + togglePermissionAppListTemplate.createPageProviders(),
rootPages = listOf(
HomePageProvider.createSettingsPage()
diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
index f403743..a15401c 100644
--- a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
+++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
@@ -115,7 +115,8 @@
override val resId = R.raw.user_aspect_ratio_education
override val resourceType = ResourceType.LOTTIE
})
- }
+ },
+ noMoreOptions = true,
)
}
diff --git a/src/com/android/settings/system/SystemDashboardFragment.java b/src/com/android/settings/system/SystemDashboardFragment.java
index 79c5b9f..678b675 100644
--- a/src/com/android/settings/system/SystemDashboardFragment.java
+++ b/src/com/android/settings/system/SystemDashboardFragment.java
@@ -16,9 +16,7 @@
package com.android.settings.system;
import android.app.settings.SettingsEnums;
-import android.content.Context;
import android.os.Bundle;
-import android.provider.SearchIndexableResource;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
@@ -29,9 +27,6 @@
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
-import java.util.Arrays;
-import java.util.List;
-
@SearchIndexable
public class SystemDashboardFragment extends DashboardFragment {
@@ -85,13 +80,5 @@
* For Search.
*/
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider() {
- @Override
- public List<SearchIndexableResource> getXmlResourcesToIndex(
- Context context, boolean enabled) {
- final SearchIndexableResource sir = new SearchIndexableResource(context);
- sir.xmlResId = R.xml.system_dashboard_fragment;
- return Arrays.asList(sir);
- }
- };
-}
\ No newline at end of file
+ new BaseSearchIndexProvider(R.xml.system_dashboard_fragment);
+}
diff --git a/src/com/android/settings/system/SystemUpdateManagerExt.kt b/src/com/android/settings/system/SystemUpdateManagerExt.kt
new file mode 100644
index 0000000..8ddf174
--- /dev/null
+++ b/src/com/android/settings/system/SystemUpdateManagerExt.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.system
+
+import android.content.Context
+import android.os.Bundle
+import android.os.SystemUpdateManager
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+private const val TAG = "SystemUpdateManagerExt"
+
+/**
+ * Gets the system update status.
+ *
+ * Note: [SystemUpdateManager.retrieveSystemUpdateInfo] must be called on worker thread to avoid
+ * StrictMode violation.
+ */
+suspend fun Context.getSystemUpdateInfo(): Bundle? = withContext(Dispatchers.Default) {
+ val updateManager = getSystemService(SystemUpdateManager::class.java)!!
+ try {
+ updateManager.retrieveSystemUpdateInfo()
+ } catch (e: Exception) {
+ Log.w(TAG, "Error getting system update info.")
+ null
+ }
+}
diff --git a/src/com/android/settings/system/SystemUpdatePreferenceController.java b/src/com/android/settings/system/SystemUpdatePreferenceController.java
deleted file mode 100644
index b2a22ff..0000000
--- a/src/com/android/settings/system/SystemUpdatePreferenceController.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2016 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.system;
-
-import static android.content.Context.CARRIER_CONFIG_SERVICE;
-import static android.content.Context.SYSTEM_UPDATE_SERVICE;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.os.SystemUpdateManager;
-import android.os.UserManager;
-import android.telephony.CarrierConfigManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.Utils;
-import com.android.settings.core.BasePreferenceController;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-public class SystemUpdatePreferenceController extends BasePreferenceController {
-
- private static final String TAG = "SysUpdatePrefContr";
-
- private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
-
- private final UserManager mUm;
- private final SystemUpdateManager mUpdateManager;
-
- public SystemUpdatePreferenceController(Context context) {
- super(context, KEY_SYSTEM_UPDATE_SETTINGS);
- mUm = UserManager.get(context);
- mUpdateManager = (SystemUpdateManager) context.getSystemService(SYSTEM_UPDATE_SERVICE);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return mContext.getResources().getBoolean(R.bool.config_show_system_update_settings)
- && mUm.isAdminUser()
- ? AVAILABLE
- : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- if (isAvailable()) {
- Utils.updatePreferenceToSpecificActivityOrRemove(mContext, screen,
- getPreferenceKey(),
- Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
- }
- }
-
- @Override
- public boolean handlePreferenceTreeClick(Preference preference) {
- if (TextUtils.equals(getPreferenceKey(), preference.getKey())) {
- CarrierConfigManager configManager =
- (CarrierConfigManager) mContext.getSystemService(CARRIER_CONFIG_SERVICE);
- PersistableBundle b = configManager.getConfig();
- if (b != null && b.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) {
- ciActionOnSysUpdate(b);
- }
- }
- // always return false here because this handler does not want to block other handlers.
- return false;
- }
-
- @Override
- public CharSequence getSummary() {
- CharSequence summary = mContext.getString(R.string.android_version_summary,
- Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY);
- final FutureTask<Bundle> bundleFutureTask = new FutureTask<>(
- // Put the API call in a future to avoid StrictMode violation.
- () -> mUpdateManager.retrieveSystemUpdateInfo());
- final Bundle updateInfo;
- try {
- bundleFutureTask.run();
- updateInfo = bundleFutureTask.get();
- } catch (InterruptedException | ExecutionException e) {
- Log.w(TAG, "Error getting system update info.");
- return summary;
- }
- switch (updateInfo.getInt(SystemUpdateManager.KEY_STATUS)) {
- case SystemUpdateManager.STATUS_WAITING_DOWNLOAD:
- case SystemUpdateManager.STATUS_IN_PROGRESS:
- case SystemUpdateManager.STATUS_WAITING_INSTALL:
- case SystemUpdateManager.STATUS_WAITING_REBOOT:
- summary = mContext.getText(R.string.android_version_pending_update_summary);
- break;
- case SystemUpdateManager.STATUS_UNKNOWN:
- Log.d(TAG, "Update statue unknown");
- // fall through to next branch
- case SystemUpdateManager.STATUS_IDLE:
- final String version = updateInfo.getString(SystemUpdateManager.KEY_TITLE);
- if (!TextUtils.isEmpty(version)) {
- summary = mContext.getString(R.string.android_version_summary, version);
- }
- break;
- }
- return summary;
- }
-
- /**
- * Trigger client initiated action (send intent) on system update
- */
- private void ciActionOnSysUpdate(PersistableBundle b) {
- String intentStr = b.getString(CarrierConfigManager.
- KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING);
- if (!TextUtils.isEmpty(intentStr)) {
- String extra = b.getString(CarrierConfigManager.
- KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING);
- String extraVal = b.getString(CarrierConfigManager.
- KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING);
-
- Intent intent = new Intent(intentStr);
- if (!TextUtils.isEmpty(extra)) {
- intent.putExtra(extra, extraVal);
- }
- Log.d(TAG, "ciActionOnSysUpdate: broadcasting intent " + intentStr +
- " with extra " + extra + ", " + extraVal);
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mContext.getApplicationContext().sendBroadcast(intent);
- }
- }
-}
diff --git a/src/com/android/settings/system/SystemUpdatePreferenceController.kt b/src/com/android/settings/system/SystemUpdatePreferenceController.kt
new file mode 100644
index 0000000..01df065
--- /dev/null
+++ b/src/com/android/settings/system/SystemUpdatePreferenceController.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.system
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.PersistableBundle
+import android.os.SystemUpdateManager
+import android.os.UserManager
+import android.telephony.CarrierConfigManager
+import android.util.Log
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settings.core.BasePreferenceController
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import kotlinx.coroutines.launch
+
+open class SystemUpdatePreferenceController(context: Context, preferenceKey: String) :
+ BasePreferenceController(context, preferenceKey) {
+ private val userManager: UserManager = context.userManager
+ private lateinit var preference: Preference
+
+ override fun getAvailabilityStatus() =
+ if (mContext.resources.getBoolean(R.bool.config_show_system_update_settings) &&
+ userManager.isAdminUser
+ ) AVAILABLE else UNSUPPORTED_ON_DEVICE
+
+ override fun displayPreference(screen: PreferenceScreen) {
+ super.displayPreference(screen)
+ preference = screen.findPreference(preferenceKey)!!
+ if (isAvailable) {
+ Utils.updatePreferenceToSpecificActivityOrRemove(
+ mContext,
+ screen,
+ preferenceKey,
+ Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY,
+ )
+ }
+ }
+
+ override fun handlePreferenceTreeClick(preference: Preference): Boolean {
+ if (preferenceKey == preference.key) {
+ val configManager = mContext.getSystemService(CarrierConfigManager::class.java)!!
+ configManager.getConfig(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)?.let {
+ if (it.getBoolean(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_BOOL)) {
+ ciActionOnSysUpdate(it)
+ }
+ }
+ }
+ // always return false here because this handler does not want to block other handlers.
+ return false
+ }
+
+ override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ preference.summary = calculateSummary()
+ }
+ }
+ }
+
+ private suspend fun calculateSummary(): String {
+ val updateInfo = mContext.getSystemUpdateInfo() ?: return getReleaseVersionSummary()
+
+ val status = updateInfo.getInt(SystemUpdateManager.KEY_STATUS)
+ if (status == SystemUpdateManager.STATUS_UNKNOWN) {
+ Log.d(TAG, "Update statue unknown")
+ }
+ when (status) {
+ SystemUpdateManager.STATUS_WAITING_DOWNLOAD,
+ SystemUpdateManager.STATUS_IN_PROGRESS,
+ SystemUpdateManager.STATUS_WAITING_INSTALL,
+ SystemUpdateManager.STATUS_WAITING_REBOOT -> {
+ return mContext.getString(R.string.android_version_pending_update_summary)
+ }
+
+ SystemUpdateManager.STATUS_IDLE,
+ SystemUpdateManager.STATUS_UNKNOWN -> {
+ val version = updateInfo.getString(SystemUpdateManager.KEY_TITLE)
+ if (!version.isNullOrEmpty()) {
+ return mContext.getString(R.string.android_version_summary, version)
+ }
+ }
+ }
+ return getReleaseVersionSummary()
+ }
+
+ private fun getReleaseVersionSummary(): String = mContext.getString(
+ R.string.android_version_summary,
+ Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY,
+ )
+
+ /**
+ * Trigger client initiated action (send intent) on system update
+ */
+ private fun ciActionOnSysUpdate(b: PersistableBundle) {
+ val intentStr = b.getString(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING)
+ if (intentStr.isNullOrEmpty()) return
+ val extra = b.getString(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING)
+ val extraVal =
+ b.getString(CarrierConfigManager.KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING)
+ Log.d(
+ TAG,
+ "ciActionOnSysUpdate: broadcasting intent $intentStr with extra $extra, $extraVal"
+ )
+ val intent = Intent(intentStr).apply {
+ if (!extra.isNullOrEmpty()) putExtra(extra, extraVal)
+ addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND)
+ }
+ mContext.applicationContext.sendBroadcast(intent)
+ }
+
+ companion object {
+ private const val TAG = "SysUpdatePrefContr"
+ }
+}
diff --git a/src/com/android/settings/widget/HighlightablePreferenceGroupAdapter.java b/src/com/android/settings/widget/HighlightablePreferenceGroupAdapter.java
index a64ec89..a93a986 100644
--- a/src/com/android/settings/widget/HighlightablePreferenceGroupAdapter.java
+++ b/src/com/android/settings/widget/HighlightablePreferenceGroupAdapter.java
@@ -122,6 +122,9 @@
&& TextUtils.equals(mHighlightKey, getItem(position).getKey()))) {
// This position should be highlighted. If it's highlighted before - skip animation.
addHighlightBackground(holder, !mFadeInAnimated);
+ if (v != null) {
+ v.requestAccessibilityFocus();
+ }
} else if (Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted))) {
// View with highlight is reused for a view that should not have highlight
removeHighlightBackground(holder, false /* animate */);
diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml
index 82edea5..a5767e4 100644
--- a/tests/robotests/res/values-mcc999/config.xml
+++ b/tests/robotests/res/values-mcc999/config.xml
@@ -53,7 +53,6 @@
<bool name="config_show_pointer_speed">false</bool>
<bool name="config_show_vibrate_input_devices">false</bool>
<bool name="config_show_reset_dashboard">false</bool>
- <bool name="config_show_system_update_settings">false</bool>
<bool name="config_show_device_model">false</bool>
<bool name="config_show_top_level_accessibility">false</bool>
<bool name="config_show_top_level_battery">false</bool>
diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
index 6d45af2..9368ec8 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
@@ -156,14 +156,13 @@
}
@Test
- public void launchHearingAidPairingDialog_deviceSupportsCsip_csipEnabled_noDialog() {
+ public void launchHearingAidPairingDialog_deviceSupportsCsip_noDialog() {
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
HearingAidInfo.DeviceSide.SIDE_LEFT);
makeDeviceSupportCsip();
- makeDeviceEnableCsip(true);
HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice,
TEST_LAUNCH_PAGE);
@@ -174,24 +173,6 @@
}
@Test
- public void launchHearingAidPairingDialog_deviceSupportsCsip_csipDisabled_dialogShown() {
- when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
- when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
- HearingAidInfo.DeviceMode.MODE_BINAURAL);
- when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
- HearingAidInfo.DeviceSide.SIDE_LEFT);
- makeDeviceSupportCsip();
- makeDeviceEnableCsip(false);
-
- HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice,
- TEST_LAUNCH_PAGE);
-
- shadowMainLooper().idle();
- final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- }
-
- @Test
public void launchHearingAidPairingDialog_dialogShown() {
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
@@ -213,11 +194,6 @@
when(mCachedBluetoothDevice.getProfiles()).thenReturn(uuids);
}
- private void makeDeviceEnableCsip(boolean enabled) {
- when(mCsipSetCoordinatorProfile.isEnabled(mCachedBluetoothDevice.getDevice()))
- .thenReturn(enabled);
- }
-
private void setupEnvironment() {
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java
index 04b48c0..f3fa69d 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java
@@ -39,21 +39,21 @@
import android.graphics.Color;
import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.testing.FragmentScenario;
+import androidx.lifecycle.Lifecycle;
import com.android.settings.R;
import com.android.settings.testutils.FakeTimer;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.shadows.ShadowContextWrapper;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowLooper;
import org.robolectric.util.ReflectionHelpers;
-import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.function.Consumer;
@@ -61,7 +61,7 @@
@RunWith(RobolectricTestRunner.class)
public class ScreenFlashNotificationColorDialogFragmentTest {
- private ShadowContextWrapper mShadowContextWrapper;
+ private FragmentScenario<TestScreenFlashNotificationColorDialogFragment> mFragmentScenario;
private ScreenFlashNotificationColorDialogFragment mDialogFragment;
private AlertDialog mAlertDialog;
private ColorSelectorLayout mColorSelectorLayout;
@@ -69,35 +69,32 @@
@Before
public void setUp() {
- FragmentActivity fragmentActivity = Robolectric.setupActivity(FragmentActivity.class);
- mShadowContextWrapper = shadowOf(fragmentActivity);
-
mCurrentColor = ROSE.mColorInt;
- mDialogFragment = createFragment();
+ mFragmentScenario = FragmentScenario.launch(
+ TestScreenFlashNotificationColorDialogFragment.class,
+ /* fragmentArgs= */ null,
+ R.style.Theme_AlertDialog_SettingsLib,
+ Lifecycle.State.INITIALIZED);
+ setupFragment();
+ }
- mDialogFragment.show(fragmentActivity.getSupportFragmentManager(), "test");
-
- mAlertDialog = (AlertDialog) mDialogFragment.getDialog();
- if (mAlertDialog != null) {
- mColorSelectorLayout = mAlertDialog.findViewById(R.id.color_selector_preference);
- }
+ @After
+ public void cleanUp() {
+ mFragmentScenario.close();
}
@Test
- @Ignore
public void test_assertShow() {
assertThat(mAlertDialog.isShowing()).isTrue();
}
@Test
- @Ignore
public void clickNeutral_assertShow() {
performClickOnDialog(BUTTON_NEUTRAL);
assertThat(mAlertDialog.isShowing()).isTrue();
}
@Test
- @Ignore
public void clickNeutral_assertStartPreview() {
performClickOnDialog(BUTTON_NEUTRAL);
getTimerFromFragment().runOneTask();
@@ -106,7 +103,6 @@
}
@Test
- @Ignore
public void clickNeutral_flushAllScheduledTasks_assertStopPreview() {
performClickOnDialog(BUTTON_NEUTRAL);
getTimerFromFragment().runAllTasks();
@@ -115,31 +111,28 @@
}
@Test
- @Ignore
public void clickNegative_assertNotShow() {
performClickOnDialog(BUTTON_NEGATIVE);
assertThat(mAlertDialog.isShowing()).isFalse();
}
@Test
- @Ignore
public void clickPositive_assertNotShow() {
performClickOnDialog(BUTTON_POSITIVE);
assertThat(mAlertDialog.isShowing()).isFalse();
}
@Test
- @Ignore
public void clickNeutralAndPause_assertStopPreview() {
performClickOnDialog(BUTTON_NEUTRAL);
getTimerFromFragment().runOneTask();
- mDialogFragment.onPause();
+ // move the state from RESUMED to CREATED to make fragment's onPause() to be called
+ mFragmentScenario.moveToState(Lifecycle.State.CREATED);
assertStopPreview();
}
@Test
- @Ignore
public void clickNeutralAndClickNegative_assertStopPreview() {
performClickOnDialog(BUTTON_NEUTRAL);
getTimerFromFragment().runOneTask();
@@ -149,7 +142,6 @@
}
@Test
- @Ignore
public void clickNeutralAndClickPositive_assertStopPreview() {
performClickOnDialog(BUTTON_NEUTRAL);
getTimerFromFragment().runOneTask();
@@ -159,7 +151,6 @@
}
@Test
- @Ignore
public void clickNeutralAndClickColor_assertStartPreview() {
performClickOnDialog(BUTTON_NEUTRAL);
getTimerFromFragment().runOneTask();
@@ -177,7 +168,6 @@
}
@Test
- @Ignore
public void clickColorAndClickNegative_assertColor() {
checkColorButton(AZURE);
performClickOnDialog(BUTTON_NEGATIVE);
@@ -187,7 +177,6 @@
}
@Test
- @Ignore
public void clickColorAndClickPositive_assertColor() {
checkColorButton(BLUE);
performClickOnDialog(BUTTON_POSITIVE);
@@ -201,23 +190,32 @@
private void performClickOnDialog(int whichButton) {
mAlertDialog.getButton(whichButton).performClick();
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
}
private Intent getLastCapturedIntent() {
- final List<Intent> capturedIntents = new ArrayList<>(
- mShadowContextWrapper.getBroadcastIntents());
+ final List<Intent> capturedIntents =
+ shadowOf(RuntimeEnvironment.getApplication()).getBroadcastIntents();
final int size = capturedIntents.size();
return capturedIntents.get(size - 1);
}
- private ScreenFlashNotificationColorDialogFragment createFragment() {
- ScreenFlashNotificationColorDialogFragmentWithFakeTimer fragment =
- new ScreenFlashNotificationColorDialogFragmentWithFakeTimer();
- ReflectionHelpers.setField(fragment, "mCurrentColor", mCurrentColor);
- ReflectionHelpers.setField(fragment, "mConsumer",
- (Consumer<Integer>) selectedColor -> mCurrentColor = selectedColor);
+ private void setupFragment() {
+ mFragmentScenario.onFragment(fragment -> {
+ ReflectionHelpers.setField(fragment, "mCurrentColor", mCurrentColor);
+ ReflectionHelpers.setField(fragment, "mConsumer",
+ (Consumer<Integer>) selectedColor -> mCurrentColor = selectedColor);
+ });
+ mFragmentScenario.moveToState(Lifecycle.State.RESUMED);
- return fragment;
+ mFragmentScenario.onFragment(fragment -> {
+ assertThat(fragment.getDialog()).isNotNull();
+ assertThat(fragment.requireDialog().isShowing()).isTrue();
+ assertThat(fragment.requireDialog()).isInstanceOf(AlertDialog.class);
+ mAlertDialog = (AlertDialog) fragment.requireDialog();
+ mDialogFragment = fragment;
+ mColorSelectorLayout = mAlertDialog.findViewById(R.id.color_selector_preference);
+ });
}
private FakeTimer getTimerFromFragment() {
@@ -243,7 +241,7 @@
* A {@link ScreenFlashNotificationColorDialogFragment} that uses a fake timer so that it won't
* create unmanageable timer threads during test.
*/
- public static class ScreenFlashNotificationColorDialogFragmentWithFakeTimer extends
+ public static class TestScreenFlashNotificationColorDialogFragment extends
ScreenFlashNotificationColorDialogFragment {
@Override
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java
index 090fb0c..2bd2d74 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java
@@ -34,6 +34,9 @@
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import java.util.HashSet;
+import java.util.Set;
+
/** Tests for {@link BluetoothDetailsPairOtherController}. */
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsPairOtherControllerTest extends BluetoothDetailsControllerTestBase {
@@ -60,8 +63,9 @@
mScreen.addPreference(mSpacePreference);
}
+ /** Test the pair other side button title during initialization. */
@Test
- public void init_leftSideDevice_rightSideButtonTitle() {
+ public void init_leftSideDevice_pairRightSideButtonTitle() {
when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidInfo.DeviceSide.SIDE_LEFT);
mController.init(mScreen);
@@ -70,8 +74,9 @@
mContext.getString(R.string.bluetooth_pair_right_ear_button));
}
+ /** Test the pair other side button title during initialization. */
@Test
- public void init_rightSideDevice_leftSideButtonTitle() {
+ public void init_rightSideDevice_pairLeftSideButtonTitle() {
when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidInfo.DeviceSide.SIDE_RIGHT);
mController.init(mScreen);
@@ -80,9 +85,10 @@
mContext.getString(R.string.bluetooth_pair_left_ear_button));
}
+ /** Test the pair other side button visibility during initialization. */
@Test
- public void init_isNotConnectedAshaHearingAidDevice_notVisiblePreference() {
- when(mCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(false);
+ public void init_isNotConnectedHearingAidDevice_preferenceIsNotVisible() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(false);
mController.init(mScreen);
@@ -90,23 +96,49 @@
assertThat(mSpacePreference.isVisible()).isFalse();
}
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Hearing aids is not connected
+ * Expected result:
+ * The controller is not available. No need to show pair other side hint for
+ * not connected device.
+ */
@Test
- public void isAvailable_isNotConnectedAshaHearingAidDevice_notAvailable() {
- when(mCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(false);
+ public void isAvailable_isNotConnectedHearingAidDevice_notAvailable() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
}
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Monaural hearing aids
+ * Expected result:
+ * The controller is not available. No need to show pair other side hint for
+ * monaural device.
+ */
@Test
- public void isAvailable_isConnectedAshaHearingAidDevice_isMonaural_notAvailable() {
- when(mCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
+ public void isAvailable_isConnectedHearingAidDevice_isMonaural_notAvailable() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_MONAURAL);
assertThat(mController.isAvailable()).isFalse();
}
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Binaural ASHA hearing aids
+ * 2. Sub device is added
+ * 3. Sub device is connected
+ * Expected result:
+ * The controller is not available. Both sides are already paired and connected.
+ */
@Test
- public void isAvailable_subDeviceIsConnectedAshaHearingAidDevice_notAvailable() {
+ public void isAvailable_ashaDevice_otherDeviceIsConnected_notAvailable() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mSubCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
@@ -115,8 +147,18 @@
assertThat(mController.isAvailable()).isFalse();
}
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Binaural ASHA hearing aids
+ * 2. Sub device is added
+ * 3. Sub device is not connected
+ * Expected result:
+ * The controller is available. Need to show the hint to pair the other side.
+ */
@Test
- public void isAvailable_subDeviceIsNotConnectedAshaHearingAidDevice_available() {
+ public void isAvailable_ashaDevice_otherDeviceIsNotConnected_available() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mSubCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(false);
@@ -125,8 +167,17 @@
assertThat(mController.isAvailable()).isTrue();
}
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Binaural ASHA hearing aids
+ * 2. No sub device added
+ * Expected result:
+ * The controller is available. Need to show the hint to pair the other side.
+ */
@Test
- public void isAvailable_subDeviceNotExist_available() {
+ public void isAvailable_ashaDevice_otherDeviceIsNotExist_available() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
when(mCachedDevice.getSubDevice()).thenReturn(null);
@@ -134,8 +185,67 @@
assertThat(mController.isAvailable()).isTrue();
}
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Binaural LE Audio hearing aids
+ * 2. Member device is added
+ * 3. Member device is connected
+ * Expected result:
+ * The controller is not available. Both sides are already paired and connected.
+ */
@Test
- public void refresh_leftSideDevice_leftSideButtonTitle() {
+ public void isAvailable_leAudioDevice_otherDeviceIsConnected_notAvailable() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.isConnectedLeAudioHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ when(mSubCachedDevice.isConnectedLeAudioHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mSubCachedDevice));
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Binaural LE Audio hearing aids
+ * 2. Member device is added
+ * 3. Member device is not connected
+ * Expected result:
+ * The controller is available. Need to show the hint to pair the other side.
+ */
+ @Test
+ public void isAvailable_leAudioDevice_otherDeviceIsNotConnected_available() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.isConnectedLeAudioHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ when(mSubCachedDevice.isConnectedLeAudioHearingAidDevice()).thenReturn(false);
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mSubCachedDevice));
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ /**
+ * Test if the controller is available.
+ * Conditions:
+ * 1. Binaural LE Audio hearing aids
+ * 2. No member device added
+ * Expected result:
+ * The controller is available. Need to show the hint to pair the other side.
+ */
+ @Test
+ public void isAvailable_leAudioDevice_otherDeviceIsNotExist_available() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.isConnectedLeAudioHearingAidDevice()).thenReturn(true);
+ when(mCachedDevice.getDeviceMode()).thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ when(mCachedDevice.getMemberDevice()).thenReturn(new HashSet<>());
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ /** Test the pair other side button title after refreshing. */
+ @Test
+ public void refresh_rightSideDevice_pairLeftSideButtonTitle() {
when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidInfo.DeviceSide.SIDE_RIGHT);
mController.init(mScreen);
@@ -145,9 +255,10 @@
mContext.getString(R.string.bluetooth_pair_left_ear_button));
}
+ /** Test the pair other side button visibility after refreshing. */
@Test
- public void refresh_isNotConnectedAshaHearingAidDevice_notVisiblePreference() {
- when(mCachedDevice.isConnectedAshaHearingAidDevice()).thenReturn(false);
+ public void refresh_isNotConnectedHearingAidDevice_preferenceIsNotVisible() {
+ when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(false);
mController.init(mScreen);
mController.refresh();
diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java
index 4640efe..b16d336 100644
--- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java
@@ -17,7 +17,6 @@
package com.android.settings.datausage;
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -44,19 +43,15 @@
import androidx.preference.PreferenceManager;
import com.android.settings.R;
-import com.android.settings.SettingsActivity;
import com.android.settings.network.MobileDataEnabledListener;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.widget.LoadingViewController;
-import com.android.settingslib.AppItem;
import com.android.settingslib.NetworkPolicyEditor;
import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
-import com.android.settingslib.net.NetworkCycleChartData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
@@ -67,9 +62,6 @@
import org.robolectric.annotation.Implements;
import org.robolectric.util.ReflectionHelpers;
-import java.util.ArrayList;
-import java.util.List;
-
@RunWith(RobolectricTestRunner.class)
public class DataUsageListTest {
@@ -196,34 +188,6 @@
}
@Test
- public void startAppDataUsage_shouldAddCyclesInfoToLaunchArguments() {
- final long startTime = 1521583200000L;
- final long endTime = 1521676800000L;
- final List<NetworkCycleChartData> data = new ArrayList<>();
- final NetworkCycleChartData.Builder builder = new NetworkCycleChartData.Builder();
- builder.setStartTime(startTime)
- .setEndTime(endTime);
- data.add(builder.build());
- ReflectionHelpers.setField(mDataUsageList, "mCycleData", data);
- final Spinner spinner = mock(Spinner.class);
- when(spinner.getSelectedItemPosition()).thenReturn(0);
- ReflectionHelpers.setField(mDataUsageList, "mCycleSpinner", spinner);
- final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
-
- mDataUsageList.startAppDataUsage(new AppItem());
-
- verify(mActivity).startActivity(intent.capture());
- final Bundle arguments =
- intent.getValue().getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
- assertThat(arguments.getLong(AppDataUsage.ARG_SELECTED_CYCLE)).isEqualTo(endTime);
- final ArrayList<Long> cycles =
- (ArrayList) arguments.getSerializable(AppDataUsage.ARG_NETWORK_CYCLES);
- assertThat(cycles).hasSize(2);
- assertThat(cycles.get(0)).isEqualTo(endTime);
- assertThat(cycles.get(1)).isEqualTo(startTime);
- }
-
- @Test
public void onViewCreated_shouldHideCycleSpinner() {
final View view = new View(mActivity);
final View header = getHeader();
@@ -255,7 +219,6 @@
mDataUsageList.onPause();
verify(mLoaderManager).destroyLoader(DataUsageList.LOADER_CHART_DATA);
- verify(mLoaderManager).destroyLoader(DataUsageList.LOADER_SUMMARY);
}
private View getHeader() {
diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceControllerTest.java
index b405f9e..13bc6a4 100644
--- a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceControllerTest.java
@@ -16,6 +16,9 @@
package com.android.settings.development;
+import static com.android.settings.development.BluetoothLeAudioDeviceDetailsPreferenceController
+ .LE_AUDIO_TOGGLE_VISIBLE_PROPERTY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
@@ -25,12 +28,11 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
-import android.provider.DeviceConfig;
+import android.os.SystemProperties;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
-import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import org.junit.After;
@@ -77,9 +79,8 @@
public void onPreferenceChanged_settingEnabled_shouldTurnOnLeAudioDeviceDetailSetting() {
mController.sLeAudioSupportedStateCache = BluetoothStatusCodes.FEATURE_SUPPORTED;
mController.onPreferenceChange(mPreference, true /* new value */);
- final boolean isEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED, false);
+ final boolean isEnabled = SystemProperties.getBoolean(
+ LE_AUDIO_TOGGLE_VISIBLE_PROPERTY, false);
assertThat(isEnabled).isTrue();
}
@@ -88,9 +89,8 @@
public void onPreferenceChanged_settingDisabled_shouldTurnOffLeAudioDeviceDetailSetting() {
mController.sLeAudioSupportedStateCache = BluetoothStatusCodes.FEATURE_SUPPORTED;
mController.onPreferenceChange(mPreference, false /* new value */);
- final boolean isEnabled = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED, false);
+ final boolean isEnabled = SystemProperties.getBoolean(
+ LE_AUDIO_TOGGLE_VISIBLE_PROPERTY, true);
assertThat(isEnabled).isFalse();
}
@@ -98,18 +98,15 @@
@Test
public void updateState_settingEnabled_preferenceShouldBeChecked() {
mController.sLeAudioSupportedStateCache = BluetoothStatusCodes.FEATURE_SUPPORTED;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED, "true", false);
+ SystemProperties.set(LE_AUDIO_TOGGLE_VISIBLE_PROPERTY, "true");
mController.updateState(mPreference);
-
verify(mPreference).setChecked(true);
}
@Test
public void updateState_settingDisabled_preferenceShouldNotBeChecked() {
mController.sLeAudioSupportedStateCache = BluetoothStatusCodes.FEATURE_SUPPORTED;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
- SettingsUIDeviceConfig.BT_LE_AUDIO_DEVICE_DETAIL_ENABLED, "false", false);
+ SystemProperties.set(LE_AUDIO_TOGGLE_VISIBLE_PROPERTY, "false");
mController.updateState(mPreference);
verify(mPreference).setChecked(false);
diff --git a/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java
index 3ad14e5..19eac82 100644
--- a/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java
@@ -132,23 +132,23 @@
}
@Test
- public void isSliceableCorrectKey_returnsTrue() {
+ public void isPublicSliceCorrectKey_returnsTrue() {
final AmbientDisplayAlwaysOnPreferenceController controller =
new AmbientDisplayAlwaysOnPreferenceController(mContext,
"ambient_display_always_on");
- assertThat(controller.isSliceable()).isTrue();
+ assertThat(controller.isPublicSlice()).isTrue();
}
@Test
- public void isSliceableIncorrectKey_returnsFalse() {
+ public void isPublicSliceIncorrectKey_returnsFalse() {
final AmbientDisplayAlwaysOnPreferenceController controller =
new AmbientDisplayAlwaysOnPreferenceController(mContext, "bad_key");
- assertThat(controller.isSliceable()).isFalse();
+ assertThat(controller.isPublicSlice()).isFalse();
}
@Test
- public void isPublicSlice_returnTrue() {
- assertThat(mController.isPublicSlice()).isTrue();
+ public void isSliceable_returnTrue() {
+ assertThat(mController.isSliceable()).isTrue();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/display/FoldLockBehaviorSettingsTest.java b/tests/robotests/src/com/android/settings/display/FoldLockBehaviorSettingsTest.java
index 37b9391..991f529 100644
--- a/tests/robotests/src/com/android/settings/display/FoldLockBehaviorSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/display/FoldLockBehaviorSettingsTest.java
@@ -24,13 +24,19 @@
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.settings.testutils.shadow.ShadowSystemSettings;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+ ShadowSystemSettings.class,
+})
public class FoldLockBehaviorSettingsTest {
private Context mContext;
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
index 7104206..c9e201b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/LowBatteryDetectorTest.java
@@ -53,7 +53,6 @@
mPolicy = spy(new BatteryTipPolicy(RuntimeEnvironment.application));
mContext = RuntimeEnvironment.application;
ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", true);
- ReflectionHelpers.setField(mPolicy, "lowBatteryHour", 3);
mBatteryInfo.discharging = true;
mLowBatteryDetector = new LowBatteryDetector(mContext, mPolicy, mBatteryInfo,
@@ -78,13 +77,9 @@
@Test
public void testDetect_lowBattery_tipNew() {
- mBatteryInfo.batteryLevel = 3;
+ mBatteryInfo.batteryLevel = 20;
mBatteryInfo.remainingTimeUs = TimeUnit.DAYS.toMillis(1);
assertThat(mLowBatteryDetector.detect().getState()).isEqualTo(BatteryTip.StateType.NEW);
-
- mBatteryInfo.batteryLevel = 50;
- mBatteryInfo.remainingTimeUs = TimeUnit.MINUTES.toMillis(1);
- assertThat(mLowBatteryDetector.detect().getState()).isEqualTo(BatteryTip.StateType.NEW);
}
@Test
@@ -104,9 +99,9 @@
}
@Test
- public void testDetect_timeEstimationZero_tipInvisible() {
+ public void testDetect_lowTimeEstimation_tipInvisible() {
mBatteryInfo.batteryLevel = 50;
- mBatteryInfo.remainingTimeUs = 0;
+ mBatteryInfo.remainingTimeUs = TimeUnit.MINUTES.toMillis(1);
assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapperTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapperTest.java
new file mode 100644
index 0000000..60e0af0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapperTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+
+import com.android.settings.testutils.BatteryTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.TimeZone;
+
+@RunWith(RobolectricTestRunner.class)
+public class AnomalyEventWrapperTest {
+ private AnomalyEventWrapper mAnomalyEventWrapper;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
+ mContext = spy(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void getDismissRecordKey_returnExpectedResult() {
+ mAnomalyEventWrapper = new AnomalyEventWrapper(mContext,
+ BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent());
+ assertThat(mAnomalyEventWrapper.getDismissRecordKey())
+ .isEqualTo("KEY_BRIGHTNESS");
+
+ mAnomalyEventWrapper = new AnomalyEventWrapper(mContext,
+ BatteryTestUtils.createScreenTimeoutAnomalyEvent());
+ assertThat(mAnomalyEventWrapper.getDismissRecordKey())
+ .isEqualTo("KEY_SCREEN_TIMEOUT");
+
+ mAnomalyEventWrapper = new AnomalyEventWrapper(mContext,
+ BatteryTestUtils.createAppAnomalyEvent());
+ assertThat(mAnomalyEventWrapper.getDismissRecordKey())
+ .isEqualTo("KEY_APP_1");
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
index 630ff45..63cb1b3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -58,13 +59,14 @@
private BatteryTipsCardPreference mBatteryTipsCardPreference;
private PowerUsageAdvanced mPowerUsageAdvanced;
private BatteryTipsController mBatteryTipsController;
+ private BatteryChartPreferenceController mBatteryChartPreferenceController;
@Mock
private View mFakeView;
@Mock
- private BatteryChartPreferenceController mBatteryChartPreferenceController;
- @Mock
private BatteryUsageBreakdownController mBatteryUsageBreakdownController;
+ @Mock
+ private BatteryDiffEntry mFakeEntry;
@Before
public void setUp() {
@@ -73,8 +75,13 @@
mFeatureFactory = FakeFeatureFactory.setupForTest();
mBatteryTipsCardPreference = new BatteryTipsCardPreference(mContext, /*attrs=*/ null);
mBatteryTipsController = new BatteryTipsController(mContext);
+ mBatteryChartPreferenceController =
+ spy(new BatteryChartPreferenceController(mContext, null, null));
+ mBatteryChartPreferenceController.mPrefContext = mContext;
mBatteryTipsController.mCardPreference = mBatteryTipsCardPreference;
- mPowerUsageAdvanced = new PowerUsageAdvanced();
+
+ mPowerUsageAdvanced = spy(new PowerUsageAdvanced());
+ doReturn(mContext).when(mPowerUsageAdvanced).getContext();
mPowerUsageAdvanced.mBatteryTipsController = mBatteryTipsController;
mPowerUsageAdvanced.mBatteryChartPreferenceController = mBatteryChartPreferenceController;
mPowerUsageAdvanced.mBatteryUsageBreakdownController = mBatteryUsageBreakdownController;
@@ -82,6 +89,7 @@
1694354400000L, 1, // 2023-09-10 22:00:00
1694361600000L, 2, // 2023-09-11 00:00:00
1694368800000L, 3))); // 2023-09-11 02:00:00
+ doReturn("TestEntriesKey").when(mFakeEntry).getKey();
}
@Test
@@ -99,7 +107,8 @@
when(mFakeView.getId()).thenReturn(R.id.main_button);
doNothing().when(mContext).startActivity(captor.capture());
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(adaptiveBrightnessAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(
+ adaptiveBrightnessAnomaly, adaptiveBrightnessAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
@@ -110,18 +119,21 @@
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, -1))
.isEqualTo(SettingsEnums.DISPLAY);
verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "BrightnessAnomaly");
+ verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "BrightnessAnomaly");
}
@Test
- public void onClick_dismissBtn_cardDismissAndLogged() {
+ public void onClick_dismissBtnOfSettingsAnomaly_cardDismissAndLogged() {
final PowerAnomalyEvent screenTimeoutAnomaly =
BatteryTestUtils.createScreenTimeoutAnomalyEvent();
DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext);
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
when(mFakeView.getId()).thenReturn(R.id.dismiss_button);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(screenTimeoutAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(
+ screenTimeoutAnomaly, screenTimeoutAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
@@ -129,6 +141,8 @@
assertThat(DatabaseUtils.getDismissedPowerAnomalyKeys(mContext))
.contains(PowerAnomalyKey.KEY_SCREEN_TIMEOUT.name());
verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "ScreenTimeoutAnomaly");
+ verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, "ScreenTimeoutAnomaly");
}
@@ -137,30 +151,40 @@
final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
when(mFakeView.getId()).thenReturn(R.id.main_button);
+ doNothing().when(mBatteryChartPreferenceController).selectHighlightSlotIndex();
+ when(mPowerUsageAdvanced.findRelatedBatteryDiffEntry(any())).thenReturn(mFakeEntry);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly, appsAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
verify(mContext, never()).startActivity(any(Intent.class));
+ verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(
+ eq(1), eq(0));
verify(mBatteryChartPreferenceController).selectHighlightSlotIndex();
verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly");
+ verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "AppAnomaly");
}
@Test
- public void onClick_dismissBtnOfAppsAnomaly_removeHighlightSlotIndex() {
+ public void onClick_dismissBtnOfAppsAnomaly_keepHighlightSlotIndex() {
final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
when(mFakeView.getId()).thenReturn(R.id.dismiss_button);
+ when(mPowerUsageAdvanced.findRelatedBatteryDiffEntry(any())).thenReturn(mFakeEntry);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(appsAnomaly, appsAnomaly);
mBatteryTipsCardPreference.onClick(mFakeView);
assertThat(mBatteryTipsCardPreference.isVisible()).isFalse();
+ verify(mContext, never()).startActivity(any(Intent.class));
verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(
- eq(BatteryChartViewModel.SELECTED_INDEX_INVALID),
- eq(BatteryChartViewModel.SELECTED_INDEX_INVALID));
+ eq(1), eq(0));
+ verify(mBatteryChartPreferenceController, never()).selectHighlightSlotIndex();
+ verify(mFeatureFactory.metricsFeatureProvider).action(
+ mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly");
verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, "AppAnomaly");
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
index 913c00a..b8afe98 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
@@ -16,8 +16,6 @@
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 static org.mockito.Mockito.verify;
@@ -70,30 +68,18 @@
@Test
public void handleBatteryTipsCardUpdated_null_hidePreference() {
- mBatteryTipsController.handleBatteryTipsCardUpdated(/* powerAnomalyEvents= */ null);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(/* powerAnomalyEvents= */ null, false);
verify(mBatteryTipsCardPreference).setVisible(false);
}
@Test
- public void getDismissRecordKey_returnExpectedResult() {
- assertThat(BatteryTipsController.getDismissRecordKey(
- BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent()))
- .isEqualTo("KEY_BRIGHTNESS");
- assertThat(BatteryTipsController.getDismissRecordKey(
- BatteryTestUtils.createScreenTimeoutAnomalyEvent()))
- .isEqualTo("KEY_SCREEN_TIMEOUT");
- assertThat(BatteryTipsController.getDismissRecordKey(
- BatteryTestUtils.createAppAnomalyEvent()))
- .isEqualTo("KEY_APP_1");
- }
-
- @Test
public void handleBatteryTipsCardUpdated_adaptiveBrightnessAnomaly_showAnomaly() {
PowerAnomalyEvent event = BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ new AnomalyEventWrapper(mContext, event), false);
// Check pre-defined string
verify(mBatteryTipsCardPreference).setTitle(
@@ -114,7 +100,8 @@
PowerAnomalyEvent event = BatteryTestUtils.createScreenTimeoutAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ new AnomalyEventWrapper(mContext, event), false);
verify(mBatteryTipsCardPreference).setTitle("Reduce screen timeout to extend battery life");
verify(mBatteryTipsCardPreference).setIconResourceId(R.drawable.ic_battery_tips_lightbulb);
@@ -139,7 +126,8 @@
.build();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ mBatteryTipsController.handleBatteryTipsCardUpdated(
+ new AnomalyEventWrapper(mContext, event), false);
verify(mBatteryTipsCardPreference).setTitle(testTitle);
verify(mBatteryTipsCardPreference).setIconResourceId(R.drawable.ic_battery_tips_lightbulb);
@@ -157,10 +145,13 @@
PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent();
when(mFeatureFactory.powerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
- mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+ AnomalyEventWrapper eventWrapper = new AnomalyEventWrapper(mContext, event);
+ eventWrapper.setRelatedBatteryDiffEntry(
+ new BatteryDiffEntry(mContext, "", "Chrome", 0));
+ mBatteryTipsController.handleBatteryTipsCardUpdated(eventWrapper, false);
verify(mBatteryTipsCardPreference).setTitle(
- "Chrome used more battery than usual in foreground");
+ "Chrome used more battery than usual");
verify(mBatteryTipsCardPreference).setIconResourceId(
R.drawable.ic_battery_tips_warning_icon);
verify(mBatteryTipsCardPreference).setMainButtonStrokeColorResourceId(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
index d89c06b..a721ad4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
@@ -69,7 +69,7 @@
@Mock
private BatteryHistEntry mBatteryHistEntry;
@Mock
- private PowerGaugePreference mPowerGaugePreference;
+ private AnomalyAppItemPreference mAnomalyAppItemPreference;
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
@@ -123,13 +123,14 @@
BatteryDiffEntry.sResourceCache.put(
"fakeBatteryDiffEntryKey",
new BatteryEntry.NameAndIcon("fakeName", /*icon=*/ null, /*iconId=*/ 1));
+ doReturn(mAnomalyAppItemPreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
}
@Test
public void onDestroy_clearPreferenceCacheAndPreferenceGroupRemoveAll() {
// Ensures the testing environment is correct.
mBatteryUsageBreakdownController.mPreferenceCache.put(
- PREF_KEY, mPowerGaugePreference);
+ PREF_KEY, mAnomalyAppItemPreference);
assertThat(mBatteryUsageBreakdownController.mPreferenceCache).hasSize(1);
mBatteryUsageBreakdownController.onDestroy();
@@ -178,7 +179,6 @@
doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel();
doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
mBatteryUsageBreakdownController.addAllPreferences();
@@ -188,27 +188,25 @@
@Test
public void removeAndCacheAllUnusedPreferences_removePref_buildCacheAndRemoveAllPreference() {
doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0);
+ doReturn(mAnomalyAppItemPreference).when(mAppListPreferenceGroup).getPreference(0);
doReturn(PREF_KEY2).when(mBatteryHistEntry).getKey();
- doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
+ doReturn(PREF_KEY).when(mAnomalyAppItemPreference).getKey();
// Ensures the testing data is correct.
assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty();
mBatteryUsageBreakdownController.removeAndCacheAllUnusedPreferences();
assertThat(mBatteryUsageBreakdownController.mPreferenceCache.get(PREF_KEY))
- .isEqualTo(mPowerGaugePreference);
- verify(mAppListPreferenceGroup).removePreference(mPowerGaugePreference);
+ .isEqualTo(mAnomalyAppItemPreference);
+ verify(mAppListPreferenceGroup).removePreference(mAnomalyAppItemPreference);
}
@Test
public void removeAndCacheAllUnusedPreferences_keepPref_KeepAllPreference() {
doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0);
+ doReturn(mAnomalyAppItemPreference).when(mAppListPreferenceGroup).getPreference(0);
doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey();
- doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
- doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
+ doReturn(PREF_KEY).when(mAnomalyAppItemPreference).getKey();
// Ensures the testing data is correct.
assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty();
@@ -232,10 +230,10 @@
@Test
public void handlePreferenceTreeClick_forAppEntry_returnTrue() {
mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY;
- doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
+ doReturn(mBatteryDiffEntry).when(mAnomalyAppItemPreference).getBatteryDiffEntry();
assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick(
- mPowerGaugePreference)).isTrue();
+ mAnomalyAppItemPreference)).isTrue();
verify(mMetricsFeatureProvider)
.action(
SettingsEnums.OPEN_BATTERY_USAGE,
@@ -248,10 +246,10 @@
@Test
public void handlePreferenceTreeClick_forSystemEntry_returnTrue() {
mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_UID_BATTERY;
- doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
+ doReturn(mBatteryDiffEntry).when(mAnomalyAppItemPreference).getBatteryDiffEntry();
assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick(
- mPowerGaugePreference)).isTrue();
+ mAnomalyAppItemPreference)).isTrue();
verify(mMetricsFeatureProvider)
.action(
SettingsEnums.OPEN_BATTERY_USAGE,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
index f06dc63..cd594d3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -72,6 +72,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
mContext = spy(RuntimeEnvironment.application);
ConvertUtils.sUsageSource = ConvertUtils.EMPTY_USAGE_SOURCE;
when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java
index 953c2d4..9753bd2 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvancedTest.java
@@ -20,8 +20,8 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -41,7 +41,9 @@
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.TimeZone;
+import java.util.function.Predicate;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowDashboardFragment.class)
@@ -50,6 +52,9 @@
private Context mContext;
private PowerUsageAdvanced mPowerUsageAdvanced;
+ private Predicate<PowerAnomalyEvent> mCardFilterPredicate;
+ private Predicate<PowerAnomalyEvent> mSlotFilterPredicate;
+
@Mock
private BatteryTipsController mBatteryTipsController;
@Mock
@@ -65,7 +70,7 @@
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
mContext = spy(RuntimeEnvironment.application);
- mPowerUsageAdvanced = new PowerUsageAdvanced();
+ mPowerUsageAdvanced = spy(new PowerUsageAdvanced());
mPowerUsageAdvanced.mBatteryTipsController = mBatteryTipsController;
mPowerUsageAdvanced.mBatteryChartPreferenceController = mBatteryChartPreferenceController;
mPowerUsageAdvanced.mScreenOnTimeController = mScreenOnTimeController;
@@ -74,43 +79,63 @@
1694354400000L, 1, // 2023-09-10 22:00:00
1694361600000L, 2, // 2023-09-11 00:00:00
1694368800000L, 3))); // 2023-09-11 02:00:00
+ doReturn(mContext).when(mPowerUsageAdvanced).getContext();
+ mSlotFilterPredicate = PowerAnomalyEvent::hasWarningItemInfo;
}
@Test
- public void getHighestScoreAnomalyEvent_withEmptyOrNullList_getNull() {
- assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, null)).isNull();
- assertThat(PowerUsageAdvanced.getHighestScoreAnomalyEvent(
- mContext, BatteryTestUtils.createEmptyPowerAnomalyEventList())).isNull();
+ public void getFilterAnomalyEvent_withEmptyOrNullList_getNull() {
+ prepareCardFilterPredicate(null);
+ assertThat(PowerUsageAdvanced
+ .getAnomalyEvent(null, mCardFilterPredicate)).isNull();
+ assertThat(PowerUsageAdvanced
+ .getAnomalyEvent(null, mSlotFilterPredicate)).isNull();
+ assertThat(PowerUsageAdvanced.getAnomalyEvent(
+ BatteryTestUtils.createEmptyPowerAnomalyEventList(), mCardFilterPredicate))
+ .isNull();
+ assertThat(PowerUsageAdvanced.getAnomalyEvent(
+ BatteryTestUtils.createEmptyPowerAnomalyEventList(), mSlotFilterPredicate))
+ .isNull();
}
@Test
- public void getHighestScoreAnomalyEvent_withoutDismissed_getHighestScoreEvent() {
+ public void getFilterAnomalyEvent_withoutDismissed_getHighestScoreEvent() {
final PowerAnomalyEventList powerAnomalyEventList =
BatteryTestUtils.createNonEmptyPowerAnomalyEventList();
- final PowerAnomalyEvent highestScoreEvent =
- PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList);
+ final PowerAnomalyEvent slotEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mSlotFilterPredicate);
+ prepareCardFilterPredicate(slotEvent);
+ final PowerAnomalyEvent cardEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mCardFilterPredicate);
- assertThat(highestScoreEvent)
- .isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent());
+ assertThat(cardEvent).isEqualTo(BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent());
+ assertThat(slotEvent).isNull();
}
@Test
- public void getHighestScoreAnomalyEvent_withBrightnessDismissed_getScreenTimeout() {
+ public void getFilterAnomalyEvent_withBrightnessDismissed_getScreenTimeout() {
final PowerAnomalyEventList powerAnomalyEventList =
BatteryTestUtils.createNonEmptyPowerAnomalyEventList();
DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext);
DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, PowerAnomalyKey.KEY_BRIGHTNESS.name());
- final PowerAnomalyEvent highestScoreEvent =
- PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList);
+ final PowerAnomalyEvent slotEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mSlotFilterPredicate);
+ prepareCardFilterPredicate(slotEvent);
+ final PowerAnomalyEvent cardEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mCardFilterPredicate);
- assertThat(highestScoreEvent)
- .isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent());
+ assertThat(cardEvent).isEqualTo(BatteryTestUtils.createScreenTimeoutAnomalyEvent());
+ assertThat(slotEvent).isNull();
}
@Test
- public void getHighestScoreAnomalyEvent_withAllDismissed_getNull() {
+ public void getFilterAnomalyEvent_withAllDismissed_getNull() {
final PowerAnomalyEventList powerAnomalyEventList =
BatteryTestUtils.createNonEmptyPowerAnomalyEventList();
DatabaseUtils.removeDismissedPowerAnomalyKeys(mContext);
@@ -118,20 +143,26 @@
DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, key.name());
}
- final PowerAnomalyEvent highestScoreEvent =
- PowerUsageAdvanced.getHighestScoreAnomalyEvent(mContext, powerAnomalyEventList);
+ final PowerAnomalyEvent slotEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mSlotFilterPredicate);
+ prepareCardFilterPredicate(slotEvent);
+ final PowerAnomalyEvent cardEvent =
+ PowerUsageAdvanced.getAnomalyEvent(powerAnomalyEventList,
+ mCardFilterPredicate);
- assertThat(highestScoreEvent).isNull();
+ assertThat(cardEvent).isNull();
+ assertThat(slotEvent).isNull();
}
@Test
public void onDisplayAnomalyEventUpdated_withSettingsAnomalyEvent_skipHighlightSlotEffect() {
final PowerAnomalyEvent event = BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent();
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event, event);
- assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isEqualTo(event);
- verify(mBatteryTipsController).handleBatteryTipsCardUpdated(eq(event));
+ assertThat(mPowerUsageAdvanced.mHighlightEventWrapper.get().getEventId())
+ .isEqualTo(event.getEventId());
verify(mPowerUsageAdvanced.mBatteryTipsController).setOnAnomalyConfirmListener(isNull());
verify(mPowerUsageAdvanced.mBatteryTipsController).setOnAnomalyRejectListener(isNull());
verify(mPowerUsageAdvanced.mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(
@@ -140,46 +171,44 @@
}
@Test
- public void onDisplayAnomalyEventUpdated_withAppAnomalyEvent_setHighlightSlotEffect() {
+ public void onDisplayAnomalyEventUpdated_onlyAppAnomalyEvent_setHighlightSlotEffect() {
final PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent();
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event, event);
- assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isEqualTo(event);
- verify(mBatteryTipsController).handleBatteryTipsCardUpdated(eq(event));
+ assertThat(mPowerUsageAdvanced.mHighlightEventWrapper.get().getEventId())
+ .isEqualTo(event.getEventId());
verify(mBatteryTipsController).setOnAnomalyConfirmListener(isNull());
verify(mBatteryTipsController).setOnAnomalyRejectListener(isNull());
-
- assertThat(event.getWarningItemInfo().hasStartTimestamp()).isTrue();
- assertThat(event.getWarningItemInfo().hasEndTimestamp()).isTrue();
assertThat(mPowerUsageAdvanced.mBatteryLevelData.get().getIndexByTimestamps(
event.getWarningItemInfo().getStartTimestamp(),
event.getWarningItemInfo().getEndTimestamp()
)).isEqualTo(Pair.create(1, 0));
verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(eq(1), eq(0));
verify(mBatteryTipsController).setOnAnomalyConfirmListener(notNull());
- verify(mBatteryTipsController).setOnAnomalyRejectListener(notNull());
}
@Test
- public void onDisplayAnomalyEventUpdated_withNull_removeHighlightSlotEffect() {
- final PowerAnomalyEvent event = BatteryTestUtils.createAppAnomalyEvent();
+ public void onDisplayAnomalyEventUpdated_withSettingsCardAndAppsSlotEvent_showExpected() {
+ final PowerAnomalyEvent settingsEvent =
+ BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent();
+ final PowerAnomalyEvent appsEvent =
+ BatteryTestUtils.createAppAnomalyEvent();
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(event);
- mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(null);
+ mPowerUsageAdvanced.onDisplayAnomalyEventUpdated(settingsEvent, appsEvent);
- assertThat(mPowerUsageAdvanced.mPowerAnomalyEvent).isNull();
- verify(mBatteryTipsController, times(2))
- .setOnAnomalyConfirmListener(isNull());
- verify(mBatteryTipsController, times(2))
- .setOnAnomalyRejectListener(isNull());
- verify(mBatteryTipsController).setOnAnomalyConfirmListener(notNull());
- verify(mBatteryTipsController).setOnAnomalyRejectListener(notNull());
-
- verify(mBatteryChartPreferenceController)
- .onHighlightSlotIndexUpdate(eq(1), eq(0));
- verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(
- eq(BatteryChartViewModel.SELECTED_INDEX_INVALID),
- eq(BatteryChartViewModel.SELECTED_INDEX_INVALID));
+ assertThat(mPowerUsageAdvanced.mHighlightEventWrapper.get().getEventId())
+ .isEqualTo(appsEvent.getEventId());
+ verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(eq(1), eq(0));
+ verify(mBatteryTipsController).setOnAnomalyConfirmListener(isNull());
+ verify(mBatteryTipsController).setOnAnomalyRejectListener(isNull());
}
-}
+
+ private void prepareCardFilterPredicate(PowerAnomalyEvent slotEvent) {
+ final Set<String> dismissedPowerAnomalyKeys =
+ DatabaseUtils.getDismissedPowerAnomalyKeys(mContext);
+ mCardFilterPredicate = event -> !dismissedPowerAnomalyKeys.contains(
+ event.getDismissRecordKey())
+ && (event.equals(slotEvent) || !event.hasWarningItemInfo());
+ }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAssistantPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationAssistantPreferenceControllerTest.java
index c8cf290..54a6bd4 100644
--- a/tests/robotests/src/com/android/settings/notification/NotificationAssistantPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/NotificationAssistantPreferenceControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -235,7 +236,7 @@
mPreferenceController.getDefaultNASIntent();
mPreferenceController.updateState(mPreference);
- verify(mPreference).setSwitchEnabled(eq(false));
+ verify(mPreference, atLeastOnce()).setSwitchEnabled(eq(false));
assertFalse(mPreference.isEnabled());
}
}
diff --git a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
index 6abfe69..942db3f 100644
--- a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
@@ -116,6 +116,7 @@
when(mPreference.isEnabled()).thenReturn(true);
doCallRealMethod().when(mPreference).init();
+ mPreference.setStream(STREAM);
mPreference.init();
verify(mPreference, never()).updateContentDescription(CONTENT_DESCRIPTION);
@@ -136,6 +137,7 @@
@Test
public void init_changeProgress_overrideStateDescriptionCalled() {
final int progress = 4;
+ when(mPreference.isEnabled()).thenReturn(true);
when(mPreference.formatStateDescription(progress)).thenReturn(CONTENT_DESCRIPTION);
doCallRealMethod().when(mPreference).init();
@@ -157,6 +159,7 @@
when(mAudioManager.getStreamMaxVolume(STREAM)).thenReturn(max);
when(mAudioManager.getStreamMinVolumeInt(STREAM)).thenReturn(min);
when(mAudioManager.getStreamVolume(STREAM)).thenReturn(progress);
+ when(mPreference.isEnabled()).thenReturn(true);
when(mPreference.getMin()).thenReturn(min);
when(mPreference.getMax()).thenReturn(max);
when(mPreference.getContext()).thenReturn(mContext);
@@ -168,6 +171,8 @@
mPreference.setStream(STREAM);
mPreference.init();
+ verify(mSeekBarVolumizerFactory).create(eq(STREAM), eq(null), mSbvc.capture());
+
// On progress change, Round down the percent to match it with what the talkback says.
// (b/285458191)
// when progress is 4, the percent is 0.187. The state description should be set to 18%.
diff --git a/tests/robotests/src/com/android/settings/notification/app/ConversationPriorityPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/app/ConversationPriorityPreferenceTest.java
index 2d2fcc8..178aee5 100644
--- a/tests/robotests/src/com/android/settings/notification/app/ConversationPriorityPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/ConversationPriorityPreferenceTest.java
@@ -21,17 +21,15 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.util.Pair;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.TextView;
import androidx.preference.PreferenceViewHolder;
@@ -50,7 +48,8 @@
@Before
public void setUp() {
- mContext = RuntimeEnvironment.application;
+ Context context = spy(RuntimeEnvironment.application.getApplicationContext());
+ mContext = new ContextThemeWrapper(context, R.style.Theme_Settings);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceTest.java b/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceTest.java
index 39a5714..c5733bf 100644
--- a/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/ImportancePreferenceTest.java
@@ -28,13 +28,15 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.preference.PreferenceViewHolder;
+
import com.android.settings.R;
-import com.android.settings.notification.app.ImportancePreference;
import org.junit.Before;
import org.junit.Test;
@@ -42,8 +44,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import androidx.preference.PreferenceViewHolder;
-
@RunWith(RobolectricTestRunner.class)
public class ImportancePreferenceTest {
@@ -51,7 +51,8 @@
@Before
public void setUp() {
- mContext = RuntimeEnvironment.application;
+ Context context = spy(RuntimeEnvironment.application.getApplicationContext());
+ mContext = new ContextThemeWrapper(context, R.style.Theme_Settings);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java
index 516d088..9322317 100644
--- a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java
@@ -33,9 +33,13 @@
import android.content.Context;
import android.net.Uri;
+import android.text.TextUtils;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
@@ -44,6 +48,7 @@
import com.android.settings.panel.PanelSlicesAdapter.SliceRowViewHolder;
import com.android.settings.testutils.FakeFeatureFactory;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,7 +99,6 @@
.get()
.getSupportFragmentManager()
.findFragmentById(R.id.main_content));
-
}
private void addTestLiveData(Uri uri) {
@@ -106,6 +110,61 @@
mData.put(uri, liveData);
}
+ /**
+ * Edge case where fragment context is not available.
+ */
+ @Test
+ public void withPanelFragmentContextNull_createAdapter_noExceptionThrown() {
+ when(mPanelFragment.getContext()).thenReturn(null);
+
+ final PanelSlicesAdapter adapter = spy(new PanelSlicesAdapter(mPanelFragment, mData, 0));
+
+ Assert.assertNotNull(adapter);
+ }
+
+ /**
+ * ViewHolder should load and set the action label correctly.
+ */
+ @Test
+ public void setActionLabel_loadsActionLabel() {
+ addTestLiveData(VOLUME_NOTIFICATION_URI);
+ final PanelSlicesAdapter adapter = new PanelSlicesAdapter(mPanelFragment, mData, 0);
+ final ViewGroup view = new FrameLayout(mContext);
+ final SliceRowViewHolder viewHolder = adapter.onCreateViewHolder(view, VIEW_TYPE_SLIDER);
+
+ // now let's see if setActionLabel can load and set the label correctly.
+ LinearLayout llRow = new LinearLayout(mContext);
+ viewHolder.setActionLabel(llRow);
+
+ boolean isLabelSet = isActionLabelSet(llRow);
+ Assert.assertTrue("Action label was not set correctly.", isLabelSet);
+ }
+
+ /**
+ * @param rowView the view with id row_view
+ * @return whether the accessibility action label is set
+ */
+ private boolean isActionLabelSet(View rowView) {
+ View.AccessibilityDelegate delegate = rowView.getAccessibilityDelegate();
+ if (delegate == null) {
+ return false;
+ }
+ AccessibilityNodeInfo node = new AccessibilityNodeInfo(rowView);
+ delegate.onInitializeAccessibilityNodeInfo(rowView, node);
+
+ boolean foundLabel = false;
+ final String expectedLabel =
+ mContext.getString(R.string.accessibility_action_label_panel_slice);
+ for (AccessibilityNodeInfo.AccessibilityAction action : node.getActionList()) {
+ if (action.equals(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)
+ && TextUtils.equals(action.getLabel(), expectedLabel)) {
+ foundLabel = true;
+ break;
+ }
+ }
+ return foundLabel;
+ }
+
@Test
public void sizeOfAdapter_shouldNotExceedMaxNum() {
for (int i = 0; i < MAX_NUM_OF_SLICES + 2; i++) {
@@ -141,7 +200,7 @@
}
@Test
- public void onCreateViewHolder_viewTypeSlider_verifyActionLabelSet() {
+ public void onBindViewHolder_viewTypeSlider_verifyActionLabelSet() {
addTestLiveData(VOLUME_NOTIFICATION_URI);
final PanelSlicesAdapter adapter =
diff --git a/tests/robotests/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.java
deleted file mode 100644
index 61aa294..0000000
--- a/tests/robotests/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2016 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.system;
-
-import static android.os.SystemUpdateManager.KEY_STATUS;
-import static android.os.SystemUpdateManager.KEY_TITLE;
-import static android.os.SystemUpdateManager.STATUS_IDLE;
-import static android.os.SystemUpdateManager.STATUS_UNKNOWN;
-import static android.os.SystemUpdateManager.STATUS_WAITING_DOWNLOAD;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.SystemUpdateManager;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.testutils.shadow.ShadowUserManager;
-
-import org.junit.After;
-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;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowApplication;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = ShadowUserManager.class)
-public class SystemUpdatePreferenceControllerTest {
-
- @Mock
- private PreferenceScreen mScreen;
- @Mock
- private SystemUpdateManager mSystemUpdateManager;
-
- private Context mContext;
- private ShadowUserManager mShadowUserManager;
- private SystemUpdatePreferenceController mController;
- private Preference mPreference;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- mShadowUserManager = ShadowUserManager.getShadow();
-
- ShadowApplication.getInstance().setSystemService(Context.SYSTEM_UPDATE_SERVICE,
- mSystemUpdateManager);
- mController = new SystemUpdatePreferenceController(mContext);
- mPreference = new Preference(RuntimeEnvironment.application);
- mPreference.setKey(mController.getPreferenceKey());
- when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
- }
-
- @After
- public void cleanUp() {
- mShadowUserManager.setIsAdminUser(false);
- }
-
- @Test
- public void updateNonIndexable_ifAvailable_shouldNotUpdate() {
- final List<String> keys = new ArrayList<>();
- mShadowUserManager.setIsAdminUser(true);
-
- mController.updateNonIndexableKeys(keys);
-
- assertThat(keys).isEmpty();
- }
-
- @Test
- public void updateNonIndexable_ifNotAvailable_shouldUpdate() {
- mShadowUserManager.setIsAdminUser(false);
- final List<String> keys = new ArrayList<>();
-
- mController.updateNonIndexableKeys(keys);
-
- assertThat(keys).hasSize(1);
- }
-
- @Test
- public void displayPrefs_ifVisible_butNotAdminUser_shouldNotDisplay() {
- mShadowUserManager.setIsAdminUser(false);
- mController.displayPreference(mScreen);
-
- assertThat(mPreference.isVisible()).isFalse();
- }
-
- @Test
- @Config(qualifiers = "mcc999")
- public void displayPrefs_ifAdminUser_butNotVisible_shouldNotDisplay() {
- mShadowUserManager.setIsAdminUser(true);
- mController.displayPreference(mScreen);
-
- assertThat(mPreference.isVisible()).isFalse();
- }
-
- @Test
- public void displayPrefs_ifAvailable_shouldDisplay() {
- mShadowUserManager.setIsAdminUser(true);
-
- mController.displayPreference(mScreen);
-
- assertThat(mPreference.isVisible()).isTrue();
- }
-
- @Test
- public void updateState_systemUpdateStatusUnknown_shouldSetToAndroidVersion() {
- final Bundle bundle = new Bundle();
- bundle.putInt(KEY_STATUS, STATUS_UNKNOWN);
- when(mSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle);
-
- mController.updateState(mPreference);
-
- assertThat(mPreference.getSummary())
- .isEqualTo(mContext.getString(R.string.android_version_summary,
- Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY));
- }
-
- @Test
- public void updateState_systemUpdateStatusIdle_shouldSetToAndroidVersion() {
- final String testReleaseName = "ANDROID TEST VERSION";
-
- final Bundle bundle = new Bundle();
- bundle.putInt(KEY_STATUS, STATUS_IDLE);
- bundle.putString(KEY_TITLE, testReleaseName);
- when(mSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle);
-
- mController.updateState(mPreference);
-
- assertThat(mPreference.getSummary())
- .isEqualTo(mContext.getString(R.string.android_version_summary, testReleaseName));
- }
-
- @Test
- public void updateState_systemUpdateInProgress_shouldSetToUpdatePending() {
- final Bundle bundle = new Bundle();
- bundle.putInt(KEY_STATUS, STATUS_WAITING_DOWNLOAD);
- when(mSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle);
-
- mController.updateState(mPreference);
-
- assertThat(mPreference.getSummary())
- .isEqualTo(mContext.getString(R.string.android_version_pending_update_summary));
- }
-}
diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
index 1035560..e98ea1b 100644
--- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
@@ -247,6 +247,7 @@
.setEventId("BrightnessAnomaly")
.setType(PowerAnomalyType.TYPE_SETTINGS_BANNER)
.setKey(PowerAnomalyKey.KEY_BRIGHTNESS)
+ .setDismissRecordKey(PowerAnomalyKey.KEY_BRIGHTNESS.name())
.setScore(1.2f)
.setWarningBannerInfo(WarningBannerInfo.newBuilder()
.setMainButtonDestination(DisplaySettings.class.getName())
@@ -264,6 +265,7 @@
.setEventId("ScreenTimeoutAnomaly")
.setType(PowerAnomalyType.TYPE_SETTINGS_BANNER)
.setKey(PowerAnomalyKey.KEY_SCREEN_TIMEOUT)
+ .setDismissRecordKey(PowerAnomalyKey.KEY_SCREEN_TIMEOUT.name())
.setScore(1.1f)
.setWarningBannerInfo(WarningBannerInfo.newBuilder()
.setMainButtonDestination(ScreenTimeoutSettings.class.getName())
@@ -280,15 +282,12 @@
return PowerAnomalyEvent.newBuilder()
.setEventId("AppAnomaly")
.setType(PowerAnomalyType.TYPE_APPS_ITEM)
- .setKey(PowerAnomalyKey.KEY_APP)
+ .setKey(PowerAnomalyKey.KEY_APP_TOTAL_HIGHER_THAN_USUAL)
+ .setDismissRecordKey("KEY_APP_1")
.setScore(2.0f)
.setWarningItemInfo(WarningItemInfo.newBuilder()
- .setDismissRecordKey("KEY_APP_1")
.setStartTimestamp(1694361600000L) // 2023-09-11 00:00:00
.setEndTimestamp(1694368800000L) // 2023-09-11 02:00:00
- .setTitleString("Chrome used more battery than usual in foreground")
- .setMainButtonString("Check")
- .setCancelButtonString("Got it")
.build())
.build();
}
diff --git a/tests/robotests/src/com/android/settings/widget/HighlightablePreferenceGroupAdapterTest.java b/tests/robotests/src/com/android/settings/widget/HighlightablePreferenceGroupAdapterTest.java
index 00f2e19..d6d5abf 100644
--- a/tests/robotests/src/com/android/settings/widget/HighlightablePreferenceGroupAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/widget/HighlightablePreferenceGroupAdapterTest.java
@@ -174,6 +174,20 @@
assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isNull();
}
+ /**
+ * When background is being updated, we also request the a11y focus on the preference
+ */
+ @Test
+ public void updateBackground_shouldRequestAccessibilityFocus() {
+ View viewItem = mock(View.class);
+ mViewHolder = PreferenceViewHolder.createInstanceForTests(viewItem);
+ ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
+
+ mAdapter.updateBackground(mViewHolder, 10);
+
+ verify(viewItem).requestAccessibilityFocus();
+ }
+
@Test
public void updateBackground_highlight_shouldAnimateBackgroundAndSetHighlightedTag() {
ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10);
diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageListAppsControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListAppsControllerTest.kt
new file mode 100644
index 0000000..af5dc89
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListAppsControllerTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.datausage
+
+import android.content.Context
+import android.content.Intent
+import android.net.NetworkTemplate
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.SettingsActivity
+import com.android.settingslib.AppItem
+import com.android.settingslib.net.NetworkCycleChartData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class DataUsageListAppsControllerTest {
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ doNothing().whenever(mock).startActivity(any())
+ }
+
+ private val controller = DataUsageListAppsController(context, "test_key")
+
+ @Before
+ fun setUp() {
+ controller.init(mock<NetworkTemplate>())
+ val data = NetworkCycleChartData.Builder().apply {
+ setStartTime(START_TIME)
+ setEndTime(END_TIME)
+ }.build()
+ controller.setCycleData(listOf(data))
+ }
+
+ @Test
+ fun startAppDataUsage_shouldAddCyclesInfoToLaunchArguments() {
+ controller.startAppDataUsage(AppItem(), END_TIME)
+
+ val intent = argumentCaptor<Intent> {
+ verify(context).startActivity(capture())
+ }.firstValue
+ val arguments = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!!
+ assertThat(arguments.getLong(AppDataUsage.ARG_SELECTED_CYCLE)).isEqualTo(END_TIME)
+ assertThat(
+ arguments.getSerializable(AppDataUsage.ARG_NETWORK_CYCLES, ArrayList::class.java)
+ ).containsExactly(END_TIME, START_TIME).inOrder()
+ }
+
+ private companion object {
+ const val START_TIME = 1521583200000L
+ const val END_TIME = 1521676800000L
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt
index 016d6d2..531e6e7 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageRepositoryTest.kt
@@ -20,12 +20,13 @@
import android.content.pm.UserInfo
import android.content.res.Resources
import android.net.NetworkPolicyManager
+import android.net.NetworkTemplate
import android.os.UserHandle
import android.os.UserManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
-import com.android.settings.datausage.lib.AppDataUsageRepository.Bucket
+import com.android.settings.datausage.lib.AppDataUsageRepository.Companion.Bucket
import com.android.settingslib.AppItem
import com.android.settingslib.spaprivileged.framework.common.userManager
import com.google.common.truth.Truth.assertThat
@@ -72,15 +73,15 @@
val repository = AppDataUsageRepository(
context = context,
currentUserId = USER_ID,
- carrierId = null,
- getPackageName = { "" },
+ template = Template,
+ getPackageName = { null },
)
val buckets = listOf(
Bucket(uid = APP_ID_1, bytes = 1),
Bucket(uid = APP_ID_2, bytes = 2),
)
- val appPercentList = repository.getAppPercent(buckets)
+ val appPercentList = repository.getAppPercent(null, buckets)
assertThat(appPercentList).hasSize(2)
appPercentList[0].first.apply {
@@ -102,15 +103,15 @@
val repository = AppDataUsageRepository(
context = context,
currentUserId = USER_ID,
- carrierId = HIDING_CARRIER_ID,
- getPackageName = { if (it.key == APP_ID_1) HIDING_PACKAGE_NAME else "" },
+ template = Template,
+ getPackageName = { if (it.key == APP_ID_1) HIDING_PACKAGE_NAME else null },
)
val buckets = listOf(
Bucket(uid = APP_ID_1, bytes = 1),
Bucket(uid = APP_ID_2, bytes = 2),
)
- val appPercentList = repository.getAppPercent(buckets)
+ val appPercentList = repository.getAppPercent(HIDING_CARRIER_ID, buckets)
assertThat(appPercentList).hasSize(1)
appPercentList[0].first.apply {
@@ -127,5 +128,7 @@
const val APP_ID_2 = 110002
const val HIDING_CARRIER_ID = 4
const val HIDING_PACKAGE_NAME = "hiding.package.name"
+
+ val Template: NetworkTemplate = mock<NetworkTemplate>()
}
}
diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
new file mode 100644
index 0000000..c6c37d5
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.network.apn
+
+import android.content.Context
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChild
+import androidx.compose.ui.test.onChildAt
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performScrollToNode
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ApnEditPageProviderTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val apnName = "apn_name"
+ private val mmsc = "mmsc"
+ private val mmsProxy = "mms_proxy"
+ private val mnc = "mnc"
+ private val apnType = "apn_type"
+ private val apnRoaming = "IPv4"
+ private val apnEnable = context.resources.getString(R.string.carrier_enabled)
+ private val apnData = ApnData(
+ name = apnName,
+ mmsc = mmsc,
+ mmsProxy = mmsProxy,
+ mnc = mnc,
+ apnType = apnType,
+ apnRoaming = apnRoaming,
+ apnEnable = true
+ )
+
+ @Test
+ fun apnEditPageProvider_name() {
+ Truth.assertThat(ApnEditPageProvider.name).isEqualTo("Apn")
+ }
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onNodeWithText(context.getString(R.string.apn_edit)).assertIsDisplayed()
+ }
+
+ @Test
+ fun name_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onNodeWithText(apnName, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun mmsc_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(mmsc, true))
+ composeTestRule.onNodeWithText(mmsc, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun mms_proxy_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(mmsProxy, true))
+ composeTestRule.onNodeWithText(mmsProxy, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun mnc_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(mnc, true))
+ composeTestRule.onNodeWithText(mnc, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun apn_type_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(apnType, true))
+ composeTestRule.onNodeWithText(apnType, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun apn_roaming_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(apnRoaming, true))
+ composeTestRule.onNodeWithText(apnRoaming, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun carrier_enabled_displayed() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(apnEnable, true))
+ composeTestRule.onNodeWithText(apnEnable, true).assertIsDisplayed()
+ }
+
+ @Test
+ fun carrier_enabled_isChecked() {
+ composeTestRule.setContent {
+ ApnPage(remember {
+ mutableStateOf(apnData)
+ })
+ }
+ composeTestRule.onRoot().onChild().onChildAt(0)
+ .performScrollToNode(hasText(apnEnable, true))
+ composeTestRule.onNodeWithText(apnEnable, true).assertIsOn()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
index 0deeaf7..2524308 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
@@ -30,7 +30,6 @@
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.printToLog
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
@@ -38,6 +37,7 @@
import com.android.settings.Utils
import com.android.settings.applications.AppStoreUtil
import com.android.settingslib.applications.AppUtils
+import com.android.settingslib.spa.testutils.delay
import com.android.settingslib.spa.testutils.waitUntilExists
import com.android.settingslib.spaprivileged.model.app.userHandle
import org.junit.After
@@ -45,13 +45,13 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
import org.mockito.MockitoSession
import org.mockito.Spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
-import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidJUnit4::class)
class AppInstallerInfoPreferenceTest {
@@ -136,7 +136,6 @@
setContent(instantApp)
waitUntilDisplayed()
- composeTestRule.onRoot().printToLog("AAA")
composeTestRule.onNodeWithText("More info on installer label")
.assertIsDisplayed()
.assertIsEnabled()
@@ -147,7 +146,6 @@
setContent()
waitUntilDisplayed()
- composeTestRule.onRoot().printToLog("AAA")
composeTestRule.onNodeWithText("App installed from installer label")
.assertIsDisplayed()
.assertIsEnabled()
@@ -158,6 +156,7 @@
setContent()
waitUntilDisplayed()
composeTestRule.onRoot().performClick()
+ composeTestRule.delay()
verify(context).startActivityAsUser(STORE_LINK, APP.userHandle)
}
diff --git a/tests/spa_unit/src/com/android/settings/system/SystemUpdateManagerExtKtTest.kt b/tests/spa_unit/src/com/android/settings/system/SystemUpdateManagerExtKtTest.kt
new file mode 100644
index 0000000..0ba91df
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/system/SystemUpdateManagerExtKtTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.system
+
+import android.content.Context
+import android.os.Bundle
+import android.os.SystemUpdateManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class SystemUpdateManagerExtKtTest {
+
+ private val mockSystemUpdateManager = mock<SystemUpdateManager>()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(SystemUpdateManager::class.java) } doReturn mockSystemUpdateManager
+ }
+
+ @Test
+ fun getSystemUpdateInfo() = runTest {
+ val bundle = Bundle()
+ whenever(mockSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle)
+
+ val info = context.getSystemUpdateInfo()
+
+ assertThat(info).isSameInstanceAs(bundle)
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.kt
new file mode 100644
index 0000000..17cdf04
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/system/SystemUpdatePreferenceControllerTest.kt
@@ -0,0 +1,168 @@
+/*
+ * 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.system
+
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.os.SystemClock
+import android.os.SystemUpdateManager
+import android.os.UserManager
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class SystemUpdatePreferenceControllerTest {
+ private val mockUserManager = mock<UserManager>()
+ private val mockSystemUpdateManager = mock<SystemUpdateManager>()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { userManager } doReturn mockUserManager
+ on { getSystemService(SystemUpdateManager::class.java) } doReturn mockSystemUpdateManager
+ }
+
+ private val resources = spy(context.resources) {
+ on { getBoolean(R.bool.config_show_system_update_settings) } doReturn true
+ }
+
+ private val preference = Preference(context).apply { key = KEY }
+ private val preferenceScreen = mock<PreferenceScreen> {
+ onGeneric { findPreference(KEY) } doReturn preference
+ }
+ private val controller = SystemUpdatePreferenceController(context, KEY)
+
+ @Before
+ fun setUp() {
+ whenever(context.resources).thenReturn(resources)
+ }
+
+ @Test
+ fun updateNonIndexable_ifAvailable_shouldNotUpdate() {
+ whenever(mockUserManager.isAdminUser).thenReturn(true)
+ val keys = mutableListOf<String>()
+
+ controller.updateNonIndexableKeys(keys)
+
+ assertThat(keys).isEmpty()
+ }
+
+ @Test
+ fun updateNonIndexable_ifNotAvailable_shouldUpdate() {
+ whenever(mockUserManager.isAdminUser).thenReturn(false)
+ val keys = mutableListOf<String>()
+
+ controller.updateNonIndexableKeys(keys)
+
+ assertThat(keys).containsExactly(KEY)
+ }
+
+ @Test
+ fun displayPrefs_ifVisible_butNotAdminUser_shouldNotDisplay() {
+ whenever(mockUserManager.isAdminUser).thenReturn(false)
+
+ controller.displayPreference(preferenceScreen)
+
+ assertThat(preference.isVisible).isFalse()
+ }
+
+ @Test
+ fun displayPrefs_ifAdminUser_butNotVisible_shouldNotDisplay() {
+ whenever(mockUserManager.isAdminUser).thenReturn(true)
+ whenever(resources.getBoolean(R.bool.config_show_system_update_settings)).thenReturn(false)
+
+ controller.displayPreference(preferenceScreen)
+
+ assertThat(preference.isVisible).isFalse()
+ }
+
+ @Test
+ fun displayPrefs_ifAvailable_shouldDisplay() {
+ whenever(mockUserManager.isAdminUser).thenReturn(true)
+
+ controller.displayPreference(preferenceScreen)
+
+ assertThat(preference.isVisible).isTrue()
+ }
+
+ @Test
+ fun updateState_systemUpdateStatusUnknown_shouldSetToAndroidVersion() {
+ val bundle = Bundle().apply {
+ putInt(SystemUpdateManager.KEY_STATUS, SystemUpdateManager.STATUS_UNKNOWN)
+ }
+ whenever(mockSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle)
+ controller.displayPreference(preferenceScreen)
+
+ controller.onViewCreated(TestLifecycleOwner())
+ SystemClock.sleep(100)
+
+ assertThat(preference.summary).isEqualTo(
+ context.getString(
+ R.string.android_version_summary,
+ Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY,
+ )
+ )
+ }
+
+ @Test
+ fun updateState_systemUpdateStatusIdle_shouldSetToAndroidVersion() {
+ val testReleaseName = "ANDROID TEST VERSION"
+ val bundle = Bundle().apply {
+ putInt(SystemUpdateManager.KEY_STATUS, SystemUpdateManager.STATUS_IDLE)
+ putString(SystemUpdateManager.KEY_TITLE, testReleaseName)
+ }
+ whenever(mockSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle)
+ controller.displayPreference(preferenceScreen)
+
+ controller.onViewCreated(TestLifecycleOwner())
+ SystemClock.sleep(100)
+
+ assertThat(preference.summary)
+ .isEqualTo(context.getString(R.string.android_version_summary, testReleaseName))
+ }
+
+ @Test
+ fun updateState_systemUpdateInProgress_shouldSetToUpdatePending() {
+ val bundle = Bundle().apply {
+ putInt(SystemUpdateManager.KEY_STATUS, SystemUpdateManager.STATUS_WAITING_DOWNLOAD)
+ }
+ whenever(mockSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle)
+ controller.displayPreference(preferenceScreen)
+
+ controller.onViewCreated(TestLifecycleOwner())
+ SystemClock.sleep(100)
+
+ assertThat(preference.summary)
+ .isEqualTo(context.getString(R.string.android_version_pending_update_summary))
+ }
+
+ private companion object {
+ const val KEY = "test_key"
+ }
+}
diff --git a/tests/uitests/src/com/android/settings/ui/ConnectedDeviceTests.java b/tests/uitests/src/com/android/settings/ui/ConnectedDeviceTests.java
deleted file mode 100644
index 36beb90..0000000
--- a/tests/uitests/src/com/android/settings/ui/ConnectedDeviceTests.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.ui;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.Intent;
-import android.nfc.NfcAdapter;
-import android.nfc.NfcManager;
-import android.os.RemoteException;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Ignore
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ConnectedDeviceTests {
-
- private static final String SETTINGS_PACKAGE = "com.android.settings";
- private static final int TIMEOUT = 2000;
- private UiDevice mDevice;
-
- @Before
- public void setUp() throws Exception {
- mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- try {
- mDevice.setOrientationNatural();
- } catch (RemoteException e) {
- throw new RuntimeException("failed to freeze device orientation", e);
- }
- }
-
- @After
- public void tearDown() throws Exception {
- mDevice.pressBack();
- mDevice.pressHome();
- }
-
- // This NFC toggle test is set up this way since there's no way to set
- // the NFC flag to enabled or disabled without touching UI.
- // This way, we get coverage for whether or not the toggle button works.
- @Test
- public void testNFCToggle() throws Exception {
- NfcManager manager = (NfcManager) InstrumentationRegistry.getTargetContext()
- .getSystemService(Context.NFC_SERVICE);
- NfcAdapter nfcAdapter = manager.getDefaultAdapter();
- boolean nfcInitiallyEnabled = nfcAdapter.isEnabled();
- InstrumentationRegistry.getContext().startActivity(new Intent()
- .setClassName(
- SETTINGS_PACKAGE,
- "com.android.settings.Settings$ConnectedDeviceDashboardActivity"));
- UiObject2 nfcSetting = mDevice.wait(Until.findObject(By.text("NFC")), TIMEOUT);
- nfcSetting.click();
- Thread.sleep(TIMEOUT * 2);
- if (nfcInitiallyEnabled) {
- assertFalse("NFC wasn't disabled on toggle", nfcAdapter.isEnabled());
- nfcSetting.click();
- Thread.sleep(TIMEOUT * 2);
- assertTrue("NFC wasn't enabled on toggle", nfcAdapter.isEnabled());
- } else {
- assertTrue("NFC wasn't enabled on toggle", nfcAdapter.isEnabled());
- nfcSetting.click();
- Thread.sleep(TIMEOUT * 2);
- assertFalse("NFC wasn't disabled on toggle", nfcAdapter.isEnabled());
- }
- }
-}
diff --git a/tests/uitests/src/com/android/settings/ui/DataUsageSettingsTest.kt b/tests/uitests/src/com/android/settings/ui/DataUsageSettingsTest.kt
new file mode 100644
index 0000000..413fae7
--- /dev/null
+++ b/tests/uitests/src/com/android/settings/ui/DataUsageSettingsTest.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.ui
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.settings.ui.testutils.SettingsTestUtils.assertHasTexts
+import com.android.settings.ui.testutils.SettingsTestUtils.startMainActivityFromHomeScreen
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DataUsageSettingsTest {
+ private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+ @Before
+ fun setUp() {
+ device.startMainActivityFromHomeScreen(Settings.ACTION_DATA_USAGE_SETTINGS)
+ }
+
+ @Test
+ fun hasTexts() {
+ device.assertHasTexts(ON_SCREEN_TEXTS)
+ }
+
+ private companion object {
+ val ON_SCREEN_TEXTS = listOf(
+ "0 B",
+ "Data Saver",
+ "Wi‑Fi data usage",
+ )
+ }
+}
diff --git a/tests/uitests/src/com/android/settings/ui/DataUsageSettingsTests.java b/tests/uitests/src/com/android/settings/ui/DataUsageSettingsTests.java
deleted file mode 100644
index 3befca3..0000000
--- a/tests/uitests/src/com/android/settings/ui/DataUsageSettingsTests.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.ui;
-
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.system.helpers.SettingsHelper;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.Until;
-
-import org.junit.Ignore;
-
-@Ignore
-public class DataUsageSettingsTests extends InstrumentationTestCase {
-
- private static final String SETTINGS_PACKAGE = "com.android.settings";
- private static final int TIMEOUT = 2000;
- private UiDevice mDevice;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mDevice = UiDevice.getInstance(getInstrumentation());
- try {
- mDevice.setOrientationNatural();
- } catch (RemoteException e) {
- throw new RuntimeException("failed to freeze device orientaion", e);
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- // Need to finish settings activity
- mDevice.pressBack();
- mDevice.pressHome();
- super.tearDown();
- }
-
- @MediumTest
- public void testElementsOnDataUsageScreen() throws Exception {
- launchDataUsageSettings();
- assertNotNull("Data usage element not found",
- mDevice.wait(Until.findObject(By.text("Usage")),
- TIMEOUT));
- assertNotNull("Data usage bar not found",
- mDevice.wait(Until.findObject(By.res(SETTINGS_PACKAGE,
- "color_bar")), TIMEOUT));
- assertNotNull("WiFi Data usage element not found",
- mDevice.wait(Until.findObject(By.text("Wi-Fi data usage")),
- TIMEOUT));
- assertNotNull("Network restrictions element not found",
- mDevice.wait(Until.findObject(By.text("Network restrictions")),
- TIMEOUT));
- }
-
- public void launchDataUsageSettings() throws Exception {
- SettingsHelper.launchSettingsPage(getInstrumentation().getContext(),
- Settings.ACTION_SETTINGS);
- mDevice.wait(Until
- .findObject(By.text("Network & Internet")), TIMEOUT)
- .click();
- Thread.sleep(TIMEOUT * 2);
- assertNotNull("Network & internet screen not loaded", mDevice.wait(
- Until.findObject(By.text("Data usage")), TIMEOUT));
- mDevice.wait(Until
- .findObject(By.text("Data usage")), TIMEOUT)
- .click();
- }
-}
diff --git a/tests/uitests/src/com/android/settings/ui/DevelopmentSettingsTest.kt b/tests/uitests/src/com/android/settings/ui/DevelopmentSettingsTest.kt
index 1afa3ab..1215383 100644
--- a/tests/uitests/src/com/android/settings/ui/DevelopmentSettingsTest.kt
+++ b/tests/uitests/src/com/android/settings/ui/DevelopmentSettingsTest.kt
@@ -16,13 +16,14 @@
package com.android.settings.ui
-import android.os.SystemClock
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import com.android.settings.ui.testutils.SettingsTestUtils.assertHasTexts
+import com.android.settings.ui.testutils.SettingsTestUtils.clickObject
import com.android.settings.ui.testutils.SettingsTestUtils.startMainActivityFromHomeScreen
import org.junit.Before
import org.junit.Test
@@ -35,8 +36,11 @@
@Before
fun setUp() {
- device.executeShellCommand("settings put global development_settings_enabled 1")
- SystemClock.sleep(1000)
+ device.startMainActivityFromHomeScreen(Settings.ACTION_DEVICE_INFO_SETTINGS)
+ device.assertHasTexts(listOf(BUILD_NUMBER))
+ repeat(7) { // Enable development mode
+ device.clickObject(By.text(BUILD_NUMBER))
+ }
device.startMainActivityFromHomeScreen(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
}
@@ -46,6 +50,7 @@
}
private companion object {
+ private const val BUILD_NUMBER = "Build number"
val ON_SCREEN_TEXTS = listOf(
"Use developer options",
"Memory",
diff --git a/tests/uitests/src/com/android/settings/ui/MoreWirelessSettingsTests.java b/tests/uitests/src/com/android/settings/ui/MoreWirelessSettingsTests.java
index 5bfc59d..25b4767 100644
--- a/tests/uitests/src/com/android/settings/ui/MoreWirelessSettingsTests.java
+++ b/tests/uitests/src/com/android/settings/ui/MoreWirelessSettingsTests.java
@@ -24,6 +24,7 @@
import android.test.suitebuilder.annotation.MediumTest;
import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
@@ -86,8 +87,8 @@
public void testVPNMenuLoad() throws Exception {
SettingsHelper.launchSettingsPage(getInstrumentation().getContext(),
Settings.ACTION_WIRELESS_SETTINGS);
- mDevice.wait(Until
- .findObject(By.text("VPN")), TIMEOUT)
+ mDevice.findObject(By.res(SETTINGS_PACKAGE, "main_content"))
+ .scrollUntil(Direction.DOWN, Until.findObject(By.text("VPN")))
.click();
Thread.sleep(TIMEOUT);
UiObject2 usbTethering = mDevice.wait(Until
diff --git a/tests/uitests/src/com/android/settings/ui/NetworkOperatorSettingsTest.kt b/tests/uitests/src/com/android/settings/ui/NetworkOperatorSettingsTest.kt
new file mode 100644
index 0000000..701ba2f
--- /dev/null
+++ b/tests/uitests/src/com/android/settings/ui/NetworkOperatorSettingsTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.ui
+
+import android.provider.Settings
+import android.telephony.SubscriptionManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.settings.ui.testutils.SettingsTestUtils.assertHasTexts
+import com.android.settings.ui.testutils.SettingsTestUtils.startMainActivityFromHomeScreen
+import com.google.common.truth.TruthJUnit.assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NetworkOperatorSettingsTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val device = UiDevice.getInstance(instrumentation)
+
+ @Before
+ fun setUp() {
+ assume().that(
+ instrumentation.context.getSystemService(SubscriptionManager::class.java)!!
+ .availableSubscriptionInfoList
+ ).isNotEmpty()
+ device.startMainActivityFromHomeScreen(Settings.ACTION_NETWORK_OPERATOR_SETTINGS)
+ }
+
+ @Test
+ fun hasTexts() {
+ device.assertHasTexts(ON_SCREEN_TEXTS)
+ }
+
+ private companion object {
+ val ON_SCREEN_TEXTS = listOf(
+ "Use SIM",
+ "0 B",
+ "Calls preference",
+ "SMS preference",
+ "Mobile data",
+ "Roaming",
+ "App data usage",
+ "Data warning & limit",
+ "Preferred network type",
+ "Carrier settings version",
+ "Automatically select network",
+ "Choose network",
+ "Access Point Names",
+ )
+ }
+}
diff --git a/tests/uitests/src/com/android/settings/ui/NfcSettingsTest.kt b/tests/uitests/src/com/android/settings/ui/NfcSettingsTest.kt
new file mode 100644
index 0000000..2fbbfe5
--- /dev/null
+++ b/tests/uitests/src/com/android/settings/ui/NfcSettingsTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.ui
+
+import android.nfc.NfcAdapter
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.settings.ui.testutils.SettingsTestUtils.assertHasTexts
+import com.android.settings.ui.testutils.SettingsTestUtils.startMainActivityFromHomeScreen
+import com.google.common.truth.TruthJUnit.assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NfcSettingsTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val device = UiDevice.getInstance(instrumentation)
+
+ @Before
+ fun setUp() {
+ assume().that(NfcAdapter.getDefaultAdapter(instrumentation.context)).isNotNull()
+ device.startMainActivityFromHomeScreen(Settings.ACTION_NFC_SETTINGS)
+ }
+
+ @Test
+ fun hasTexts() {
+ device.assertHasTexts(ON_SCREEN_TEXTS)
+ }
+
+ private companion object {
+ val ON_SCREEN_TEXTS = listOf(
+ "Use NFC",
+ "Require device unlock for NFC",
+ "Contactless payments",
+ )
+ }
+}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 0ded397..c0d63e1 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -27,6 +27,7 @@
"platform-test-annotations",
"truth-prebuilt",
"kotlinx_coroutines_test",
+ "flag-junit-base",
// Don't add SettingsLib libraries here - you can use them directly as they are in the
// instrumented Settings app.
],
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
index e2bdd17..f807f70 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
@@ -22,8 +22,10 @@
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flow
/** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
@@ -32,6 +34,7 @@
var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
+ val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1)
var pressToAuthEnabled = true
var sensorProps =
@@ -53,16 +56,25 @@
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> {
return challengeToGenerate
}
- override val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
- flowOf(enrolledFingerprintsInternal)
- override val canEnrollFingerprints: Flow<Boolean> =
- flowOf(enrolledFingerprintsInternal.size < enrollableFingerprints)
+ override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
+ emit(enrolledFingerprintsInternal)
+ }
- override val sensorPropertiesInternal: Flow<FingerprintSensorPropertiesInternal?> =
- flowOf(sensorProps.first())
+ override val canEnrollFingerprints: Flow<Boolean> = flow {
+ emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
+ }
- override val maxEnrollableFingerprints: Flow<Int> = flowOf(enrollableFingerprints)
+ override val sensorPropertiesInternal: Flow<FingerprintSensorPropertiesInternal?> = flow {
+ emit(sensorProps.first())
+ }
+
+ override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
+
+ override suspend fun enroll(
+ hardwareAuthToken: ByteArray?,
+ enrollReason: EnrollReason
+ ): Flow<FingerEnrollStateViewModel> = flow { emit(enrollStateViewModel) }
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
return enrolledFingerprintsInternal.remove(fp)
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
index 70943f0..de2c494 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
@@ -30,6 +30,8 @@
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason.FindSensor
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import com.android.settings.password.ChooseLockSettingsHelper
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelAndJoin
@@ -143,7 +145,7 @@
.thenReturn(byteArray)
val generateChallengeCallback: ArgumentCaptor<FingerprintManager.GenerateChallengeCallback> =
- ArgumentCaptor.forClass(FingerprintManager.GenerateChallengeCallback::class.java)
+ argumentCaptor()
var result: Pair<Long, ByteArray?>? = null
val job = testScope.launch { result = underTest.generateChallenge(1L) }
@@ -165,8 +167,7 @@
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
- val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
- ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
+ val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
var result: Boolean? = null
val job =
@@ -189,8 +190,7 @@
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
- val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
- ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
+ val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
var result: Boolean? = null
val job =
@@ -229,8 +229,7 @@
var result: FingerprintAuthAttemptViewModel? = null
val job = launch { result = underTest.authenticate() }
- val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
- ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
+ val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
runCurrent()
@@ -257,8 +256,7 @@
var result: FingerprintAuthAttemptViewModel? = null
val job = launch { result = underTest.authenticate() }
- val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
- ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
+ val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
runCurrent()
@@ -280,8 +278,82 @@
)
}
+ @Test
+ fun testEnroll_progress() =
+ testScope.runTest {
+ val token = byteArrayOf(5, 3, 2)
+ var result: FingerEnrollStateViewModel? = null
+ val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
+ val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
+ runCurrent()
+
+ verify(fingerprintManager)
+ .enroll(
+ eq(token),
+ any(CancellationSignal::class.java),
+ anyInt(),
+ capture(enrollCallback),
+ eq(FingerprintManager.ENROLL_FIND_SENSOR)
+ )
+ enrollCallback.value.onEnrollmentProgress(1)
+ runCurrent()
+ job.cancelAndJoin()
+
+ assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollProgress(1))
+ }
+
+ @Test
+ fun testEnroll_help() =
+ testScope.runTest {
+ val token = byteArrayOf(5, 3, 2)
+ var result: FingerEnrollStateViewModel? = null
+ val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
+ val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
+ runCurrent()
+
+ verify(fingerprintManager)
+ .enroll(
+ eq(token),
+ any(CancellationSignal::class.java),
+ anyInt(),
+ capture(enrollCallback),
+ eq(FingerprintManager.ENROLL_FIND_SENSOR)
+ )
+ enrollCallback.value.onEnrollmentHelp(-1, "help")
+ runCurrent()
+ job.cancelAndJoin()
+
+ assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollHelp(-1, "help"))
+ }
+
+ @Test
+ fun testEnroll_error() =
+ testScope.runTest {
+ val token = byteArrayOf(5, 3, 2)
+ var result: FingerEnrollStateViewModel? = null
+ val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
+ val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
+ runCurrent()
+
+ verify(fingerprintManager)
+ .enroll(
+ eq(token),
+ any(CancellationSignal::class.java),
+ anyInt(),
+ capture(enrollCallback),
+ eq(FingerprintManager.ENROLL_FIND_SENSOR)
+ )
+ enrollCallback.value.onEnrollmentError(-2, "error")
+ runCurrent()
+ job.cancelAndJoin()
+
+ assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollError(-2, "error"))
+ }
+
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+ inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
+ ArgumentCaptor.forClass(T::class.java)
}
diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
index f063042..587e734 100644
--- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
@@ -18,9 +18,7 @@
import static com.android.settings.network.SubscriptionUtil.KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME;
import static com.android.settings.network.SubscriptionUtil.SUB_ID;
-
import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
@@ -185,7 +183,7 @@
@Ignore
@Test
public void getUniqueDisplayNames_identicalCarriers_fourDigitsUsed() {
- // Both subscriptoins have the same display name.
+ // Both subscriptions have the same display name.
final SubscriptionInfo info1 = mock(SubscriptionInfo.class);
final SubscriptionInfo info2 = mock(SubscriptionInfo.class);
when(info1.getSubscriptionId()).thenReturn(SUBID_1);
@@ -215,7 +213,7 @@
@Ignore
@Test
public void getUniqueDisplayNames_identicalCarriersAfterTrim_fourDigitsUsed() {
- // Both subscriptoins have the same display name.
+ // Both subscriptions have the same display name.
final SubscriptionInfo info1 = mock(SubscriptionInfo.class);
final SubscriptionInfo info2 = mock(SubscriptionInfo.class);
when(info1.getSubscriptionId()).thenReturn(SUBID_1);
@@ -244,8 +242,8 @@
@Ignore
@Test
- public void getUniqueDisplayNames_phoneNumberBlocked_subscriptoinIdFallback() {
- // Both subscriptoins have the same display name.
+ public void getUniqueDisplayNames_phoneNumberBlocked_subscriptionIdFallback() {
+ // Both subscriptions have the same display name.
final SubscriptionInfo info1 = mock(SubscriptionInfo.class);
final SubscriptionInfo info2 = mock(SubscriptionInfo.class);
when(info1.getSubscriptionId()).thenReturn(SUBID_1);
@@ -273,9 +271,9 @@
@Ignore
@Test
- public void getUniqueDisplayNames_phoneNumberIdentical_subscriptoinIdFallback() {
+ public void getUniqueDisplayNames_phoneNumberIdentical_subscriptionIdFallback() {
// TODO have three here from the same carrier
- // Both subscriptoins have the same display name.
+ // Both subscriptions have the same display name.
final SubscriptionInfo info1 = mock(SubscriptionInfo.class);
final SubscriptionInfo info2 = mock(SubscriptionInfo.class);
final SubscriptionInfo info3 = mock(SubscriptionInfo.class);
@@ -464,8 +462,8 @@
SharedPreferences sp = mock(SharedPreferences.class);
when(mContext.getSharedPreferences(
KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME, Context.MODE_PRIVATE)).thenReturn(sp);
- when(sp.getString(eq(SUB_ID + SUBID_1), anyString())).thenReturn(CARRIER_1 + "6789");
- when(sp.getString(eq(SUB_ID + SUBID_2), anyString())).thenReturn(CARRIER_1 + "4321");
+ when(sp.getString(eq(SUB_ID + SUBID_1), anyString())).thenReturn(CARRIER_1 + " 6789");
+ when(sp.getString(eq(SUB_ID + SUBID_2), anyString())).thenReturn(CARRIER_1 + " 4321");
final CharSequence nameOfSub1 =
@@ -475,8 +473,41 @@
assertThat(nameOfSub1).isNotNull();
assertThat(nameOfSub2).isNotNull();
- assertEquals(CARRIER_1 + "6789", nameOfSub1.toString());
- assertEquals(CARRIER_1 + "4321", nameOfSub2.toString());
+ assertEquals(CARRIER_1 + " 6789", nameOfSub1.toString());
+ assertEquals(CARRIER_1 + " 4321", nameOfSub2.toString());
+ }
+
+ @Test
+ public void getUniqueDisplayName_hasRecordAndNameIsChanged_doesNotUseRecordBeTheResult() {
+ final SubscriptionInfo info1 = mock(SubscriptionInfo.class);
+ final SubscriptionInfo info2 = mock(SubscriptionInfo.class);
+ when(info1.getSubscriptionId()).thenReturn(SUBID_1);
+ when(info2.getSubscriptionId()).thenReturn(SUBID_2);
+ when(info1.getDisplayName()).thenReturn(CARRIER_1);
+ when(info2.getDisplayName()).thenReturn(CARRIER_2);
+ when(mSubMgr.getAvailableSubscriptionInfoList()).thenReturn(
+ Arrays.asList(info1, info2));
+
+ SharedPreferences sp = mock(SharedPreferences.class);
+ SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
+ when(mContext.getSharedPreferences(
+ KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME, Context.MODE_PRIVATE)).thenReturn(sp);
+ when(sp.edit()).thenReturn(editor);
+ when(editor.remove(anyString())).thenReturn(editor);
+
+ when(sp.getString(eq(SUB_ID + SUBID_1), anyString())).thenReturn(CARRIER_1 + " 6789");
+ when(sp.getString(eq(SUB_ID + SUBID_2), anyString())).thenReturn(CARRIER_1 + " 4321");
+
+
+ final CharSequence nameOfSub1 =
+ SubscriptionUtil.getUniqueSubscriptionDisplayName(info1, mContext);
+ final CharSequence nameOfSub2 =
+ SubscriptionUtil.getUniqueSubscriptionDisplayName(info2, mContext);
+
+ assertThat(nameOfSub1).isNotNull();
+ assertThat(nameOfSub2).isNotNull();
+ assertEquals(CARRIER_1 + " 6789", nameOfSub1.toString());
+ assertEquals(CARRIER_2.toString(), nameOfSub2.toString());
}
@Test
@@ -501,4 +532,60 @@
assertTrue(SubscriptionUtil.isSimHardwareVisible(mContext));
}
+
+ @Test
+ public void isValidCachedDisplayName_matchesRule1_returnTrue() {
+ String originalName = "originalName";
+ String cacheString = "originalName 1234";
+
+ assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isTrue();
+ }
+
+ @Test
+ public void isValidCachedDisplayName_matchesRule2_returnTrue() {
+ String originalName = "original Name";
+ String cacheString = originalName + " " + 1234;
+
+ assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isTrue();
+ }
+
+ @Test
+ public void isValidCachedDisplayName_nameIsEmpty1_returnFalse() {
+ String originalName = "original Name";
+ String cacheString = "";
+
+ assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse();
+ }
+
+ @Test
+ public void isValidCachedDisplayName_nameIsEmpty2_returnFalse() {
+ String originalName = "";
+ String cacheString = "originalName 1234";
+
+ assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse();
+ }
+
+ @Test
+ public void isValidCachedDisplayName_nameIsDifferent_returnFalse() {
+ String originalName = "original Name";
+ String cacheString = "originalName 1234";
+
+ assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse();
+ }
+
+ @Test
+ public void isValidCachedDisplayName_noNumber_returnFalse() {
+ String originalName = "original Name";
+ String cacheString = originalName;
+
+ assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse();
+ }
+
+ @Test
+ public void isValidCachedDisplayName_noSpace_returnFalse() {
+ String originalName = "original Name";
+ String cacheString = originalName;
+
+ assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse();
+ }
}
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java
index 2dc00e1..ddf5287 100644
--- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceSafetySourceTest.java
@@ -27,10 +27,11 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceStatus;
-import android.util.FeatureFlagUtils;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -39,6 +40,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -51,15 +53,13 @@
new SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build();
private Context mContext = ApplicationProvider.getApplicationContext();
@Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
/** Required setup before a test. */
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
-
- FeatureFlagUtils
- .setEnabled(mContext, FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, true);
}
/** Required setup after a test. */
@@ -83,6 +83,7 @@
@Test
public void onDeviceRebootedEvent_whenSafetyCenterEnabled_setsData() {
when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED);
@@ -90,13 +91,11 @@
any(), eq(SAFETY_SOURCE_ID), any(), eq(EVENT_TYPE_DEVICE_REBOOTED));
}
- // TODO(b/295516544): Modify this test for the new trunk stable flag instead when available.
/** Tests that when the feature is disabled null data is set. */
@Test
public void setSafetySourceData_whenFeatureDisabled_setsNullData() {
when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true);
- FeatureFlagUtils
- .setEnabled(mContext, FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, false);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED);
@@ -105,15 +104,13 @@
any(), eq(SAFETY_SOURCE_ID), captor.capture(), eq(EVENT_TYPE_DEVICE_REBOOTED));
SafetySourceData safetySourceData = captor.getValue();
assertThat(safetySourceData).isNull();
-
- FeatureFlagUtils
- .setEnabled(mContext, FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS, true);
}
/** Tests that setSafetySourceData sets the source status enabled. */
@Test
public void setSafetySourceData_setsEnabled() {
when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED);
@@ -129,6 +126,7 @@
@Test
public void setSafetySourceData_setsPsIntent() {
when(mSafetyCenterManagerWrapper.isEnabled(mContext)).thenReturn(true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
PrivateSpaceSafetySource.setSafetySourceData(mContext, EVENT_TYPE_DEVICE_REBOOTED);
diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
index 4a6afb1..3538727 100644
--- a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
@@ -64,14 +64,11 @@
private Context mApplicationContext;
- @Mock
- private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+ @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
- @Mock
- private ScreenLockPreferenceDetailsUtils mScreenLockPreferenceDetailsUtils;
+ @Mock private ScreenLockPreferenceDetailsUtils mScreenLockPreferenceDetailsUtils;
- @Mock
- private LockPatternUtils mLockPatternUtils;
+ @Mock private LockPatternUtils mLockPatternUtils;
@Before
public void setUp() {
@@ -94,11 +91,11 @@
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false);
when(mScreenLockPreferenceDetailsUtils.isAvailable()).thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
- verify(mSafetyCenterManagerWrapper, never()).setSafetySourceData(
- any(), any(), any(), any());
+ verify(mSafetyCenterManagerWrapper, never())
+ .setSafetySourceData(any(), any(), any(), any());
}
@Test
@@ -106,11 +103,12 @@
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mScreenLockPreferenceDetailsUtils.isAvailable()).thenReturn(false);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), eq(null), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), eq(null), any());
}
@Test
@@ -118,11 +116,12 @@
whenScreenLockIsEnabled();
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), any(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), any(), any());
}
@Test
@@ -130,11 +129,11 @@
whenScreenLockIsEnabled();
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), any(), any(), eq(EVENT_SOURCE_STATE_CHANGED));
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(any(), any(), any(), eq(EVENT_SOURCE_STATE_CHANGED));
}
@Test
@@ -142,26 +141,28 @@
whenScreenLockIsEnabled();
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), any(), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(any(), any(), captor.capture(), any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
assertThat(safetySourceStatus.getTitle().toString())
- .isEqualTo(ResourcesUtils.getResourcesString(
- mApplicationContext,
- "unlock_set_unlock_launch_picker_title"));
- assertThat(safetySourceStatus.getSummary().toString())
- .isEqualTo(SUMMARY);
+ .isEqualTo(
+ ResourcesUtils.getResourcesString(
+ mApplicationContext, "unlock_set_unlock_launch_picker_title"));
+ assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(SUMMARY);
assertThat(safetySourceStatus.getPendingIntent().getIntent()).isNotNull();
assertThat(safetySourceStatus.getPendingIntent().getIntent().getAction())
.isEqualTo(FAKE_ACTION_OPEN_SUB_SETTING);
assertThat(
- safetySourceStatus.getPendingIntent().getIntent().getStringExtra(EXTRA_DESTINATION))
+ safetySourceStatus
+ .getPendingIntent()
+ .getIntent()
+ .getStringExtra(EXTRA_DESTINATION))
.isEqualTo(FAKE_CHOOSE_LOCK_GENERIC_FRAGMENT);
}
@@ -173,12 +174,16 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(false);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
@@ -194,12 +199,16 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(false);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
@@ -215,12 +224,16 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
@@ -236,12 +249,16 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
@@ -257,12 +274,12 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(false);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), any(), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(any(), any(), captor.capture(), any());
SafetySourceData safetySourceData = captor.getValue();
assertThat(safetySourceData.getIssues()).isEmpty();
@@ -276,34 +293,41 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(false);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
assertThat(safetySourceData.getIssues()).hasSize(1);
SafetySourceIssue issue = safetySourceData.getIssues().get(0);
assertThat(issue.getId()).isEqualTo(LockScreenSafetySource.NO_SCREEN_LOCK_ISSUE_ID);
- assertThat(issue.getTitle().toString()).isEqualTo(
- ResourcesUtils.getResourcesString(mApplicationContext,
- "no_screen_lock_issue_title"));
- assertThat(issue.getSummary().toString()).isEqualTo(
- ResourcesUtils.getResourcesString(mApplicationContext,
- "no_screen_lock_issue_summary"));
- assertThat(issue.getSeverityLevel()).isEqualTo(
- SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION);
- assertThat(issue.getIssueTypeId()).isEqualTo(
- LockScreenSafetySource.NO_SCREEN_LOCK_ISSUE_TYPE_ID);
+ assertThat(issue.getTitle().toString())
+ .isEqualTo(
+ ResourcesUtils.getResourcesString(
+ mApplicationContext, "no_screen_lock_issue_title"));
+ assertThat(issue.getSummary().toString())
+ .isEqualTo(
+ ResourcesUtils.getResourcesString(
+ mApplicationContext, "no_screen_lock_issue_summary"));
+ assertThat(issue.getSeverityLevel())
+ .isEqualTo(SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION);
+ assertThat(issue.getIssueTypeId())
+ .isEqualTo(LockScreenSafetySource.NO_SCREEN_LOCK_ISSUE_TYPE_ID);
assertThat(issue.getIssueCategory()).isEqualTo(SafetySourceIssue.ISSUE_CATEGORY_DEVICE);
assertThat(issue.getActions()).hasSize(1);
SafetySourceIssue.Action action = issue.getActions().get(0);
assertThat(action.getId()).isEqualTo(LockScreenSafetySource.SET_SCREEN_LOCK_ACTION_ID);
- assertThat(action.getLabel().toString()).isEqualTo(
- ResourcesUtils.getResourcesString(mApplicationContext,
- "no_screen_lock_issue_action_label"));
+ assertThat(action.getLabel().toString())
+ .isEqualTo(
+ ResourcesUtils.getResourcesString(
+ mApplicationContext, "no_screen_lock_issue_action_label"));
assertThat(action.getPendingIntent().getIntent().getAction())
.isEqualTo(FAKE_ACTION_OPEN_SUB_SETTING);
assertThat(action.getPendingIntent().getIntent().getStringExtra(EXTRA_DESTINATION))
@@ -318,12 +342,12 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), any(), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(any(), any(), captor.capture(), any());
SafetySourceData safetySourceData = captor.getValue();
assertThat(safetySourceData.getIssues()).isEmpty();
@@ -337,12 +361,16 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
assertThat(safetySourceData.getIssues()).isEmpty();
@@ -355,16 +383,28 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
assertThat(safetySourceStatus.isEnabled()).isFalse();
+ assertThat(safetySourceStatus.getPendingIntent()).isNull();
+ assertThat(safetySourceStatus.getIconAction()).isNull();
+ assertThat(safetySourceStatus.getSeverityLevel())
+ .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED);
+ assertThat(safetySourceStatus.getSummary().toString())
+ .isEqualTo(
+ ResourcesUtils.getResourcesString(
+ mApplicationContext, "disabled_by_policy_title"));
}
@Test
@@ -374,16 +414,25 @@
when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
.thenReturn(false);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
assertThat(safetySourceStatus.isEnabled()).isTrue();
+ assertThat(safetySourceStatus.getPendingIntent()).isNotNull();
+ assertThat(safetySourceStatus.getIconAction()).isNotNull();
+ assertThat(safetySourceStatus.getSeverityLevel())
+ .isEqualTo(SafetySourceData.SEVERITY_LEVEL_INFORMATION);
+ assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(SUMMARY);
}
@Test
@@ -392,20 +441,23 @@
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mScreenLockPreferenceDetailsUtils.shouldShowGearMenu()).thenReturn(true);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
- final ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(
- SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ final ArgumentCaptor<SafetySourceData> captor =
+ ArgumentCaptor.forClass(SafetySourceData.class);
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
final IconAction iconAction = captor.getValue().getStatus().getIconAction();
assertThat(iconAction.getIconType()).isEqualTo(IconAction.ICON_TYPE_GEAR);
assertThat(iconAction.getPendingIntent().getIntent().getAction())
.isEqualTo(FAKE_ACTION_OPEN_SUB_SETTING);
- assertThat(
- iconAction.getPendingIntent().getIntent().getStringExtra(EXTRA_DESTINATION))
+ assertThat(iconAction.getPendingIntent().getIntent().getStringExtra(EXTRA_DESTINATION))
.isEqualTo(FAKE_SCREEN_LOCK_SETTINGS);
}
@@ -415,12 +467,16 @@
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
when(mScreenLockPreferenceDetailsUtils.shouldShowGearMenu()).thenReturn(false);
- LockScreenSafetySource.setSafetySourceData(mApplicationContext,
- mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
+ LockScreenSafetySource.setSafetySourceData(
+ mApplicationContext, mScreenLockPreferenceDetailsUtils, EVENT_SOURCE_STATE_CHANGED);
ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), captor.capture(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(LockScreenSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
SafetySourceData safetySourceData = captor.getValue();
SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
@@ -434,10 +490,12 @@
LockScreenSafetySource.onLockScreenChange(mApplicationContext);
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), any(), any());
- verify(mSafetyCenterManagerWrapper).setSafetySourceData(
- any(), eq(BiometricsSafetySource.SAFETY_SOURCE_ID), any(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(LockScreenSafetySource.SAFETY_SOURCE_ID), any(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(BiometricsSafetySource.SAFETY_SOURCE_ID), any(), any());
}
@Test
@@ -447,8 +505,8 @@
LockScreenSafetySource.onLockScreenChange(mApplicationContext);
- verify(mSafetyCenterManagerWrapper, never()).setSafetySourceData(
- any(), any(), any(), any());
+ verify(mSafetyCenterManagerWrapper, never())
+ .setSafetySourceData(any(), any(), any(), any());
}
private void whenScreenLockIsEnabled() {
@@ -456,8 +514,8 @@
when(mScreenLockPreferenceDetailsUtils.getSummary(anyInt())).thenReturn(SUMMARY);
Intent launchChooseLockGenericFragment = new Intent(FAKE_ACTION_OPEN_SUB_SETTING);
- launchChooseLockGenericFragment.putExtra(EXTRA_DESTINATION,
- FAKE_CHOOSE_LOCK_GENERIC_FRAGMENT);
+ launchChooseLockGenericFragment.putExtra(
+ EXTRA_DESTINATION, FAKE_CHOOSE_LOCK_GENERIC_FRAGMENT);
when(mScreenLockPreferenceDetailsUtils.getLaunchChooseLockGenericFragmentIntent(anyInt()))
.thenReturn(launchChooseLockGenericFragment);
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
index caae44a..85bd0e2 100644
--- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
@@ -30,9 +30,10 @@
import android.content.Context;
import android.content.Intent;
+import android.os.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
-import android.util.FeatureFlagUtils;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -43,6 +44,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -62,6 +64,7 @@
@Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
@Mock private LockPatternUtils mLockPatternUtils;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -241,15 +244,10 @@
}
/** Tests that the PS source sets null data when it's disabled. */
- // TODO(b/295516544): Modify this test for the new trunk stable flag instead when available.
@Test
public void onReceive_onRefresh_withPrivateSpaceFeatureDisabled_setsNullData() {
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
- FeatureFlagUtils
- .setEnabled(
- mApplicationContext,
- FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS,
- false);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
Intent intent =
new Intent()
@@ -265,12 +263,6 @@
.setSafetySourceData(any(), any(), captor.capture(), any());
assertThat(captor.getValue()).isEqualTo(null);
-
- FeatureFlagUtils
- .setEnabled(
- mApplicationContext,
- FeatureFlagUtils.SETTINGS_PRIVATE_SPACE_SETTINGS,
- true);
}
@Test