Merge "API for launcher User Awareness" into main
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 563ed7d..e9f419e 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.LocusId;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.LauncherActivityInfoInternal;
@@ -62,6 +63,7 @@
             in Bundle opts, in UserHandle user);
     PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
             in UserHandle user);
+    LauncherUserInfo getLauncherUserInfo(in UserHandle user);
     void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
             String callingFeatureId, in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index dbaa4c9..0cd4358 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -55,6 +56,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Flags;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -776,6 +778,28 @@
     }
 
     /**
+     * Returns information related to a user which is useful for displaying UI elements
+     * to distinguish it from other users (eg, badges). Only system launchers should
+     * call this API.
+     *
+     * @param userHandle user handle of the user for which LauncherUserInfo is requested
+     * @return the LauncherUserInfo object related to the user specified.
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) {
+        if (DEBUG) {
+            Log.i(TAG, "getLauncherUserInfo " + userHandle);
+        }
+        try {
+            return mService.getLauncherUserInfo(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
      * returns null.
      *
diff --git a/core/java/android/content/pm/LauncherUserInfo.aidl b/core/java/android/content/pm/LauncherUserInfo.aidl
new file mode 100644
index 0000000..f875f1e
--- /dev/null
+++ b/core/java/android/content/pm/LauncherUserInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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 android.content.pm;
+
+parcelable LauncherUserInfo;
diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java
new file mode 100644
index 0000000..214c3e4
--- /dev/null
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 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 android.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+/**
+ * The LauncherUserInfo object holds information about an Android user that is required to display
+ * the Launcher related UI elements specific to the user (like badges).
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+public final class LauncherUserInfo implements Parcelable {
+
+    private final String mUserType;
+
+    // Serial number for the user, should be same as in the {@link UserInfo} object.
+    private final int mUserSerialNumber;
+
+    /**
+     * Returns type of the user as defined in {@link UserManager}. e.g.,
+     * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE}
+     * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug
+     * is resolved.
+     *
+     * @return the userType for the user whose LauncherUserInfo this is
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    @NonNull
+    public String getUserType() {
+        return mUserType;
+    }
+
+    /**
+     * Returns serial number of user as returned by
+     * {@link UserManager#getSerialNumberForUser(UserHandle)}
+     *
+     * @return the serial number associated with the user
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public int getUserSerialNumber() {
+        return mUserSerialNumber;
+    }
+
+    private LauncherUserInfo(@NonNull Parcel in) {
+        mUserType = in.readString16NoHelper();
+        mUserSerialNumber = in.readInt();
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString16NoHelper(mUserType);
+        dest.writeInt(mUserSerialNumber);
+    }
+
+    @Override
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public static final @android.annotation.NonNull Creator<LauncherUserInfo> CREATOR =
+            new Creator<LauncherUserInfo>() {
+                @Override
+                public LauncherUserInfo createFromParcel(Parcel in) {
+                    return new LauncherUserInfo(in);
+                }
+
+                @Override
+                public LauncherUserInfo[] newArray(int size) {
+                    return new LauncherUserInfo[size];
+                }
+            };
+
+    /**
+     * @hide
+     */
+    public static final class Builder {
+        private final String mUserType;
+
+        private final int mUserSerialNumber;
+
+        public Builder(@NonNull String userType, int userSerialNumber) {
+            this.mUserType = userType;
+            this.mUserSerialNumber = userSerialNumber;
+        }
+
+        /**
+         * Builds the LauncherUserInfo object
+         */
+        @NonNull public LauncherUserInfo build() {
+            return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber);
+        }
+
+    } // End builder
+
+    private LauncherUserInfo(@NonNull  String userType, int userSerialNumber) {
+        this.mUserType = userType;
+        this.mUserSerialNumber = userSerialNumber;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a161e8c..1135466 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -65,6 +65,7 @@
 import android.content.pm.LauncherActivityInfoInternal;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
@@ -1377,6 +1378,25 @@
         }
 
         @Override
+        public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) {
+            // Only system launchers, which have access to recents should have access to this API.
+            // TODO(b/303803157): Add the new permission check if we decide to have one.
+            if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+                throw new SecurityException("Caller is not the recents app");
+            }
+            if (!canAccessProfile(user.getIdentifier(),
+                    "Can't access LauncherUserInfo for another user")) {
+                return null;
+            }
+            long ident = injectClearCallingIdentity();
+            try {
+                return mUserManagerInternal.getLauncherUserInfo(user.getIdentifier());
+            } finally {
+                injectRestoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
         public void startActivityAsUser(IApplicationThread caller, String callingPackage,
                 String callingFeatureId, ComponentName component, Rect sourceBounds,
                 Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 04cd183..0e7ce2e 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.UserProperties;
 import android.graphics.Bitmap;
@@ -407,6 +408,11 @@
     public abstract @NonNull UserInfo[] getUserInfos();
 
     /**
+     * Gets a {@link LauncherUserInfo} for the given {@code userId}, or {@code null} if not found.
+     */
+    public abstract @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId);
+
+    /**
      * Sets all default cross profile intent filters between {@code parentUserId} and
      * {@code profileUserId}.
      */
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7331bc1..154ee6e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -62,6 +62,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.LauncherUserInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -7153,6 +7154,24 @@
         }
 
         @Override
+        public @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId) {
+            UserInfo userInfo;
+            synchronized (mUsersLock) {
+                userInfo = getUserInfoLU(userId);
+            }
+            if (userInfo != null) {
+                final UserTypeDetails userDetails = getUserTypeDetails(userInfo);
+                final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder(
+                        userDetails.getName(),
+                        userInfo.serialNumber)
+                        .build();
+                return uiInfo;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
         public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
             int state;
             synchronized (mUserStates) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5dfce06..89c6a220 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -974,7 +974,6 @@
         assertThrows(SecurityException.class, userProps::getAlwaysVisible);
     }
 
-
     // Make sure only max managed profiles can be created
     @MediumTest
     @Test