Add API for apps to declare their "category".

Upcoming platform features need to cluster apps together into broad
categories to help summarize information to users.  (For example,
when presenting battery, network, and disk usage.)

We are tightly limiting the set of categories to keep them easily
presentable to users when summarizing information.  This feature is
not designed to be a general-purpose taxonomy, nor should it be
allowed to become one.

Older apps may not have defined a category in their manifests, so
allow the installing app to define a category on their behalf.

Test: builds, boots
Bug: 33815939
Change-Id: I785b882ee7c18072ef47d56e0fc19ad72888e1b7
diff --git a/api/current.txt b/api/current.txt
index c7577e4..d759f3a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -284,6 +284,7 @@
     field public static final int anyDensity = 16843372; // 0x101026c
     field public static final int apduServiceBanner = 16843757; // 0x10103ed
     field public static final int apiKey = 16843281; // 0x1010211
+    field public static final int appCategory = 16844101; // 0x1010545
     field public static final int author = 16843444; // 0x10102b4
     field public static final int authorities = 16842776; // 0x1010018
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
@@ -9592,7 +9593,17 @@
     ctor public ApplicationInfo(android.content.pm.ApplicationInfo);
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
+    method public static java.lang.CharSequence getCategoryTitle(android.content.Context, int);
     method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager);
+    field public static final int CATEGORY_AUDIO = 1; // 0x1
+    field public static final int CATEGORY_GAME = 0; // 0x0
+    field public static final int CATEGORY_IMAGE = 3; // 0x3
+    field public static final int CATEGORY_MAPS = 6; // 0x6
+    field public static final int CATEGORY_NEWS = 5; // 0x5
+    field public static final int CATEGORY_PRODUCTIVITY = 7; // 0x7
+    field public static final int CATEGORY_SOCIAL = 4; // 0x4
+    field public static final int CATEGORY_UNDEFINED = -1; // 0xffffffff
+    field public static final int CATEGORY_VIDEO = 2; // 0x2
     field public static final android.os.Parcelable.Creator<android.content.pm.ApplicationInfo> CREATOR;
     field public static final int FLAG_ALLOW_BACKUP = 32768; // 0x8000
     field public static final int FLAG_ALLOW_CLEAR_USER_DATA = 64; // 0x40
@@ -9606,7 +9617,7 @@
     field public static final int FLAG_HAS_CODE = 4; // 0x4
     field public static final int FLAG_INSTALLED = 8388608; // 0x800000
     field public static final int FLAG_IS_DATA_ONLY = 16777216; // 0x1000000
-    field public static final int FLAG_IS_GAME = 33554432; // 0x2000000
+    field public static final deprecated int FLAG_IS_GAME = 33554432; // 0x2000000
     field public static final int FLAG_KILL_AFTER_RESTORE = 65536; // 0x10000
     field public static final int FLAG_LARGE_HEAP = 1048576; // 0x100000
     field public static final int FLAG_MULTIARCH = -2147483648; // 0x80000000
@@ -9627,6 +9638,7 @@
     field public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 134217728; // 0x8000000
     field public static final int FLAG_VM_SAFE_MODE = 16384; // 0x4000
     field public java.lang.String backupAgentName;
+    field public int category;
     field public java.lang.String className;
     field public int compatibleWidthLimitDp;
     field public java.lang.String dataDir;
@@ -10046,6 +10058,7 @@
     method public abstract android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
     method public abstract android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
     method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
+    method public abstract void setApplicationCategoryHint(java.lang.String, int);
     method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
     method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
@@ -39019,6 +39032,7 @@
     method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
     method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
     method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
+    method public void setApplicationCategoryHint(java.lang.String, int);
     method public void setApplicationEnabledSetting(java.lang.String, int, int);
     method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method public void setInstallerPackageName(java.lang.String, java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 28674ca..9563521 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -393,6 +393,7 @@
     field public static final int anyDensity = 16843372; // 0x101026c
     field public static final int apduServiceBanner = 16843757; // 0x10103ed
     field public static final int apiKey = 16843281; // 0x1010211
+    field public static final int appCategory = 16844101; // 0x1010545
     field public static final int author = 16843444; // 0x10102b4
     field public static final int authorities = 16842776; // 0x1010018
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
@@ -9975,7 +9976,17 @@
     ctor public ApplicationInfo(android.content.pm.ApplicationInfo);
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
+    method public static java.lang.CharSequence getCategoryTitle(android.content.Context, int);
     method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager);
