Merge "Calling requestQuietMode with peindingIntent to match PS settings with PS state" into main
diff --git a/res/layout/accessibility_shortcut_option_checkable.xml b/res/layout/accessibility_shortcut_option_checkable.xml
index a5c5595..1551352 100644
--- a/res/layout/accessibility_shortcut_option_checkable.xml
+++ b/res/layout/accessibility_shortcut_option_checkable.xml
@@ -32,23 +32,22 @@
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:gravity="center_vertical"
android:orientation="vertical"
+ app:layout_constrainedHeight="true"
+ app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@android:id/title"
- app:layout_constraintEnd_toStartOf="@android:id/title"
- app:layout_constraintHeight="true"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintWidth="true" />
+ app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@android:id/checkbox"
@@ -61,6 +60,7 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
+ app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@android:id/title"
app:layout_constraintTop_toBottomOf="@android:id/title"
@@ -75,6 +75,7 @@
android:adjustViewBounds="true"
android:background="@drawable/protection_background"
android:maxHeight="@dimen/accessibility_imageview_size"
+ app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@android:id/title"
diff --git a/res/values/config.xml b/res/values/config.xml
index d084ff9..d0cd96b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -38,6 +38,11 @@
<!-- Whether to show Camera laser sensor switch in Developer Options -->
<bool name="config_show_camera_laser_sensor">false</bool>
+ <!-- Intent action to open Avatar Picker app -->
+ <string name="config_avatar_picker_action" translatable="false">
+ com.android.avatarpicker.FULL_SCREEN_ACTIVITY
+ </string>
+
<!-- Package name and fully-qualified class name for the wallpaper picker activity. -->
<string name="config_wallpaper_picker_package" translatable="false">com.android.settings</string>
<string name="config_wallpaper_picker_class" translatable="false">com.android.settings.Settings$WallpaperSettingsActivity</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7465a60..0b12142 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1288,7 +1288,7 @@
<string name="private_space_error_screen_title">Couldn\u2019t set up private space</string>
<!-- Label for button to retry creating private space again on creation error. [CHAR LIMIT=30] -->
<string name="private_space_tryagain_label">Try Again</string>
- <!-- Title for private space lock setup screen. [CHAR LIMIT=50] -->
+ <!-- Title for private space lock setup screen. [CHAR LIMIT=90] -->
<string name="private_space_lockscreen_title">Use screen lock to unlock private space?</string>
<!-- Summary for the private space lock setup screen. [CHAR LIMIT=NONE] -->
<string name="private_space_lockscreen_summary">You can unlock your private space the same way you unlock your device, or choose a different lock</string>
diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml
index 48835fc..4af02dd 100644
--- a/res/xml/private_space_settings.xml
+++ b/res/xml/private_space_settings.xml
@@ -63,4 +63,10 @@
</PreferenceCategory>
+ <com.android.settings.accessibility.AccessibilityFooterPreference
+ android:key="private_space_footer"
+ android:title="@string/private_space_apps_permission_text"
+ android:selectable="false"
+ settings:searchable="false"/>
+
</PreferenceScreen>
diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java
index 4628221..422610a 100644
--- a/src/com/android/settings/IccLockSettings.java
+++ b/src/com/android/settings/IccLockSettings.java
@@ -52,6 +52,7 @@
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
@@ -716,13 +717,18 @@
return slotId;
}
+ @Nullable
private SubscriptionInfo getVisibleSubscriptionInfoForSimSlotIndex(int slotId) {
final List<SubscriptionInfo> subInfoList =
mProxySubscriptionMgr.getActiveSubscriptionsInfo();
if (subInfoList == null) {
return null;
}
- final CarrierConfigManager carrierConfigManager = getContext().getSystemService(
+ Context context = getContext();
+ if (context == null) {
+ return null;
+ }
+ final CarrierConfigManager carrierConfigManager = context.getSystemService(
CarrierConfigManager.class);
for (SubscriptionInfo subInfo : subInfoList) {
if ((isSubscriptionVisible(carrierConfigManager, subInfo)
diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
index 3b79b47..50134ba 100644
--- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
+++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
@@ -34,6 +34,7 @@
import androidx.window.embedding.SplitPlaceholderRule;
import androidx.window.embedding.SplitRule;
+import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.SettingsActivity;
import com.android.settings.SubSettings;
@@ -261,8 +262,13 @@
addActivityFilter(activityFilters, FaceEnrollIntroduction.class);
addActivityFilter(activityFilters, RemoteAuthActivity.class);
addActivityFilter(activityFilters, RemoteAuthActivityInternal.class);
- addActivityFilter(activityFilters, AvatarPickerActivity.class);
addActivityFilter(activityFilters, ChooseLockPattern.class);
+ if (android.multiuser.Flags.avatarSync()) {
+ String action = mContext.getString(R.string.config_avatar_picker_action);
+ addActivityFilter(activityFilters, new Intent(action));
+ } else {
+ addActivityFilter(activityFilters, AvatarPickerActivity.class);
+ }
ActivityRule activityRule = new ActivityRule.Builder(activityFilters).setAlwaysExpand(true)
.build();
mRuleController.addRule(activityRule);
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index 80d3947..081d99a 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -16,6 +16,8 @@
package com.android.settings.applications;
+import static android.webkit.Flags.updateServiceV2;
+
import android.Manifest;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -41,6 +43,7 @@
import com.android.internal.telephony.SmsApplication;
import com.android.settings.R;
+import com.android.settings.webview.WebViewUpdateServiceWrapper;
import java.util.ArrayList;
import java.util.List;
@@ -54,6 +57,7 @@
private final IPackageManager mPms;
private final DevicePolicyManager mDpm;
private final UserManager mUm;
+ private final WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
/** Flags to use when querying PackageManager for Euicc component implementations. */
private static final int EUICC_QUERY_FLAGS =
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
@@ -61,11 +65,16 @@
public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
IPackageManager pms, DevicePolicyManager dpm) {
+ this(context, pm, pms, dpm, new WebViewUpdateServiceWrapper());
+ }
+ public ApplicationFeatureProviderImpl(Context context, PackageManager pm,
+ IPackageManager pms, DevicePolicyManager dpm, WebViewUpdateServiceWrapper wvusWrapper) {
mContext = context.getApplicationContext();
mPm = pm;
mPms = pms;
mDpm = dpm;
mUm = UserManager.get(mContext);
+ mWebViewUpdateServiceWrapper = wvusWrapper;
}
@Override
@@ -159,6 +168,14 @@
keepEnabledPackages.add(euicc.packageName);
}
+ // Keep WebView default package enabled.
+ if (updateServiceV2()) {
+ String packageName = mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName();
+ if (packageName != null) {
+ keepEnabledPackages.add(packageName);
+ }
+ }
+
keepEnabledPackages.addAll(getEnabledPackageAllowlist());
final LocationManager locationManager =
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 6e90885..1b80838 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -53,7 +53,6 @@
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ProgressBar;
@@ -191,7 +190,7 @@
private boolean mHaveShownSfpsRightEdgeLottie;
private boolean mShouldShowLottie;
- private ObjectAnimator mHelpAnimation;
+ private Animator mHelpAnimation;
private OrientationEventListener mOrientationEventListener;
private int mPreviousRotation = 0;
@@ -341,16 +340,10 @@
}
private void setHelpAnimation() {
- final float translationX = 40;
- final int duration = 550;
final RelativeLayout progressLottieLayout = findViewById(R.id.progress_lottie);
- mHelpAnimation = ObjectAnimator.ofFloat(progressLottieLayout,
- "translationX" /* propertyName */,
- 0, translationX, -1 * translationX, translationX, 0f);
- mHelpAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
- mHelpAnimation.setDuration(duration);
- mHelpAnimation.setAutoCancel(false);
+ mHelpAnimation = mSfpsEnrollmentFeature.getHelpAnimator(progressLottieLayout);
}
+
@Override
protected BiometricEnrollSidecar getSidecar() {
final FingerprintEnrollSidecar sidecar = new FingerprintEnrollSidecar(this,
@@ -703,6 +696,9 @@
view.setComposition(composition);
view.setVisibility(View.VISIBLE);
view.playAnimation();
+ if (mCanAssumeSfps) {
+ mSfpsEnrollmentFeature.handleOnEnrollmentLottieComposition(view);
+ }
}
@EnrollStage
@@ -1226,5 +1222,10 @@
public float getEnrollStageThreshold(@NonNull Context context, int index) {
throw new IllegalStateException(exceptionStr);
}
+
+ @Override
+ public Animator getHelpAnimator(@NonNull View target) {
+ throw new IllegalStateException(exceptionStr);
+ }
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
index a1a18e5..f99d394 100644
--- a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
@@ -16,12 +16,16 @@
package com.android.settings.biometrics.fingerprint.feature;
+import android.animation.Animator;
import android.content.Context;
+import android.view.View;
import androidx.annotation.NonNull;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
+import com.airbnb.lottie.LottieAnimationView;
+
import java.util.function.Function;
import java.util.function.Supplier;
@@ -76,4 +80,17 @@
* @return threshold
*/
float getEnrollStageThreshold(@NonNull Context context, int index);
+
+ /**
+ * Gets the help animator used when get help message.
+ * @param target the target view to animate
+ * @return animator
+ */
+ Animator getHelpAnimator(@NonNull View target);
+
+ /**
+ * Handles extra stuffs on lottie composition.
+ * @param lottieView the view related to the lottie
+ */
+ default void handleOnEnrollmentLottieComposition(LottieAnimationView lottieView) {}
}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
index 5a97537..abc31c9 100644
--- a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
@@ -23,8 +23,12 @@
import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_RIGHT_EDGE;
import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.STAGE_UNKNOWN;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -88,4 +92,17 @@
}
return mFingerprintManager.getEnrollStageThreshold(index);
}
+
+ @Override
+ public Animator getHelpAnimator(@NonNull View target) {
+ final float translationX = 40;
+ final int duration = 550;
+ final ObjectAnimator help = ObjectAnimator.ofFloat(target,
+ "translationX" /* propertyName */,
+ 0, translationX, -1 * translationX, translationX, 0f);
+ help.setInterpolator(new AccelerateInterpolator());
+ help.setDuration(duration);
+ help.setAutoCancel(false);
+ return help;
+ }
}
diff --git a/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt b/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt
index 780978f..34dcb4d 100644
--- a/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt
+++ b/src/com/android/settings/datausage/ChartDataUsagePreferenceController.kt
@@ -18,39 +18,22 @@
import android.content.Context
import android.net.NetworkTemplate
-import androidx.annotation.OpenForTesting
-import androidx.annotation.VisibleForTesting
-import androidx.lifecycle.LifecycleCoroutineScope
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceScreen
import com.android.settings.core.BasePreferenceController
import com.android.settings.datausage.lib.INetworkCycleDataRepository
import com.android.settings.datausage.lib.NetworkCycleChartData
import com.android.settings.datausage.lib.NetworkCycleDataRepository
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-@OpenForTesting
-open class ChartDataUsagePreferenceController(context: Context, preferenceKey: String) :
+class ChartDataUsagePreferenceController(context: Context, preferenceKey: String) :
BasePreferenceController(context, preferenceKey) {
private lateinit var repository: INetworkCycleDataRepository
private lateinit var preference: ChartDataUsagePreference
- private lateinit var lifecycleScope: LifecycleCoroutineScope
- private var lastStartTime: Long? = null
- private var lastEndTime: Long? = null
- open fun init(template: NetworkTemplate) {
+ fun init(template: NetworkTemplate) {
this.repository = NetworkCycleDataRepository(mContext, template)
}
- @VisibleForTesting
- fun init(repository: INetworkCycleDataRepository) {
- this.repository = repository
- }
-
override fun getAvailabilityStatus() = AVAILABLE
override fun displayPreference(screen: PreferenceScreen) {
@@ -58,33 +41,20 @@
preference = screen.findPreference(preferenceKey)!!
}
- override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
- lifecycleScope = viewLifecycleOwner.lifecycleScope
- }
-
/**
* Sets whether billing cycle modifiable.
*
* Don't bind warning / limit sweeps if not modifiable.
*/
- open fun setBillingCycleModifiable(isModifiable: Boolean) {
+ fun setBillingCycleModifiable(isModifiable: Boolean) {
preference.setNetworkPolicy(
if (isModifiable) repository.getPolicy() else null
)
}
- fun update(startTime: Long, endTime: Long) {
- if (lastStartTime == startTime && lastEndTime == endTime) return
- lastStartTime = startTime
- lastEndTime = endTime
-
- preference.setTime(startTime, endTime)
- preference.setNetworkCycleData(NetworkCycleChartData.AllZero)
- lifecycleScope.launch {
- val chartData = withContext(Dispatchers.Default) {
- repository.queryChartData(startTime, endTime)
- }
- preference.setNetworkCycleData(chartData)
- }
+ /** Updates chart to show selected cycle. */
+ fun update(chartData: NetworkCycleChartData) {
+ preference.setTime(chartData.total.startTime, chartData.total.endTime)
+ preference.setNetworkCycleData(chartData)
}
}
diff --git a/src/com/android/settings/datausage/DataUsageList.kt b/src/com/android/settings/datausage/DataUsageList.kt
index 30e8db3..6a187d8 100644
--- a/src/com/android/settings/datausage/DataUsageList.kt
+++ b/src/com/android/settings/datausage/DataUsageList.kt
@@ -27,6 +27,7 @@
import android.view.View
import androidx.annotation.OpenForTesting
import androidx.annotation.VisibleForTesting
+import androidx.fragment.app.viewModels
import androidx.preference.Preference
import com.android.settings.R
import com.android.settings.datausage.lib.BillingCycleRepository
@@ -59,6 +60,8 @@
private lateinit var chartDataUsagePreferenceController: ChartDataUsagePreferenceController
private lateinit var billingCycleRepository: BillingCycleRepository
+ private val viewModel: DataUsageListViewModel by viewModels()
+
@VisibleForTesting
var dataUsageListHeaderController: DataUsageListHeaderController? = null
@@ -104,14 +107,21 @@
.collectLatestWithLifecycle(viewLifecycleOwner) { updatePolicy() }
val template = template ?: return
+ viewModel.templateFlow.value = template
dataUsageListHeaderController = DataUsageListHeaderController(
setPinnedHeaderView(R.layout.apps_filter_spinner),
template,
metricsCategory,
viewLifecycleOwner,
- ::onCyclesLoad,
+ viewModel.cyclesFlow,
::updateSelectedCycle,
)
+ viewModel.cyclesFlow.collectLatestWithLifecycle(viewLifecycleOwner) { cycles ->
+ dataUsageListAppsController.updateCycles(cycles)
+ }
+ viewModel.chartDataFlow.collectLatestWithLifecycle(viewLifecycleOwner) { chartData ->
+ chartDataUsagePreferenceController.update(chartData)
+ }
}
override fun getPreferenceScreenResId() = R.xml.data_usage_list
@@ -158,10 +168,6 @@
.getActiveSubscriptionInfo(subId) != null)
}
- private fun onCyclesLoad(networkUsageData: List<NetworkUsageData>) {
- dataUsageListAppsController.updateCycles(networkUsageData)
- }
-
/**
* Updates the chart and detail data when initial loaded or selected cycle changed.
*/
@@ -169,19 +175,11 @@
Log.d(TAG, "showing cycle $usageData")
usageAmount.title = usageData.getDataUsedString(requireContext())
+ viewModel.selectedCycleFlow.value = usageData
- updateChart(usageData)
updateApps(usageData)
}
- /** Updates chart to show selected cycle. */
- private fun updateChart(usageData: NetworkUsageData) {
- chartDataUsagePreferenceController.update(
- startTime = usageData.startTime,
- endTime = usageData.endTime,
- )
- }
-
/** Updates applications data usage. */
private fun updateApps(usageData: NetworkUsageData) {
dataUsageListAppsController.update(
diff --git a/src/com/android/settings/datausage/DataUsageListHeaderController.kt b/src/com/android/settings/datausage/DataUsageListHeaderController.kt
index ad76ede..ade891a 100644
--- a/src/com/android/settings/datausage/DataUsageListHeaderController.kt
+++ b/src/com/android/settings/datausage/DataUsageListHeaderController.kt
@@ -24,19 +24,13 @@
import android.widget.AdapterView
import android.widget.Spinner
import androidx.annotation.OpenForTesting
-import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
import com.android.settings.R
import com.android.settings.core.SubSettingLauncher
import com.android.settings.datausage.CycleAdapter.SpinnerInterface
-import com.android.settings.datausage.lib.INetworkCycleDataRepository
-import com.android.settings.datausage.lib.NetworkCycleDataRepository
import com.android.settings.datausage.lib.NetworkUsageData
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import kotlinx.coroutines.flow.Flow
@OpenForTesting
open class DataUsageListHeaderController(
@@ -44,10 +38,8 @@
template: NetworkTemplate,
sourceMetricsCategory: Int,
viewLifecycleOwner: LifecycleOwner,
- private val onCyclesLoad: (usageDataList: List<NetworkUsageData>) -> Unit,
+ cyclesFlow: Flow<List<NetworkUsageData>>,
private val updateSelectedCycle: (usageData: NetworkUsageData) -> Unit,
- private val repository: INetworkCycleDataRepository =
- NetworkCycleDataRepository(header.context, template),
) {
private val context = header.context
@@ -104,13 +96,9 @@
}
}
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- cycles = withContext(Dispatchers.Default) {
- repository.loadCycles()
- }
- updateCycleData()
- }
+ cyclesFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
+ cycles = it
+ updateCycleData()
}
}
@@ -121,7 +109,6 @@
private fun updateCycleData() {
cycleAdapter.updateCycleList(cycles.map { Range(it.startTime, it.endTime) })
cycleSpinner.visibility = View.VISIBLE
- onCyclesLoad(cycles)
}
private fun setSelectedCycle(position: Int) {
diff --git a/src/com/android/settings/datausage/DataUsageListViewModel.kt b/src/com/android/settings/datausage/DataUsageListViewModel.kt
new file mode 100644
index 0000000..7d7a1d6
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageListViewModel.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.Application
+import android.net.NetworkTemplate
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.settings.datausage.lib.NetworkCycleBucketRepository
+import com.android.settings.datausage.lib.NetworkStatsRepository
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.filterTime
+import com.android.settings.datausage.lib.NetworkUsageData
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.plus
+
+data class SelectedBuckets(
+ val selectedCycle: NetworkUsageData,
+ val buckets: List<Bucket>,
+)
+
+class DataUsageListViewModel(application: Application) : AndroidViewModel(application) {
+ private val scope = viewModelScope + Dispatchers.Default
+
+ val templateFlow = MutableStateFlow<NetworkTemplate?>(null)
+
+ private val bucketsFlow = templateFlow.filterNotNull().map { template ->
+ NetworkStatsRepository(getApplication(), template).queryDetailsForDevice()
+ }.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ val cyclesFlow = combine(templateFlow.filterNotNull(), bucketsFlow) { template, buckets ->
+ NetworkCycleBucketRepository(application, template, buckets).loadCycles()
+ }.flowOn(Dispatchers.Default)
+
+ val selectedCycleFlow = MutableStateFlow<NetworkUsageData?>(null)
+
+ private val selectedBucketsFlow =
+ combine(selectedCycleFlow.filterNotNull(), bucketsFlow) { selectedCycle, buckets ->
+ SelectedBuckets(
+ selectedCycle = selectedCycle,
+ buckets = buckets.filterTime(selectedCycle.startTime, selectedCycle.endTime),
+ )
+ }.flowOn(Dispatchers.Default)
+
+ val chartDataFlow =
+ combine(templateFlow.filterNotNull(), selectedBucketsFlow) { template, selectedBuckets ->
+ NetworkCycleBucketRepository(application, template, selectedBuckets.buckets)
+ .queryChartData(selectedBuckets.selectedCycle)
+ }.flowOn(Dispatchers.Default)
+}
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt b/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt
new file mode 100644
index 0000000..652919e0
--- /dev/null
+++ b/src/com/android/settings/datausage/lib/NetworkCycleBucketRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.lib
+
+import android.content.Context
+import android.net.NetworkTemplate
+import android.text.format.DateUtils
+import android.util.Range
+import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.bucketRange
+import com.android.settings.datausage.lib.NetworkCycleDataRepository.Companion.reverseBucketRange
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.aggregate
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.filterTime
+
+class NetworkCycleBucketRepository(
+ context: Context,
+ networkTemplate: NetworkTemplate,
+ private val buckets: List<Bucket>,
+ private val networkCycleDataRepository: NetworkCycleDataRepository =
+ NetworkCycleDataRepository(context, networkTemplate)
+) {
+
+ fun loadCycles(): List<NetworkUsageData> =
+ getCycles().map { aggregateUsage(it) }.filter { it.usage > 0 }
+
+ private fun getCycles(): List<Range<Long>> {
+ val policy = networkCycleDataRepository.getPolicy() ?: return queryCyclesAsFourWeeks()
+ return policy.cycleIterator().asSequence().map {
+ Range(it.lower.toInstant().toEpochMilli(), it.upper.toInstant().toEpochMilli())
+ }.toList()
+ }
+
+ private fun queryCyclesAsFourWeeks(): List<Range<Long>> {
+ val timeRange = buckets.aggregate()?.timeRange ?: return emptyList()
+ return reverseBucketRange(
+ startTime = timeRange.lower,
+ endTime = timeRange.upper,
+ step = DateUtils.WEEK_IN_MILLIS * 4,
+ )
+ }
+
+ fun queryChartData(usageData: NetworkUsageData) = NetworkCycleChartData(
+ total = usageData,
+ dailyUsage = bucketRange(
+ startTime = usageData.startTime,
+ endTime = usageData.endTime,
+ step = NetworkCycleChartData.BUCKET_DURATION.inWholeMilliseconds,
+ ).map { aggregateUsage(it) },
+ )
+
+ private fun aggregateUsage(range: Range<Long>) = NetworkUsageData(
+ startTime = range.lower,
+ endTime = range.upper,
+ usage = buckets.filterTime(range.lower, range.upper).aggregate()?.usage ?: 0,
+ )
+}
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt b/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt
index 4e73190..fd3c504 100644
--- a/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt
+++ b/src/com/android/settings/datausage/lib/NetworkCycleChartData.kt
@@ -26,11 +26,6 @@
val dailyUsage: List<NetworkUsageData>,
) {
companion object {
- val AllZero = NetworkCycleChartData(
- total = NetworkUsageData.AllZero,
- dailyUsage = emptyList(),
- )
-
val BUCKET_DURATION = 1.days
}
}
diff --git a/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt b/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
index 4256130..cde64df 100644
--- a/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
+++ b/src/com/android/settings/datausage/lib/NetworkCycleDataRepository.kt
@@ -23,13 +23,10 @@
import android.text.format.DateUtils
import android.util.Range
import com.android.settingslib.NetworkPolicyEditor
-import com.android.settingslib.spa.framework.util.asyncMap
interface INetworkCycleDataRepository {
- suspend fun loadCycles(): List<NetworkUsageData>
fun getCycles(): List<Range<Long>>
fun getPolicy(): NetworkPolicy?
- suspend fun queryChartData(startTime: Long, endTime: Long): NetworkCycleChartData?
}
class NetworkCycleDataRepository(
@@ -41,9 +38,6 @@
private val policyManager = context.getSystemService(NetworkPolicyManager::class.java)!!
- override suspend fun loadCycles(): List<NetworkUsageData> =
- getCycles().queryUsage().filter { it.usage > 0 }
-
fun loadFirstCycle(): NetworkUsageData? = getCycles().firstOrNull()?.let { queryUsage(it) }
override fun getCycles(): List<Range<Long>> {
@@ -68,23 +62,6 @@
getPolicy(networkTemplate)
}
- override suspend fun queryChartData(startTime: Long, endTime: Long): NetworkCycleChartData? {
- val usage = networkStatsRepository.querySummaryForDevice(startTime, endTime)
- if (usage > 0L) {
- return NetworkCycleChartData(
- total = NetworkUsageData(startTime, endTime, usage),
- dailyUsage = bucketRange(
- startTime = startTime,
- endTime = endTime,
- step = NetworkCycleChartData.BUCKET_DURATION.inWholeMilliseconds,
- ).queryUsage(),
- )
- }
- return null
- }
-
- private suspend fun List<Range<Long>>.queryUsage(): List<NetworkUsageData> =
- asyncMap { queryUsage(it) }
fun queryUsage(range: Range<Long>) = NetworkUsageData(
startTime = range.lower,
@@ -92,10 +69,12 @@
usage = networkStatsRepository.querySummaryForDevice(range.lower, range.upper),
)
- private fun bucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
- (startTime..endTime step step).zipWithNext(::Range)
+ companion object {
+ fun bucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
+ (startTime..endTime step step).zipWithNext(::Range)
- private fun reverseBucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
- (endTime downTo (startTime - step + 1) step step)
- .zipWithNext { bucketEnd, bucketStart -> Range(bucketStart, bucketEnd) }
+ fun reverseBucketRange(startTime: Long, endTime: Long, step: Long): List<Range<Long>> =
+ (endTime downTo (startTime - step + 1) step step)
+ .zipWithNext { bucketEnd, bucketStart -> Range(bucketStart, bucketEnd) }
+ }
}
diff --git a/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt b/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
index f2e18f2..56b1931 100644
--- a/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
+++ b/src/com/android/settings/datausage/lib/NetworkStatsRepository.kt
@@ -33,20 +33,22 @@
): NetworkUsageData? = try {
networkStatsManager.queryDetailsForUidTagState(
template, range.lower, range.upper, uid, NetworkStats.Bucket.TAG_NONE, state,
- ).aggregate()
+ ).convertToBuckets().aggregate()
} catch (e: Exception) {
Log.e(TAG, "Exception queryDetailsForUidTagState", e)
null
}
- fun getTimeRange(): Range<Long>? = try {
+ fun queryDetailsForDevice(): List<Bucket> = try {
networkStatsManager.queryDetailsForDevice(template, Long.MIN_VALUE, Long.MAX_VALUE)
- .aggregate()?.timeRange
+ .convertToBuckets()
} catch (e: Exception) {
Log.e(TAG, "Exception queryDetailsForDevice", e)
- null
+ emptyList()
}
+ fun getTimeRange(): Range<Long>? = queryDetailsForDevice().aggregate()?.timeRange
+
fun querySummaryForDevice(startTime: Long, endTime: Long): Long = try {
networkStatsManager.querySummaryForDevice(template, startTime, endTime).bytes
} catch (e: Exception) {
@@ -70,33 +72,38 @@
val uid: Int,
val bytes: Long,
val state: Int = NetworkStats.Bucket.STATE_ALL,
+ val startTimeStamp: Long,
+ val endTimeStamp: Long,
)
+ fun List<Bucket>.aggregate(): NetworkUsageData? = when {
+ isEmpty() -> null
+ else -> NetworkUsageData(
+ startTime = minOf { it.startTimeStamp },
+ endTime = maxOf { it.endTimeStamp },
+ usage = sumOf { it.bytes },
+ )
+ }
+
+ fun List<Bucket>.filterTime(startTime: Long, endTime: Long): List<Bucket> = filter {
+ it.startTimeStamp >= startTime && it.endTimeStamp <= endTime
+ }
+
private fun NetworkStats.convertToBuckets(): List<Bucket> = use {
val buckets = mutableListOf<Bucket>()
val bucket = NetworkStats.Bucket()
while (getNextBucket(bucket)) {
- buckets += Bucket(uid = bucket.uid, bytes = bucket.bytes, state = bucket.state)
+ buckets += Bucket(
+ uid = bucket.uid,
+ bytes = bucket.bytes,
+ state = bucket.state,
+ startTimeStamp = bucket.startTimeStamp,
+ endTimeStamp = bucket.endTimeStamp,
+ )
}
buckets
}
- private fun NetworkStats.aggregate(): NetworkUsageData? = use {
- var startTime = Long.MAX_VALUE
- var endTime = Long.MIN_VALUE
- var usage = 0L
- val bucket = NetworkStats.Bucket()
- while (getNextBucket(bucket)) {
- startTime = startTime.coerceAtMost(bucket.startTimeStamp)
- endTime = endTime.coerceAtLeast(bucket.endTimeStamp)
- usage += bucket.bytes
- }
- when {
- startTime > endTime -> null
- else -> NetworkUsageData(startTime, endTime, usage)
- }
- }
-
private val NetworkStats.Bucket.bytes: Long
get() = rxBytes + txBytes
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index 403263c..c0fa313 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -63,7 +63,7 @@
@Composable
private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
- if (featureFlags.archiving()) {
+ if (isArchivingEnabled(featureFlags)) {
if (app.isArchived) {
appRestoreButton.getActionButton(app)
} else {
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 85e59de..9291892 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -125,7 +125,7 @@
title = stringResource(R.string.application_info_label),
actions = {
packageInfoState.value?.applicationInfo?.let { app ->
- if (featureFlags.archiving()) TopBarAppLaunchButton(packageInfoPresenter, app)
+ if (isArchivingEnabled(featureFlags)) TopBarAppLaunchButton(packageInfoPresenter, app)
AppInfoSettingsMoreOptions(packageInfoPresenter, app)
}
}
@@ -174,3 +174,6 @@
appInfoProvider.FooterAppVersion()
}
}
+
+fun isArchivingEnabled(featureFlags: FeatureFlags) =
+ featureFlags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index 8c802d1..230ccb9 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -166,7 +166,7 @@
flags = PackageManager.MATCH_ANY_USER.toLong() or
PackageManager.MATCH_DISABLED_COMPONENTS.toLong() or
PackageManager.GET_PERMISSIONS.toLong() or
- if (featureFlags.archiving()) PackageManager.MATCH_ARCHIVED_PACKAGES else 0,
+ if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0,
userId = userId,
)
}
diff --git a/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
index ff4ae41..05855e4 100644
--- a/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
+++ b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
@@ -22,11 +22,14 @@
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.util.Log;
+import android.webkit.IWebViewUpdateService;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+
import java.util.ArrayList;
import java.util.List;
@@ -104,5 +107,24 @@
toast.show();
}
+ /**
+ * Fetch the package name of the default WebView provider.
+ */
+ @Nullable
+ public String getDefaultWebViewPackageName() {
+ try {
+ IWebViewUpdateService service = WebViewFactory.getUpdateService();
+ if (service != null) {
+ WebViewProviderInfo provider = service.getDefaultWebViewPackage();
+ if (provider != null) {
+ return provider.packageName;
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when trying to fetch default WebView package Name", e);
+ }
+ return null;
+ }
+
static final int PACKAGE_FLAGS = PackageManager.MATCH_ANY_USER;
}
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 95a78c7..d313878 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -57,6 +57,7 @@
"Settings_robolectric_meta_service_file",
"SettingsLib-robo-testutils",
"Settings-robo-testutils",
+ "android.webkit.flags-aconfig-java",
"androidx.test.core",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
diff --git a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
index 6bed1bc..bac0de9 100644
--- a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
@@ -37,13 +37,20 @@
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.webkit.Flags;
import com.android.settings.testutils.ApplicationTestUtils;
+import com.android.settings.webview.WebViewUpdateServiceWrapper;
import com.android.settingslib.testutils.shadow.ShadowDefaultDialerManager;
import com.android.settingslib.testutils.shadow.ShadowSmsApplication;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -67,6 +74,9 @@
@LooperMode(LooperMode.Mode.LEGACY)
public final class ApplicationFeatureProviderImplTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private final int MAIN_USER_ID = 0;
private final int MANAGED_PROFILE_ID = 10;
@@ -91,6 +101,8 @@
private DevicePolicyManager mDevicePolicyManager;
@Mock
private LocationManager mLocationManager;
+ @Mock
+ private WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
private ApplicationFeatureProvider mProvider;
@@ -106,7 +118,7 @@
when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
mProvider = new ApplicationFeatureProviderImpl(mContext, mPackageManager,
- mPackageManagerService, mDevicePolicyManager);
+ mPackageManagerService, mDevicePolicyManager, mWebViewUpdateServiceWrapper);
}
private void verifyCalculateNumberOfPolicyInstalledApps(boolean async) {
@@ -342,6 +354,26 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_UPDATE_SERVICE_V2)
+ public void getKeepEnabledPackages_shouldContainWebViewPackage() {
+ final String testWebViewPackageName = "com.android.webview";
+ when(mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName())
+ .thenReturn(testWebViewPackageName);
+ final Set<String> allowlist = mProvider.getKeepEnabledPackages();
+ assertThat(allowlist).contains(testWebViewPackageName);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_UPDATE_SERVICE_V2)
+ public void getKeepEnabledPackages_shouldNotContainWebViewPackageIfFlagDisabled() {
+ final String testWebViewPackageName = "com.android.webview";
+ when(mWebViewUpdateServiceWrapper.getDefaultWebViewPackageName())
+ .thenReturn(testWebViewPackageName);
+ final Set<String> allowlist = mProvider.getKeepEnabledPackages();
+ assertThat(allowlist).doesNotContain(testWebViewPackageName);
+ }
+
+ @Test
@Config(shadows = {ShadowSmsApplication.class, ShadowDefaultDialerManager.class})
public void getKeepEnabledPackages_shouldContainPackageInstaller() {
final String testDialer = "com.android.test.defaultdialer";
diff --git a/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt
index ae09ef9..f061fe1 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/ChartDataUsagePreferenceControllerTest.kt
@@ -17,16 +17,12 @@
package com.android.settings.datausage
import android.content.Context
-import android.util.Range
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.preference.PreferenceScreen
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settings.datausage.lib.INetworkCycleDataRepository
import com.android.settings.datausage.lib.NetworkCycleChartData
import com.android.settings.datausage.lib.NetworkUsageData
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,17 +34,6 @@
class ChartDataUsagePreferenceControllerTest {
private val context: Context = ApplicationProvider.getApplicationContext()
- private val repository = object : INetworkCycleDataRepository {
- override suspend fun loadCycles() = emptyList<NetworkUsageData>()
- override fun getCycles() = emptyList<Range<Long>>()
- override fun getPolicy() = null
-
- override suspend fun queryChartData(startTime: Long, endTime: Long) = when {
- startTime == START_TIME && endTime == END_TIME -> CycleChartDate
- else -> null
- }
- }
-
private val preference = mock<ChartDataUsagePreference>()
private val preferenceScreen = mock<PreferenceScreen> {
onGeneric { findPreference(KEY) } doReturn preference
@@ -58,15 +43,13 @@
@Before
fun setUp() {
- controller.init(repository)
controller.displayPreference(preferenceScreen)
controller.onViewCreated(TestLifecycleOwner())
}
@Test
- fun update() = runBlocking {
- controller.update(START_TIME, END_TIME)
- delay(100L)
+ fun update() {
+ controller.update(CycleChartDate)
verify(preference).setTime(START_TIME, END_TIME)
verify(preference).setNetworkCycleData(CycleChartDate)
diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt
index 6d5be6b..8c4f315 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/DataUsageListHeaderControllerTest.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.net.NetworkTemplate
-import android.util.Range
import android.view.LayoutInflater
import android.view.View
import android.widget.Spinner
@@ -27,10 +26,9 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
-import com.android.settings.datausage.lib.INetworkCycleDataRepository
-import com.android.settings.datausage.lib.NetworkUsageData
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,13 +45,6 @@
doNothing().whenever(mock).startActivity(any())
}
- private val repository = object : INetworkCycleDataRepository {
- override suspend fun loadCycles() = emptyList<NetworkUsageData>()
- override fun getCycles() = emptyList<Range<Long>>()
- override fun getPolicy() = null
- override suspend fun queryChartData(startTime: Long, endTime: Long) = null
- }
-
private val header =
LayoutInflater.from(context).inflate(R.layout.apps_filter_spinner, null, false)
@@ -68,9 +59,8 @@
template = mock<NetworkTemplate>(),
sourceMetricsCategory = 0,
viewLifecycleOwner = testLifecycleOwner,
- onCyclesLoad = {},
+ cyclesFlow = flowOf(emptyList()),
updateSelectedCycle = {},
- repository = repository,
)
@Test
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt
index 85431a4..fda3dc9 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/AppDataUsageDetailsRepositoryTest.kt
@@ -49,13 +49,17 @@
on { queryBuckets(CYCLE1_START_TIME, CYCLE1_END_TIME) } doReturn listOf(
Bucket(
uid = UID,
- state = NetworkStats.Bucket.STATE_DEFAULT,
bytes = BACKGROUND_USAGE,
+ state = NetworkStats.Bucket.STATE_DEFAULT,
+ startTimeStamp = 0L,
+ endTimeStamp = 0L,
),
Bucket(
uid = UID,
- state = NetworkStats.Bucket.STATE_FOREGROUND,
bytes = FOREGROUND_USAGE,
+ state = NetworkStats.Bucket.STATE_FOREGROUND,
+ startTimeStamp = 0L,
+ endTimeStamp = 0L,
),
)
}
@@ -86,13 +90,17 @@
on { queryBuckets(CYCLE1_END_TIME, CYCLE2_END_TIME) } doReturn listOf(
Bucket(
uid = UID,
- state = NetworkStats.Bucket.STATE_DEFAULT,
bytes = BACKGROUND_USAGE,
+ state = NetworkStats.Bucket.STATE_DEFAULT,
+ startTimeStamp = 0L,
+ endTimeStamp = 0L,
),
Bucket(
uid = UID,
- state = NetworkStats.Bucket.STATE_FOREGROUND,
bytes = FOREGROUND_USAGE,
+ state = NetworkStats.Bucket.STATE_FOREGROUND,
+ startTimeStamp = 0L,
+ endTimeStamp = 0L,
),
)
}
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 f2bf524..3f517a9 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
@@ -77,8 +77,8 @@
getPackageName = { null },
)
val buckets = listOf(
- Bucket(uid = APP_ID_1, bytes = 1),
- Bucket(uid = APP_ID_2, bytes = 2),
+ Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
+ Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
)
val appPercentList = repository.getAppPercent(null, buckets)
@@ -107,8 +107,8 @@
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),
+ Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
+ Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
)
val appPercentList = repository.getAppPercent(HIDING_CARRIER_ID, buckets)
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleBucketRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleBucketRepositoryTest.kt
new file mode 100644
index 0000000..f83b85f
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleBucketRepositoryTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.lib
+
+import android.content.Context
+import android.net.NetworkPolicy
+import android.net.NetworkTemplate
+import android.text.format.DateUtils
+import android.util.Range
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
+import com.google.common.truth.Truth.assertThat
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class NetworkCycleBucketRepositoryTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val template = mock<NetworkTemplate>()
+
+ private val mockNetworkCycleDataRepository = mock<NetworkCycleDataRepository>()
+
+ @Test
+ fun loadCycles_byPolicy() {
+ val policy = mock<NetworkPolicy> {
+ on { cycleIterator() } doReturn listOf(
+ Range(zonedDateTime(CYCLE1_START_TIME), zonedDateTime(CYCLE1_END_TIME)),
+ ).iterator()
+ }
+ mockNetworkCycleDataRepository.stub {
+ on { getPolicy() } doReturn policy
+ }
+ val repository = NetworkCycleBucketRepository(
+ context = context,
+ networkTemplate = template,
+ buckets = listOf(
+ Bucket(
+ uid = 0,
+ bytes = CYCLE1_BYTES,
+ startTimeStamp = CYCLE1_START_TIME,
+ endTimeStamp = CYCLE1_END_TIME,
+ )
+ ),
+ networkCycleDataRepository = mockNetworkCycleDataRepository,
+ )
+
+ val cycles = repository.loadCycles()
+
+ assertThat(cycles).containsExactly(
+ NetworkUsageData(
+ startTime = CYCLE1_START_TIME,
+ endTime = CYCLE1_END_TIME,
+ usage = CYCLE1_BYTES,
+ ),
+ )
+ }
+
+ @Test
+ fun loadCycles_asFourWeeks() {
+ mockNetworkCycleDataRepository.stub {
+ on { getPolicy() } doReturn null
+ }
+ val repository = NetworkCycleBucketRepository(
+ context = context,
+ networkTemplate = template,
+ buckets = listOf(
+ Bucket(
+ uid = 0,
+ bytes = CYCLE2_BYTES,
+ startTimeStamp = CYCLE2_START_TIME,
+ endTimeStamp = CYCLE2_END_TIME,
+ )
+ ),
+ networkCycleDataRepository = mockNetworkCycleDataRepository,
+ )
+
+ val cycles = repository.loadCycles()
+
+ assertThat(cycles).containsExactly(
+ NetworkUsageData(
+ startTime = CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4,
+ endTime = CYCLE2_END_TIME,
+ usage = CYCLE2_BYTES,
+ ),
+ )
+ }
+
+ @Test
+ fun queryChartData() {
+ val cycle = NetworkUsageData(
+ startTime = CYCLE3_START_TIME,
+ endTime = CYCLE4_END_TIME,
+ usage = CYCLE3_BYTES + CYCLE4_BYTES,
+ )
+ val repository = NetworkCycleBucketRepository(
+ context = context,
+ networkTemplate = template,
+ buckets = listOf(
+ Bucket(
+ uid = 0,
+ bytes = CYCLE3_BYTES,
+ startTimeStamp = CYCLE3_START_TIME,
+ endTimeStamp = CYCLE3_END_TIME,
+ ),
+ Bucket(
+ uid = 0,
+ bytes = CYCLE4_BYTES,
+ startTimeStamp = CYCLE4_START_TIME,
+ endTimeStamp = CYCLE4_END_TIME,
+ ),
+ ),
+ networkCycleDataRepository = mockNetworkCycleDataRepository,
+ )
+
+ val summary = repository.queryChartData(cycle)
+
+ assertThat(summary).isEqualTo(
+ NetworkCycleChartData(
+ total = cycle,
+ dailyUsage = listOf(
+ NetworkUsageData(
+ startTime = CYCLE3_START_TIME,
+ endTime = CYCLE3_END_TIME,
+ usage = CYCLE3_BYTES,
+ ),
+ NetworkUsageData(
+ startTime = CYCLE4_START_TIME,
+ endTime = CYCLE4_END_TIME,
+ usage = CYCLE4_BYTES,
+ ),
+ ),
+ )
+ )
+ }
+
+ private fun zonedDateTime(epochMilli: Long): ZonedDateTime? =
+ ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault())
+
+ private companion object {
+ const val CYCLE1_START_TIME = 1L
+ const val CYCLE1_END_TIME = 2L
+ const val CYCLE1_BYTES = 11L
+
+ const val CYCLE2_START_TIME = 1695555555000L
+ const val CYCLE2_END_TIME = 1695566666000L
+ const val CYCLE2_BYTES = 22L
+
+ const val CYCLE3_START_TIME = 1695555555000L
+ const val CYCLE3_END_TIME = CYCLE3_START_TIME + DateUtils.DAY_IN_MILLIS
+ const val CYCLE3_BYTES = 33L
+
+ const val CYCLE4_START_TIME = CYCLE3_END_TIME
+ const val CYCLE4_END_TIME = CYCLE4_START_TIME + DateUtils.DAY_IN_MILLIS
+ const val CYCLE4_BYTES = 44L
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt
index 5678503..f0a5309 100644
--- a/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/datausage/lib/NetworkCycleDataRepositoryTest.kt
@@ -63,7 +63,7 @@
spy(NetworkCycleDataRepository(context, template, mockNetworkStatsRepository))
@Test
- fun loadCycles_byPolicy() = runTest {
+ fun loadFirstCycle_byPolicy() = runTest {
val policy = mock<NetworkPolicy> {
on { cycleIterator() } doReturn listOf(
Range(zonedDateTime(CYCLE1_START_TIME), zonedDateTime(CYCLE1_END_TIME))
@@ -71,23 +71,23 @@
}
doReturn(policy).whenever(repository).getPolicy()
- val cycles = repository.loadCycles()
+ val firstCycle = repository.loadFirstCycle()
- assertThat(cycles).containsExactly(
+ assertThat(firstCycle).isEqualTo(
NetworkUsageData(startTime = 1, endTime = 2, usage = CYCLE1_BYTES),
)
}
@Test
- fun loadCycles_asFourWeeks() = runTest {
+ fun loadFirstCycle_asFourWeeks() = runTest {
doReturn(null).whenever(repository).getPolicy()
mockNetworkStatsRepository.stub {
on { getTimeRange() } doReturn Range(CYCLE2_START_TIME, CYCLE2_END_TIME)
}
- val cycles = repository.loadCycles()
+ val firstCycle = repository.loadFirstCycle()
- assertThat(cycles).containsExactly(
+ assertThat(firstCycle).isEqualTo(
NetworkUsageData(
startTime = CYCLE2_END_TIME - DateUtils.WEEK_IN_MILLIS * 4,
endTime = CYCLE2_END_TIME,
@@ -96,33 +96,6 @@
)
}
- @Test
- fun querySummary() = runTest {
- val summary = repository.queryChartData(CYCLE3_START_TIME, CYCLE4_END_TIME)
-
- assertThat(summary).isEqualTo(
- NetworkCycleChartData(
- total = NetworkUsageData(
- startTime = CYCLE3_START_TIME,
- endTime = CYCLE4_END_TIME,
- usage = CYCLE3_BYTES + CYCLE4_BYTES,
- ),
- dailyUsage = listOf(
- NetworkUsageData(
- startTime = CYCLE3_START_TIME,
- endTime = CYCLE3_END_TIME,
- usage = CYCLE3_BYTES,
- ),
- NetworkUsageData(
- startTime = CYCLE4_START_TIME,
- endTime = CYCLE4_END_TIME,
- usage = CYCLE4_BYTES,
- ),
- ),
- )
- )
- }
-
private fun zonedDateTime(epochMilli: Long): ZonedDateTime? =
ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault())