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.os.Process.INVALID_UID; 20 21 import android.annotation.IntDef; 22 import android.app.ActivityManager; 23 import android.app.admin.SecurityLog; 24 import android.content.pm.PackageManager; 25 import android.content.pm.parsing.ApkLiteParseUtils; 26 import android.os.UserHandle; 27 import android.util.Pair; 28 import android.util.SparseArray; 29 30 import com.android.internal.util.FrameworkStatsLog; 31 import com.android.server.LocalServices; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.nio.file.Files; 38 import java.nio.file.Path; 39 import java.util.ArrayList; 40 import java.util.concurrent.atomic.AtomicLong; 41 import java.util.stream.Stream; 42 43 /** 44 * Metrics class for reporting stats to logging infrastructures like statsd 45 */ 46 final class PackageMetrics { 47 public static final int STEP_PREPARE = 1; 48 public static final int STEP_SCAN = 2; 49 public static final int STEP_RECONCILE = 3; 50 public static final int STEP_COMMIT = 4; 51 public static final int STEP_DEXOPT = 5; 52 53 @IntDef(prefix = {"STEP_"}, value = { 54 STEP_PREPARE, 55 STEP_SCAN, 56 STEP_RECONCILE, 57 STEP_COMMIT, 58 STEP_DEXOPT 59 }) 60 @Retention(RetentionPolicy.SOURCE) 61 public @interface StepInt { 62 } 63 64 private final long mInstallStartTimestampMillis; 65 private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>(); 66 private final InstallRequest mInstallRequest; 67 PackageMetrics(InstallRequest installRequest)68 PackageMetrics(InstallRequest installRequest) { 69 // New instance is used for tracking installation metrics only. 70 // Other metrics should use static methods of this class. 71 mInstallStartTimestampMillis = System.currentTimeMillis(); 72 mInstallRequest = installRequest; 73 } 74 onInstallSucceed()75 public void onInstallSucceed() { 76 reportInstallationToSecurityLog(mInstallRequest.getUserId()); 77 reportInstallationStats(true /* success */); 78 } 79 onInstallFailed()80 public void onInstallFailed() { 81 reportInstallationStats(false /* success */); 82 } 83 reportInstallationStats(boolean success)84 private void reportInstallationStats(boolean success) { 85 final UserManagerInternal userManagerInternal = 86 LocalServices.getService(UserManagerInternal.class); 87 if (userManagerInternal == null) { 88 // UserManagerService is not available. Skip metrics reporting. 89 return; 90 } 91 92 final long installDurationMillis = 93 System.currentTimeMillis() - mInstallStartTimestampMillis; 94 // write to stats 95 final Pair<int[], long[]> stepDurations = getInstallStepDurations(); 96 final int[] newUsers = mInstallRequest.getNewUsers(); 97 final int[] originalUsers = mInstallRequest.getOriginUsers(); 98 final String packageName; 99 // only reporting package name for failed non-adb installations 100 if (success || mInstallRequest.isInstallFromAdb()) { 101 packageName = null; 102 } else { 103 packageName = mInstallRequest.getName(); 104 } 105 106 final int installerPackageUid = mInstallRequest.getInstallerPackageUid(); 107 108 long versionCode = 0, apksSize = 0; 109 if (success) { 110 final PackageSetting ps = mInstallRequest.getScannedPackageSetting(); 111 if (ps != null) { 112 versionCode = ps.getVersionCode(); 113 apksSize = getApksSize(ps.getPath()); 114 } 115 } 116 117 118 FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED, 119 mInstallRequest.getSessionId() /* session_id */, 120 packageName /* package_name */, 121 getUid(mInstallRequest.getAppId(), mInstallRequest.getUserId()) /* uid */, 122 newUsers /* user_ids */, 123 userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */, 124 originalUsers /* original_user_ids */, 125 userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */, 126 mInstallRequest.getReturnCode() /* public_return_code */, 127 mInstallRequest.getInternalErrorCode() /* internal_error_code */, 128 apksSize /* apks_size_bytes */, 129 versionCode /* version_code */, 130 stepDurations.first /* install_steps */, 131 stepDurations.second /* step_duration_millis */, 132 installDurationMillis /* total_duration_millis */, 133 mInstallRequest.getInstallFlags() /* install_flags */, 134 installerPackageUid /* installer_package_uid */, 135 -1 /* original_installer_package_uid */, 136 mInstallRequest.getDataLoaderType() /* data_loader_type */, 137 mInstallRequest.getRequireUserAction() /* user_action_required_type */, 138 mInstallRequest.isInstantInstall() /* is_instant */, 139 mInstallRequest.isInstallReplace() /* is_replace */, 140 mInstallRequest.isInstallSystem() /* is_system */, 141 mInstallRequest.isInstallInherit() /* is_inherit */, 142 mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */, 143 mInstallRequest.isInstallMove() /* is_move_install */, 144 false /* is_staged */ 145 ); 146 } 147 getUid(int appId, int userId)148 private static int getUid(int appId, int userId) { 149 if (userId == UserHandle.USER_ALL) { 150 userId = ActivityManager.getCurrentUser(); 151 } 152 return UserHandle.getUid(userId, appId); 153 } 154 getApksSize(File apkDir)155 private long getApksSize(File apkDir) { 156 // TODO(b/249294752): also count apk sizes for failed installs 157 final AtomicLong apksSize = new AtomicLong(); 158 try (Stream<Path> walkStream = Files.walk(apkDir.toPath())) { 159 walkStream.filter(p -> p.toFile().isFile() 160 && ApkLiteParseUtils.isApkFile(p.toFile())).forEach( 161 f -> apksSize.addAndGet(f.toFile().length())); 162 } catch (IOException e) { 163 // ignore 164 } 165 return apksSize.get(); 166 } 167 onStepStarted(@tepInt int step)168 public void onStepStarted(@StepInt int step) { 169 mInstallSteps.put(step, new InstallStep()); 170 } 171 onStepFinished(@tepInt int step)172 public void onStepFinished(@StepInt int step) { 173 final InstallStep installStep = mInstallSteps.get(step); 174 if (installStep != null) { 175 // Only valid if the start timestamp is set; otherwise no-op 176 installStep.finish(); 177 } 178 } 179 onStepFinished(@tepInt int step, long durationMillis)180 public void onStepFinished(@StepInt int step, long durationMillis) { 181 mInstallSteps.put(step, new InstallStep(durationMillis)); 182 } 183 184 // List of steps (e.g., 1, 2, 3) and corresponding list of durations (e.g., 200ms, 100ms, 150ms) getInstallStepDurations()185 private Pair<int[], long[]> getInstallStepDurations() { 186 ArrayList<Integer> steps = new ArrayList<>(); 187 ArrayList<Long> durations = new ArrayList<>(); 188 for (int i = 0; i < mInstallSteps.size(); i++) { 189 final long duration = mInstallSteps.valueAt(i).getDurationMillis(); 190 if (duration >= 0) { 191 steps.add(mInstallSteps.keyAt(i)); 192 durations.add(mInstallSteps.valueAt(i).getDurationMillis()); 193 } 194 } 195 int[] stepsArray = new int[steps.size()]; 196 long[] durationsArray = new long[durations.size()]; 197 for (int i = 0; i < stepsArray.length; i++) { 198 stepsArray[i] = steps.get(i); 199 durationsArray[i] = durations.get(i); 200 } 201 return new Pair<>(stepsArray, durationsArray); 202 } 203 204 private static class InstallStep { 205 private final long mStartTimestampMillis; 206 private long mDurationMillis = -1; 207 InstallStep()208 InstallStep() { 209 mStartTimestampMillis = System.currentTimeMillis(); 210 } 211 InstallStep(long durationMillis)212 InstallStep(long durationMillis) { 213 mStartTimestampMillis = -1; 214 mDurationMillis = durationMillis; 215 } 216 finish()217 void finish() { 218 mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis; 219 } 220 getDurationMillis()221 long getDurationMillis() { 222 return mDurationMillis; 223 } 224 } 225 onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags, int userId)226 public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags, int userId) { 227 if (info.mIsUpdate) { 228 // Not logging uninstalls caused by app updates 229 return; 230 } 231 final UserManagerInternal userManagerInternal = 232 LocalServices.getService(UserManagerInternal.class); 233 if (userManagerInternal == null) { 234 // UserManagerService is not available. Skip metrics reporting. 235 return; 236 } 237 final int[] removedUsers = info.mRemovedUsers; 238 final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers); 239 final int[] originalUsers = info.mOrigUsers; 240 final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers); 241 FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED, 242 getUid(info.mUid, userId), removedUsers, removedUserTypes, originalUsers, 243 originalUserTypes, deleteFlags, PackageManager.DELETE_SUCCEEDED, 244 info.mIsRemovedPackageSystemUpdate, !info.mRemovedForAllUsers); 245 final String packageName = info.mRemovedPackage; 246 final long versionCode = info.mRemovedPackageVersionCode; 247 reportUninstallationToSecurityLog(packageName, versionCode, userId); 248 } 249 onVerificationFailed(VerifyingSession verifyingSession)250 public static void onVerificationFailed(VerifyingSession verifyingSession) { 251 FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED, 252 verifyingSession.getSessionId() /* session_id */, 253 null /* package_name */, 254 INVALID_UID /* uid */, 255 null /* user_ids */, 256 null /* user_types */, 257 null /* original_user_ids */, 258 null /* original_user_types */, 259 verifyingSession.getRet() /* public_return_code */, 260 0 /* internal_error_code */, 261 0 /* apks_size_bytes */, 262 0 /* version_code */, 263 null /* install_steps */, 264 null /* step_duration_millis */, 265 0 /* total_duration_millis */, 266 0 /* install_flags */, 267 verifyingSession.getInstallerPackageUid() /* installer_package_uid */, 268 INVALID_UID /* original_installer_package_uid */, 269 verifyingSession.getDataLoaderType() /* data_loader_type */, 270 verifyingSession.getUserActionRequiredType() /* user_action_required_type */, 271 verifyingSession.isInstant() /* is_instant */, 272 false /* is_replace */, 273 false /* is_system */, 274 verifyingSession.isInherit() /* is_inherit */, 275 false /* is_installing_existing_as_user */, 276 false /* is_move_install */, 277 verifyingSession.isStaged() /* is_staged */ 278 ); 279 } 280 reportInstallationToSecurityLog(int userId)281 private void reportInstallationToSecurityLog(int userId) { 282 if (!SecurityLog.isLoggingEnabled()) { 283 return; 284 } 285 final PackageSetting ps = mInstallRequest.getScannedPackageSetting(); 286 if (ps == null) { 287 return; 288 } 289 final String packageName = ps.getPackageName(); 290 final long versionCode = ps.getVersionCode(); 291 if (!mInstallRequest.isInstallReplace()) { 292 SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_INSTALLED, packageName, versionCode, 293 userId); 294 } else { 295 SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UPDATED, packageName, versionCode, 296 userId); 297 } 298 } 299 reportUninstallationToSecurityLog(String packageName, long versionCode, int userId)300 private static void reportUninstallationToSecurityLog(String packageName, long versionCode, 301 int userId) { 302 if (!SecurityLog.isLoggingEnabled()) { 303 return; 304 } 305 SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode, 306 userId); 307 } 308 } 309