+    field public static final int CATEGORY_AUDIO = 1; // 0x1
+    field public static final int CATEGORY_GAME = 0; // 0x0
+    field public static final int CATEGORY_IMAGE = 3; // 0x3
+    field public static final int CATEGORY_MAPS = 6; // 0x6
+    field public static final int CATEGORY_NEWS = 5; // 0x5
+    field public static final int CATEGORY_PRODUCTIVITY = 7; // 0x7
+    field public static final int CATEGORY_SOCIAL = 4; // 0x4
+    field public static final int CATEGORY_UNDEFINED = -1; // 0xffffffff
+    field public static final int CATEGORY_VIDEO = 2; // 0x2
     field public static final android.os.Parcelable.Creator<android.content.pm.ApplicationInfo> CREATOR;
     field public static final int FLAG_ALLOW_BACKUP = 32768; // 0x8000
     field public static final int FLAG_ALLOW_CLEAR_USER_DATA = 64; // 0x40
@@ -9989,7 +10000,7 @@
     field public static final int FLAG_HAS_CODE = 4; // 0x4
     field public static final int FLAG_INSTALLED = 8388608; // 0x800000
     field public static final int FLAG_IS_DATA_ONLY = 16777216; // 0x1000000
-    field public static final int FLAG_IS_GAME = 33554432; // 0x2000000
+    field public static final deprecated int FLAG_IS_GAME = 33554432; // 0x2000000
     field public static final int FLAG_KILL_AFTER_RESTORE = 65536; // 0x10000
     field public static final int FLAG_LARGE_HEAP = 1048576; // 0x100000
     field public static final int FLAG_MULTIARCH = -2147483648; // 0x80000000
@@ -10010,6 +10021,7 @@
     field public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 134217728; // 0x8000000
     field public static final int FLAG_VM_SAFE_MODE = 16384; // 0x4000
     field public java.lang.String backupAgentName;
+    field public int category;
     field public java.lang.String className;
     field public int compatibleWidthLimitDp;
     field public java.lang.String credentialProtectedDataDir;
@@ -10475,6 +10487,7 @@
     method public abstract android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
     method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
     method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public abstract void setApplicationCategoryHint(java.lang.String, int);
     method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
     method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
@@ -42241,6 +42254,7 @@
     method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
     method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
     method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public void setApplicationCategoryHint(java.lang.String, int);
     method public void setApplicationEnabledSetting(java.lang.String, int, int);
     method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method public void setInstallerPackageName(java.lang.String, java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 71b8c1d..3a7c5fd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -284,6 +284,7 @@
     field public static final int anyDensity = 16843372; // 0x101026c
     field public static final int apduServiceBanner = 16843757; // 0x10103ed
     field public static final int apiKey = 16843281; // 0x1010211
+    field public static final int appCategory = 16844101; // 0x1010545
     field public static final int author = 16843444; // 0x10102b4
     field public static final int authorities = 16842776; // 0x1010018
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
@@ -9617,9 +9618,19 @@
     ctor public ApplicationInfo(android.content.pm.ApplicationInfo);
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
+    method public static java.lang.CharSequence getCategoryTitle(android.content.Context, int);
     method public boolean isPrivilegedApp();
     method public boolean isSystemApp();
     method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager);
