Optimize UserHandle.of()

There will be more calls to *ForUser() APIs over time by mainline modules.
Let's avoid creating new objects in common cases.

Bug: 142134660
Fix: 140429319
Test: atest cts/tests/app/src/android/app/cts/UserHandleTest.java
Change-Id: Ie71850bd08a8d9831232ddb40807ae49e3a56841
diff --git a/api/test-current.txt b/api/test-current.txt
index 9396515..ce2c49a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2033,10 +2033,18 @@
   public final class UserHandle implements android.os.Parcelable {
     method public static int getAppId(int);
     method public int getIdentifier();
+    method public static int getUid(int, int);
+    method public static int getUserId(int);
     method public static boolean isApp(int);
+    method public static int myUserId();
+    method public static android.os.UserHandle of(int);
     field @NonNull public static final android.os.UserHandle ALL;
     field @NonNull public static final android.os.UserHandle CURRENT;
+    field public static final int MIN_SECONDARY_USER_ID = 10; // 0xa
     field @NonNull public static final android.os.UserHandle SYSTEM;
+    field public static final int USER_ALL = -1; // 0xffffffff
+    field public static final int USER_NULL = -10000; // 0xffffd8f0
+    field public static final int USER_SYSTEM = 0; // 0x0
   }
 
   public class UserManager {
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 0754dc7..3558fcd 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -39,6 +39,7 @@
 
     /** @hide A user id to indicate all users on the device */
     @UnsupportedAppUsage
+    @TestApi
     public static final @UserIdInt int USER_ALL = -1;
 
     /** @hide A user handle to indicate all users on the device */
@@ -69,8 +70,11 @@
 
     /** @hide An undefined user id */
     @UnsupportedAppUsage
+    @TestApi
     public static final @UserIdInt int USER_NULL = -10000;
 
+    private static final @NonNull UserHandle NULL = new UserHandle(USER_NULL);
+
     /**
      * @hide A user id constant to indicate the "owner" user of the device
      * @deprecated Consider using either {@link UserHandle#USER_SYSTEM} constant or
@@ -91,6 +95,7 @@
 
     /** @hide A user id constant to indicate the "system" user of the device */
     @UnsupportedAppUsage
+    @TestApi
     public static final @UserIdInt int USER_SYSTEM = 0;
 
     /** @hide A user serial constant to indicate the "system" user of the device */
@@ -110,6 +115,27 @@
     public static final boolean MU_ENABLED = true;
 
     /** @hide */
+    @TestApi
+    public static final int MIN_SECONDARY_USER_ID = 10;
+
+    /**
+     * Arbitrary user handle cache size. We use the cache even when {@link #MU_ENABLED} is false
+     * anyway, so we can always assume in CTS that UserHandle.of(10) returns a cached instance
+     * even on non-multiuser devices.
+     */
+    private static final int NUM_CACHED_USERS = 4;
+
+    private static final UserHandle[] CACHED_USER_INFOS = new UserHandle[NUM_CACHED_USERS];
+
+    static {
+        // Not lazily initializing the cache, so that we can share them across processes.
+        // (We'll create them in zygote.)
+        for (int i = 0; i < CACHED_USER_INFOS.length; i++) {
+            CACHED_USER_INFOS[i] = new UserHandle(MIN_SECONDARY_USER_ID + i);
+        }
+    }
+
+    /** @hide */
     @UnsupportedAppUsage
     public static final int ERR_GID = -1;
     /** @hide */
@@ -209,6 +235,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public static @UserIdInt int getUserId(int uid) {
         if (MU_ENABLED) {
             return uid / PER_USER_RANGE;
@@ -229,9 +256,31 @@
     }
 
     /** @hide */
+    @TestApi
     @SystemApi
     public static UserHandle of(@UserIdInt int userId) {
-        return userId == USER_SYSTEM ? SYSTEM : new UserHandle(userId);
+        if (userId == USER_SYSTEM) {
+            return SYSTEM; // Most common.
+        }
+        // These are sequential; so use a switch. Maybe they'll be optimized to a table lookup.
+        switch (userId) {
+            case USER_ALL:
+                return ALL;
+
+            case USER_CURRENT:
+                return CURRENT;
+
+            case USER_CURRENT_OR_SELF:
+                return CURRENT_OR_SELF;
+        }
+        if (userId >= MIN_SECONDARY_USER_ID
+                && userId < (MIN_SECONDARY_USER_ID + CACHED_USER_INFOS.length)) {
+            return CACHED_USER_INFOS[userId - MIN_SECONDARY_USER_ID];
+        }
+        if (userId == USER_NULL) { // Not common.
+            return NULL;
+        }
+        return new UserHandle(userId);
     }
 
     /**
@@ -239,6 +288,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public static int getUid(@UserIdInt int userId, @AppIdInt int appId) {
         if (MU_ENABLED) {
             return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
@@ -404,6 +454,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static @UserIdInt int myUserId() {
         return getUserId(Process.myUid());
     }
@@ -513,7 +564,9 @@
     public static final @android.annotation.NonNull Parcelable.Creator<UserHandle> CREATOR
             = new Parcelable.Creator<UserHandle>() {
         public UserHandle createFromParcel(Parcel in) {
-            return new UserHandle(in);
+            // Try to avoid allocation; use of() here. Keep this and the constructor below
+            // in sync.
+            return UserHandle.of(in.readInt());
         }
 
         public UserHandle[] newArray(int size) {
@@ -532,6 +585,6 @@
      * positioned at the location in the buffer where it was written.
      */
     public UserHandle(Parcel in) {
-        mHandle = in.readInt();
+        mHandle = in.readInt(); // Keep this and createFromParcel() in sync.
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8431fa3..95baa01 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -209,7 +209,8 @@
             | UserInfo.FLAG_DEMO;
 
     @VisibleForTesting
-    static final int MIN_USER_ID = 10;
+    static final int MIN_USER_ID = UserHandle.MIN_SECONDARY_USER_ID;
+
     // We need to keep process uid within Integer.MAX_VALUE.
     @VisibleForTesting
     static final int MAX_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;