Merge "Reorganize intents to have the same order" into qt-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7e2ec7a..4944e33 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -52,6 +52,7 @@
<uses-permission android:name="android.permission.REBOOT"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+ <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"/>
<uses-permission android:name="android.permission.SET_TIME"/>
<uses-permission android:name="android.permission.SET_TIME_ZONE"/>
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index 8f2e4ca..a523478 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -65,6 +65,8 @@
<string name="pk_data_limit_group" translatable="false">data_limit_group</string>
<string name="pk_data_set_limit" translatable="false">data_set_limit</string>
<string name="pk_data_limit" translatable="false">data_limit</string>
+ <string name="pk_app_data_usage" translatable="false">app_data_usage</string>
+ <string name="pk_app_data_usage_detail" translatable="false">app_data_usage_detail</string>
<string name="pk_wifi_tether_settings_entry" translatable="false">wifi_tether_settings_entry
</string>
<string name="pk_wifi_tether_auto_off" translatable="false">wifi_tether_auto_off</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cd9589b..0014141 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -123,6 +123,10 @@
<string name="no_carrier_update_now_text">Updated just now</string>
<!-- Button to launch external data plan app [CHAR LIMIT=30] -->
<string name="launch_manage_plan_text">View plan</string>
+ <!-- Title for mobile data preference, to display the mobile data usage for each app. [CHAR LIMIT=30] -->
+ <string name="app_data_usage">App data usage</string>
+ <!-- Label for application which has its data usage restricted. [CHAR LIMIT=16] -->
+ <string name="data_usage_app_restricted">restricted</string>
<!-- Title of dialog for editing data usage cycle reset date. [CHAR LIMIT=48] -->
<string name="cycle_reset_day_of_month_picker_title">Usage cycle reset date</string>
diff --git a/res/xml/app_data_usage_fragment.xml b/res/xml/app_data_usage_fragment.xml
new file mode 100644
index 0000000..647b7d4
--- /dev/null
+++ b/res/xml/app_data_usage_fragment.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/app_data_usage">
+ <com.android.car.settings.common.LogicalPreferenceGroup
+ android:key="@string/pk_app_data_usage_detail"
+ settings:controller="com.android.car.settings.datausage.AppDataUsagePreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/app_storage_settings_details_fragment.xml b/res/xml/app_storage_settings_details_fragment.xml
index b9eeb41..00e0627 100644
--- a/res/xml/app_storage_settings_details_fragment.xml
+++ b/res/xml/app_storage_settings_details_fragment.xml
@@ -19,7 +19,7 @@
android:title="@string/storage_settings_title">
<Preference
android:key="@string/pk_storage_application_details"
- settings:controller="com.android.car.settings.storage.StorageApplicationPreferenceController"/>
+ settings:controller="com.android.car.settings.applications.ApplicationWithVersionPreferenceController"/>
<com.android.car.settings.storage.StorageAppDetailPreference
android:key="@string/pk_storage_application_size"
android:selectable="false"
diff --git a/res/xml/data_usage_fragment.xml b/res/xml/data_usage_fragment.xml
index 46bf580..fcb5590 100644
--- a/res/xml/data_usage_fragment.xml
+++ b/res/xml/data_usage_fragment.xml
@@ -24,6 +24,10 @@
settings:controller="com.android.car.settings.datausage.DataUsageSummaryPreferenceController"
settings:singleLineTitle="false"/>
<Preference
+ android:key="@string/pk_app_data_usage"
+ android:title="@string/app_data_usage"
+ settings:controller="com.android.car.settings.datausage.DataUsagePreferenceController"/>
+ <Preference
android:fragment="com.android.car.settings.datausage.DataWarningAndLimitFragment"
android:key="@string/pk_data_warning_and_limit"
android:title="@string/data_warning_limit_title"
diff --git a/src/com/android/car/settings/storage/StorageApplicationPreferenceController.java b/src/com/android/car/settings/applications/ApplicationWithVersionPreferenceController.java
similarity index 75%
rename from src/com/android/car/settings/storage/StorageApplicationPreferenceController.java
rename to src/com/android/car/settings/applications/ApplicationWithVersionPreferenceController.java
index ed55031..d8c1fcf 100644
--- a/src/com/android/car/settings/storage/StorageApplicationPreferenceController.java
+++ b/src/com/android/car/settings/applications/ApplicationWithVersionPreferenceController.java
@@ -14,23 +14,19 @@
* limitations under the License.
*/
-package com.android.car.settings.storage;
+package com.android.car.settings.applications;
import android.car.drivingstate.CarUxRestrictions;
import android.content.Context;
import androidx.preference.Preference;
-import com.android.car.settings.applications.ApplicationPreferenceController;
import com.android.car.settings.common.FragmentController;
-/**
- * Preference showing the application icon, title and version on the storage size application
- * details page.
- */
-public class StorageApplicationPreferenceController extends ApplicationPreferenceController {
+/** Preference showing the application icon, title, and version. */
+public class ApplicationWithVersionPreferenceController extends ApplicationPreferenceController {
- public StorageApplicationPreferenceController(Context context,
+ public ApplicationWithVersionPreferenceController(Context context,
String preferenceKey, FragmentController fragmentController,
CarUxRestrictions uxRestrictions) {
super(context, preferenceKey, fragmentController, uxRestrictions);
diff --git a/src/com/android/car/settings/datausage/AppDataUsageFragment.java b/src/com/android/car/settings/datausage/AppDataUsageFragment.java
new file mode 100644
index 0000000..b613e68
--- /dev/null
+++ b/src/com/android/car/settings/datausage/AppDataUsageFragment.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 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.car.settings.datausage;
+
+import android.content.Context;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.util.Pair;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.XmlRes;
+import androidx.loader.app.LoaderManager;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.SettingsFragment;
+import com.android.settingslib.NetworkPolicyEditor;
+
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+
+/**
+ * Screen to display list of applications using the data.
+ */
+public class AppDataUsageFragment extends SettingsFragment {
+
+ private static final Logger LOG = new Logger(AppDataUsageFragment.class);
+
+ private static final String ARG_NETWORK_SUB_ID = "network_sub_id";
+ /** Value to represent that the subscription id hasn't been computed yet. */
+ private static final int SUB_ID_NULL = Integer.MIN_VALUE;
+
+ private AppsNetworkStatsManager mAppsNetworkStatsManager;
+ private NetworkPolicyEditor mPolicyEditor;
+ private NetworkTemplate mNetworkTemplate;
+
+ private Bundle mBundle;
+
+ /**
+ * Creates a new instance of the {@link AppDataUsageFragment}, which shows settings related to
+ * the given {@code subId}.
+ */
+ public static AppDataUsageFragment newInstance(int subId) {
+ AppDataUsageFragment fragment = new AppDataUsageFragment();
+ Bundle args = new Bundle();
+ args.putInt(ARG_NETWORK_SUB_ID, subId);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ @XmlRes
+ protected int getPreferenceScreenResId() {
+ return R.xml.app_data_usage_fragment;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+ int subId = getArguments() != null
+ ? getArguments().getInt(ARG_NETWORK_SUB_ID, SUB_ID_NULL) : SUB_ID_NULL;
+ if (subId == SUB_ID_NULL) {
+ LOG.d("Cannot get the subscription id from arguments. Switching to default "
+ + "subscription Id: " + subId);
+ SubscriptionManager subscriptionManager = context.getSystemService(
+ SubscriptionManager.class);
+ subId = DataUsageUtils.getDefaultSubscriptionId(subscriptionManager);
+ }
+ mNetworkTemplate = DataUsageUtils.getMobileNetworkTemplate(telephonyManager, subId);
+ mPolicyEditor = new NetworkPolicyEditor(NetworkPolicyManager.from(context));
+ mAppsNetworkStatsManager = new AppsNetworkStatsManager(getContext());
+ mAppsNetworkStatsManager.registerListener(
+ use(AppDataUsagePreferenceController.class, R.string.pk_app_data_usage_detail));
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle bundle = getBundleForNetworkStats();
+
+ LoaderManager loaderManager = LoaderManager.getInstance(this);
+ mAppsNetworkStatsManager.startLoading(loaderManager, bundle);
+ }
+
+ @VisibleForTesting
+ Bundle getBundleForNetworkStats() {
+ long historyStart = System.currentTimeMillis();
+ long historyEnd = historyStart + 1;
+
+ long start = 0;
+ long end = 0;
+
+ boolean hasCycles = false;
+
+ NetworkPolicy policy = mPolicyEditor.getPolicy(mNetworkTemplate);
+ if (policy != null) {
+ Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = NetworkPolicyManager
+ .cycleIterator(policy);
+ while (it.hasNext()) {
+ Pair<ZonedDateTime, ZonedDateTime> cycle = it.next();
+ start = cycle.first.toInstant().toEpochMilli();
+ end = cycle.second.toInstant().toEpochMilli();
+ hasCycles = true;
+ }
+ }
+
+ if (!hasCycles) {
+ // no policy defined cycles; show entry for each four-week period
+ long cycleEnd = historyEnd;
+ while (cycleEnd > historyStart) {
+ long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
+
+ start = cycleStart;
+ end = cycleEnd;
+ cycleEnd = cycleStart;
+ }
+ }
+
+ return SummaryForAllUidLoader.buildArgs(mNetworkTemplate, start, end);
+ }
+}
diff --git a/src/com/android/car/settings/datausage/AppDataUsagePreferenceController.java b/src/com/android/car/settings/datausage/AppDataUsagePreferenceController.java
new file mode 100644
index 0000000..577a602
--- /dev/null
+++ b/src/com/android/car/settings/datausage/AppDataUsagePreferenceController.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2019 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.car.settings.datausage;
+
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.net.NetworkStats;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+import com.android.car.settings.common.ProgressBarPreference;
+import com.android.settingslib.AppItem;
+import com.android.settingslib.net.UidDetail;
+import com.android.settingslib.net.UidDetailProvider;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+/**
+ * Controller that adds all the applications using the data sorted by the amount of data used. The
+ * first application that used most amount of data will be at the top with progress 100 percentage.
+ * All other progress are calculated relatively.
+ */
+public class AppDataUsagePreferenceController extends
+ PreferenceController<PreferenceGroup> implements AppsNetworkStatsManager.Callback {
+
+ private final UidDetailProvider mUidDetailProvider;
+ private final CarUserManagerHelper mCarUserManagerHelper;
+
+ public AppDataUsagePreferenceController(Context context, String preferenceKey,
+ FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+ super(context, preferenceKey, fragmentController, uxRestrictions);
+ mUidDetailProvider = new UidDetailProvider(getContext());
+ mCarUserManagerHelper = new CarUserManagerHelper(getContext());
+ }
+
+ @Override
+ protected Class<PreferenceGroup> getPreferenceType() {
+ return PreferenceGroup.class;
+ }
+
+ @Override
+ public void onDataLoaded(@Nullable NetworkStats stats, @Nullable int[] restrictedUids) {
+ List<AppItem> items = new ArrayList<>();
+ long largest = 0;
+
+ List<UserInfo> profiles = mCarUserManagerHelper.getAllUsers();
+ SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
+
+ NetworkStats.Entry entry = null;
+ if (stats != null) {
+ for (int i = 0; i < stats.size(); i++) {
+ entry = stats.getValues(i, entry);
+ long size = aggregateDataUsage(knownItems, items, entry, profiles);
+ largest = Math.max(size, largest);
+ }
+ }
+
+ updateRestrictedState(restrictedUids, knownItems, items, profiles);
+ sortAndAddPreferences(items, largest);
+ }
+
+ private long aggregateDataUsage(SparseArray<AppItem> knownItems, List<AppItem> items,
+ NetworkStats.Entry entry, List<UserInfo> profiles) {
+ int currentUserId = mCarUserManagerHelper.getCurrentProcessUserId();
+
+ // Decide how to collapse items together.
+ int uid = entry.uid;
+
+ int collapseKey;
+ int category;
+ int userId = UserHandle.getUserId(uid);
+
+ if (isUidValid(uid)) {
+ collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
+ return accumulate(collapseKey, knownItems, entry, category, items);
+ }
+
+ if (!UserHandle.isApp(uid)) {
+ collapseKey = android.os.Process.SYSTEM_UID;
+ category = AppItem.CATEGORY_APP;
+ return accumulate(collapseKey, knownItems, entry, category, items);
+ }
+
+ if (profileContainsUserId(profiles, userId) && userId == currentUserId) {
+ // Add to app item.
+ collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
+ return accumulate(collapseKey, knownItems, entry, category, items);
+ }
+
+ if (profileContainsUserId(profiles, userId) && userId != currentUserId) {
+ // Add to a managed user item.
+ int managedKey = UidDetailProvider.buildKeyForUser(userId);
+ long usersLargest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER,
+ items);
+ collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
+ long appLargest = accumulate(collapseKey, knownItems, entry, category, items);
+ return Math.max(usersLargest, appLargest);
+ }
+
+ // If it is a removed user add it to the removed users' key.
+ Optional<UserInfo> info = profiles.stream().filter(
+ userInfo -> userInfo.id == userId).findFirst();
+ if (!info.isPresent()) {
+ collapseKey = UID_REMOVED;
+ category = AppItem.CATEGORY_APP;
+ } else {
+ // Add to other user item.
+ collapseKey = UidDetailProvider.buildKeyForUser(userId);
+ category = AppItem.CATEGORY_USER;
+ }
+
+ return accumulate(collapseKey, knownItems, entry, category, items);
+ }
+
+ /**
+ * UID does not belong to a regular app and maybe belongs to a removed application or
+ * application using for tethering traffic.
+ */
+ private boolean isUidValid(int uid) {
+ return !UserHandle.isApp(uid) && (uid == UID_REMOVED || uid == UID_TETHERING);
+ }
+
+ private boolean profileContainsUserId(List<UserInfo> profiles, int userId) {
+ return profiles.stream().anyMatch(userInfo -> userInfo.id == userId);
+ }
+
+ private void updateRestrictedState(@Nullable int[] restrictedUids,
+ SparseArray<AppItem> knownItems, List<AppItem> items, List<UserInfo> profiles) {
+ if (restrictedUids == null) {
+ return;
+ }
+
+ for (int i = 0; i < restrictedUids.length; ++i) {
+ int uid = restrictedUids[i];
+ // Only splice in restricted state for current user or managed users.
+ if (!profileContainsUserId(profiles, uid)) {
+ continue;
+ }
+
+ AppItem item = knownItems.get(uid);
+ if (item == null) {
+ item = new AppItem(uid);
+ item.total = -1;
+ items.add(item);
+ knownItems.put(item.key, item);
+ }
+ item.restricted = true;
+ }
+ }
+
+ private void sortAndAddPreferences(List<AppItem> items, long largest) {
+ Collections.sort(items);
+ for (int i = 0; i < items.size(); i++) {
+ int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0;
+ AppDataUsagePreference preference = new AppDataUsagePreference(getContext(),
+ items.get(i), percentTotal, mUidDetailProvider);
+ getPreference().addPreference(preference);
+ }
+ }
+
+ /**
+ * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
+ * Creates the item if needed.
+ *
+ * @param collapseKey the collapse key used to map the item.
+ * @param knownItems collection of known (already existing) items.
+ * @param entry the network stats entry to extract data usage from.
+ * @param itemCategory the item is categorized on the list view by this category. Must be
+ */
+ private static long accumulate(int collapseKey, SparseArray<AppItem> knownItems,
+ NetworkStats.Entry entry, int itemCategory, List<AppItem> items) {
+ int uid = entry.uid;
+ AppItem item = knownItems.get(collapseKey);
+ if (item == null) {
+ item = new AppItem(collapseKey);
+ item.category = itemCategory;
+ items.add(item);
+ knownItems.put(item.key, item);
+ }
+ item.addUid(uid);
+ item.total += entry.rxBytes + entry.txBytes;
+ return item.total;
+ }
+
+ private class AppDataUsagePreference extends ProgressBarPreference {
+
+ private final AppItem mItem;
+ private final int mPercent;
+ private UidDetail mDetail;
+
+ AppDataUsagePreference(Context context, AppItem item, int percent,
+ UidDetailProvider provider) {
+ super(context);
+ mItem = item;
+ mPercent = percent;
+ setLayoutResource(R.layout.progress_bar_preference);
+ setKey(String.valueOf(item.key));
+ if (item.restricted && item.total <= 0) {
+ setSummary(R.string.data_usage_app_restricted);
+ } else {
+ CharSequence s = DataUsageUtils.bytesToIecUnits(context, item.total);
+ setSummary(s);
+ }
+ mDetail = provider.getUidDetail(item.key, false /* blocking */);
+ if (mDetail != null) {
+ setAppInfo();
+ } else {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ mDetail = provider.getUidDetail(mItem.key, true /* blocking */);
+ ThreadUtils.postOnMainThread(() -> setAppInfo());
+ });
+ }
+ }
+
+ private void setAppInfo() {
+ if (mDetail != null) {
+ setIcon(mDetail.icon);
+ setTitle(mDetail.label);
+ setProgress(mPercent);
+ } else {
+ setIcon(null);
+ setTitle(null);
+ }
+ }
+ }
+}
diff --git a/src/com/android/car/settings/datausage/DataUsagePreferenceController.java b/src/com/android/car/settings/datausage/DataUsagePreferenceController.java
new file mode 100644
index 0000000..9c0652a
--- /dev/null
+++ b/src/com/android/car/settings/datausage/DataUsagePreferenceController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 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.car.settings.datausage;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller to handle the business logic for AppDataUsage preference on the data usage screen
+ */
+public class DataUsagePreferenceController extends PreferenceController<Preference> {
+
+ private SubscriptionManager mSubscriptionManager;
+
+ public DataUsagePreferenceController(Context context, String preferenceKey,
+ FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+ super(context, preferenceKey, fragmentController, uxRestrictions);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+ }
+
+ @Override
+ protected Class<Preference> getPreferenceType() {
+ return Preference.class;
+ }
+
+ @Override
+ protected boolean handlePreferenceClicked(Preference preference) {
+ int subId = DataUsageUtils.getDefaultSubscriptionId(mSubscriptionManager);
+ AppDataUsageFragment appDataUsageFragment = AppDataUsageFragment.newInstance(subId);
+ getFragmentController().launchFragment(appDataUsageFragment);
+ return true;
+ }
+}
diff --git a/src/com/android/car/settings/datausage/DataUsageUtils.java b/src/com/android/car/settings/datausage/DataUsageUtils.java
index 735a074..ea85ccb 100644
--- a/src/com/android/car/settings/datausage/DataUsageUtils.java
+++ b/src/com/android/car/settings/datausage/DataUsageUtils.java
@@ -74,7 +74,7 @@
* Format byte value to readable string using IEC units.
*/
public static CharSequence bytesToIecUnits(Context context, long byteValue) {
- final Formatter.BytesResult res = Formatter.formatBytes(context.getResources(), byteValue,
+ Formatter.BytesResult res = Formatter.formatBytes(context.getResources(), byteValue,
Formatter.FLAG_IEC_UNITS);
return BidiFormatter.getInstance().unicodeWrap(context.getString(
com.android.internal.R.string.fileSizeSuffix, res.value, res.units));
diff --git a/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragment.java b/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragment.java
index 5e29aa1..a303117 100644
--- a/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragment.java
+++ b/src/com/android/car/settings/storage/AppStorageSettingsDetailsFragment.java
@@ -37,6 +37,7 @@
import androidx.loader.app.LoaderManager;
import com.android.car.settings.R;
+import com.android.car.settings.applications.ApplicationWithVersionPreferenceController;
import com.android.car.settings.common.ConfirmationDialogFragment;
import com.android.car.settings.common.Logger;
import com.android.car.settings.common.SettingsFragment;
@@ -160,7 +161,7 @@
}
mAppsStorageStatsManager = new AppsStorageStatsManager(context);
mAppsStorageStatsManager.registerListener(this);
- use(StorageApplicationPreferenceController.class,
+ use(ApplicationWithVersionPreferenceController.class,
R.string.pk_storage_application_details)
.setAppEntry(mAppEntry)
.setAppState(mAppState);
diff --git a/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java
new file mode 100644
index 0000000..e4cf647
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 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.car.settings.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.INetworkStatsService;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.util.Pair;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.testutils.FragmentController;
+import com.android.car.settings.testutils.ShadowINetworkStatsServiceStub;
+import com.android.car.settings.testutils.ShadowNetworkPolicyEditor;
+import com.android.car.settings.testutils.ShadowNetworkPolicyManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/** Unit test for {@link AppDataUsageFragment}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowNetworkPolicyEditor.class, ShadowNetworkPolicyManager.class,
+ ShadowINetworkStatsServiceStub.class})
+public class AppDataUsageFragmentTest {
+
+ private static final String KEY_START = "start";
+ private static final String KEY_END = "end";
+
+ private AppDataUsageFragment mFragment;
+ private FragmentController<AppDataUsageFragment> mFragmentController;
+
+ @Mock
+ private NetworkPolicy mNetworkPolicy;
+
+ @Mock
+ private NetworkPolicyManager mNetworkPolicyManager;
+
+ @Mock
+ private INetworkStatsService mINetworkStatsService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFragment = new AppDataUsageFragment();
+ mFragmentController = FragmentController.of(mFragment);
+
+ ShadowNetworkPolicyManager.setNetworkPolicyManager(mNetworkPolicyManager);
+ ShadowINetworkStatsServiceStub.setINetworkStatsSession(mINetworkStatsService);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowNetworkPolicyEditor.reset();
+ ShadowINetworkStatsServiceStub.reset();
+ ShadowNetworkPolicyManager.reset();
+ }
+
+ @Test
+ public void onActivityCreated_policyIsNull_startAndEndDateShouldHaveFourWeeksDifference() {
+ mFragmentController.create();
+
+ Bundle bundle = mFragment.getBundleForNetworkStats();
+ long start = bundle.getLong(KEY_START);
+ long end = bundle.getLong(KEY_END);
+ long timeDiff = end - start;
+
+ assertThat(timeDiff).isEqualTo(DateUtils.WEEK_IN_MILLIS * 4);
+ }
+
+ @Test
+ public void onActivityCreated_iteratorIsEmpty_startAndEndDateShouldHaveFourWeeksDifference() {
+ ShadowNetworkPolicyEditor.setNetworkPolicy(mNetworkPolicy);
+
+ ArrayList<Pair<ZonedDateTime, ZonedDateTime>> list = new ArrayList<>();
+ Iterator iterator = list.iterator();
+ ShadowNetworkPolicyManager.setCycleIterator(iterator);
+ mFragmentController.create();
+
+ Bundle bundle = mFragment.getBundleForNetworkStats();
+ long start = bundle.getLong(KEY_START);
+ long end = bundle.getLong(KEY_END);
+ long timeDiff = end - start;
+
+ assertThat(timeDiff).isEqualTo(DateUtils.WEEK_IN_MILLIS * 4);
+ }
+
+ @Test
+ public void onActivityCreated_iteratorIsNotEmpty_startAndEndDateShouldBeLastOneInIterator() {
+ ShadowNetworkPolicyEditor.setNetworkPolicy(mNetworkPolicy);
+
+ ZonedDateTime start1 = ZonedDateTime.now();
+ ZonedDateTime end1 = ZonedDateTime.now();
+ ZonedDateTime start2 = ZonedDateTime.now();
+ ZonedDateTime end2 = ZonedDateTime.now();
+
+ Pair pair1 = new Pair(start1, end1);
+ Pair pair2 = new Pair(start2, end2);
+
+ ArrayList<Pair<ZonedDateTime, ZonedDateTime>> list = new ArrayList<>();
+ list.add(pair1);
+ list.add(pair2);
+
+ Iterator iterator = list.iterator();
+ ShadowNetworkPolicyManager.setCycleIterator(iterator);
+ mFragmentController.create();
+
+ Bundle bundle = mFragment.getBundleForNetworkStats();
+ long start = bundle.getLong(KEY_START);
+ long end = bundle.getLong(KEY_END);
+
+ assertThat(start).isEqualTo(start2.toInstant().toEpochMilli());
+ assertThat(end).isEqualTo(end2.toInstant().toEpochMilli());
+ }
+}
diff --git a/tests/robotests/src/com/android/car/settings/datausage/AppDataUsagePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsagePreferenceControllerTest.java
new file mode 100644
index 0000000..202b093
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsagePreferenceControllerTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.car.settings.datausage;
+
+import static android.net.TrafficStats.UID_TETHERING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.net.NetworkStats;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.common.ProgressBarPreference;
+import com.android.car.settings.testutils.ShadowCarUserManagerHelper;
+import com.android.car.settings.testutils.ShadowUidDetailProvider;
+import com.android.settingslib.net.UidDetail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit test for {@link AppDataUsagePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCarUserManagerHelper.class, ShadowUidDetailProvider.class})
+public class AppDataUsagePreferenceControllerTest {
+
+ private static final int USER_ID = 10;
+
+ private Context mContext;
+ private LogicalPreferenceGroup mLogicalPreferenceGroup;
+ private AppDataUsagePreferenceController mController;
+ private PreferenceControllerTestHelper<AppDataUsagePreferenceController>
+ mPreferenceControllerHelper;
+
+ @Mock
+ private CarUserManagerHelper mCarUserManagerHelper;
+
+ @Mock
+ private UidDetail mUidDetail;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mLogicalPreferenceGroup = new LogicalPreferenceGroup(mContext);
+ mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+ AppDataUsagePreferenceController.class, mLogicalPreferenceGroup);
+ mController = mPreferenceControllerHelper.getController();
+ when(mCarUserManagerHelper.getCurrentProcessUserId()).thenReturn(USER_ID);
+
+ ShadowCarUserManagerHelper.setMockInstance(mCarUserManagerHelper);
+ mPreferenceControllerHelper.markState(Lifecycle.State.CREATED);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowUidDetailProvider.reset();
+ ShadowCarUserManagerHelper.reset();
+ }
+
+ @Test
+ public void defaultInitialize_hasNoPreference() {
+ assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void onDataLoaded_dataNotLoaded_hasNoPreference() {
+ mController.onDataLoaded(null, new int[0]);
+
+ assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void onDataLoaded_statsSizeZero_hasNoPreference() {
+ NetworkStats networkStats = new NetworkStats(0, 0);
+
+ mController.onDataLoaded(networkStats, new int[0]);
+
+ assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void onDataLoaded_statsLoaded_hasTwoPreference() {
+ NetworkStats networkStats = new NetworkStats(0, 0);
+ NetworkStats.Entry entry1 = new NetworkStats.Entry();
+ entry1.rxBytes = 100;
+ networkStats.addValues(entry1);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry();
+ entry2.uid = UID_TETHERING;
+ entry2.rxBytes = 200;
+ networkStats.addValues(entry2);
+
+ mController.onDataLoaded(networkStats, new int[0]);
+
+ assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void onDataLoaded_statsLoaded_hasOnePreference() {
+ ShadowUidDetailProvider.setUidDetail(mUidDetail);
+ NetworkStats networkStats = new NetworkStats(0, 0);
+ NetworkStats.Entry entry1 = new NetworkStats.Entry();
+ entry1.rxBytes = 100;
+ networkStats.addValues(entry1);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry();
+ entry2.uid = UID_TETHERING;
+ entry2.rxBytes = 200;
+ networkStats.addValues(entry2);
+
+ mController.onDataLoaded(networkStats, new int[0]);
+
+ ProgressBarPreference preference1 =
+ (ProgressBarPreference) mLogicalPreferenceGroup.getPreference(0);
+ ProgressBarPreference preference2 =
+ (ProgressBarPreference) mLogicalPreferenceGroup.getPreference(1);
+
+ assertThat(mLogicalPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+ assertThat(preference1.getProgress()).isEqualTo(100);
+ assertThat(preference2.getProgress()).isEqualTo(50);
+ }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyEditor.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyEditor.java
new file mode 100644
index 0000000..85d16a6
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyEditor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 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.car.settings.testutils;
+
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+
+import com.android.settingslib.NetworkPolicyEditor;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(NetworkPolicyEditor.class)
+public class ShadowNetworkPolicyEditor {
+
+ private static NetworkPolicy sNetworkPolicy;
+
+ @Implementation
+ public NetworkPolicy getPolicy(NetworkTemplate template) {
+ return sNetworkPolicy;
+ }
+
+ public static void setNetworkPolicy(NetworkPolicy networkPolicy) {
+ sNetworkPolicy = networkPolicy;
+ }
+
+ @Resetter
+ public static void reset() {
+ sNetworkPolicy = null;
+ }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyManager.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyManager.java
index 9bf5e94..3d506d0 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyManager.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowNetworkPolicyManager.java
@@ -17,19 +17,24 @@
package com.android.car.settings.testutils;
import android.content.Context;
+import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
+import android.util.Pair;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
+import java.time.ZonedDateTime;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
@Implements(NetworkPolicyManager.class)
public class ShadowNetworkPolicyManager {
private static NetworkPolicyManager sNetworkPolicyManager;
+ private static Iterator<Pair<ZonedDateTime, ZonedDateTime>> sCycleIterator;
private static Map<String, Integer> sResetCalledForSubscriberCount = new HashMap<>();
public static boolean verifyFactoryResetCalled(String subscriber, int numTimes) {
@@ -50,6 +55,17 @@
}
@Implementation
+ protected static Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator(
+ NetworkPolicy policy) {
+ return sCycleIterator;
+ }
+
+ public static void setCycleIterator(
+ Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator) {
+ sCycleIterator = cycleIterator;
+ }
+
+ @Implementation
public static NetworkPolicyManager from(Context context) {
return sNetworkPolicyManager;
}
@@ -61,6 +77,7 @@
@Resetter
public static void reset() {
sResetCalledForSubscriberCount.clear();
+ sCycleIterator = null;
sNetworkPolicyManager = null;
}
}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowUidDetailProvider.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowUidDetailProvider.java
new file mode 100644
index 0000000..2db468a
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowUidDetailProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 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.car.settings.testutils;
+
+import com.android.settingslib.net.UidDetail;
+import com.android.settingslib.net.UidDetailProvider;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(UidDetailProvider.class)
+public class ShadowUidDetailProvider {
+
+ private static UidDetail sUidDetail;
+
+ @Resetter
+ public static void reset() {
+ sUidDetail = null;
+ }
+
+ @Implementation
+ public UidDetail getUidDetail(int uid, boolean blocking) {
+ return sUidDetail;
+ }
+
+ public static void setUidDetail(UidDetail uidDetail) {
+ sUidDetail = uidDetail;
+ }
+}