+    field public static final int CATEGORY_AUDIO = 1; // 0x1
+    field public static final int CATEGORY_GAME = 0; // 0x0
+    field public static final int CATEGORY_IMAGE = 3; // 0x3
+    field public static final int CATEGORY_MAPS = 6; // 0x6
+    field public static final int CATEGORY_NEWS = 5; // 0x5
+    field public static final int CATEGORY_PRODUCTIVITY = 7; // 0x7
+    field public static final int CATEGORY_SOCIAL = 4; // 0x4
+    field public static final int CATEGORY_UNDEFINED = -1; // 0xffffffff
+    field public static final int CATEGORY_VIDEO = 2; // 0x2
     field public static final android.os.Parcelable.Creator<android.content.pm.ApplicationInfo> CREATOR;
     field public static final int FLAG_ALLOW_BACKUP = 32768; // 0x8000
     field public static final int FLAG_ALLOW_CLEAR_USER_DATA = 64; // 0x40
@@ -9633,7 +9644,7 @@
     field public static final int FLAG_HAS_CODE = 4; // 0x4
     field public static final int FLAG_INSTALLED = 8388608; // 0x800000
     field public static final int FLAG_IS_DATA_ONLY = 16777216; // 0x1000000
-    field public static final int FLAG_IS_GAME = 33554432; // 0x2000000
+    field public static final deprecated int FLAG_IS_GAME = 33554432; // 0x2000000
     field public static final int FLAG_KILL_AFTER_RESTORE = 65536; // 0x10000
     field public static final int FLAG_LARGE_HEAP = 1048576; // 0x100000
     field public static final int FLAG_MULTIARCH = -2147483648; // 0x80000000
@@ -9654,6 +9665,7 @@
     field public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 134217728; // 0x8000000
     field public static final int FLAG_VM_SAFE_MODE = 16384; // 0x4000
     field public java.lang.String backupAgentName;
+    field public int category;
     field public java.lang.String className;
     field public int compatibleWidthLimitDp;
     field public java.lang.String dataDir;
@@ -10075,6 +10087,7 @@
     method public abstract android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
     method public abstract android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
     method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
+    method public abstract void setApplicationCategoryHint(java.lang.String, int);
     method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
     method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
@@ -39139,6 +39152,7 @@
     method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
     method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
     method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
+    method public void setApplicationCategoryHint(java.lang.String, int);
     method public void setApplicationEnabledSetting(java.lang.String, int, int);
     method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method public void setInstallerPackageName(java.lang.String, java.lang.String);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 627e661..aedcdce 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2027,6 +2027,17 @@
         }
     }
 
