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