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