• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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