1 /* 2 * Copyright (C) 2022 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.telemetry; 18 19 import static com.android.car.telemetry.CarTelemetryService.DEBUG; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.car.builtin.util.Slogf; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.UserHandle; 33 import android.os.UserManager; 34 import android.util.SparseArray; 35 36 import com.android.car.CarLog; 37 import com.android.internal.annotations.VisibleForTesting; 38 39 import java.util.ArrayDeque; 40 import java.util.ArrayList; 41 import java.util.List; 42 43 /** 44 * Maps app package name to UID using {@link PackageManager}, and app install/remove and user 45 * add/remove broadcasts. It also stores some uninstalled apps, because some publishers may have 46 * data for recent uninstalled apps. 47 * 48 * <p>See https://source.android.com/security/app-sandbox to learn more about UIDs. Note that an app 49 * (package name) has single UID, but a UID can have multiple apps, there is one-to-many 50 * relationship. 51 * 52 * <p>Use {@code adb shell pm list packages -U -u --show-versioncode} to list the packages. 53 */ 54 public class UidPackageMapper { 55 // Store removed app info just in case some publishers (e.g. ConnectivityPublisher) may send 56 // data related to them. 57 private static final int DEFAULT_MAX_REMOVED_APPS_COUNT = 100; 58 59 private final Context mContext; 60 private final Handler mTelemetryHandler; 61 private final int mMaxRemovedAppsCount; 62 63 // Maps uid to the list of AppInfo. 64 private final SparseArray<ArrayList<AppInfo>> mUidAppInfo = new SparseArray<>(); 65 66 // Caches "mMaxRemovedAppsCount" removed apps, as there will be statistics even for the 67 // uninstalled apps. 68 // 69 // Note that it may contain different AppInfo object for the same uid/packageName, because 70 // of refetchAllAppInfo() method. 71 private final ArrayDeque<AppInfo> mRemovedApps = new ArrayDeque<>(); 72 73 private final BroadcastReceiver mAppUpdateReceiver = new AppUpdateReceiver(); 74 private final BroadcastReceiver mUserUpdateReceiver = new UserUpdateReceiver(); 75 76 /** Constructs an instance. */ UidPackageMapper(@onNull Context context, @NonNull Handler telemetryHandler)77 public UidPackageMapper(@NonNull Context context, @NonNull Handler telemetryHandler) { 78 this(context, telemetryHandler, DEFAULT_MAX_REMOVED_APPS_COUNT); 79 } 80 81 @VisibleForTesting UidPackageMapper( @onNull Context context, @NonNull Handler telemetryHandler, int maxRemovedAppsCount)82 UidPackageMapper( 83 @NonNull Context context, @NonNull Handler telemetryHandler, int maxRemovedAppsCount) { 84 mContext = context; 85 mTelemetryHandler = telemetryHandler; 86 mMaxRemovedAppsCount = maxRemovedAppsCount; 87 } 88 89 /** 90 * Subscribes for broadcast events and initializes the mapper by fetching all the apps from 91 * PackageManager. 92 */ init()93 public void init() { 94 // Setup broadcast receiver for app updates. 95 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED); 96 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 97 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 98 filter.addDataScheme("package"); 99 mContext.registerReceiverForAllUsers(mAppUpdateReceiver, filter, null, null); 100 101 // Setup receiver for user initialize (happens once for a new user) and if a user is 102 // removed. 103 filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE); 104 filter.addAction(Intent.ACTION_USER_REMOVED); 105 mContext.registerReceiverForAllUsers(mUserUpdateReceiver, filter, null, null); 106 107 refetchAllAppInfo(mContext); 108 } 109 110 /** Releases resources. */ release()111 public void release() { 112 mContext.unregisterReceiver(mAppUpdateReceiver); 113 mContext.unregisterReceiver(mUserUpdateReceiver); 114 } 115 116 /** 117 * Returns the list of packages for uid, including APEX and some uninstalled apps. May return 118 * an empty list. 119 */ 120 @NonNull getPackagesForUid(int uid)121 public List<String> getPackagesForUid(int uid) { 122 List<AppInfo> uidApps = mUidAppInfo.get(uid); 123 if (uidApps == null) { 124 return List.of(); 125 } 126 ArrayList<String> result = new ArrayList<>(); 127 for (int i = 0; i < uidApps.size(); i++) { 128 result.add(uidApps.get(i).mPackageName); 129 } 130 return result; 131 } 132 133 /** Gets AppInfo from "mUidAppInfo" map. */ 134 @Nullable getAppInfo(int uid, @NonNull String packageName)135 private AppInfo getAppInfo(int uid, @NonNull String packageName) { 136 ArrayList<AppInfo> uidApps = mUidAppInfo.get(uid); 137 if (uidApps == null) { 138 uidApps = new ArrayList<>(); 139 mUidAppInfo.put(uid, uidApps); 140 } 141 for (int i = 0; i < uidApps.size(); i++) { 142 AppInfo current = uidApps.get(i); 143 if (current.mPackageName.equals(packageName)) { 144 return current; 145 } 146 } 147 return null; 148 } 149 onAppAddedOrUpdated(int uid, @NonNull String packageName)150 private void onAppAddedOrUpdated(int uid, @NonNull String packageName) { 151 AppInfo appInfo = getAppInfo(uid, packageName); 152 if (appInfo == null) { 153 // The uid always exists in mUidAppInfo after getAppInfo() was called. 154 mUidAppInfo.get(uid).add(new AppInfo(uid, packageName)); 155 } else { 156 appInfo.mIsRemoved = false; 157 } 158 } 159 160 /** Marks the AppInfo removed */ onAppRemoved(int uid, @NonNull String packageName)161 private void onAppRemoved(int uid, @NonNull String packageName) { 162 AppInfo appInfo = getAppInfo(uid, packageName); 163 if (appInfo == null) { 164 Slogf.i( 165 CarLog.TAG_TELEMETRY, 166 "UidPackageMapper failed to remove the app from its cache, " 167 + "the app not found."); 168 return; 169 } 170 if (appInfo.mIsRemoved) { 171 return; // ignore the already removed apps 172 } 173 appInfo.mIsRemoved = true; 174 175 mRemovedApps.add(appInfo); 176 if (mRemovedApps.size() > mMaxRemovedAppsCount) { 177 AppInfo completelyRemoved = mRemovedApps.removeFirst(); 178 if (completelyRemoved.mIsRemoved && mUidAppInfo.contains(completelyRemoved.mUid)) { 179 mUidAppInfo 180 .get(completelyRemoved.mUid) 181 .removeIf(app -> app.mPackageName.equals(completelyRemoved.mPackageName)); 182 } 183 } 184 } 185 186 /** Returns installed and uninstalled packages, including Apex packages. */ 187 @NonNull getAllPackagesIncludingApex( @onNull PackageManager pm, @NonNull UserHandle user)188 private static List<PackageInfo> getAllPackagesIncludingApex( 189 @NonNull PackageManager pm, @NonNull UserHandle user) { 190 ArrayList<PackageInfo> packages = 191 new ArrayList<>( 192 pm.getInstalledPackagesAsUser( 193 PackageManager.MATCH_UNINSTALLED_PACKAGES 194 | PackageManager.MATCH_ANY_USER, 195 user.getIdentifier())); 196 // Get only installed APEX packages, because inactive apexes can conflict with active ones. 197 for (PackageInfo info : pm.getInstalledPackages(PackageManager.MATCH_APEX)) { 198 if (info.isApex) { 199 packages.add(info); 200 } 201 } 202 return packages; 203 } 204 refetchAllAppInfo(@onNull Context context)205 private void refetchAllAppInfo(@NonNull Context context) { 206 UserManager um = context.getSystemService(UserManager.class); 207 PackageManager pm = context.getPackageManager(); 208 List<UserHandle> users = um.getUserHandles(/* excludeDying= */ true); 209 mUidAppInfo.clear(); 210 if (DEBUG) { 211 Slogf.d(CarLog.TAG_TELEMETRY, "Fetching packages for %d users", users.size()); 212 } 213 for (int i = 0; i < users.size(); i++) { 214 List<PackageInfo> packages = getAllPackagesIncludingApex(pm, users.get(i)); 215 for (int j = 0; j < packages.size(); j++) { 216 onAppAddedOrUpdated( 217 packages.get(j).applicationInfo.uid, packages.get(j).packageName); 218 } 219 } 220 // Add removed apps back to the "mUidAppInfo". 221 for (AppInfo removedApp : mRemovedApps) { 222 // This "appInfo" instance is different than the "removedApp" instance. 223 AppInfo appInfo = getAppInfo(removedApp.mUid, removedApp.mPackageName); 224 if (appInfo == null) { 225 onAppAddedOrUpdated(removedApp.mUid, removedApp.mPackageName); 226 } 227 } 228 } 229 230 private class AppUpdateReceiver extends BroadcastReceiver { 231 @Override onReceive(Context context, Intent intent)232 public void onReceive(Context context, Intent intent) { 233 if (DEBUG) { 234 Slogf.d( 235 CarLog.TAG_TELEMETRY, 236 "UidPackageMapper received intent %s", 237 intent); 238 } 239 if (intent == null || intent.getAction() == null || intent.getData() == null) { 240 Slogf.w( 241 CarLog.TAG_TELEMETRY, 242 "UidPackageMapper received null intent or null action or null data." 243 + " Ignoring."); 244 return; 245 } 246 /* 247 * App updates (ACTION_PACKAGE_REPLACED) actually consist of REMOVE, ADD, and then 248 * REPLACE broadcasts. To avoid waste, we ignore the extra REMOVE and ADD broadcasts 249 * that contain the replacing flag (EXTRA_REPLACING). 250 */ 251 if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) 252 && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 253 return; // Keep only replacing or normal add and remove. 254 } 255 256 Bundle extra = intent.getExtras(); 257 if (extra == null) { 258 Slogf.w( 259 CarLog.TAG_TELEMETRY, 260 "UidPackageMapper received an intent with null extras. Ignoring."); 261 return; 262 } 263 int uid = extra.getInt(Intent.EXTRA_UID, -1); 264 String packageName = intent.getData().getSchemeSpecificPart(); 265 266 if (uid == -1) { 267 Slogf.w( 268 CarLog.TAG_TELEMETRY, 269 "UidPackageMapper received app update intent with no uid. Ignoring."); 270 return; 271 } 272 273 if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) { 274 mTelemetryHandler.post(() -> onAppRemoved(uid, packageName)); 275 } else { 276 mTelemetryHandler.post(() -> onAppAddedOrUpdated(uid, packageName)); 277 } 278 } 279 } 280 281 private class UserUpdateReceiver extends BroadcastReceiver { 282 @Override onReceive(Context context, Intent intent)283 public void onReceive(Context context, Intent intent) { 284 mTelemetryHandler.post(() -> refetchAllAppInfo(context)); 285 } 286 } 287 288 /** Stores information about an app identified by "uid" and "packageName". */ 289 private static class AppInfo { 290 int mUid; 291 @NonNull String mPackageName; 292 boolean mIsRemoved; 293 AppInfo(int uid, @NonNull String packageName)294 AppInfo(int uid, @NonNull String packageName) { 295 mUid = uid; 296 mPackageName = packageName; 297 mIsRemoved = false; 298 } 299 } 300 } 301