+    /** @hide */
+    @Override
+    public void setApplicationCategoryHint(String packageName, int categoryHint) {
+        try {
+            mPM.setApplicationCategoryHint(packageName, categoryHint,
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @Override
     public void getPackageSizeInfoAsUser(String packageName, int userHandle,
             IPackageStatsObserver observer) {
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index adc99d3..71071e1 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -16,6 +16,9 @@
 
 package android.content.pm;
 
+import static android.os.Build.VERSION_CODES.DONUT;
+
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
@@ -31,13 +34,13 @@
 
 import com.android.internal.util.ArrayUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.Collator;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Objects;
 
-import static android.os.Build.VERSION_CODES.DONUT;
-
 /**
  * Information you can retrieve about a particular application.  This
  * corresponds to information collected from the AndroidManifest.xml's
@@ -344,9 +347,12 @@
     public static final int FLAG_IS_DATA_ONLY = 1<<24;
 
     /**
-     * Value for {@link #flags}: true if the application was declared to be a game, or
-     * false if it is a non-game application.
+     * Value for {@link #flags}: true if the application was declared to be a
+     * game, or false if it is a non-game application.
+     *
+     * @deprecated use {@link #CATEGORY_GAME} instead.
      */
+    @Deprecated
     public static final int FLAG_IS_GAME = 1<<25;
 
     /**
@@ -779,6 +785,134 @@
      */
     public int networkSecurityConfigRes;
 
+    /**
+     * The category of this app. Categories are used to cluster multiple apps
+     * together into meaningful groups, such as when summarizing battery,
+     * network, or disk usage. Apps should only define this value when they fit
+     * well into one of the specific categories.
+     * <p>
+     * Set from the {@link android.R.attr#appCategory} attribute in the
+     * manifest. If the manifest doesn't define a category, this value may have
+     * been provided by the installer via
+     * {@link PackageManager#setApplicationCategoryHint(String, int)}.
+     */
+    public @Category int category = CATEGORY_UNDEFINED;
+
+    /** {@hide} */
+    @IntDef({
+            CATEGORY_UNDEFINED,
+            CATEGORY_GAME,
+            CATEGORY_AUDIO,
+            CATEGORY_VIDEO,
+            CATEGORY_IMAGE,
+            CATEGORY_SOCIAL,
+            CATEGORY_NEWS,
+            CATEGORY_MAPS,
+            CATEGORY_PRODUCTIVITY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Category {
+    }
+
+    /**
+     * Value when category is undefined.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_UNDEFINED = -1;
+
+    /**
+     * Category for apps which are primarily games.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_GAME = 0;
+
+    /**
+     * Category for apps which primarily work with audio or music, such as music
+     * players.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_AUDIO = 1;
+
+    /**
+     * Category for apps which primarily work with video or movies, such as
+     * streaming video apps.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_VIDEO = 2;
+
+    /**
+     * Category for apps which primarily work with images or photos, such as
+     * camera or gallery apps.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_IMAGE = 3;
+
+    /**
+     * Category for apps which are primarily social apps, such as messaging,
+     * communication, or social network apps.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_SOCIAL = 4;
+
+    /**
+     * Category for apps which are primarily news apps, such as newspapers,
+     * magazines, or sports apps.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_NEWS = 5;
+
+    /**
+     * Category for apps which are primarily maps apps, such as navigation apps.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_MAPS = 6;
+
+    /**
+     * Category for apps which are primarily productivity apps, such as cloud
+     * storage or workplace apps.
+     *
+     * @see #category
+     */
+    public static final int CATEGORY_PRODUCTIVITY = 7;
+
+    /**
+     * Return a concise, localized title for the given
+     * {@link ApplicationInfo#category} value, or {@code null} for unknown
+     * values such as {@link #CATEGORY_UNDEFINED}.
+     *
+     * @see #category
+     */
+    public static CharSequence getCategoryTitle(Context context, @Category int category) {
+        switch (category) {
+            case ApplicationInfo.CATEGORY_GAME:
+                return context.getText(com.android.internal.R.string.app_category_game);
+            case ApplicationInfo.CATEGORY_AUDIO:
+                return context.getText(com.android.internal.R.string.app_category_audio);
+            case ApplicationInfo.CATEGORY_VIDEO:
+                return context.getText(com.android.internal.R.string.app_category_video);
+            case ApplicationInfo.CATEGORY_IMAGE:
+                return context.getText(com.android.internal.R.string.app_category_image);
+            case ApplicationInfo.CATEGORY_SOCIAL:
+                return context.getText(com.android.internal.R.string.app_category_social);
+            case ApplicationInfo.CATEGORY_NEWS:
+                return context.getText(com.android.internal.R.string.app_category_news);
+            case ApplicationInfo.CATEGORY_MAPS:
+                return context.getText(com.android.internal.R.string.app_category_maps);
+            case ApplicationInfo.CATEGORY_PRODUCTIVITY:
+                return context.getText(com.android.internal.R.string.app_category_productivity);
+            default:
+                return null;
+        }
+    }
+
     public void dump(Printer pw, String prefix) {
         dump(pw, prefix, DUMP_FLAG_ALL);
     }
@@ -854,6 +988,9 @@
                 pw.println(prefix + "networkSecurityConfigRes=0x"
                         + Integer.toHexString(networkSecurityConfigRes));
             }
+            if (category != CATEGORY_UNDEFINED) {
+                pw.println(prefix + "category=" + category);
+            }
         }
         super.dumpBack(pw, prefix);
     }
@@ -941,6 +1078,7 @@
         backupAgentName = orig.backupAgentName;
         fullBackupContent = orig.fullBackupContent;
         networkSecurityConfigRes = orig.networkSecurityConfigRes;
+        category = orig.category;
     }
 
     public String toString() {
@@ -997,6 +1135,7 @@
         dest.writeInt(uiOptions);
         dest.writeInt(fullBackupContent);
         dest.writeInt(networkSecurityConfigRes);
+        dest.writeInt(category);
     }
 
     public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1053,6 +1192,7 @@
         uiOptions = source.readInt();
         fullBackupContent = source.readInt();
         networkSecurityConfigRes = source.readInt();
+        category = source.readInt();
     }
 
     /**
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b9b61db..eb0ca2e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -226,6 +226,8 @@
 
     void setInstallerPackageName(in String targetPackage, in String installerPackageName);
 
+    void setApplicationCategoryHint(String packageName, int categoryHint, String callerPackageName);
+
     /** @deprecated rawr, don't call AIDL methods directly! */
     void deletePackageAsUser(in String packageName, IPackageDeleteObserver observer,
             int userId, int flags);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6a2325b..0cd67b7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5585,6 +5585,16 @@
      */
     public abstract boolean isPackageSuspendedForUser(String packageName, int userId);
 
+    /**
+     * Provide a hint of what the {@link ApplicationInfo#category} value should
+     * be for the given package.
+     * <p>
+     * This hint can only be set by the app which installed this package, as
+     * determined by {@link #getInstallerPackageName(String)}.
+     */
+    public abstract void setApplicationCategoryHint(String packageName,
+            @ApplicationInfo.Category int categoryHint);
+
     /** {@hide} */
     public static boolean isMoveStatusFinished(int status) {
         return (status < 0 || status > 100);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ad1ed55..9e1fe79 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -16,15 +16,30 @@
 
 package android.content.pm;
 
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -42,6 +57,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.PatternMatcher;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -62,6 +79,16 @@
 import android.util.jar.StrictJarFile;
 import android.view.Gravity;
 
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -83,38 +110,10 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.zip.ZipEntry;
 
-import libcore.io.IoUtils;
-
-import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
-import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_VIA_SDK_VERSION;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
-import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
-
 /**
  * Parser for package files (APKs) on disk. This supports apps packaged either
  * as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
@@ -3274,6 +3273,9 @@
         ai.networkSecurityConfigRes = sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestApplication_networkSecurityConfig,
                 0);
+        ai.category = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestApplication_appCategory,
+                ApplicationInfo.CATEGORY_UNDEFINED);
 
         String str;
         str = sa.getNonConfigurationString(
@@ -6255,6 +6257,9 @@
             ai.enabled = false;
         }
         ai.enabledSetting = state.enabled;
+        if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+            ai.category = state.categoryHint;
+        }
     }
 
     public static ApplicationInfo generateApplicationInfo(Package p, int flags,
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 1821458..eb3f275 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -47,6 +47,7 @@
     public String lastDisableAppCaller;
     public int domainVerificationStatus;
     public int appLinkGeneration;
+    public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
 
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
@@ -72,6 +73,7 @@
         lastDisableAppCaller = o.lastDisableAppCaller;
         domainVerificationStatus = o.domainVerificationStatus;
         appLinkGeneration = o.appLinkGeneration;
+        categoryHint = o.categoryHint;
         disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
         enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
     }
@@ -197,6 +199,9 @@
         if (appLinkGeneration != oldState.appLinkGeneration) {
             return false;
         }
+        if (categoryHint != oldState.categoryHint) {
+            return false;
+        }
         if ((disabledComponents == null && oldState.disabledComponents != null)
                 || (disabledComponents != null && oldState.disabledComponents == null)) {
             return false;
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c91f0a5..b961394 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1338,6 +1338,7 @@
              any accounts. The type should correspond to the account authenticator type, such as
              "com.google". -->
         <attr name="requiredAccountType" format="string"/>
+        <!-- @deprecated replaced by setting appCategory attribute to "game" -->
         <attr name="isGame" />
         <!-- Declare that this application may use cleartext traffic, such as HTTP rather than
              HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP without STARTTLS or
@@ -1353,6 +1354,29 @@
         <attr name="directBootAware" />
         <attr name="resizeableActivity" />
         <attr name="networkSecurityConfig" />
+        <!-- Declare the category of this app. Categories are used to cluster multiple apps
+             together into meaningful groups, such as when summarizing battery, network, or
+             disk usage. Apps should only define this value when they fit well into one of
+             the specific categories. -->
+        <attr name="appCategory">
+            <!-- Apps which are primarily games. -->
+            <enum name="game" value="0" />
+            <!-- Apps which primarily work with audio or music, such as music players. -->
+            <enum name="audio" value="1" />
+            <!-- Apps which primarily work with video or movies, such as streaming video apps. -->
+            <enum name="video" value="2" />
+            <!-- Apps which primarily work with images or photos, such as camera or gallery apps. -->
+            <enum name="image" value="3" />
+            <!-- Apps which are primarily social apps, such as messaging, communication, or social network apps. -->
+            <enum name="social" value="4" />
+            <!-- Apps which are primarily news apps, such as newspapers, magazines, or sports apps. -->
+            <enum name="news" value="5" />
+            <!-- Apps which are primarily maps apps, such as navigation apps. -->
+            <enum name="maps" value="6" />
+            <!-- Apps which are primarily productivity apps, such as cloud storage or workplace apps. -->
+            <enum name="productivity" value="7" />
+        </attr>
+
     </declare-styleable>
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ef61cc6..bbc2d14 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2782,6 +2782,7 @@
         <public name="nextSectionForward" />
         <public name="textColorError" />
         <public name="focusedByDefault" />
+        <public name="appCategory" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b0d64577..87a4732 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4394,4 +4394,22 @@
 
     <!-- Window title for a tooltip [CHAR LIMIT=NONE] -->
     <string name="tooltip_popup_title">Tooltip</string>
+
+    <!-- Category title for apps which are primarily games. [CHAR LIMIT=32] -->
+    <string name="app_category_game">Games</string>
+    <!-- Category title for apps which primarily work with audio or music, such as music players. [CHAR LIMIT=32] -->
+    <string name="app_category_audio">Music &amp; Audio</string>
+    <!-- Category title for apps which primarily work with video or movies, such as streaming video apps. [CHAR LIMIT=32] -->
+    <string name="app_category_video">Movies &amp; Video</string>
+    <!-- Category title for apps which primarily work with images or photos, such as camera or gallery apps. [CHAR LIMIT=32] -->
+    <string name="app_category_image">Photos &amp; Images</string>
+    <!-- Category title for apps which are primarily social apps, such as messaging, communication, or social network apps. [CHAR LIMIT=32] -->
+    <string name="app_category_social">Social &amp; Communication</string>
+    <!-- Category title for apps which are primarily news apps, such as newspapers, magazines, or sports apps. [CHAR LIMIT=32] -->
+    <string name="app_category_news">News &amp; Magazines</string>
+    <!-- Category title for apps which are primarily maps apps, such as navigation apps. [CHAR LIMIT=32] -->
+    <string name="app_category_maps">Maps &amp; Navigation</string>
+    <!-- Category title for apps which are primarily productivity apps, such as cloud storage or workplace apps. [CHAR LIMIT=32] -->
+    <string name="app_category_productivity">Productivity</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f44b039..cd07d88 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2783,4 +2783,14 @@
   <java-symbol type="drawable" name="lockscreen_selected" />
 
   <java-symbol type="string" name="config_defaultCellBroadcastReceiverComponent" />
+
+  <java-symbol type="string" name="app_category_game" />
+  <java-symbol type="string" name="app_category_audio" />
+  <java-symbol type="string" name="app_category_video" />
+  <java-symbol type="string" name="app_category_image" />
+  <java-symbol type="string" name="app_category_social" />
+  <java-symbol type="string" name="app_category_news" />
+  <java-symbol type="string" name="app_category_maps" />
+  <java-symbol type="string" name="app_category_productivity" />
+
 </resources>
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 76ae57e..395ec1f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12956,6 +12956,27 @@
         }
     }
 
+    @Override
+    public void setApplicationCategoryHint(String packageName, int categoryHint,
+            String callerPackageName) {
+        mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
+                callerPackageName);
+        synchronized (mPackages) {
+            PackageSetting ps = mSettings.mPackages.get(packageName);
+            if (ps == null) {
+                throw new IllegalArgumentException("Unknown target package " + packageName);
+            }
+
+            if (!Objects.equals(callerPackageName, ps.installerPackageName)) {
+                throw new IllegalArgumentException("Calling package " + callerPackageName
+                        + " is not installer for " + packageName);
+            }
+
+            ps.categoryHint = categoryHint;
+            scheduleWriteSettingsLocked();
+        }
+    }
+
     private void processPendingInstall(final InstallArgs args, final int currentStatus) {
         // Queue up an async operation since the package installation may take a little while.
         mHandler.post(new Runnable() {
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 6089d2e..9456a5c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
@@ -129,6 +130,8 @@
     boolean isOrphaned;
     /** UUID of {@link VolumeInfo} hosting this app */
     String volumeUuid;
+    /** The category of this app, as hinted by the installer */
+    int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
 
     IntentFilterVerificationInfo verificationInfo;
 
@@ -246,6 +249,7 @@
         verificationInfo = orig.verificationInfo;
         versionCode = orig.versionCode;
         volumeUuid = orig.volumeUuid;
+        categoryHint = orig.categoryHint;
     }
 
     private PackageUserState modifyUserState(int userId) {
@@ -259,10 +263,11 @@
 
     public PackageUserState readUserState(int userId) {
         PackageUserState state = userState.get(userId);
-        if (state != null) {
-            return state;
+        if (state == null) {
+            return DEFAULT_USER_STATE;
         }
-        return DEFAULT_USER_STATE;
+        state.categoryHint = categoryHint;
+        return state;
     }
 
     void setEnabled(int state, int userId, String callingPackage) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7187fc2..395108b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2673,6 +2673,10 @@
         if (pkg.volumeUuid != null) {
             serializer.attribute(null, "volumeUuid", pkg.volumeUuid);
         }
+        if (pkg.categoryHint != ApplicationInfo.CATEGORY_UNDEFINED) {
+            serializer.attribute(null, "categoryHint",
+                    Integer.toString(pkg.categoryHint));
+        }
         if (pkg.parentPackageName != null) {
             serializer.attribute(null, "parentPackageName", pkg.parentPackageName);
         }
@@ -3538,6 +3542,8 @@
         String installerPackageName = null;
         String isOrphaned = null;
         String volumeUuid = null;
+        String categoryHintString = null;
+        int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
         String uidError = null;
         int pkgFlags = 0;
         int pkgPrivateFlags = 0;
@@ -3580,6 +3586,13 @@
             installerPackageName = parser.getAttributeValue(null, "installer");
             isOrphaned = parser.getAttributeValue(null, "isOrphaned");
             volumeUuid = parser.getAttributeValue(null, "volumeUuid");
+            categoryHintString = parser.getAttributeValue(null, "categoryHint");
+            if (categoryHintString != null) {
+                try {
+                    categoryHint = Integer.parseInt(categoryHintString);
+                } catch (NumberFormatException e) {
+                }
+            }
 
             systemStr = parser.getAttributeValue(null, "publicFlags");
             if (systemStr != null) {
@@ -3731,6 +3744,7 @@
             packageSetting.installerPackageName = installerPackageName;
             packageSetting.isOrphaned = "true".equals(isOrphaned);
             packageSetting.volumeUuid = volumeUuid;
+            packageSetting.categoryHint = categoryHint;
             packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
             packageSetting.primaryCpuAbiString = primaryCpuAbiString;
             packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 1bd5b1d..7158969 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -886,6 +886,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public void setApplicationCategoryHint(String packageName, int categoryHint) {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * @hide
      */
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index f47b105..8835b58 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -784,6 +784,10 @@
     }
 
     @Override
+    public void setApplicationCategoryHint(String packageName, int categoryHint) {
+    }
+
+    @Override
     public int getMoveStatus(int moveId) {
         return 0;
     }