blob: 80d6ebbd90b3b889c7652fa5f00883e73e14903a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.admin.SecurityLog;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.os.UserHandle;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
/**
* Metrics class for reporting stats to logging infrastructures like statsd
*/
final class PackageMetrics {
public static final int STEP_PREPARE = 1;
public static final int STEP_SCAN = 2;
public static final int STEP_RECONCILE = 3;
public static final int STEP_COMMIT = 4;
public static final int STEP_DEXOPT = 5;
@IntDef(prefix = {"STEP_"}, value = {
STEP_PREPARE,
STEP_SCAN,
STEP_RECONCILE,
STEP_COMMIT,
STEP_DEXOPT
})
@Retention(RetentionPolicy.SOURCE)
public @interface StepInt {
}
private final long mInstallStartTimestampMillis;
private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>();
private final InstallRequest mInstallRequest;
PackageMetrics(InstallRequest installRequest) {
// New instance is used for tracking installation metrics only.
// Other metrics should use static methods of this class.
mInstallStartTimestampMillis = System.currentTimeMillis();
mInstallRequest = installRequest;
}
public void onInstallSucceed() {
reportInstallationToSecurityLog(mInstallRequest.getUserId());
reportInstallationStats(true /* success */);
}
public void onInstallFailed() {
reportInstallationStats(false /* success */);
}
private void reportInstallationStats(boolean success) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
if (userManagerInternal == null) {
// UserManagerService is not available. Skip metrics reporting.
return;
}
final long installDurationMillis =
System.currentTimeMillis() - mInstallStartTimestampMillis;
// write to stats
final Pair<int[], long[]> stepDurations = getInstallStepDurations();
final int[] newUsers = mInstallRequest.getNewUsers();
final int[] originalUsers = mInstallRequest.getOriginUsers();
final String packageName;
// only reporting package name for failed non-adb installations
if (success || mInstallRequest.isInstallFromAdb()) {
packageName = null;
} else {
packageName = mInstallRequest.getName();
}
final int installerPackageUid = mInstallRequest.getInstallerPackageUid();
long versionCode = 0, apksSize = 0;
if (success) {
final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
if (ps != null) {
versionCode = ps.getVersionCode();
apksSize = getApksSize(ps.getPath());
}
}
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
mInstallRequest.getSessionId() /* session_id */,
packageName /* package_name */,
getUid(mInstallRequest.getAppId(), mInstallRequest.getUserId()) /* uid */,
newUsers /* user_ids */,
userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
originalUsers /* original_user_ids */,
userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */,
mInstallRequest.getReturnCode() /* public_return_code */,
mInstallRequest.getInternalErrorCode() /* internal_error_code */,
apksSize /* apks_size_bytes */,
versionCode /* version_code */,
stepDurations.first /* install_steps */,
stepDurations.second /* step_duration_millis */,
installDurationMillis /* total_duration_millis */,
mInstallRequest.getInstallFlags() /* install_flags */,
installerPackageUid /* installer_package_uid */,
-1 /* original_installer_package_uid */,
mInstallRequest.getDataLoaderType() /* data_loader_type */,
mInstallRequest.getRequireUserAction() /* user_action_required_type */,
mInstallRequest.isInstantInstall() /* is_instant */,
mInstallRequest.isInstallReplace() /* is_replace */,
mInstallRequest.isInstallSystem() /* is_system */,
mInstallRequest.isInstallInherit() /* is_inherit */,
mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */,
mInstallRequest.isInstallMove() /* is_move_install */,
false /* is_staged */
);
}
private static int getUid(int appId, int userId) {
if (userId == UserHandle.USER_ALL) {
userId = ActivityManager.getCurrentUser();
}
return UserHandle.getUid(userId, appId);
}
private long getApksSize(File apkDir) {
// TODO(b/249294752): also count apk sizes for failed installs
final AtomicLong apksSize = new AtomicLong();
try (Stream<Path> walkStream = Files.walk(apkDir.toPath())) {
walkStream.filter(p -> p.toFile().isFile()
&& ApkLiteParseUtils.isApkFile(p.toFile())).forEach(
f -> apksSize.addAndGet(f.toFile().length()));
} catch (IOException e) {
// ignore
}
return apksSize.get();
}
public void onStepStarted(@StepInt int step) {
mInstallSteps.put(step, new InstallStep());
}
public void onStepFinished(@StepInt int step) {
final InstallStep installStep = mInstallSteps.get(step);
if (installStep != null) {
// Only valid if the start timestamp is set; otherwise no-op
installStep.finish();
}
}
public void onStepFinished(@StepInt int step, long durationMillis) {
mInstallSteps.put(step, new InstallStep(durationMillis));
}
// List of steps (e.g., 1, 2, 3) and corresponding list of durations (e.g., 200ms, 100ms, 150ms)
private Pair<int[], long[]> getInstallStepDurations() {
ArrayList<Integer> steps = new ArrayList<>();
ArrayList<Long> durations = new ArrayList<>();
for (int i = 0; i < mInstallSteps.size(); i++) {
final long duration = mInstallSteps.valueAt(i).getDurationMillis();
if (duration >= 0) {
steps.add(mInstallSteps.keyAt(i));
durations.add(mInstallSteps.valueAt(i).getDurationMillis());
}
}
int[] stepsArray = new int[steps.size()];
long[] durationsArray = new long[durations.size()];
for (int i = 0; i < stepsArray.length; i++) {
stepsArray[i] = steps.get(i);
durationsArray[i] = durations.get(i);
}
return new Pair<>(stepsArray, durationsArray);
}
private static class InstallStep {
private final long mStartTimestampMillis;
private long mDurationMillis = -1;
InstallStep() {
mStartTimestampMillis = System.currentTimeMillis();
}
InstallStep(long durationMillis) {
mStartTimestampMillis = -1;
mDurationMillis = durationMillis;
}
void finish() {
mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis;
}
long getDurationMillis() {
return mDurationMillis;
}
}
public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags, int userId) {
if (info.mIsUpdate) {
// Not logging uninstalls caused by app updates
return;
}
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
if (userManagerInternal == null) {
// UserManagerService is not available. Skip metrics reporting.
return;
}
final int[] removedUsers = info.mRemovedUsers;
final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers);
final int[] originalUsers = info.mOrigUsers;
final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers);
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED,
getUid(info.mUid, userId), removedUsers, removedUserTypes, originalUsers,
originalUserTypes, deleteFlags, PackageManager.DELETE_SUCCEEDED,
info.mIsRemovedPackageSystemUpdate, !info.mRemovedForAllUsers);
final String packageName = info.mRemovedPackage;
final long versionCode = info.mRemovedPackageVersionCode;
reportUninstallationToSecurityLog(packageName, versionCode, userId);
}
public static void onVerificationFailed(VerifyingSession verifyingSession) {
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
verifyingSession.getSessionId() /* session_id */,
null /* package_name */,
INVALID_UID /* uid */,
null /* user_ids */,
null /* user_types */,
null /* original_user_ids */,
null /* original_user_types */,
verifyingSession.getRet() /* public_return_code */,
0 /* internal_error_code */,
0 /* apks_size_bytes */,
0 /* version_code */,
null /* install_steps */,
null /* step_duration_millis */,
0 /* total_duration_millis */,
0 /* install_flags */,
verifyingSession.getInstallerPackageUid() /* installer_package_uid */,
INVALID_UID /* original_installer_package_uid */,
verifyingSession.getDataLoaderType() /* data_loader_type */,
verifyingSession.getUserActionRequiredType() /* user_action_required_type */,
verifyingSession.isInstant() /* is_instant */,
false /* is_replace */,
false /* is_system */,
verifyingSession.isInherit() /* is_inherit */,
false /* is_installing_existing_as_user */,
false /* is_move_install */,
verifyingSession.isStaged() /* is_staged */
);
}
private void reportInstallationToSecurityLog(int userId) {
if (!SecurityLog.isLoggingEnabled()) {
return;
}
final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
if (ps == null) {
return;
}
final String packageName = ps.getPackageName();
final long versionCode = ps.getVersionCode();
if (!mInstallRequest.isInstallReplace()) {
SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_INSTALLED, packageName, versionCode,
userId);
} else {
SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UPDATED, packageName, versionCode,
userId);
}
}
private static void reportUninstallationToSecurityLog(String packageName, long versionCode,
int userId) {
if (!SecurityLog.isLoggingEnabled()) {
return;
}
SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode,
userId);
}
}