Loading services/core/java/com/android/server/pm/MultiuserDeprecationReporter.java 0 → 100644 +131 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 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 android.annotation.Nullable; import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.SparseIntArray; import com.android.server.LocalServices; import java.io.PrintWriter; // TODO(b/414326600): rename (and add unit tests) once it's used to log blocked HSU actions /** * Class used to report deprecated calls. */ final class MultiuserDeprecationReporter { private final Handler mHandler; // Key is "absolute" uid / app id (i.e., stripping out the user id part), value is count. @Nullable // Only set when logging is enabled private final SparseIntArray mGetMainUserCalls; // Set on demand, Should not be used directly (but through getPackageManagerInternal() instead). @Nullable private PackageManagerInternal mPmInternal; MultiuserDeprecationReporter(Handler handler) { mHandler = handler; if (Build.isDebuggable()) { mGetMainUserCalls = new SparseIntArray(); } else mGetMainUserCalls = null; } // TODO(b/414326600): add unit tests (once the proper formats are determined). void logGetMainUserCall() { if (mGetMainUserCalls == null) { return; } // Must set before posting to the handler (otherwise it would always return the system UID) int uid = Binder.getCallingUid(); mHandler.post(() -> { int canonicalUid = UserHandle.getAppId(uid); int newCount = mGetMainUserCalls.get(canonicalUid, 0) + 1; mGetMainUserCalls.put(canonicalUid, newCount); }); } // NOTE: output format might changed, so it should not be used for automated testing purposes // (a proto version will be provided when it's ready) void dump(PrintWriter pw) { // TODO(b/414326600): add unit tests (once the proper formats are determined). if (mGetMainUserCalls == null) { pw.println("Not logging getMainUser() calls"); return; } // TODO(b/414326600): should dump in the mHandler thread (as its state is written in that // thread) , but it would require blocking the caller until it's done // TODO(b/414326600): should also dump on proto, but we need to wait until the format is // properly defined (for example, we might want to log a generic "user violation" that would // include other metrics such as stuff that shouldn't be called when the current user is the // headless system user) int size = mGetMainUserCalls.size(); if (size == 0) { pw.println("Good News, Everyone!: no app called getMainUser()!"); return; } pw.printf("%d apps called getMainUser():\n", size); var pm = getPackageManagerInternal(); for (int i = 0; i < size; i++) { int canonicalUid = mGetMainUserCalls.keyAt(i); int count = mGetMainUserCalls.valueAt(i); String pkgName = getPackageNameForLoggingPurposes(pm, canonicalUid); // uid is the canonical UID, but including "canonical" would add extra churn / bytes pw.printf(" %s (uid %d): %d calls\n", pkgName, canonicalUid, count); } } /** Retrieves the internal package manager interface. */ private PackageManagerInternal getPackageManagerInternal() { // Don't need to synchronize; worst-case scenario LocalServices will be called twice. if (mPmInternal == null) { mPmInternal = LocalServices.getService(PackageManagerInternal.class); } return mPmInternal; } // TODO(b/414326600): this method is taking a simplest aproach to get the uid, but it's not // handling corner cases like an app not available on user 0 or multiple apps with the same uid. // This is fine for now, but the final solution need to take those scenarios in account. private static String getPackageNameForLoggingPurposes(PackageManagerInternal pm, int uid) { if (uid == Process.SYSTEM_UID) { // Many apps might be running as system (because they declare sharedUserId in the // manifest), so we wouldn't know for sure which one calls it here return "system"; } var pkg = pm.getPackage(uid); // TODO(b/414326600): if it's from system, it might be useful to log the method that's // calling it, but that's expensive (so we should guard using a system property) and we'd // need to change the type of mGetMainUserCalls as well - for now, the solution is to look // at logcat (which logs the full stacktrace the tag is VERBOSE). // TODO(b/414326600): figure out proper way to handle null (for example, it'is also null // for root UID). return pkg == null ? "system" : pkg.getPackageName(); } } services/core/java/com/android/server/pm/UserManagerService.java +28 −17 Original line number Original line Diff line number Diff line Loading @@ -398,6 +398,7 @@ public class UserManagerService extends IUserManager.Stub { private final Object mAppRestrictionsLock = new Object(); private final Object mAppRestrictionsLock = new Object(); private final Handler mHandler; private final Handler mHandler; private final MultiuserDeprecationReporter mDeprecationReporter; private final ThreadPoolExecutor mInternalExecutor; private final ThreadPoolExecutor mInternalExecutor; Loading Loading @@ -1111,6 +1112,7 @@ public class UserManagerService extends IUserManager.Stub { mPackagesLock = packagesLock; mPackagesLock = packagesLock; mUsers = users != null ? users : new SparseArray<>(); mUsers = users != null ? users : new SparseArray<>(); mHandler = new MainHandler(); mHandler = new MainHandler(); mDeprecationReporter = new MultiuserDeprecationReporter(mHandler); mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1, mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1, /* keepAliveTime */ 24, TimeUnit.HOURS, new LinkedBlockingQueue<>()); /* keepAliveTime */ 24, TimeUnit.HOURS, new LinkedBlockingQueue<>()); mUserVisibilityMediator = new UserVisibilityMediator(mHandler); mUserVisibilityMediator = new UserVisibilityMediator(mHandler); Loading Loading @@ -1369,6 +1371,7 @@ public class UserManagerService extends IUserManager.Stub { @Override @Override public @CanBeNULL @UserIdInt int getMainUserId() { public @CanBeNULL @UserIdInt int getMainUserId() { checkQueryOrCreateUsersPermission("get main user id"); checkQueryOrCreateUsersPermission("get main user id"); mDeprecationReporter.logGetMainUserCall(); return getMainUserIdUnchecked(); return getMainUserIdUnchecked(); } } Loading @@ -1391,7 +1394,6 @@ public class UserManagerService extends IUserManager.Stub { return null; return null; } } private @CanBeNULL @UserIdInt int getPrivateProfileUserId() { private @CanBeNULL @UserIdInt int getPrivateProfileUserId() { synchronized (mUsersLock) { synchronized (mUsersLock) { for (int userId : getUserIds()) { for (int userId : getUserIds()) { Loading Loading @@ -4775,10 +4777,13 @@ public class UserManagerService extends IUserManager.Stub { /** /** * Checks whether the default state of the device is headless system user mode, i.e. what the * Checks whether the default state of the device is headless system user mode, i.e. what the * mode would be if we did a fresh factory reset. * mode would be if we did a fresh factory reset. * If the mode is being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be * * returned instead. * <p>If the mode is being emulated (through the * Note that, even in the absence of emulation, a device might deviate from the current default * {@link UserManager#SYSTEM_USER_MODE_EMULATION_PROPERTY} system property) then the value * due to an OTA changing the default (which won't change the already-decided mode). * represented by that system property will be returned instead. * * <p>Note that, even in the absence of emulation, a device might deviate from the current * default due to an OTA changing the default (which won't change the already-decided mode). */ */ private boolean isDefaultHeadlessSystemUserMode() { private boolean isDefaultHeadlessSystemUserMode() { if (!Build.isDebuggable()) { if (!Build.isDebuggable()) { Loading Loading @@ -8041,6 +8046,9 @@ public class UserManagerService extends IUserManager.Stub { case "--visibility-mediator": case "--visibility-mediator": mUserVisibilityMediator.dump(pw, args); mUserVisibilityMediator.dump(pw, args); return; return; case "--deprecated-calls": mDeprecationReporter.dump(pw); return; } } } } Loading Loading @@ -8179,17 +8187,23 @@ public class UserManagerService extends IUserManager.Stub { mUserTypes.valueAt(i).dump(pw, " "); mUserTypes.valueAt(i).dump(pw, " "); } } // TODO: create IndentingPrintWriter at the beginning of dump() and use the proper pw.println(); // indentation methods instead of explicit printing " " mDeprecationReporter.dump(pw); try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) { // NOTE: add new stuff here, as pw is closed after the try-with-resources block below // TODO(b/163423525): create IndentingPrintWriter at the beginning of dump() and use the // proper indentation methods instead of explicit printing " "; that would also solve the // pw closure as well. try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) { // Dump SystemPackageInstaller info // Dump SystemPackageInstaller info ipw.println(); ipw.println(); mSystemPackageInstaller.dump(ipw); mSystemPackageInstaller.dump(ipw); } // NOTE: pw's not available after this point as it's auto-closed by ipw, so new dump // NOTE: pw's not available after this point as it's auto-closed by ipw, so new dump // statements should use ipw below // statements should use ipw below } } } private void dumpUser(PrintWriter pw, @CanBeCURRENT @UserIdInt int userId, StringBuilder sb, private void dumpUser(PrintWriter pw, @CanBeCURRENT @UserIdInt int userId, StringBuilder sb, Loading Loading @@ -8348,8 +8362,8 @@ public class UserManagerService extends IUserManager.Stub { if (userData != null) { if (userData != null) { writeUserLP(userData); writeUserLP(userData); } else { } else { Slog.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user " + userId Slogf.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user %d, it was " + ", it was probably removed before handler could handle it"); + "probably removed before handler could handle it", userId); } } } } break; break; Loading Loading @@ -8858,8 +8872,6 @@ public class UserManagerService extends IUserManager.Stub { } } } // class LocalService } // class LocalService /** /** * Check if user has restrictions * Check if user has restrictions * @param restriction restrictions to check * @param restriction restrictions to check Loading Loading @@ -8978,7 +8990,7 @@ public class UserManagerService extends IUserManager.Stub { /** Retrieves the internal package manager interface. */ /** Retrieves the internal package manager interface. */ private PackageManagerInternal getPackageManagerInternal() { private PackageManagerInternal getPackageManagerInternal() { // Don't need to synchonize; worst-case scenario LocalServices will be called twice. // Don't need to synchronize; worst-case scenario LocalServices will be called twice. if (mPmInternal == null) { if (mPmInternal == null) { mPmInternal = LocalServices.getService(PackageManagerInternal.class); mPmInternal = LocalServices.getService(PackageManagerInternal.class); } } Loading Loading @@ -9156,5 +9168,4 @@ public class UserManagerService extends IUserManager.Stub { public UserJourneyLogger getUserJourneyLogger() { public UserJourneyLogger getUserJourneyLogger() { return mUserJourneyLogger; return mUserJourneyLogger; } } } } Loading
services/core/java/com/android/server/pm/MultiuserDeprecationReporter.java 0 → 100644 +131 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 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 android.annotation.Nullable; import android.content.pm.PackageManagerInternal; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.SparseIntArray; import com.android.server.LocalServices; import java.io.PrintWriter; // TODO(b/414326600): rename (and add unit tests) once it's used to log blocked HSU actions /** * Class used to report deprecated calls. */ final class MultiuserDeprecationReporter { private final Handler mHandler; // Key is "absolute" uid / app id (i.e., stripping out the user id part), value is count. @Nullable // Only set when logging is enabled private final SparseIntArray mGetMainUserCalls; // Set on demand, Should not be used directly (but through getPackageManagerInternal() instead). @Nullable private PackageManagerInternal mPmInternal; MultiuserDeprecationReporter(Handler handler) { mHandler = handler; if (Build.isDebuggable()) { mGetMainUserCalls = new SparseIntArray(); } else mGetMainUserCalls = null; } // TODO(b/414326600): add unit tests (once the proper formats are determined). void logGetMainUserCall() { if (mGetMainUserCalls == null) { return; } // Must set before posting to the handler (otherwise it would always return the system UID) int uid = Binder.getCallingUid(); mHandler.post(() -> { int canonicalUid = UserHandle.getAppId(uid); int newCount = mGetMainUserCalls.get(canonicalUid, 0) + 1; mGetMainUserCalls.put(canonicalUid, newCount); }); } // NOTE: output format might changed, so it should not be used for automated testing purposes // (a proto version will be provided when it's ready) void dump(PrintWriter pw) { // TODO(b/414326600): add unit tests (once the proper formats are determined). if (mGetMainUserCalls == null) { pw.println("Not logging getMainUser() calls"); return; } // TODO(b/414326600): should dump in the mHandler thread (as its state is written in that // thread) , but it would require blocking the caller until it's done // TODO(b/414326600): should also dump on proto, but we need to wait until the format is // properly defined (for example, we might want to log a generic "user violation" that would // include other metrics such as stuff that shouldn't be called when the current user is the // headless system user) int size = mGetMainUserCalls.size(); if (size == 0) { pw.println("Good News, Everyone!: no app called getMainUser()!"); return; } pw.printf("%d apps called getMainUser():\n", size); var pm = getPackageManagerInternal(); for (int i = 0; i < size; i++) { int canonicalUid = mGetMainUserCalls.keyAt(i); int count = mGetMainUserCalls.valueAt(i); String pkgName = getPackageNameForLoggingPurposes(pm, canonicalUid); // uid is the canonical UID, but including "canonical" would add extra churn / bytes pw.printf(" %s (uid %d): %d calls\n", pkgName, canonicalUid, count); } } /** Retrieves the internal package manager interface. */ private PackageManagerInternal getPackageManagerInternal() { // Don't need to synchronize; worst-case scenario LocalServices will be called twice. if (mPmInternal == null) { mPmInternal = LocalServices.getService(PackageManagerInternal.class); } return mPmInternal; } // TODO(b/414326600): this method is taking a simplest aproach to get the uid, but it's not // handling corner cases like an app not available on user 0 or multiple apps with the same uid. // This is fine for now, but the final solution need to take those scenarios in account. private static String getPackageNameForLoggingPurposes(PackageManagerInternal pm, int uid) { if (uid == Process.SYSTEM_UID) { // Many apps might be running as system (because they declare sharedUserId in the // manifest), so we wouldn't know for sure which one calls it here return "system"; } var pkg = pm.getPackage(uid); // TODO(b/414326600): if it's from system, it might be useful to log the method that's // calling it, but that's expensive (so we should guard using a system property) and we'd // need to change the type of mGetMainUserCalls as well - for now, the solution is to look // at logcat (which logs the full stacktrace the tag is VERBOSE). // TODO(b/414326600): figure out proper way to handle null (for example, it'is also null // for root UID). return pkg == null ? "system" : pkg.getPackageName(); } }
services/core/java/com/android/server/pm/UserManagerService.java +28 −17 Original line number Original line Diff line number Diff line Loading @@ -398,6 +398,7 @@ public class UserManagerService extends IUserManager.Stub { private final Object mAppRestrictionsLock = new Object(); private final Object mAppRestrictionsLock = new Object(); private final Handler mHandler; private final Handler mHandler; private final MultiuserDeprecationReporter mDeprecationReporter; private final ThreadPoolExecutor mInternalExecutor; private final ThreadPoolExecutor mInternalExecutor; Loading Loading @@ -1111,6 +1112,7 @@ public class UserManagerService extends IUserManager.Stub { mPackagesLock = packagesLock; mPackagesLock = packagesLock; mUsers = users != null ? users : new SparseArray<>(); mUsers = users != null ? users : new SparseArray<>(); mHandler = new MainHandler(); mHandler = new MainHandler(); mDeprecationReporter = new MultiuserDeprecationReporter(mHandler); mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1, mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1, /* keepAliveTime */ 24, TimeUnit.HOURS, new LinkedBlockingQueue<>()); /* keepAliveTime */ 24, TimeUnit.HOURS, new LinkedBlockingQueue<>()); mUserVisibilityMediator = new UserVisibilityMediator(mHandler); mUserVisibilityMediator = new UserVisibilityMediator(mHandler); Loading Loading @@ -1369,6 +1371,7 @@ public class UserManagerService extends IUserManager.Stub { @Override @Override public @CanBeNULL @UserIdInt int getMainUserId() { public @CanBeNULL @UserIdInt int getMainUserId() { checkQueryOrCreateUsersPermission("get main user id"); checkQueryOrCreateUsersPermission("get main user id"); mDeprecationReporter.logGetMainUserCall(); return getMainUserIdUnchecked(); return getMainUserIdUnchecked(); } } Loading @@ -1391,7 +1394,6 @@ public class UserManagerService extends IUserManager.Stub { return null; return null; } } private @CanBeNULL @UserIdInt int getPrivateProfileUserId() { private @CanBeNULL @UserIdInt int getPrivateProfileUserId() { synchronized (mUsersLock) { synchronized (mUsersLock) { for (int userId : getUserIds()) { for (int userId : getUserIds()) { Loading Loading @@ -4775,10 +4777,13 @@ public class UserManagerService extends IUserManager.Stub { /** /** * Checks whether the default state of the device is headless system user mode, i.e. what the * Checks whether the default state of the device is headless system user mode, i.e. what the * mode would be if we did a fresh factory reset. * mode would be if we did a fresh factory reset. * If the mode is being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be * * returned instead. * <p>If the mode is being emulated (through the * Note that, even in the absence of emulation, a device might deviate from the current default * {@link UserManager#SYSTEM_USER_MODE_EMULATION_PROPERTY} system property) then the value * due to an OTA changing the default (which won't change the already-decided mode). * represented by that system property will be returned instead. * * <p>Note that, even in the absence of emulation, a device might deviate from the current * default due to an OTA changing the default (which won't change the already-decided mode). */ */ private boolean isDefaultHeadlessSystemUserMode() { private boolean isDefaultHeadlessSystemUserMode() { if (!Build.isDebuggable()) { if (!Build.isDebuggable()) { Loading Loading @@ -8041,6 +8046,9 @@ public class UserManagerService extends IUserManager.Stub { case "--visibility-mediator": case "--visibility-mediator": mUserVisibilityMediator.dump(pw, args); mUserVisibilityMediator.dump(pw, args); return; return; case "--deprecated-calls": mDeprecationReporter.dump(pw); return; } } } } Loading Loading @@ -8179,17 +8187,23 @@ public class UserManagerService extends IUserManager.Stub { mUserTypes.valueAt(i).dump(pw, " "); mUserTypes.valueAt(i).dump(pw, " "); } } // TODO: create IndentingPrintWriter at the beginning of dump() and use the proper pw.println(); // indentation methods instead of explicit printing " " mDeprecationReporter.dump(pw); try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) { // NOTE: add new stuff here, as pw is closed after the try-with-resources block below // TODO(b/163423525): create IndentingPrintWriter at the beginning of dump() and use the // proper indentation methods instead of explicit printing " "; that would also solve the // pw closure as well. try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) { // Dump SystemPackageInstaller info // Dump SystemPackageInstaller info ipw.println(); ipw.println(); mSystemPackageInstaller.dump(ipw); mSystemPackageInstaller.dump(ipw); } // NOTE: pw's not available after this point as it's auto-closed by ipw, so new dump // NOTE: pw's not available after this point as it's auto-closed by ipw, so new dump // statements should use ipw below // statements should use ipw below } } } private void dumpUser(PrintWriter pw, @CanBeCURRENT @UserIdInt int userId, StringBuilder sb, private void dumpUser(PrintWriter pw, @CanBeCURRENT @UserIdInt int userId, StringBuilder sb, Loading Loading @@ -8348,8 +8362,8 @@ public class UserManagerService extends IUserManager.Stub { if (userData != null) { if (userData != null) { writeUserLP(userData); writeUserLP(userData); } else { } else { Slog.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user " + userId Slogf.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user %d, it was " + ", it was probably removed before handler could handle it"); + "probably removed before handler could handle it", userId); } } } } break; break; Loading Loading @@ -8858,8 +8872,6 @@ public class UserManagerService extends IUserManager.Stub { } } } // class LocalService } // class LocalService /** /** * Check if user has restrictions * Check if user has restrictions * @param restriction restrictions to check * @param restriction restrictions to check Loading Loading @@ -8978,7 +8990,7 @@ public class UserManagerService extends IUserManager.Stub { /** Retrieves the internal package manager interface. */ /** Retrieves the internal package manager interface. */ private PackageManagerInternal getPackageManagerInternal() { private PackageManagerInternal getPackageManagerInternal() { // Don't need to synchonize; worst-case scenario LocalServices will be called twice. // Don't need to synchronize; worst-case scenario LocalServices will be called twice. if (mPmInternal == null) { if (mPmInternal == null) { mPmInternal = LocalServices.getService(PackageManagerInternal.class); mPmInternal = LocalServices.getService(PackageManagerInternal.class); } } Loading Loading @@ -9156,5 +9168,4 @@ public class UserManagerService extends IUserManager.Stub { public UserJourneyLogger getUserJourneyLogger() { public UserJourneyLogger getUserJourneyLogger() { return mUserJourneyLogger; return mUserJourneyLogger; } } } }