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