1 /* <lambda>null2 * Copyright (C) 2023 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.settings.datausage.lib 18 19 import android.app.usage.NetworkStats 20 import android.content.Context 21 import android.content.pm.UserProperties 22 import android.net.NetworkPolicyManager 23 import android.net.NetworkTemplate 24 import android.os.Process 25 import android.os.UserHandle 26 import android.os.UserManager 27 import android.util.SparseArray 28 import android.util.SparseBooleanArray 29 import androidx.annotation.VisibleForTesting 30 import androidx.core.util.keyIterator 31 import com.android.settings.R 32 import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket 33 import com.android.settingslib.AppItem 34 import com.android.settingslib.net.UidDetailProvider 35 import com.android.settingslib.spaprivileged.framework.common.userManager 36 37 class AppDataUsageRepository( 38 private val context: Context, 39 private val currentUserId: Int, 40 template: NetworkTemplate, 41 private val getPackageName: (AppItem) -> String?, 42 ) { 43 private val networkStatsRepository = NetworkStatsRepository(context, template) 44 45 fun getAppPercent(carrierId: Int?, startTime: Long, endTime: Long): List<Pair<AppItem, Int>> { 46 val buckets = networkStatsRepository.queryBuckets(startTime, endTime) 47 return getAppPercent(carrierId, buckets) 48 } 49 50 @VisibleForTesting 51 fun getAppPercent(carrierId: Int?, buckets: List<Bucket>): List<Pair<AppItem, Int>> { 52 val items = ArrayList<AppItem>() 53 val knownItems = SparseArray<AppItem>() 54 val profiles = context.userManager.userProfiles 55 val userManager : UserManager = context.getSystemService(Context.USER_SERVICE) as UserManager 56 val userIdToIsHiddenMap = profiles.associate { profile -> 57 profile.identifier to shouldSkipProfile(userManager, profile) 58 } 59 bindStats(buckets, userIdToIsHiddenMap, knownItems, items) 60 val restrictedUids = context.getSystemService(NetworkPolicyManager::class.java)!! 61 .getUidsWithPolicy(NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND) 62 for (uid in restrictedUids) { 63 // Only splice in restricted state for current user or managed users 64 if (!profiles.contains(UserHandle.getUserHandleForUid(uid))) { 65 continue 66 } 67 var item = knownItems[uid] 68 if (item == null) { 69 item = AppItem(uid) 70 item.total = 0 71 item.addUid(uid) 72 items.add(item) 73 knownItems.put(item.key, item) 74 } 75 item.restricted = true 76 } 77 78 val filteredItems = filterItems(carrierId, items).sorted() 79 val largest: Long = filteredItems.maxOfOrNull { it.total } ?: 0 80 return filteredItems.map { item -> 81 val percentTotal = if (largest > 0) (item.total * 100 / largest).toInt() else 0 82 item to percentTotal 83 } 84 } 85 86 private fun filterItems(carrierId: Int?, items: List<AppItem>): List<AppItem> { 87 // When there is no specified SubscriptionInfo, Wi-Fi data usage will be displayed. 88 // In this case, the carrier service package also needs to be hidden. 89 if (carrierId != null && carrierId !in context.resources.getIntArray( 90 R.array.datausage_hiding_carrier_service_carrier_id 91 ) 92 ) { 93 return items 94 } 95 val hiddenPackageNames = context.resources.getStringArray( 96 R.array.datausage_hiding_carrier_service_package_names 97 ) 98 return items.filter { item -> 99 // Do not show carrier service package in data usage list if it should be hidden for 100 // the carrier. 101 getPackageName(item) !in hiddenPackageNames 102 } 103 } 104 105 private fun bindStats( 106 buckets: List<Bucket>, 107 userIdToIsHiddenMap: Map<Int, Boolean>, 108 knownItems: SparseArray<AppItem>, 109 items: ArrayList<AppItem>, 110 ) { 111 for (bucket in buckets) { 112 // Decide how to collapse items together 113 val uid = bucket.uid 114 val collapseKey: Int 115 val category: Int 116 val userId = UserHandle.getUserId(uid) 117 if(userIdToIsHiddenMap[userId] == true) { 118 continue 119 } 120 if (UserHandle.isApp(uid) || Process.isSdkSandboxUid(uid)) { 121 if (userIdToIsHiddenMap.keys.contains(userId)) { 122 if (userId != currentUserId) { 123 // Add to a managed user item. 124 accumulate( 125 collapseKey = UidDetailProvider.buildKeyForUser(userId), 126 knownItems = knownItems, 127 bucket = bucket, 128 itemCategory = AppItem.CATEGORY_USER, 129 items = items, 130 ) 131 } 132 collapseKey = getAppUid(uid) 133 category = AppItem.CATEGORY_APP 134 } else { 135 // If it is a removed user add it to the removed users' key 136 if (context.userManager.getUserInfo(userId) == null) { 137 collapseKey = NetworkStats.Bucket.UID_REMOVED 138 category = AppItem.CATEGORY_APP 139 } else { 140 // Add to other user item. 141 collapseKey = UidDetailProvider.buildKeyForUser(userId) 142 category = AppItem.CATEGORY_USER 143 } 144 } 145 } else if (uid == NetworkStats.Bucket.UID_REMOVED || 146 uid == NetworkStats.Bucket.UID_TETHERING || 147 uid == Process.OTA_UPDATE_UID 148 ) { 149 collapseKey = uid 150 category = AppItem.CATEGORY_APP 151 } else { 152 collapseKey = Process.SYSTEM_UID 153 category = AppItem.CATEGORY_APP 154 } 155 accumulate( 156 collapseKey = collapseKey, 157 knownItems = knownItems, 158 bucket = bucket, 159 itemCategory = category, 160 items = items, 161 ) 162 } 163 } 164 165 private fun shouldSkipProfile(userManager : UserManager, userHandle: UserHandle): Boolean { 166 if (android.os.Flags.allowPrivateProfile() 167 && android.multiuser.Flags.enablePrivateSpaceFeatures() 168 && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) { 169 return (userManager.isQuietModeEnabled(userHandle) 170 && userManager.getUserProperties(userHandle).showInQuietMode 171 == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) 172 } 173 return false 174 } 175 176 /** 177 * Accumulate data usage of a network stats entry for the item mapped by the collapse key. 178 * Creates the item if needed. 179 * 180 * @param collapseKey the collapse key used to map the item. 181 * @param knownItems collection of known (already existing) items. 182 * @param bucket the network stats bucket to extract data usage from. 183 * @param itemCategory the item is categorized on the list view by this category. Must be 184 */ 185 private fun accumulate( 186 collapseKey: Int, 187 knownItems: SparseArray<AppItem>, 188 bucket: Bucket, 189 itemCategory: Int, 190 items: ArrayList<AppItem>, 191 ) { 192 var item = knownItems[collapseKey] 193 if (item == null) { 194 item = AppItem(collapseKey) 195 item.category = itemCategory 196 items.add(item) 197 knownItems.put(item.key, item) 198 } 199 item.addUid(bucket.uid) 200 item.total += bucket.bytes 201 } 202 203 companion object { 204 @JvmStatic 205 fun getAppUidList(uids: SparseBooleanArray) = 206 uids.keyIterator().asSequence().map { getAppUid(it) }.distinct().toList() 207 208 @JvmStatic 209 fun getAppUid(uid: Int): Int { 210 if (Process.isSdkSandboxUid(uid)) { 211 // For a sandbox process, get the associated app UID 212 return Process.getAppUidForSdkSandboxUid(uid) 213 } 214 return uid 215 } 216 217 /** 218 * Gets the apps' uids, also add the apps' SDK sandboxes' uids. 219 * 220 * In case we've been asked data usage for an app, include data usage of the corresponding 221 * SDK sandbox. 222 */ 223 fun withSdkSandboxUids(uids: List<Int>): List<Int> { 224 val set = uids.toMutableSet() 225 for (uid in uids) { 226 if (Process.isApplicationUid(uid)) { 227 set += Process.toSdkSandboxUid(uid) 228 } 229 } 230 return set.toList() 231 } 232 } 233 } 234