• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.settings.datausage;
18 
19 import static android.net.TrafficStats.UID_REMOVED;
20 import static android.net.TrafficStats.UID_TETHERING;
21 
22 import android.car.drivingstate.CarUxRestrictions;
23 import android.content.Context;
24 import android.content.pm.UserInfo;
25 import android.net.NetworkStats;
26 import android.os.UserHandle;
27 import android.util.SparseArray;
28 
29 import androidx.preference.PreferenceGroup;
30 
31 import com.android.car.settings.R;
32 import com.android.car.settings.common.FragmentController;
33 import com.android.car.settings.common.PreferenceController;
34 import com.android.car.settings.common.ProgressBarPreference;
35 import com.android.car.settings.profiles.ProfileHelper;
36 import com.android.settingslib.AppItem;
37 import com.android.settingslib.net.UidDetail;
38 import com.android.settingslib.net.UidDetailProvider;
39 import com.android.settingslib.utils.ThreadUtils;
40 
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.List;
44 import java.util.Optional;
45 
46 import javax.annotation.Nullable;
47 
48 /**
49  * Controller that adds all the applications using the data sorted by the amount of data used. The
50  * first application that used most amount of data will be at the top with progress 100 percentage.
51  * All other progress are calculated relatively.
52  */
53 public class AppDataUsagePreferenceController extends
54         PreferenceController<PreferenceGroup> implements AppsNetworkStatsManager.Callback {
55 
56     private final UidDetailProvider mUidDetailProvider;
57 
AppDataUsagePreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)58     public AppDataUsagePreferenceController(Context context, String preferenceKey,
59             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
60         super(context, preferenceKey, fragmentController, uxRestrictions);
61         mUidDetailProvider = new UidDetailProvider(getContext());
62     }
63 
64     @Override
getPreferenceType()65     protected Class<PreferenceGroup> getPreferenceType() {
66         return PreferenceGroup.class;
67     }
68 
69     @Override
onDataLoaded(@ullable NetworkStats stats, @Nullable int[] restrictedUids)70     public void onDataLoaded(@Nullable NetworkStats stats, @Nullable int[] restrictedUids) {
71         List<AppItem> items = new ArrayList<>();
72         long largest = 0;
73 
74         List<UserInfo> profiles = ProfileHelper.getInstance(getContext()).getAllProfiles();
75         SparseArray<AppItem> knownItems = new SparseArray<>();
76 
77         NetworkStats.Entry entry = null;
78         if (stats != null) {
79             for (int i = 0; i < stats.size(); i++) {
80                 entry = stats.getValues(i, entry);
81                 long size = aggregateDataUsage(knownItems, items, entry, profiles);
82                 largest = Math.max(size, largest);
83             }
84         }
85 
86         updateRestrictedState(restrictedUids, knownItems, items, profiles);
87         sortAndAddPreferences(items, largest);
88     }
89 
aggregateDataUsage(SparseArray<AppItem> knownItems, List<AppItem> items, NetworkStats.Entry entry, List<UserInfo> profiles)90     private long aggregateDataUsage(SparseArray<AppItem> knownItems, List<AppItem> items,
91             NetworkStats.Entry entry, List<UserInfo> profiles) {
92         int currentUserId = UserHandle.myUserId();
93 
94         // Decide how to collapse items together.
95         int uid = entry.uid;
96 
97         int collapseKey;
98         int category;
99         int userId = UserHandle.getUserId(uid);
100 
101         if (isUidValid(uid)) {
102             collapseKey = uid;
103             category = AppItem.CATEGORY_APP;
104             return accumulate(collapseKey, knownItems, entry, category, items);
105         }
106 
107         if (!UserHandle.isApp(uid)) {
108             collapseKey = android.os.Process.SYSTEM_UID;
109             category = AppItem.CATEGORY_APP;
110             return accumulate(collapseKey, knownItems, entry, category, items);
111         }
112 
113         if (profileContainsUserId(profiles, userId) && userId == currentUserId) {
114             // Add to app item.
115             collapseKey = uid;
116             category = AppItem.CATEGORY_APP;
117             return accumulate(collapseKey, knownItems, entry, category, items);
118         }
119 
120         if (profileContainsUserId(profiles, userId) && userId != currentUserId) {
121             // Add to a managed user item.
122             int managedKey = UidDetailProvider.buildKeyForUser(userId);
123             long usersLargest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER,
124                     items);
125             collapseKey = uid;
126             category = AppItem.CATEGORY_APP;
127             long appLargest = accumulate(collapseKey, knownItems, entry, category, items);
128             return Math.max(usersLargest, appLargest);
129         }
130 
131         // If it is a removed user add it to the removed users' key.
132         Optional<UserInfo> info = profiles.stream().filter(
133                 userInfo -> userInfo.id == userId).findFirst();
134         if (!info.isPresent()) {
135             collapseKey = UID_REMOVED;
136             category = AppItem.CATEGORY_APP;
137         } else {
138             // Add to other user item.
139             collapseKey = UidDetailProvider.buildKeyForUser(userId);
140             category = AppItem.CATEGORY_USER;
141         }
142 
143         return accumulate(collapseKey, knownItems, entry, category, items);
144     }
145 
146     /**
147      * UID does not belong to a regular app and maybe belongs to a removed application or
148      * application using for tethering traffic.
149      */
isUidValid(int uid)150     private boolean isUidValid(int uid) {
151         return !UserHandle.isApp(uid) && (uid == UID_REMOVED || uid == UID_TETHERING);
152     }
153 
profileContainsUserId(List<UserInfo> profiles, int userId)154     private boolean profileContainsUserId(List<UserInfo> profiles, int userId) {
155         return profiles.stream().anyMatch(userInfo -> userInfo.id == userId);
156     }
157 
updateRestrictedState(@ullable int[] restrictedUids, SparseArray<AppItem> knownItems, List<AppItem> items, List<UserInfo> profiles)158     private void updateRestrictedState(@Nullable int[] restrictedUids,
159             SparseArray<AppItem> knownItems, List<AppItem> items, List<UserInfo> profiles) {
160         if (restrictedUids == null) {
161             return;
162         }
163 
164         for (int i = 0; i < restrictedUids.length; ++i) {
165             int uid = restrictedUids[i];
166             // Only splice in restricted state for current user or managed users.
167             if (!profileContainsUserId(profiles, uid)) {
168                 continue;
169             }
170 
171             AppItem item = knownItems.get(uid);
172             if (item == null) {
173                 item = new AppItem(uid);
174                 item.total = -1;
175                 items.add(item);
176                 knownItems.put(item.key, item);
177             }
178             item.restricted = true;
179         }
180     }
181 
sortAndAddPreferences(List<AppItem> items, long largest)182     private void sortAndAddPreferences(List<AppItem> items, long largest) {
183         Collections.sort(items);
184         for (int i = 0; i < items.size(); i++) {
185             int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0;
186             AppDataUsagePreference preference = new AppDataUsagePreference(getContext(),
187                     items.get(i), percentTotal, mUidDetailProvider);
188             getPreference().addPreference(preference);
189         }
190     }
191 
192     /**
193      * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
194      * Creates the item if needed.
195      *
196      * @param collapseKey  the collapse key used to map the item.
197      * @param knownItems   collection of known (already existing) items.
198      * @param entry        the network stats entry to extract data usage from.
199      * @param itemCategory the item is categorized on the list view by this category. Must be
200      */
accumulate(int collapseKey, SparseArray<AppItem> knownItems, NetworkStats.Entry entry, int itemCategory, List<AppItem> items)201     private static long accumulate(int collapseKey, SparseArray<AppItem> knownItems,
202             NetworkStats.Entry entry, int itemCategory, List<AppItem> items) {
203         int uid = entry.uid;
204         AppItem item = knownItems.get(collapseKey);
205         if (item == null) {
206             item = new AppItem(collapseKey);
207             item.category = itemCategory;
208             items.add(item);
209             knownItems.put(item.key, item);
210         }
211         item.addUid(uid);
212         item.total += entry.rxBytes + entry.txBytes;
213         return item.total;
214     }
215 
216     private class AppDataUsagePreference extends ProgressBarPreference {
217 
218         private final AppItem mItem;
219         private final int mPercent;
220         private UidDetail mDetail;
221 
AppDataUsagePreference(Context context, AppItem item, int percent, UidDetailProvider provider)222         AppDataUsagePreference(Context context, AppItem item, int percent,
223                 UidDetailProvider provider) {
224             super(context);
225             mItem = item;
226             mPercent = percent;
227             setLayoutResource(R.layout.progress_bar_preference);
228             setKey(String.valueOf(item.key));
229             if (item.restricted && item.total <= 0) {
230                 setSummary(R.string.data_usage_app_restricted);
231             } else {
232                 CharSequence s = DataUsageUtils.bytesToIecUnits(context, item.total);
233                 setSummary(s);
234             }
235             mDetail = provider.getUidDetail(item.key, /* blocking= */ false);
236             if (mDetail != null) {
237                 setAppInfo();
238             } else {
239                 ThreadUtils.postOnBackgroundThread(() -> {
240                     mDetail = provider.getUidDetail(mItem.key, /* blocking= */ true);
241                     ThreadUtils.postOnMainThread(() -> setAppInfo());
242                 });
243             }
244         }
245 
setAppInfo()246         private void setAppInfo() {
247             if (mDetail != null) {
248                 setIcon(mDetail.icon);
249                 setTitle(mDetail.label);
250                 setProgress(mPercent);
251             } else {
252                 setIcon(null);
253                 setTitle(null);
254             }
255         }
256     }
257 }
258