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;
+    }
+}