Snap for 11834877 from 2dbb27de91 to 24Q3-release

Change-Id: Ieed92f531be066e0b3d009c7314868390ce682ff
This commit is contained in:
Android Build Coastguard Worker
2024-05-13 23:22:22 +00:00
22 changed files with 252 additions and 98 deletions

View File

@@ -1,3 +1,6 @@
# The Android Biometric team should approve all changes to biometrics subdirectories.
set noparent
graciecheng@google.com graciecheng@google.com
ilyamaty@google.com ilyamaty@google.com
jaggies@google.com jaggies@google.com

View File

@@ -1 +1,4 @@
# The Android Biometric team should approve all changes to biometrics2 subdirectories.
set noparent
include /src/com/android/settings/biometrics/OWNERS include /src/com/android/settings/biometrics/OWNERS

View File

@@ -23,11 +23,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R import com.android.settings.R
import com.android.settings.datausage.lib.DataUsageFormatter
import com.android.settings.datausage.lib.NetworkUsageDetailsData import com.android.settings.datausage.lib.NetworkUsageDetailsData
import com.android.settings.spa.preference.ComposePreferenceController import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel 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.MutableStateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -35,17 +36,20 @@ class AppDataUsageSummaryController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) { ComposePreferenceController(context, preferenceKey) {
private val dataFlow = MutableStateFlow(NetworkUsageDetailsData.AllZero) 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 { private val totalUsageFlow = dataFlow.map {
DataUsageUtils.formatDataUsage(mContext, it.totalUsage).toString() dataUsageFormatter.formatDataUsage(it.totalUsage)
} }
private val foregroundUsageFlow = dataFlow.map { private val foregroundUsageFlow = dataFlow.map {
DataUsageUtils.formatDataUsage(mContext, it.foregroundUsage).toString() dataUsageFormatter.formatDataUsage(it.foregroundUsage)
} }
private val backgroundUsageFlow = dataFlow.map { private val backgroundUsageFlow = dataFlow.map {
DataUsageUtils.formatDataUsage(mContext, it.backgroundUsage).toString() dataUsageFormatter.formatDataUsage(it.backgroundUsage)
} }
override fun getAvailabilityStatus() = AVAILABLE override fun getAvailabilityStatus() = AVAILABLE
@@ -57,20 +61,23 @@ class AppDataUsageSummaryController(context: Context, preferenceKey: String) :
@Composable @Composable
override fun Content() { override fun Content() {
Column { Column {
val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(placeholder()) val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(placeholder()) val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(placeholder()) val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
Preference(object : PreferenceModel { Preference(object : PreferenceModel {
override val title = stringResource(R.string.total_size_label) 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 { Preference(object : PreferenceModel {
override val title = stringResource(R.string.data_usage_label_foreground) 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 { Preference(object : PreferenceModel {
override val title = stringResource(R.string.data_usage_label_background) override val title = stringResource(R.string.data_usage_label_background)
override val summary = { backgroundUsage } override val summary = { backgroundUsage.displayText }
override val summaryContentDescription = { backgroundUsage.contentDescription }
}) })
} }
} }

View File

@@ -44,6 +44,7 @@ import androidx.preference.TwoStatePreference;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 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.datausage.lib.NetworkTemplates;
import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settings.network.telephony.MobileNetworkUtils;
@@ -322,8 +323,8 @@ public class BillingCycleSettings extends DataUsageBaseFragment implements
: editor.getPolicyWarningBytes(template); : editor.getPolicyWarningBytes(template);
final String[] unitNames = new String[] { final String[] unitNames = new String[] {
DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), MIB_IN_BYTES), DataUsageFormatter.Companion.getBytesDisplayUnit(getResources(), MIB_IN_BYTES),
DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), GIB_IN_BYTES), DataUsageFormatter.Companion.getBytesDisplayUnit(getResources(), GIB_IN_BYTES),
}; };
final ArrayAdapter<String> adapter = new ArrayAdapter<String>( final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
getContext(), android.R.layout.simple_spinner_item, unitNames); getContext(), android.R.layout.simple_spinner_item, unitNames);

View File

@@ -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
}

View File

@@ -178,7 +178,7 @@ open class DataUsageList : DashboardFragment() {
private fun updateSelectedCycle(usageData: NetworkUsageData) { private fun updateSelectedCycle(usageData: NetworkUsageData) {
Log.d(TAG, "showing cycle $usageData") Log.d(TAG, "showing cycle $usageData")
usageAmount?.title = usageData.getDataUsedString(requireContext()) usageAmount?.title = usageData.getDataUsedString(requireContext()).displayText
viewModel.selectedCycleFlow.value = usageData viewModel.selectedCycleFlow.value = usageData
updateApps(usageData) updateApps(usageData)

View File

@@ -56,7 +56,10 @@ public final class DataUsageUtils {
/** /**
* Format byte value to readable string using IEC units. * 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) { public static CharSequence formatDataUsage(Context context, long byteValue) {
final BytesResult res = Formatter.formatBytes(context.getResources(), byteValue, final BytesResult res = Formatter.formatBytes(context.getResources(), byteValue,
Formatter.FLAG_IEC_UNITS); Formatter.FLAG_IEC_UNITS);

View File

@@ -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
}
}

View File

@@ -20,7 +20,7 @@ import android.content.Context
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.Range import android.util.Range
import com.android.settings.R 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. * Base data structure representing usage data in a period.
@@ -38,10 +38,11 @@ data class NetworkUsageData(
fun formatDateRange(context: Context): String = fun formatDateRange(context: Context): String =
DateUtils.formatDateRange(context, startTime, endTime, DATE_FORMAT) 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 = fun getDataUsedString(context: Context): FormattedDataUsage =
context.getString(R.string.data_used_template, formatUsage(context)) formatUsage(context).format(context, R.string.data_used_template)
companion object { companion object {
val AllZero = NetworkUsageData( val AllZero = NetworkUsageData(

View File

@@ -30,6 +30,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.android.settings.R import com.android.settings.R
import com.android.settings.datausage.DataUsageUtils 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.DataUsageLib
import com.android.settings.datausage.lib.NetworkCycleDataRepository import com.android.settings.datausage.lib.NetworkCycleDataRepository
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange
@@ -89,7 +90,7 @@ class DataUsagePreferenceController(context: Context, key: String) :
getDataUsageSummaryAndEnabled() getDataUsageSummaryAndEnabled()
} }
preference.isEnabled = enabled preference.isEnabled = enabled
preference.summary = summary preference.summary = summary?.displayText
} }
private fun getNetworkTemplate(): NetworkTemplate? = when { private fun getNetworkTemplate(): NetworkTemplate? = when {
@@ -104,15 +105,14 @@ class DataUsagePreferenceController(context: Context, key: String) :
fun createNetworkCycleDataRepository(): NetworkCycleDataRepository? = fun createNetworkCycleDataRepository(): NetworkCycleDataRepository? =
networkTemplate?.let { NetworkCycleDataRepository(mContext, it) } networkTemplate?.let { NetworkCycleDataRepository(mContext, it) }
private fun getDataUsageSummaryAndEnabled(): Pair<String?, Boolean> { private fun getDataUsageSummaryAndEnabled(): Pair<FormattedDataUsage?, Boolean> {
val repository = createNetworkCycleDataRepository() ?: return null to false val repository = createNetworkCycleDataRepository() ?: return null to false
repository.loadFirstCycle()?.let { usageData -> repository.loadFirstCycle()?.let { usageData ->
return mContext.getString( val formattedDataUsage = usageData.formatUsage(mContext)
R.string.data_usage_template, .format(mContext, R.string.data_usage_template, usageData.formatDateRange(mContext))
usageData.formatUsage(mContext), val hasUsage = usageData.usage > 0 || repository.queryUsage(AllTimeRange).usage > 0
usageData.formatDateRange(mContext), return formattedDataUsage to hasUsage
) to (usageData.usage > 0 || repository.queryUsage(AllTimeRange).usage > 0)
} }
val allTimeUsage = repository.queryUsage(AllTimeRange) val allTimeUsage = repository.queryUsage(AllTimeRange)

View File

@@ -26,9 +26,11 @@ import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -46,7 +48,15 @@ class SubscriptionRepository(private val context: Context) {
fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> = fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> =
context.getSelectableSubscriptionInfoList() context.getSelectableSubscriptionInfoList()
fun isSubscriptionEnabledFlow(subId: Int) = context.isSubscriptionEnabledFlow(subId) /** Flow of whether the subscription enabled for the given [subId]. */
fun isSubscriptionEnabledFlow(subId: Int): Flow<Boolean> {
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. */ /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
fun collectSubscriptionEnabled( fun collectSubscriptionEnabled(
@@ -65,11 +75,6 @@ val Context.subscriptionManager: SubscriptionManager?
fun Context.requireSubscriptionManager(): 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 { fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo) = subscriptionsChangedFlow().map {
SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo) SubscriptionUtil.getBidiFormattedPhoneNumber(this, subscriptionInfo)
}.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default) }.filterNot { it.isNullOrEmpty() }.flowOn(Dispatchers.Default)

View File

@@ -1,3 +1,6 @@
# The Android Biometric team should approve all changes to password subdirectories.
set noparent
# Default reviewers for this and subdirectories. # Default reviewers for this and subdirectories.
curtislb@google.com curtislb@google.com
graciecheng@google.com graciecheng@google.com

View File

@@ -113,7 +113,7 @@ private class AppDataUsagePresenter(
} else { } else {
context.getString( context.getString(
R.string.data_summary_format, R.string.data_summary_format,
appUsageData.formatUsage(context), appUsageData.formatUsage(context).displayText,
appUsageData.formatStartDate(context), appUsageData.formatStartDate(context),
) )
} }

View File

@@ -38,7 +38,7 @@ import com.android.settings.Utils
import com.android.settings.network.SubscriptionUtil import com.android.settings.network.SubscriptionUtil
import com.android.settings.network.telephony.MobileNetworkUtils import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.SubscriptionActivationRepository 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.settings.network.telephony.phoneNumberFlow
import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
@@ -63,7 +63,7 @@ fun SimsSection(subscriptionInfoList: List<SubscriptionInfo>) {
private fun SimPreference(subInfo: SubscriptionInfo) { private fun SimPreference(subInfo: SubscriptionInfo) {
val context = LocalContext.current val context = LocalContext.current
val checked = remember(subInfo.subscriptionId) { val checked = remember(subInfo.subscriptionId) {
context.isSubscriptionEnabledFlow(subInfo.subscriptionId) SubscriptionRepository(context).isSubscriptionEnabledFlow(subInfo.subscriptionId)
}.collectAsStateWithLifecycle(initialValue = false) }.collectAsStateWithLifecycle(initialValue = false)
val phoneNumber = phoneNumber(subInfo) val phoneNumber = phoneNumber(subInfo)
val isConvertedPsim by remember(subInfo) { val isConvertedPsim by remember(subInfo) {

View File

@@ -43,6 +43,7 @@ import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settings.wifi.WifiUtils; import com.android.settings.wifi.WifiUtils;
import com.android.settings.wifi.repository.SharedConnectivityRepository;
import com.android.settingslib.TetherUtil; import com.android.settingslib.TetherUtil;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexable;
@@ -346,16 +347,20 @@ public class WifiTetherSettings extends RestrictedDashboardFragment
static class SearchIndexProvider extends BaseSearchIndexProvider { static class SearchIndexProvider extends BaseSearchIndexProvider {
private final WifiRestriction mWifiRestriction; private final WifiRestriction mWifiRestriction;
private final boolean mIsInstantHotspotEnabled;
SearchIndexProvider(int xmlRes) { SearchIndexProvider(int xmlRes) {
super(xmlRes); super(xmlRes);
mWifiRestriction = new WifiRestriction(); mWifiRestriction = new WifiRestriction();
mIsInstantHotspotEnabled = SharedConnectivityRepository.isDeviceConfigEnabled();
} }
@VisibleForTesting @VisibleForTesting
SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction) { SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction,
boolean isInstantHotspotEnabled) {
super(xmlRes); super(xmlRes);
mWifiRestriction = wifiRestriction; mWifiRestriction = wifiRestriction;
mIsInstantHotspotEnabled = isInstantHotspotEnabled;
} }
@Override @Override
@@ -369,6 +374,9 @@ public class WifiTetherSettings extends RestrictedDashboardFragment
keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD); keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD);
keys.add(KEY_WIFI_TETHER_AUTO_OFF); keys.add(KEY_WIFI_TETHER_AUTO_OFF);
keys.add(KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); keys.add(KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY);
keys.add(KEY_INSTANT_HOTSPOT);
} else if (!mIsInstantHotspotEnabled) {
keys.add(KEY_INSTANT_HOTSPOT);
} }
// Remove duplicate // Remove duplicate

View File

@@ -4,21 +4,20 @@
## The full suite ## The full suite
``` ```
$ croot $ croot
$ make RunSettingsRoboTests $ atest SettingsRoboTests
``` ```
## Running a single test class ## Running a single test class
With a filter
``` ```
$ croot $ croot
$ make RunSettingsRoboTests ROBOTEST_FILTER=<ClassName> $ 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.

View File

@@ -266,7 +266,8 @@ public class WifiTetherSettingsTest {
when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true);
when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true);
WifiTetherSettings.SearchIndexProvider searchIndexProvider = WifiTetherSettings.SearchIndexProvider searchIndexProvider =
new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction,
true /* isInstantHotspotEnabled */);
final List<String> keys = searchIndexProvider.getNonIndexableKeys(mContext); final List<String> 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_NETWORK_PASSWORD);
assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF);
assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY);
assertThat(keys).doesNotContain(WifiTetherSettings.KEY_INSTANT_HOTSPOT);
} }
@Test @Test
@@ -282,7 +284,8 @@ public class WifiTetherSettingsTest {
when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false); when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false);
when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true);
WifiTetherSettings.SearchIndexProvider searchIndexProvider = WifiTetherSettings.SearchIndexProvider searchIndexProvider =
new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction,
true /* isInstantHotspotEnabled */);
final List<String> keys = searchIndexProvider.getNonIndexableKeys(mContext); final List<String> 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_NETWORK_PASSWORD);
assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF);
assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY);
assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT);
} }
@Test @Test
@@ -298,7 +302,26 @@ public class WifiTetherSettingsTest {
when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true);
when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false); when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false);
WifiTetherSettings.SearchIndexProvider searchIndexProvider = WifiTetherSettings.SearchIndexProvider searchIndexProvider =
new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction,
true /* isInstantHotspotEnabled */);
final List<String> 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<String> keys = searchIndexProvider.getNonIndexableKeys(mContext); final List<String> keys = searchIndexProvider.getNonIndexableKeys(mContext);
@@ -310,19 +333,21 @@ public class WifiTetherSettingsTest {
} }
@Test @Test
public void getNonIndexableKeys_tetherAndHotspotNotAvailable_keysReturned() { public void getNonIndexableKeys_instantHotspotNotAvailableOnly_keysContainInstantHotspotOnly() {
when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false); when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true);
when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false); when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true);
WifiTetherSettings.SearchIndexProvider searchIndexProvider = WifiTetherSettings.SearchIndexProvider searchIndexProvider =
new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction); new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction,
false /* isInstantHotspotEnabled */);
final List<String> keys = searchIndexProvider.getNonIndexableKeys(mContext); final List<String> keys = searchIndexProvider.getNonIndexableKeys(mContext);
assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME);
assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY);
assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD);
assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF);
assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY);
assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT);
} }
@Test @Test

