diff --git a/src/com/android/settings/biometrics/OWNERS b/src/com/android/settings/biometrics/OWNERS index cb0d0341828..f804b0f545c 100644 --- a/src/com/android/settings/biometrics/OWNERS +++ b/src/com/android/settings/biometrics/OWNERS @@ -1,3 +1,6 @@ +# The Android Biometric team should approve all changes to biometrics subdirectories. +set noparent + graciecheng@google.com ilyamaty@google.com jaggies@google.com diff --git a/src/com/android/settings/biometrics2/OWNERS b/src/com/android/settings/biometrics2/OWNERS index a257ed861ad..ac806eb8d48 100644 --- a/src/com/android/settings/biometrics2/OWNERS +++ b/src/com/android/settings/biometrics2/OWNERS @@ -1 +1,4 @@ +# The Android Biometric team should approve all changes to biometrics2 subdirectories. +set noparent + include /src/com/android/settings/biometrics/OWNERS diff --git a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt index a764c1d6cfe..233e1078812 100644 --- a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt +++ b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt @@ -23,11 +23,12 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R +import com.android.settings.datausage.lib.DataUsageFormatter import com.android.settings.datausage.lib.NetworkUsageDetailsData import com.android.settings.spa.preference.ComposePreferenceController import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel -import com.android.settingslib.spaprivileged.framework.compose.placeholder +import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map @@ -35,17 +36,20 @@ class AppDataUsageSummaryController(context: Context, preferenceKey: String) : ComposePreferenceController(context, preferenceKey) { private val dataFlow = MutableStateFlow(NetworkUsageDetailsData.AllZero) + private val dataUsageFormatter = DataUsageFormatter(context) + private val emptyDataUsage = + DataUsageFormatter.FormattedDataUsage(context.getPlaceholder(), context.getPlaceholder()) private val totalUsageFlow = dataFlow.map { - DataUsageUtils.formatDataUsage(mContext, it.totalUsage).toString() + dataUsageFormatter.formatDataUsage(it.totalUsage) } private val foregroundUsageFlow = dataFlow.map { - DataUsageUtils.formatDataUsage(mContext, it.foregroundUsage).toString() + dataUsageFormatter.formatDataUsage(it.foregroundUsage) } private val backgroundUsageFlow = dataFlow.map { - DataUsageUtils.formatDataUsage(mContext, it.backgroundUsage).toString() + dataUsageFormatter.formatDataUsage(it.backgroundUsage) } override fun getAvailabilityStatus() = AVAILABLE @@ -57,20 +61,23 @@ class AppDataUsageSummaryController(context: Context, preferenceKey: String) : @Composable override fun Content() { Column { - val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(placeholder()) - val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(placeholder()) - val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(placeholder()) + val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) + val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) + val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage) Preference(object : PreferenceModel { override val title = stringResource(R.string.total_size_label) - override val summary = { totalUsage } + override val summary = { totalUsage.displayText } + override val summaryContentDescription = { totalUsage.contentDescription } }) Preference(object : PreferenceModel { override val title = stringResource(R.string.data_usage_label_foreground) - override val summary = { foregroundUsage } + override val summary = { foregroundUsage.displayText } + override val summaryContentDescription = { foregroundUsage.contentDescription } }) Preference(object : PreferenceModel { override val title = stringResource(R.string.data_usage_label_background) - override val summary = { backgroundUsage } + override val summary = { backgroundUsage.displayText } + override val summaryContentDescription = { backgroundUsage.contentDescription } }) } } diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java index 9a7411a7347..69577a85574 100644 --- a/src/com/android/settings/datausage/BillingCycleSettings.java +++ b/src/com/android/settings/datausage/BillingCycleSettings.java @@ -44,6 +44,7 @@ import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.datausage.lib.DataUsageFormatter; import com.android.settings.datausage.lib.NetworkTemplates; import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.telephony.MobileNetworkUtils; @@ -322,8 +323,8 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements : editor.getPolicyWarningBytes(template); final String[] unitNames = new String[] { - DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), MIB_IN_BYTES), - DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), GIB_IN_BYTES), + DataUsageFormatter.Companion.getBytesDisplayUnit(getResources(), MIB_IN_BYTES), + DataUsageFormatter.Companion.getBytesDisplayUnit(getResources(), GIB_IN_BYTES), }; final ArrayAdapter adapter = new ArrayAdapter( getContext(), android.R.layout.simple_spinner_item, unitNames); diff --git a/src/com/android/settings/datausage/DataUsageFormatter.kt b/src/com/android/settings/datausage/DataUsageFormatter.kt deleted file mode 100644 index 16a9ae8b6b0..00000000000 --- a/src/com/android/settings/datausage/DataUsageFormatter.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.datausage - -import android.content.res.Resources -import android.text.format.Formatter - -object DataUsageFormatter { - - /** - * Gets the display unit of the given bytes. - * - * Similar to MeasureFormat.getUnitDisplayName(), but with the expected result for the bytes in - * Settings, and align with other places in Settings. - */ - fun Resources.getBytesDisplayUnit(bytes: Long): String = - Formatter.formatBytes(this, bytes, Formatter.FLAG_IEC_UNITS).units -} \ No newline at end of file diff --git a/src/com/android/settings/datausage/DataUsageList.kt b/src/com/android/settings/datausage/DataUsageList.kt index a8f5460a18c..af115d9d370 100644 --- a/src/com/android/settings/datausage/DataUsageList.kt +++ b/src/com/android/settings/datausage/DataUsageList.kt @@ -178,7 +178,7 @@ open class DataUsageList : DashboardFragment() { private fun updateSelectedCycle(usageData: NetworkUsageData) { Log.d(TAG, "showing cycle $usageData") - usageAmount?.title = usageData.getDataUsedString(requireContext()) + usageAmount?.title = usageData.getDataUsedString(requireContext()).displayText viewModel.selectedCycleFlow.value = usageData updateApps(usageData) diff --git a/src/com/android/settings/datausage/DataUsageUtils.java b/src/com/android/settings/datausage/DataUsageUtils.java index 2bbf3e2a462..b73da1c3ada 100644 --- a/src/com/android/settings/datausage/DataUsageUtils.java +++ b/src/com/android/settings/datausage/DataUsageUtils.java @@ -56,7 +56,10 @@ public final class DataUsageUtils { /** * Format byte value to readable string using IEC units. + * + * @deprecated Use {@link com.android.settings.datausage.lib.DataUsageFormatter} instead. */ + @Deprecated public static CharSequence formatDataUsage(Context context, long byteValue) { final BytesResult res = Formatter.formatBytes(context.getResources(), byteValue, Formatter.FLAG_IEC_UNITS); diff --git a/src/com/android/settings/datausage/lib/DataUsageFormatter.kt b/src/com/android/settings/datausage/lib/DataUsageFormatter.kt new file mode 100644 index 00000000000..0a4c06b264b --- /dev/null +++ b/src/com/android/settings/datausage/lib/DataUsageFormatter.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.datausage.lib + +import android.annotation.StringRes +import android.content.Context +import android.content.res.Resources +import android.icu.text.UnicodeSet +import android.icu.text.UnicodeSetSpanner +import android.text.BidiFormatter +import android.text.format.Formatter +import com.android.internal.R + +class DataUsageFormatter(private val context: Context) { + + data class FormattedDataUsage( + val displayText: String, + val contentDescription: String, + ) { + fun format(context: Context, @StringRes resId: Int, vararg formatArgs: Any?) = + FormattedDataUsage( + displayText = context.getString(resId, displayText, *formatArgs), + contentDescription = context.getString(resId, contentDescription, *formatArgs), + ) + } + + /** Formats the data usage. */ + fun formatDataUsage(sizeBytes: Long): FormattedDataUsage { + val result = Formatter.formatBytes(context.resources, sizeBytes, Formatter.FLAG_IEC_UNITS) + return FormattedDataUsage( + displayText = BidiFormatter.getInstance().unicodeWrap( + context.getString(R.string.fileSizeSuffix, result.value, result.units) + ), + contentDescription = context.getString( + R.string.fileSizeSuffix, result.value, result.unitsContentDescription + ), + ) + } + + companion object { + /** + * Gets the display unit of the given bytes. + * + * Similar to MeasureFormat.getUnitDisplayName(), but with the expected result for the bytes + * in Settings, and align with other places in Settings. + */ + fun Resources.getBytesDisplayUnit(bytes: Long): String = + Formatter.formatBytes(this, bytes, Formatter.FLAG_IEC_UNITS).units + } +} diff --git a/src/com/android/settings/datausage/lib/NetworkUsageData.kt b/src/com/android/settings/datausage/lib/NetworkUsageData.kt index f9d83d52659..26578e325b4 100644 --- a/src/com/android/settings/datausage/lib/NetworkUsageData.kt +++ b/src/com/android/settings/datausage/lib/NetworkUsageData.kt @@ -20,7 +20,7 @@ import android.content.Context import android.text.format.DateUtils import android.util.Range import com.android.settings.R -import com.android.settings.datausage.DataUsageUtils +import com.android.settings.datausage.lib.DataUsageFormatter.FormattedDataUsage /** * Base data structure representing usage data in a period. @@ -38,10 +38,11 @@ data class NetworkUsageData( fun formatDateRange(context: Context): String = DateUtils.formatDateRange(context, startTime, endTime, DATE_FORMAT) - fun formatUsage(context: Context): CharSequence = DataUsageUtils.formatDataUsage(context, usage) + fun formatUsage(context: Context): FormattedDataUsage = + DataUsageFormatter(context).formatDataUsage(usage) - fun getDataUsedString(context: Context): String = - context.getString(R.string.data_used_template, formatUsage(context)) + fun getDataUsedString(context: Context): FormattedDataUsage = + formatUsage(context).format(context, R.string.data_used_template) companion object { val AllZero = NetworkUsageData( diff --git a/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt b/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt index 1cf770b3d40..d47a246644d 100644 --- a/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt +++ b/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt @@ -30,6 +30,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.datausage.DataUsageUtils +import com.android.settings.datausage.lib.DataUsageFormatter.FormattedDataUsage import com.android.settings.datausage.lib.DataUsageLib import com.android.settings.datausage.lib.NetworkCycleDataRepository import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange @@ -89,7 +90,7 @@ class DataUsagePreferenceController(context: Context, key: String) : getDataUsageSummaryAndEnabled() } preference.isEnabled = enabled - preference.summary = summary + preference.summary = summary?.displayText } private fun getNetworkTemplate(): NetworkTemplate? = when { @@ -104,15 +105,14 @@ class DataUsagePreferenceController(context: Context, key: String) : fun createNetworkCycleDataRepository(): NetworkCycleDataRepository? = networkTemplate?.let { NetworkCycleDataRepository(mContext, it) } - private fun getDataUsageSummaryAndEnabled(): Pair { + private fun getDataUsageSummaryAndEnabled(): Pair { val repository = createNetworkCycleDataRepository() ?: return null to false repository.loadFirstCycle()?.let { usageData -> - return mContext.getString( - R.string.data_usage_template, - usageData.formatUsage(mContext), - usageData.formatDateRange(mContext), - ) to (usageData.usage > 0 || repository.queryUsage(AllTimeRange).usage > 0) + val formattedDataUsage = usageData.formatUsage(mContext) + .format(mContext, R.string.data_usage_template, usageData.formatDateRange(mContext)) + val hasUsage = usageData.usage > 0 || repository.queryUsage(AllTimeRange).usage > 0 + return formattedDataUsage to hasUsage } val allTimeUsage = repository.queryUsage(AllTimeRange) diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index 05cfad884b3..adb67e1a512 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -26,9 +26,11 @@ import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -46,7 +48,15 @@ class SubscriptionRepository(private val context: Context) { fun getSelectableSubscriptionInfoList(): List = context.getSelectableSubscriptionInfoList() - fun isSubscriptionEnabledFlow(subId: Int) = context.isSubscriptionEnabledFlow(subId) + /** Flow of whether the subscription enabled for the given [subId]. */ + fun isSubscriptionEnabledFlow(subId: Int): Flow { + if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false) + return context.subscriptionsChangedFlow() + .map { subscriptionManager.isSubscriptionEnabled(subId) } + .conflate() + .onEach { Log.d(TAG, "[$subId] isSubscriptionEnabledFlow: $it") } + .flowOn(Dispatchers.Default) + } /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */ fun collectSubscriptionEnabled( @@ -65,11 +75,6 @@ val Context.subscriptionManager: SubscriptionManager? fun Context.requireSubscriptionManager(): SubscriptionManager = subscriptionManager!! -fun Context.isSubscriptionEnabledFlow(subId: Int) = subscriptionsChangedFlow().map { - subscriptionManager?.isSubscriptionEnabled(subId) ?: false -}.conflate().onEach { Log.d(TAG, "[$subId] isSubscriptionEnabledFlow: $it") } - .flowOn(Dispatchers.Default) - fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map { SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo) }.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default) diff --git a/src/com/android/settings/password/OWNERS b/src/com/android/settings/password/OWNERS index aa03c591d0a..9cfaf7a01b2 100644 --- a/src/com/android/settings/password/OWNERS +++ b/src/com/android/settings/password/OWNERS @@ -1,3 +1,6 @@ +# The Android Biometric team should approve all changes to password subdirectories. +set noparent + # Default reviewers for this and subdirectories. curtislb@google.com graciecheng@google.com diff --git a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt index 7e6e72613b7..7b8cf8c8b55 100644 --- a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt +++ b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt @@ -113,7 +113,7 @@ private class AppDataUsagePresenter( } else { context.getString( R.string.data_summary_format, - appUsageData.formatUsage(context), + appUsageData.formatUsage(context).displayText, appUsageData.formatStartDate(context), ) } diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt index 59e63dfd8ee..07da034b33b 100644 --- a/src/com/android/settings/spa/network/SimsSection.kt +++ b/src/com/android/settings/spa/network/SimsSection.kt @@ -38,7 +38,7 @@ import com.android.settings.Utils import com.android.settings.network.SubscriptionUtil import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.network.telephony.SubscriptionActivationRepository -import com.android.settings.network.telephony.isSubscriptionEnabledFlow +import com.android.settings.network.telephony.SubscriptionRepository import com.android.settings.network.telephony.phoneNumberFlow import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel @@ -63,7 +63,7 @@ fun SimsSection(subscriptionInfoList: List) { private fun SimPreference(subInfo: SubscriptionInfo) { val context = LocalContext.current val checked = remember(subInfo.subscriptionId) { - context.isSubscriptionEnabledFlow(subInfo.subscriptionId) + SubscriptionRepository(context).isSubscriptionEnabledFlow(subInfo.subscriptionId) }.collectAsStateWithLifecycle(initialValue = false) val phoneNumber = phoneNumber(subInfo) val isConvertedPsim by remember(subInfo) { diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java index bad29eb79da..74671b5f4af 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java @@ -43,6 +43,7 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settings.wifi.WifiUtils; +import com.android.settings.wifi.repository.SharedConnectivityRepository; import com.android.settingslib.TetherUtil; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; @@ -346,16 +347,20 @@ public class WifiTetherSettings extends RestrictedDashboardFragment static class SearchIndexProvider extends BaseSearchIndexProvider { private final WifiRestriction mWifiRestriction; + private final boolean mIsInstantHotspotEnabled; SearchIndexProvider(int xmlRes) { super(xmlRes); mWifiRestriction = new WifiRestriction(); + mIsInstantHotspotEnabled = SharedConnectivityRepository.isDeviceConfigEnabled(); } @VisibleForTesting - SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction) { + SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction, + boolean isInstantHotspotEnabled) { super(xmlRes); mWifiRestriction = wifiRestriction; + mIsInstantHotspotEnabled = isInstantHotspotEnabled; } @Override @@ -369,6 +374,9 @@ public class WifiTetherSettings extends RestrictedDashboardFragment keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD); keys.add(KEY_WIFI_TETHER_AUTO_OFF); keys.add(KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + keys.add(KEY_INSTANT_HOTSPOT); + } else if (!mIsInstantHotspotEnabled) { + keys.add(KEY_INSTANT_HOTSPOT); } // Remove duplicate diff --git a/tests/robotests/README.md b/tests/robotests/README.md index 648f1af77d8..8083f75fdec 100644 --- a/tests/robotests/README.md +++ b/tests/robotests/README.md @@ -4,21 +4,20 @@ ## The full suite ``` $ croot -$ make RunSettingsRoboTests +$ atest SettingsRoboTests ``` ## Running a single test class +With a filter + ``` $ croot -$ make RunSettingsRoboTests ROBOTEST_FILTER= +$ atest SettingsRoboTests:com.android.settings.display.AdaptiveSleepPreferenceControllerTest ``` -For example: +You can also run any single test class with atest (it will try to find the correct path) ``` -make RunSettingsRoboTests ROBOTEST_FILTER=CodeInspectionTest +$ atest AdaptiveSleepPreferenceControllerTest ``` - -You can also use partial class name in ROBOTEST_FILTER. If the partial class name matches -multiple file names, all of them will be executed. diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java index 28fb8b37721..299d545c49b 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java @@ -266,7 +266,8 @@ public class WifiTetherSettingsTest { when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); + new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, + true /* isInstantHotspotEnabled */); final List keys = searchIndexProvider.getNonIndexableKeys(mContext); @@ -275,6 +276,7 @@ public class WifiTetherSettingsTest { assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_INSTANT_HOTSPOT); } @Test @@ -282,7 +284,8 @@ public class WifiTetherSettingsTest { when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false); when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); + new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, + true /* isInstantHotspotEnabled */); final List keys = searchIndexProvider.getNonIndexableKeys(mContext); @@ -291,6 +294,7 @@ public class WifiTetherSettingsTest { assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); } @Test @@ -298,7 +302,26 @@ public class WifiTetherSettingsTest { when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false); WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); + new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, + true /* isInstantHotspotEnabled */); + + final List keys = searchIndexProvider.getNonIndexableKeys(mContext); + + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); + } + + @Test + public void getNonIndexableKeys_tetherAndHotspotNotAvailable_keysReturned() { + when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false); + when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false); + WifiTetherSettings.SearchIndexProvider searchIndexProvider = + new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, + true /* isInstantHotspotEnabled */); final List keys = searchIndexProvider.getNonIndexableKeys(mContext); @@ -310,19 +333,21 @@ public class WifiTetherSettingsTest { } @Test - public void getNonIndexableKeys_tetherAndHotspotNotAvailable_keysReturned() { - when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false); - when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false); + public void getNonIndexableKeys_instantHotspotNotAvailableOnly_keysContainInstantHotspotOnly() { + when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); + when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); + new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, + false /* isInstantHotspotEnabled */); final List keys = searchIndexProvider.getNonIndexableKeys(mContext); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); } @Test diff --git a/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageSummaryControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageSummaryControllerTest.kt index 584295649aa..be839dd416f 100644 --- a/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageSummaryControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/datausage/AppDataUsageSummaryControllerTest.kt @@ -19,8 +19,9 @@ package com.android.settings.datausage import android.content.Context import android.util.Range import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasTextExactly import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.datausage.lib.NetworkUsageDetailsData @@ -52,9 +53,34 @@ class AppDataUsageSummaryControllerTest { controller.Content() } - composeTestRule.onNodeWithText("6.75 kB").assertIsDisplayed() - composeTestRule.onNodeWithText("5.54 kB").assertIsDisplayed() - composeTestRule.onNodeWithText("1.21 kB").assertIsDisplayed() + composeTestRule.onNode(hasTextExactly("Total", "6.75 kB")).assertIsDisplayed() + composeTestRule.onNode(hasTextExactly("Foreground", "5.54 kB")).assertIsDisplayed() + composeTestRule.onNode(hasTextExactly("Background", "1.21 kB")).assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("6.75 kB").assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("5.54 kB").assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("1.21 kB").assertIsDisplayed() + } + + @Test + fun summary_zero() { + val appUsage = NetworkUsageDetailsData( + range = Range(1L, 2L), + totalUsage = 3, + foregroundUsage = 1, + backgroundUsage = 2, + ) + + controller.update(appUsage) + composeTestRule.setContent { + controller.Content() + } + + composeTestRule.onNode(hasTextExactly("Total", "3 B")).assertIsDisplayed() + composeTestRule.onNode(hasTextExactly("Foreground", "1 B")).assertIsDisplayed() + composeTestRule.onNode(hasTextExactly("Background", "2 B")).assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("3 byte").assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("1 byte").assertIsDisplayed() + composeTestRule.onNodeWithContentDescription("2 byte").assertIsDisplayed() } private companion object { diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageFormatterTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/DataUsageFormatterTest.kt similarity index 59% rename from tests/spa_unit/src/com/android/settings/datausage/DataUsageFormatterTest.kt rename to tests/spa_unit/src/com/android/settings/datausage/lib/DataUsageFormatterTest.kt index dc6a421b940..071234dcd65 100644 --- a/tests/spa_unit/src/com/android/settings/datausage/DataUsageFormatterTest.kt +++ b/tests/spa_unit/src/com/android/settings/datausage/lib/DataUsageFormatterTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.settings.datausage +package com.android.settings.datausage.lib import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.datausage.DataUsageFormatter.getBytesDisplayUnit +import com.android.settings.datausage.lib.DataUsageFormatter.Companion.getBytesDisplayUnit import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -29,6 +29,32 @@ import org.junit.runner.RunWith class DataUsageFormatterTest { private val context: Context = ApplicationProvider.getApplicationContext() + private val dataUsageFormatter = DataUsageFormatter(context) + + @Test + fun formatDataUsage_0() { + val (displayText, contentDescription) = dataUsageFormatter.formatDataUsage(0) + + assertThat(displayText).isEqualTo("0 B") + assertThat(contentDescription).isEqualTo("0 byte") + } + + @Test + fun formatDataUsage_1000() { + val (displayText, contentDescription) = dataUsageFormatter.formatDataUsage(1000) + + assertThat(displayText).isEqualTo("0.98 kB") + assertThat(contentDescription).isEqualTo("0.98 kB") + } + + @Test + fun formatDataUsage_2000000() { + val (displayText, contentDescription) = dataUsageFormatter.formatDataUsage(2000000) + + assertThat(displayText).isEqualTo("1.91 MB") + assertThat(contentDescription).isEqualTo("1.91 MB") + } + @Test fun getUnitDisplayName_megaByte() { val displayName = context.resources.getBytesDisplayUnit(ONE_MEGA_BYTE_IN_BYTES) @@ -47,4 +73,4 @@ class DataUsageFormatterTest { const val ONE_MEGA_BYTE_IN_BYTES = 1024L * 1024 const val ONE_GIGA_BYTE_IN_BYTES = 1024L * 1024 * 1024 } -} \ No newline at end of file +} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt index b394eb27607..ce160979837 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt @@ -52,13 +52,24 @@ class SubscriptionRepositoryTest { on { subscriptionManager } doReturn mockSubscriptionManager } + private val repository = SubscriptionRepository(context) + @Test - fun isSubscriptionEnabledFlow() = runBlocking { + fun isSubscriptionEnabledFlow_invalidSubId() = runBlocking { + val isEnabled = repository + .isSubscriptionEnabledFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .firstWithTimeoutOrNull() + + assertThat(isEnabled).isFalse() + } + + @Test + fun isSubscriptionEnabledFlow_enabled() = runBlocking { mockSubscriptionManager.stub { on { isSubscriptionEnabled(SUB_ID_1) } doReturn true } - val isEnabled = context.isSubscriptionEnabledFlow(SUB_ID_1).firstWithTimeoutOrNull() + val isEnabled = repository.isSubscriptionEnabledFlow(SUB_ID_1).firstWithTimeoutOrNull() assertThat(isEnabled).isTrue() } diff --git a/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentControllerTest.kt b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentControllerTest.kt index d999464dddb..07bdefc7bfe 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentControllerTest.kt +++ b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkFragmentControllerTest.kt @@ -17,7 +17,6 @@ package com.android.settings.connecteddevice.threadnetwork import android.content.Context import android.platform.test.flag.junit.SetFlagsRule -import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.test.core.app.ApplicationProvider @@ -27,6 +26,7 @@ import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILA import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE import com.android.settings.flags.Flags import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors import org.junit.Before import org.junit.Rule import org.junit.Test @@ -50,7 +50,7 @@ class ThreadNetworkFragmentControllerTest { fun setUp() { mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) context = spy(ApplicationProvider.getApplicationContext()) - executor = ContextCompat.getMainExecutor(context) + executor = MoreExecutors.directExecutor() fakeThreadNetworkController = FakeThreadNetworkController() controller = newControllerWithThreadFeatureSupported(true) } diff --git a/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleControllerTest.kt b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleControllerTest.kt index b6da97f30f2..c2b3969d9eb 100644 --- a/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleControllerTest.kt +++ b/tests/unit/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkToggleControllerTest.kt @@ -27,6 +27,7 @@ import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILA import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE import com.android.settings.flags.Flags import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors import org.junit.Before import org.junit.Rule import org.junit.Test @@ -51,7 +52,7 @@ class ThreadNetworkToggleControllerTest { fun setUp() { mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) context = spy(ApplicationProvider.getApplicationContext()) - executor = Executor { runnable: Runnable -> runnable.run() } + executor = MoreExecutors.directExecutor() fakeThreadNetworkController = FakeThreadNetworkController() controller = newControllerWithThreadFeatureSupported(true) val preferenceManager = PreferenceManager(context)