Added UserInfo.convertedFromPreCreated

This attribute is useful to identify (on bugreports) whether a
user was created "from scratch" or converted from a pre-created user.

Test: adb shell cmd user list --all -v
Test: adb shell dumpsys user
Test: atest UserControllerTest UserManagerServiceUserInfoTest

Bug: 165703573

Change-Id: Iee9df636db5677b4d968d49bb5f5b3fbb9a7f02d
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index aca5b45..08b23b0 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -221,6 +221,14 @@
     public boolean preCreated;
 
     /**
+     * When {@code true}, it indicates this user was created by converting a {@link #preCreated}
+     * user.
+     *
+     * <p><b>NOTE: </b>only used for debugging purposes, it's not set when marshalled to a parcel.
+     */
+    public boolean convertedFromPreCreated;
+
+    /**
      * Creates a UserInfo whose user type is determined automatically by the flags according to
      * {@link #getDefaultUserType}; can only be used for user types handled there.
      */
@@ -413,6 +421,7 @@
         lastLoggedInFingerprint = orig.lastLoggedInFingerprint;
         partial = orig.partial;
         preCreated = orig.preCreated;
+        convertedFromPreCreated = orig.convertedFromPreCreated;
         profileGroupId = orig.profileGroupId;
         restrictedProfileParentId = orig.restrictedProfileParentId;
         guestToRemove = orig.guestToRemove;
@@ -440,6 +449,7 @@
                 + ", type=" + userType
                 + ", flags=" + flagsToString(flags)
                 + (preCreated ? " (pre-created)" : "")
+                + (convertedFromPreCreated ? " (converted)" : "")
                 + (partial ? " (partial)" : "")
                 + "]";
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 6ecaab6..4aaa8a5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -175,6 +175,7 @@
     private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
     private static final String ATTR_PARTIAL = "partial";
     private static final String ATTR_PRE_CREATED = "preCreated";
+    private static final String ATTR_CONVERTED_FROM_PRE_CREATED = "convertedFromPreCreated";
     private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove";
     private static final String ATTR_USER_VERSION = "version";
     private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId";
@@ -2912,6 +2913,9 @@
         if (userInfo.preCreated) {
             serializer.attribute(null, ATTR_PRE_CREATED, "true");
         }
+        if (userInfo.convertedFromPreCreated) {
+            serializer.attribute(null, ATTR_CONVERTED_FROM_PRE_CREATED, "true");
+        }
         if (userInfo.guestToRemove) {
             serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true");
         }
@@ -3069,6 +3073,7 @@
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
         boolean partial = false;
         boolean preCreated = false;
+        boolean converted = false;
         boolean guestToRemove = false;
         boolean persistSeedData = false;
         String seedAccountName = null;
@@ -3120,6 +3125,10 @@
             if ("true".equals(valueString)) {
                 preCreated = true;
             }
+            valueString = parser.getAttributeValue(null, ATTR_CONVERTED_FROM_PRE_CREATED);
+            if ("true".equals(valueString)) {
+                converted = true;
+            }
             valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE);
             if ("true".equals(valueString)) {
                 guestToRemove = true;
@@ -3177,6 +3186,7 @@
         userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
         userInfo.partial = partial;
         userInfo.preCreated = preCreated;
+        userInfo.convertedFromPreCreated = converted;
         userInfo.guestToRemove = guestToRemove;
         userInfo.profileGroupId = profileGroupId;
         userInfo.profileBadge = profileBadge;
@@ -3610,6 +3620,7 @@
         preCreatedUser.name = name;
         preCreatedUser.flags = newFlags;
         preCreatedUser.preCreated = false;
+        preCreatedUser.convertedFromPreCreated = true;
         preCreatedUser.creationTime = getCreationTime();
 
         synchronized (mPackagesLock) {
@@ -4703,6 +4714,7 @@
                             running ? " (running)" : "",
                             user.partial ? " (partial)" : "",
                             user.preCreated ? " (pre-created)" : "",
+                            user.convertedFromPreCreated ? " (converted)" : "",
                             current ? " (current)" : "");
                 } else {
                     // NOTE: the standard "list users" command is used by integration tests and
@@ -4788,6 +4800,9 @@
                     if (userInfo.preCreated) {
                         pw.print(" <pre-created>");
                     }
+                    if (userInfo.convertedFromPreCreated) {
+                        pw.print(" <converted>");
+                    }
                     pw.println();
                     pw.print("    Type: "); pw.println(userInfo.userType);
                     pw.print("    Flags: "); pw.print(userInfo.flags); pw.print(" (");
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 66ca839..0ccc026 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -105,7 +105,7 @@
         UserData read = mUserManagerService.readUserLP(
                 data.info.id, new ByteArrayInputStream(bytes));
 
-        assertUserInfoEquals(data.info, read.info);
+        assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
     }
 
     @Test
@@ -123,7 +123,16 @@
         UserInfo read = UserInfo.CREATOR.createFromParcel(in);
         in.recycle();
 
-        assertUserInfoEquals(info, read);
+        assertUserInfoEquals(info, read, /* parcelCopy= */ true);
+    }
+
+    @Test
+    public void testCopyConstructor() throws Exception {
+        UserInfo info = createUser();
+
+        UserInfo copy = new UserInfo(info);
+
+        assertUserInfoEquals(info, copy, /* parcelCopy= */ false);
     }
 
     @Test
@@ -227,10 +236,11 @@
         user.partial = true;
         user.guestToRemove = true;
         user.preCreated = true;
+        user.convertedFromPreCreated = true;
         return user;
     }
 
-    private void assertUserInfoEquals(UserInfo one, UserInfo two) {
+    private void assertUserInfoEquals(UserInfo one, UserInfo two, boolean parcelCopy) {
         assertEquals("Id not preserved", one.id, two.id);
         assertEquals("Name not preserved", one.name, two.name);
         assertEquals("Icon path not preserved", one.iconPath, two.iconPath);
@@ -238,11 +248,17 @@
         assertEquals("UserType not preserved", one.userType, two.userType);
         assertEquals("profile group not preserved", one.profileGroupId,
                 two.profileGroupId);
-        assertEquals("restricted profile parent not preseved", one.restrictedProfileParentId,
+        assertEquals("restricted profile parent not preserved", one.restrictedProfileParentId,
                 two.restrictedProfileParentId);
-        assertEquals("profile badge not preseved", one.profileBadge, two.profileBadge);
-        assertEquals("partial not preseved", one.partial, two.partial);
-        assertEquals("guestToRemove not preseved", one.guestToRemove, two.guestToRemove);
-        assertEquals("preCreated not preseved", one.preCreated, two.preCreated);
+        assertEquals("profile badge not preserved", one.profileBadge, two.profileBadge);
+        assertEquals("partial not preserved", one.partial, two.partial);
+        assertEquals("guestToRemove not preserved", one.guestToRemove, two.guestToRemove);
+        assertEquals("preCreated not preserved", one.preCreated, two.preCreated);
+        if (parcelCopy) {
+            assertFalse("convertedFromPreCreated should not be set", two.convertedFromPreCreated);
+        } else {
+            assertEquals("convertedFromPreCreated not preserved", one.convertedFromPreCreated,
+                    two.convertedFromPreCreated);
+        }
     }
 }