View File

@@ -19,8 +19,9 @@ package com.android.settings.datausage
import android.content.Context import android.content.Context
import android.util.Range import android.util.Range
import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasTextExactly
import androidx.compose.ui.test.junit4.createComposeRule 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.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.datausage.lib.NetworkUsageDetailsData import com.android.settings.datausage.lib.NetworkUsageDetailsData
@@ -52,9 +53,34 @@ class AppDataUsageSummaryControllerTest {
controller.Content() controller.Content()
} }
composeTestRule.onNodeWithText("6.75 kB").assertIsDisplayed() composeTestRule.onNode(hasTextExactly("Total", "6.75 kB")).assertIsDisplayed()
composeTestRule.onNodeWithText("5.54 kB").assertIsDisplayed() composeTestRule.onNode(hasTextExactly("Foreground", "5.54 kB")).assertIsDisplayed()
composeTestRule.onNodeWithText("1.21 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 { private companion object {

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.settings.datausage package com.android.settings.datausage.lib
import android.content.Context import android.content.Context
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 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 com.google.common.truth.Truth.assertThat
import org.junit.Test import org.junit.Test
@@ -29,6 +29,32 @@ import org.junit.runner.RunWith
class DataUsageFormatterTest { class DataUsageFormatterTest {
private val context: Context = ApplicationProvider.getApplicationContext() 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 @Test
fun getUnitDisplayName_megaByte() { fun getUnitDisplayName_megaByte() {
val displayName = context.resources.getBytesDisplayUnit(ONE_MEGA_BYTE_IN_BYTES) val displayName = context.resources.getBytesDisplayUnit(ONE_MEGA_BYTE_IN_BYTES)

View File

@@ -52,13 +52,24 @@ class SubscriptionRepositoryTest {
on { subscriptionManager } doReturn mockSubscriptionManager on { subscriptionManager } doReturn mockSubscriptionManager
} }
private val repository = SubscriptionRepository(context)
@Test @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 { mockSubscriptionManager.stub {
on { isSubscriptionEnabled(SUB_ID_1) } doReturn true 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() assertThat(isEnabled).isTrue()
} }

View File

@@ -17,7 +17,6 @@ package com.android.settings.connecteddevice.threadnetwork
import android.content.Context import android.content.Context
import android.platform.test.flag.junit.SetFlagsRule import android.platform.test.flag.junit.SetFlagsRule
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.test.core.app.ApplicationProvider 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.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE
import com.android.settings.flags.Flags import com.android.settings.flags.Flags
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -50,7 +50,7 @@ class ThreadNetworkFragmentControllerTest {
fun setUp() { fun setUp() {
mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED)
context = spy(ApplicationProvider.getApplicationContext<Context>()) context = spy(ApplicationProvider.getApplicationContext<Context>())
executor = ContextCompat.getMainExecutor(context) executor = MoreExecutors.directExecutor()
fakeThreadNetworkController = FakeThreadNetworkController() fakeThreadNetworkController = FakeThreadNetworkController()
controller = newControllerWithThreadFeatureSupported(true) controller = newControllerWithThreadFeatureSupported(true)
} }

View File

@@ -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.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE
import com.android.settings.flags.Flags import com.android.settings.flags.Flags
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -51,7 +52,7 @@ class ThreadNetworkToggleControllerTest {
fun setUp() { fun setUp() {
mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED) mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_SETTINGS_ENABLED)
context = spy(ApplicationProvider.getApplicationContext<Context>()) context = spy(ApplicationProvider.getApplicationContext<Context>())
executor = Executor { runnable: Runnable -> runnable.run() } executor = MoreExecutors.directExecutor()
fakeThreadNetworkController = FakeThreadNetworkController() fakeThreadNetworkController = FakeThreadNetworkController()
controller = newControllerWithThreadFeatureSupported(true) controller = newControllerWithThreadFeatureSupported(true)
val preferenceManager = PreferenceManager(context) val preferenceManager = PreferenceManager(context)