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