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