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.server.pm; 18 19 import static android.content.pm.PackageManager.GET_RESOLVED_FILTER; 20 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; 21 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 22 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; 23 import static android.os.Process.INVALID_UID; 24 import static android.os.Process.SYSTEM_UID; 25 26 import android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.annotation.UserIdInt; 30 import android.app.ActivityManager; 31 import android.app.admin.SecurityLog; 32 import android.content.ComponentName; 33 import android.content.Intent; 34 import android.content.pm.ActivityInfo; 35 import android.content.pm.Flags; 36 import android.content.pm.PackageManager; 37 import android.content.pm.ResolveInfo; 38 import android.content.pm.parsing.ApkLiteParseUtils; 39 import android.os.UserHandle; 40 import android.text.TextUtils; 41 import android.util.Pair; 42 import android.util.Slog; 43 import android.util.SparseArray; 44 45 import com.android.internal.util.FrameworkStatsLog; 46 import com.android.server.LocalServices; 47 import com.android.server.pm.pkg.AndroidPackage; 48 49 import java.io.File; 50 import java.io.IOException; 51 import java.lang.annotation.Retention; 52 import java.lang.annotation.RetentionPolicy; 53 import java.nio.file.FileVisitResult; 54 import java.nio.file.Files; 55 import java.nio.file.Path; 56 import java.nio.file.SimpleFileVisitor; 57 import java.nio.file.attribute.BasicFileAttributes; 58 import java.util.ArrayList; 59 import java.util.List; 60 import java.util.concurrent.atomic.AtomicLong; 61 62 /** 63 * Metrics class for reporting stats to logging infrastructures like statsd 64 */ 65 final class PackageMetrics { 66 private static final String TAG = "PackageMetrics"; 67 public static final int STEP_PREPARE = 1; 68 public static final int STEP_SCAN = 2; 69 public static final int STEP_RECONCILE = 3; 70 public static final int STEP_COMMIT = 4; 71 public static final int STEP_DEXOPT = 5; 72 public static final int STEP_FREEZE_INSTALL = 6; 73 74 @IntDef(prefix = {"STEP_"}, value = { 75 STEP_PREPARE, 76 STEP_SCAN, 77 STEP_RECONCILE, 78 STEP_COMMIT, 79 STEP_DEXOPT, 80 STEP_FREEZE_INSTALL 81 }) 82 @Retention(RetentionPolicy.SOURCE) 83 public @interface StepInt { 84 } 85 86 private final long mInstallStartTimestampMillis; 87 private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>(); 88 private final InstallRequest mInstallRequest; 89 PackageMetrics(InstallRequest installRequest)90 PackageMetrics(InstallRequest installRequest) { 91 // New instance is used for tracking installation metrics only. 92 // Other metrics should use static methods of this class. 93 mInstallStartTimestampMillis = System.currentTimeMillis(); 94 mInstallRequest = installRequest; 95 } 96 onInstallSucceed()97 public void onInstallSucceed() { 98 reportInstallationToSecurityLog(mInstallRequest.getUserId()); 99 reportInstallationStats(true /* success */); 100 } 101 onInstallFailed()102 public void onInstallFailed() { 103 reportInstallationStats(false /* success */); 104 } 105 reportInstallationStats(boolean success)106 private void reportInstallationStats(boolean success) { 107 final UserManagerInternal userManagerInternal = 108 LocalServices.getService(UserManagerInternal.class); 109 if (userManagerInternal == null) { 110 // UserManagerService is not available. Skip metrics reporting. 111 return; 112 } 113 114 final long installDurationMillis = 115 System.currentTimeMillis() - mInstallStartTimestampMillis; 116 // write to stats 117 final Pair<int[], long[]> stepDurations = getInstallStepDurations(); 118 final int[] newUsers = mInstallRequest.getNewUsers(); 119 final int[] originalUsers = mInstallRequest.getOriginUsers(); 120 final String packageName; 121 // only reporting package name for failed non-adb installations 122 if (success || mInstallRequest.isInstallFromAdb()) { 123 packageName = null; 124 } else { 125 packageName = mInstallRequest.getName(); 126 } 127 128 final int installerPackageUid = mInstallRequest.getInstallerPackageUid(); 129 130 long versionCode = 0, apksSize = 0; 131 if (success) { 132 if (mInstallRequest.isInstallForUsers()) { 133 // In case of installExistingPackageAsUser, there's no scanned PackageSetting 134 // in the request but the pkg object should be readily available 135 AndroidPackage pkg = mInstallRequest.getPkg(); 136 if (pkg != null) { 137 versionCode = pkg.getLongVersionCode(); 138 apksSize = getApksSize(new File(pkg.getPath())); 139 } 140 } else { 141 final PackageSetting ps = mInstallRequest.getScannedPackageSetting(); 142 if (ps != null) { 143 versionCode = ps.getVersionCode(); 144 apksSize = getApksSize(ps.getPath()); 145 } 146 } 147 } 148 149 150 FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED, 151 mInstallRequest.getSessionId() /* session_id */, 152 packageName /* package_name */, 153 getUid(mInstallRequest.getAppId(), mInstallRequest.getUserId()) /* uid */, 154 newUsers /* user_ids */, 155 userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */, 156 originalUsers /* original_user_ids */, 157 userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */, 158 mInstallRequest.getReturnCode() /* public_return_code */, 159 mInstallRequest.getInternalErrorCode() /* internal_error_code */, 160 apksSize /* apks_size_bytes */, 161 versionCode /* version_code */, 162 stepDurations.first /* install_steps */, 163 stepDurations.second /* step_duration_millis */, 164 installDurationMillis /* total_duration_millis */, 165 mInstallRequest.getInstallFlags() /* install_flags */, 166 installerPackageUid /* installer_package_uid */, 167 -1 /* original_installer_package_uid */, 168 mInstallRequest.getDataLoaderType() /* data_loader_type */, 169 mInstallRequest.getRequireUserAction() /* user_action_required_type */, 170 mInstallRequest.isInstantInstall() /* is_instant */, 171 mInstallRequest.isInstallReplace() /* is_replace */, 172 mInstallRequest.isInstallSystem() /* is_system */, 173 mInstallRequest.isInstallInherit() /* is_inherit */, 174 mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */, 175 mInstallRequest.isInstallMove() /* is_move_install */, 176 false /* is_staged */ 177 ); 178 } 179 getUid(int appId, int userId)180 private static int getUid(int appId, int userId) { 181 if (userId == UserHandle.USER_ALL) { 182 userId = ActivityManager.getCurrentUser(); 183 } 184 return UserHandle.getUid(userId, appId); 185 } 186 getApksSize(File apkDir)187 private long getApksSize(File apkDir) { 188 // TODO(b/249294752): also count apk sizes for failed installs 189 final AtomicLong apksSize = new AtomicLong(); 190 try { 191 Files.walkFileTree(apkDir.toPath(), new SimpleFileVisitor<>() { 192 @Override 193 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 194 throws IOException { 195 if (dir.equals(apkDir.toPath())) { 196 return FileVisitResult.CONTINUE; 197 } else { 198 return FileVisitResult.SKIP_SUBTREE; 199 } 200 } 201 202 @Override 203 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 204 throws IOException { 205 if (file.toFile().isFile() && ApkLiteParseUtils.isApkFile(file.toFile())) { 206 apksSize.addAndGet(file.toFile().length()); 207 } 208 return FileVisitResult.CONTINUE; 209 } 210 }); 211 } catch (IOException e) { 212 // ignore 213 } 214 return apksSize.get(); 215 } 216 onStepStarted(@tepInt int step)217 public void onStepStarted(@StepInt int step) { 218 mInstallSteps.put(step, new InstallStep()); 219 } 220 onStepFinished(@tepInt int step)221 public void onStepFinished(@StepInt int step) { 222 final InstallStep installStep = mInstallSteps.get(step); 223 if (installStep != null) { 224 // Only valid if the start timestamp is set; otherwise no-op 225 installStep.finish(); 226 } 227 } 228 onStepFinished(@tepInt int step, long durationMillis)229 public void onStepFinished(@StepInt int step, long durationMillis) { 230 mInstallSteps.put(step, new InstallStep(durationMillis)); 231 } 232 233 // List of steps (e.g., 1, 2, 3) and corresponding list of durations (e.g., 200ms, 100ms, 150ms) getInstallStepDurations()234 private Pair<int[], long[]> getInstallStepDurations() { 235 ArrayList<Integer> steps = new ArrayList<>(); 236 ArrayList<Long> durations = new ArrayList<>(); 237 for (int i = 0; i < mInstallSteps.size(); i++) { 238 final long duration = mInstallSteps.valueAt(i).getDurationMillis(); 239 if (duration >= 0) { 240 steps.add(mInstallSteps.keyAt(i)); 241 durations.add(mInstallSteps.valueAt(i).getDurationMillis()); 242 } 243 } 244 int[] stepsArray = new int[steps.size()]; 245 long[] durationsArray = new long[durations.size()]; 246 for (int i = 0; i < stepsArray.length; i++) { 247 stepsArray[i] = steps.get(i); 248 durationsArray[i] = durations.get(i); 249 } 250 return new Pair<>(stepsArray, durationsArray); 251 } 252 253 private static class InstallStep { 254 private final long mStartTimestampMillis; 255 private long mDurationMillis = -1; 256 InstallStep()257 InstallStep() { 258 mStartTimestampMillis = System.currentTimeMillis(); 259 } 260 InstallStep(long durationMillis)261 InstallStep(long durationMillis) { 262 mStartTimestampMillis = -1; 263 mDurationMillis = durationMillis; 264 } 265 finish()266 void finish() { 267 mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis; 268 } 269 getDurationMillis()270 long getDurationMillis() { 271 return mDurationMillis; 272 } 273 } 274 onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags, int userId)275 public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags, int userId) { 276 if (info.mIsUpdate) { 277 // Not logging uninstalls caused by app updates 278 return; 279 } 280 final UserManagerInternal userManagerInternal = 281 LocalServices.getService(UserManagerInternal.class); 282 if (userManagerInternal == null) { 283 // UserManagerService is not available. Skip metrics reporting. 284 return; 285 } 286 final int[] removedUsers = info.mRemovedUsers; 287 final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers); 288 final int[] originalUsers = info.mOrigUsers; 289 final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers); 290 FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED, 291 getUid(info.mUid, userId), removedUsers, removedUserTypes, originalUsers, 292 originalUserTypes, deleteFlags, PackageManager.DELETE_SUCCEEDED, 293 info.mIsRemovedPackageSystemUpdate, !info.mRemovedForAllUsers); 294 final String packageName = info.mRemovedPackage; 295 final long versionCode = info.mRemovedPackageVersionCode; 296 reportUninstallationToSecurityLog(packageName, versionCode, userId); 297 } 298 onVerificationFailed(VerifyingSession verifyingSession)299 public static void onVerificationFailed(VerifyingSession verifyingSession) { 300 FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED, 301 verifyingSession.getSessionId() /* session_id */, 302 null /* package_name */, 303 INVALID_UID /* uid */, 304 null /* user_ids */, 305 null /* user_types */, 306 null /* original_user_ids */, 307 null /* original_user_types */, 308 verifyingSession.getRet() /* public_return_code */, 309 0 /* internal_error_code */, 310 0 /* apks_size_bytes */, 311 0 /* version_code */, 312 null /* install_steps */, 313 null /* step_duration_millis */, 314 0 /* total_duration_millis */, 315 0 /* install_flags */, 316 verifyingSession.getInstallerPackageUid() /* installer_package_uid */, 317 INVALID_UID /* original_installer_package_uid */, 318 verifyingSession.getDataLoaderType() /* data_loader_type */, 319 verifyingSession.getUserActionRequiredType() /* user_action_required_type */, 320 verifyingSession.isInstant() /* is_instant */, 321 false /* is_replace */, 322 false /* is_system */, 323 verifyingSession.isInherit() /* is_inherit */, 324 false /* is_installing_existing_as_user */, 325 false /* is_move_install */, 326 verifyingSession.isStaged() /* is_staged */ 327 ); 328 } 329 reportInstallationToSecurityLog(int userId)330 private void reportInstallationToSecurityLog(int userId) { 331 if (!SecurityLog.isLoggingEnabled()) { 332 return; 333 } 334 // TODO: Remove temp try-catch to avoid IllegalStateException. The reason is because 335 // the scan result is null for installExistingPackageAsUser(). Because it's installing 336 // a package that's already existing, there's no scanning or parsing involved 337 try { 338 final PackageSetting ps = mInstallRequest.getScannedPackageSetting(); 339 if (ps == null) { 340 return; 341 } 342 final String packageName = ps.getPackageName(); 343 final long versionCode = ps.getVersionCode(); 344 if (!mInstallRequest.isInstallReplace()) { 345 SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_INSTALLED, packageName, versionCode, 346 userId); 347 } else { 348 SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UPDATED, packageName, versionCode, 349 userId); 350 } 351 } catch (IllegalStateException | NullPointerException e) { 352 // no-op 353 } 354 } 355 reportUninstallationToSecurityLog(String packageName, long versionCode, int userId)356 private static void reportUninstallationToSecurityLog(String packageName, long versionCode, 357 int userId) { 358 if (!SecurityLog.isLoggingEnabled()) { 359 return; 360 } 361 SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode, 362 userId); 363 } 364 365 public static class ComponentStateMetrics { 366 public int mUid; 367 public int mCallingUid; 368 public int mComponentOldState; 369 public int mComponentNewState; 370 public boolean mIsForWholeApp; 371 @NonNull private String mPackageName; 372 @Nullable private String mClassName; 373 ComponentStateMetrics(@onNull PackageManager.ComponentEnabledSetting setting, int uid, int componentOldState, int callingUid)374 ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid, 375 int componentOldState, int callingUid) { 376 mUid = uid; 377 mComponentOldState = componentOldState; 378 mComponentNewState = setting.getEnabledState(); 379 mIsForWholeApp = !setting.isComponent(); 380 mPackageName = setting.getPackageName(); 381 mClassName = setting.getClassName(); 382 mCallingUid = callingUid; 383 } 384 isLauncherActivity(@onNull Computer computer, @UserIdInt int userId)385 public boolean isLauncherActivity(@NonNull Computer computer, @UserIdInt int userId) { 386 if (mIsForWholeApp) { 387 return false; 388 } 389 // Query the launcher activities with the package name. 390 final Intent intent = new Intent(Intent.ACTION_MAIN); 391 intent.addCategory(Intent.CATEGORY_LAUNCHER); 392 intent.setPackage(mPackageName); 393 List<ResolveInfo> launcherActivities = computer.queryIntentActivitiesInternal( 394 intent, null, 395 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | GET_RESOLVED_FILTER 396 | MATCH_DISABLED_COMPONENTS, SYSTEM_UID, userId); 397 final int launcherActivitiesSize = 398 launcherActivities != null ? launcherActivities.size() : 0; 399 for (int i = 0; i < launcherActivitiesSize; i++) { 400 ResolveInfo resolveInfo = launcherActivities.get(i); 401 if (isSameComponent(resolveInfo.activityInfo)) { 402 return true; 403 } 404 } 405 return false; 406 } 407 isSameComponent(ActivityInfo activityInfo)408 private boolean isSameComponent(ActivityInfo activityInfo) { 409 if (activityInfo == null) { 410 return false; 411 } 412 return mIsForWholeApp ? TextUtils.equals(activityInfo.packageName, mPackageName) 413 : activityInfo.getComponentName().equals( 414 new ComponentName(mPackageName, mClassName)); 415 } 416 } 417 reportComponentStateChanged(@onNull Computer computer, List<ComponentStateMetrics> componentStateMetricsList, @UserIdInt int userId)418 public static void reportComponentStateChanged(@NonNull Computer computer, 419 List<ComponentStateMetrics> componentStateMetricsList, @UserIdInt int userId) { 420 if (!Flags.componentStateChangedMetrics()) { 421 return; 422 } 423 if (componentStateMetricsList == null || componentStateMetricsList.isEmpty()) { 424 Slog.d(TAG, "Fail to report component state due to metrics is empty"); 425 return; 426 } 427 final int metricsSize = componentStateMetricsList.size(); 428 for (int i = 0; i < metricsSize; i++) { 429 final ComponentStateMetrics componentStateMetrics = componentStateMetricsList.get(i); 430 reportComponentStateChanged(componentStateMetrics.mUid, 431 componentStateMetrics.mComponentOldState, 432 componentStateMetrics.mComponentNewState, 433 componentStateMetrics.isLauncherActivity(computer, userId), 434 componentStateMetrics.mIsForWholeApp, 435 componentStateMetrics.mCallingUid); 436 } 437 } 438 reportComponentStateChanged(int uid, int componentOldState, int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid)439 private static void reportComponentStateChanged(int uid, int componentOldState, 440 int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid) { 441 FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED, 442 uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid); 443 } 444 } 445