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.app.usage.NetworkStats.Bucket.UID_REMOVED; 20 import static android.app.usage.NetworkStats.Bucket.UID_TETHERING; 21 22 import android.annotation.NonNull; 23 import android.app.usage.NetworkStats; 24 import android.car.drivingstate.CarUxRestrictions; 25 import android.content.Context; 26 import android.content.pm.UserInfo; 27 import android.net.NetworkTemplate; 28 import android.os.UserHandle; 29 import android.util.SparseArray; 30 31 import androidx.annotation.VisibleForTesting; 32 import androidx.preference.PreferenceGroup; 33 34 import com.android.car.settings.R; 35 import com.android.car.settings.common.FragmentController; 36 import com.android.car.settings.common.PreferenceController; 37 import com.android.car.settings.common.ProgressBarPreference; 38 import com.android.car.settings.profiles.ProfileHelper; 39 import com.android.settingslib.AppItem; 40 import com.android.settingslib.net.UidDetail; 41 import com.android.settingslib.net.UidDetailProvider; 42 import com.android.settingslib.utils.ThreadUtils; 43 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.Optional; 48 49 import javax.annotation.Nullable; 50 51 /** 52 * Controller that adds all the applications using the data sorted by the amount of data used. The 53 * first application that used most amount of data will be at the top with progress 100 percentage. 54 * All other progress are calculated relatively. 55 */ 56 public class AppDataUsagePreferenceController extends 57 PreferenceController<PreferenceGroup> implements AppsNetworkStatsManager.Callback { 58 59 private final UidDetailProvider mUidDetailProvider; 60 private NetworkTemplate mNetworkTemplate; 61 AppDataUsagePreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)62 public AppDataUsagePreferenceController(Context context, String preferenceKey, 63 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 64 this(context, preferenceKey, fragmentController, uxRestrictions, 65 new UidDetailProvider(context)); 66 } 67 68 @VisibleForTesting AppDataUsagePreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, UidDetailProvider uidDetailProvider)69 AppDataUsagePreferenceController(Context context, String preferenceKey, 70 FragmentController fragmentController, CarUxRestrictions uxRestrictions, 71 UidDetailProvider uidDetailProvider) { 72 super(context, preferenceKey, fragmentController, uxRestrictions); 73 mUidDetailProvider = uidDetailProvider; 74 } 75 76 @VisibleForTesting hasNextBucket(@onNull NetworkStats stats)77 boolean hasNextBucket(@NonNull NetworkStats stats) { 78 return stats.hasNextBucket(); 79 } 80 81 @NonNull 82 @VisibleForTesting getNextBucket(@onNull NetworkStats stats)83 NetworkStats.Bucket getNextBucket(@NonNull NetworkStats stats) { 84 NetworkStats.Bucket bucket = new NetworkStats.Bucket(); 85 stats.getNextBucket(bucket); 86 return bucket; 87 } 88 89 @Override getPreferenceType()90 protected Class<PreferenceGroup> getPreferenceType() { 91 return PreferenceGroup.class; 92 } 93 94 @Override onDataLoaded(@ullable NetworkStats stats, @Nullable int[] restrictedUids)95 public void onDataLoaded(@Nullable NetworkStats stats, @Nullable int[] restrictedUids) { 96 List<AppItem> items = new ArrayList<>(); 97 long largest = 0; 98 99 List<UserInfo> profiles = ProfileHelper.getInstance(getContext()).getAllProfiles(); 100 SparseArray<AppItem> knownItems = new SparseArray<>(); 101 102 if (stats != null) { 103 while (hasNextBucket(stats)) { 104 NetworkStats.Bucket entry = getNextBucket(stats); 105 long size = aggregateDataUsage(knownItems, items, entry, profiles); 106 largest = Math.max(size, largest); 107 } 108 } 109 110 updateRestrictedState(restrictedUids, knownItems, items, profiles); 111 sortAndAddPreferences(items, largest); 112 } 113 114 /** Sets the {@link NetworkTemplate} */ setNetworkTemplate(NetworkTemplate networkTemplate)115 public void setNetworkTemplate(NetworkTemplate networkTemplate) { 116 mNetworkTemplate = networkTemplate; 117 } 118 aggregateDataUsage(SparseArray<AppItem> knownItems, List<AppItem> items, NetworkStats.Bucket entry, List<UserInfo> profiles)119 private long aggregateDataUsage(SparseArray<AppItem> knownItems, List<AppItem> items, 120 NetworkStats.Bucket entry, List<UserInfo> profiles) { 121 int currentUserId = UserHandle.myUserId(); 122 123 // Decide how to collapse items together. 124 int uid = entry.getUid(); 125 126 int collapseKey; 127 int category; 128 int userId = UserHandle.getUserId(uid); 129 130 if (isUidValid(uid)) { 131 collapseKey = uid; 132 category = AppItem.CATEGORY_APP; 133 return accumulate(collapseKey, knownItems, entry, category, items); 134 } 135 136 if (!UserHandle.isApp(uid)) { 137 collapseKey = android.os.Process.SYSTEM_UID; 138 category = AppItem.CATEGORY_APP; 139 return accumulate(collapseKey, knownItems, entry, category, items); 140 } 141 142 if (profileContainsUserId(profiles, userId) && userId == currentUserId) { 143 // Add to app item. 144 collapseKey = uid; 145 category = AppItem.CATEGORY_APP; 146 return accumulate(collapseKey, knownItems, entry, category, items); 147 } 148 149 if (profileContainsUserId(profiles, userId) && userId != currentUserId) { 150 // Add to a managed user item. 151 int managedKey = UidDetailProvider.buildKeyForUser(userId); 152 long usersLargest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER, 153 items); 154 collapseKey = uid; 155 category = AppItem.CATEGORY_APP; 156 long appLargest = accumulate(collapseKey, knownItems, entry, category, items); 157 return Math.max(usersLargest, appLargest); 158 } 159 160 // If it is a removed user add it to the removed users' key. 161 Optional<UserInfo> info = profiles.stream().filter( 162 userInfo -> userInfo.id == userId).findFirst(); 163 if (!info.isPresent()) { 164 collapseKey = UID_REMOVED; 165 category = AppItem.CATEGORY_APP; 166 } else { 167 // Add to other user item. 168 collapseKey = UidDetailProvider.buildKeyForUser(userId); 169 category = AppItem.CATEGORY_USER; 170 } 171 172 return accumulate(collapseKey, knownItems, entry, category, items); 173 } 174 175 /** 176 * UID does not belong to a regular app and maybe belongs to a removed application or 177 * application using for tethering traffic. 178 */ isUidValid(int uid)179 private boolean isUidValid(int uid) { 180 return !UserHandle.isApp(uid) && (uid == UID_REMOVED || uid == UID_TETHERING); 181 } 182 profileContainsUserId(List<UserInfo> profiles, int userId)183 private boolean profileContainsUserId(List<UserInfo> profiles, int userId) { 184 return profiles.stream().anyMatch(userInfo -> userInfo.id == userId); 185 } 186 updateRestrictedState(@ullable int[] restrictedUids, SparseArray<AppItem> knownItems, List<AppItem> items, List<UserInfo> profiles)187 private void updateRestrictedState(@Nullable int[] restrictedUids, 188 SparseArray<AppItem> knownItems, List<AppItem> items, List<UserInfo> profiles) { 189 if (restrictedUids == null) { 190 return; 191 } 192 193 for (int i = 0; i < restrictedUids.length; ++i) { 194 int uid = restrictedUids[i]; 195 // Only splice in restricted state for current user or managed users. 196 if (!profileContainsUserId(profiles, uid)) { 197 continue; 198 } 199 200 AppItem item = knownItems.get(uid); 201 if (item == null) { 202 item = new AppItem(uid); 203 item.total = -1; 204 items.add(item); 205 knownItems.put(item.key, item); 206 } 207 item.restricted = true; 208 } 209 } 210 sortAndAddPreferences(List<AppItem> items, long largest)211 private void sortAndAddPreferences(List<AppItem> items, long largest) { 212 getPreference().removeAll(); 213 Collections.sort(items); 214 for (int i = 0; i < items.size(); i++) { 215 int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0; 216 AppDataUsagePreference preference = new AppDataUsagePreference(getContext(), 217 items.get(i), percentTotal, mUidDetailProvider); 218 getPreference().addPreference(preference); 219 } 220 } 221 222 /** 223 * Accumulate data usage of a network stats entry for the item mapped by the collapse key. 224 * Creates the item if needed. 225 * 226 * @param collapseKey the collapse key used to map the item. 227 * @param knownItems collection of known (already existing) items. 228 * @param entry the network stats entry to extract data usage from. 229 * @param itemCategory the item is categorized on the list view by this category. Must be 230 */ accumulate(int collapseKey, SparseArray<AppItem> knownItems, NetworkStats.Bucket entry, int itemCategory, List<AppItem> items)231 private static long accumulate(int collapseKey, SparseArray<AppItem> knownItems, 232 NetworkStats.Bucket entry, int itemCategory, List<AppItem> items) { 233 int uid = entry.getUid(); 234 AppItem item = knownItems.get(collapseKey); 235 if (item == null) { 236 item = new AppItem(collapseKey); 237 item.category = itemCategory; 238 items.add(item); 239 knownItems.put(item.key, item); 240 } 241 item.addUid(uid); 242 item.total += entry.getRxBytes() + entry.getTxBytes(); 243 return item.total; 244 } 245 246 private class AppDataUsagePreference extends ProgressBarPreference { 247 248 private final AppItem mItem; 249 private final int mPercent; 250 private UidDetail mDetail; 251 AppDataUsagePreference(Context context, AppItem item, int percent, UidDetailProvider provider)252 AppDataUsagePreference(Context context, AppItem item, int percent, 253 UidDetailProvider provider) { 254 super(context); 255 mItem = item; 256 mPercent = percent; 257 setLayoutResource(R.layout.progress_bar_preference); 258 setKey(String.valueOf(item.key)); 259 if (item.restricted && item.total <= 0) { 260 setSummary(R.string.data_usage_app_restricted); 261 } else { 262 CharSequence s = DataUsageUtils.bytesToIecUnits(context, item.total); 263 setSummary(s); 264 } 265 mDetail = provider.getUidDetail(item.key, /* blocking= */ false); 266 if (mDetail != null) { 267 setAppInfo(); 268 setOnClickListener(); 269 } else { 270 ThreadUtils.postOnBackgroundThread(() -> { 271 mDetail = provider.getUidDetail(mItem.key, /* blocking= */ true); 272 ThreadUtils.postOnMainThread(() -> { 273 setAppInfo(); 274 setOnClickListener(); 275 }); 276 }); 277 } 278 } 279 setAppInfo()280 private void setAppInfo() { 281 if (mDetail != null) { 282 setIcon(mDetail.icon); 283 setTitle(mDetail.label); 284 setProgress(mPercent); 285 } else { 286 setIcon(null); 287 setTitle(null); 288 } 289 } 290 setOnClickListener()291 private void setOnClickListener() { 292 if (mDetail != null && mNetworkTemplate != null) { 293 setOnPreferenceClickListener(p -> { 294 getFragmentController().launchFragment( 295 AppSpecificDataUsageFragment.getInstance(mItem, mNetworkTemplate)); 296 return true; 297 }); 298 } 299 } 300 } 301 } 302