Shortcut permissions for default text classifier

- Add two shortcut related permissions as SystemAPI for the default
text classifier.

- The default text classifier package name still comes from the
system resource.

Bug: 73083596
Bug: 71555985
Test: atest CtsPermission2TestCases
Test: Manual test with a test package, with the following in config.xml
<string name="config_defaultTextClassifierPackage" translatable="false">com.example.android.pm.shortcutlauncherdemo</string>

Change-Id: I19081d9d6ba0b33eb25d19aa55b4fe2f26a75897
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 13117bc..f8f50a2 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2802,4 +2802,13 @@
             return mArtManager;
         }
     }
+
+    @Override
+    public String getSystemTextClassifierPackageName() {
+        try {
+            return mPM.getSystemTextClassifierPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 379bff4..36a74a4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -660,4 +660,6 @@
     boolean hasSigningCertificate(String packageName, in byte[] signingCertificate, int flags);
 
     boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags);
+
+    String getSystemTextClassifierPackageName();
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 07a9911..bd7961f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6022,4 +6022,14 @@
         throw new UnsupportedOperationException(
                 "hasSigningCertificate not implemented in subclass");
     }
+
+    /**
+     * @return the system defined text classifier package name, or null if there's none.
+     *
+     * @hide
+     */
+    public String getSystemTextClassifierPackageName() {
+        throw new UnsupportedOperationException(
+                "getSystemTextClassifierPackageName not implemented in subclass");
+    }
 }
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 6f093ba..41aa9c3 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -43,12 +43,14 @@
     public static final int PACKAGE_INSTALLER = 2;
     public static final int PACKAGE_VERIFIER = 3;
     public static final int PACKAGE_BROWSER = 4;
+    public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5;
     @IntDef(value = {
         PACKAGE_SYSTEM,
         PACKAGE_SETUP_WIZARD,
         PACKAGE_INSTALLER,
         PACKAGE_VERIFIER,
         PACKAGE_BROWSER,
+        PACKAGE_SYSTEM_TEXT_CLASSIFIER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KnownPackage {}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index dda4167..7490d02 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3182,7 +3182,7 @@
 
         perm.info.protectionLevel = PermissionInfo.fixProtectionLevel(perm.info.protectionLevel);
 
-        if ((perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_FLAGS) != 0) {
+        if (perm.info.getProtectionFlags() != 0) {
             if ( (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_INSTANT) == 0
                     && (perm.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) == 0
                     && (perm.info.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) !=
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 21bd7f0..938409a 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -16,12 +16,16 @@
 
 package android.content.pm;
 
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Information you can retrieve about a particular security permission
  * known to the system.  This corresponds to information collected from the
@@ -56,6 +60,16 @@
     @Deprecated
     public static final int PROTECTION_SIGNATURE_OR_SYSTEM = 3;
 
+    /** @hide */
+    @IntDef(flag = false, prefix = { "PROTECTION_" }, value = {
+            PROTECTION_NORMAL,
+            PROTECTION_DANGEROUS,
+            PROTECTION_SIGNATURE,
+            PROTECTION_SIGNATURE_OR_SYSTEM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Protection {}
+
     /**
      * Additional flag for {@link #protectionLevel}, corresponding
      * to the <code>privileged</code> value of
@@ -155,30 +169,71 @@
     public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000;
 
     /**
-     * Mask for {@link #protectionLevel}: the basic protection type.
+     * Additional flag for {@link #protectionLevel}, corresponding
+     * to the <code>text_classifier</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
      */
+    @SystemApi
+    @TestApi
+    public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 0x10000;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
+            PROTECTION_FLAG_PRIVILEGED,
+            PROTECTION_FLAG_SYSTEM,
+            PROTECTION_FLAG_DEVELOPMENT,
+            PROTECTION_FLAG_APPOP,
+            PROTECTION_FLAG_PRE23,
+            PROTECTION_FLAG_INSTALLER,
+            PROTECTION_FLAG_VERIFIER,
+            PROTECTION_FLAG_PREINSTALLED,
+            PROTECTION_FLAG_SETUP,
+            PROTECTION_FLAG_INSTANT,
+            PROTECTION_FLAG_RUNTIME_ONLY,
+            PROTECTION_FLAG_OEM,
+            PROTECTION_FLAG_VENDOR_PRIVILEGED,
+            PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProtectionFlags {}
+
+    /**
+     * Mask for {@link #protectionLevel}: the basic protection type.
+     *
+     * @deprecated Use #getProtection() instead.
+     */
+    @Deprecated
     public static final int PROTECTION_MASK_BASE = 0xf;
 
     /**
      * Mask for {@link #protectionLevel}: additional flag bits.
+     *
+     * @deprecated Use #getProtectionFlags() instead.
      */
+    @Deprecated
     public static final int PROTECTION_MASK_FLAGS = 0xfff0;
 
     /**
      * The level of access this permission is protecting, as per
      * {@link android.R.attr#protectionLevel}. Consists of
-     * a base permission type and zero or more flags:
+     * a base permission type and zero or more flags. Use the following functions
+     * to extract them.
      *
      * <pre>
-     * int basePermissionType = protectionLevel & {@link #PROTECTION_MASK_BASE};
-     * int permissionFlags = protectionLevel & {@link #PROTECTION_MASK_FLAGS};
+     * int basePermissionType = permissionInfo.getProtection();
+     * int permissionFlags = permissionInfo.getProtectionFlags();
      * </pre>
      *
      * <p></p>Base permission types are {@link #PROTECTION_NORMAL},
      * {@link #PROTECTION_DANGEROUS}, {@link #PROTECTION_SIGNATURE}
      * and the deprecated {@link #PROTECTION_SIGNATURE_OR_SYSTEM}.
      * Flags are listed under {@link android.R.attr#protectionLevel}.
+     *
+     * @deprecated Use #getProtection() and #getProtectionFlags() instead.
      */
+    @Deprecated
     public int protectionLevel;
 
     /**
@@ -304,6 +359,9 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0) {
             protLevel += "|vendorPrivileged";
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0) {
+            protLevel += "|textClassifier";
+        }
         return protLevel;
     }
 
@@ -344,6 +402,22 @@
         return null;
     }
 
+    /**
+     * Return the base permission type.
+     */
+    @Protection
+    public int getProtection() {
+        return protectionLevel & PROTECTION_MASK_BASE;
+    }
+
+    /**
+     * Return the additional flags in {@link #protectionLevel}.
+     */
+    @ProtectionFlags
+    public int getProtectionFlags() {
+        return protectionLevel & ~PROTECTION_MASK_BASE;
+    }
+
     @Override
     public String toString() {
         return "PermissionInfo{"
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 6c8c8bc..2c8c4ec 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.CancellationSignal;
 import android.os.IBinder;
@@ -37,8 +38,6 @@
 import android.view.textclassifier.TextLinks;
 import android.view.textclassifier.TextSelection;
 
-import com.android.internal.R;
-
 /**
  * Abstract base class for the TextClassifier service.
  *
@@ -263,28 +262,33 @@
      */
     @Nullable
     public static ComponentName getServiceComponentName(Context context) {
-        final String str = context.getString(R.string.config_defaultTextClassifierService);
-        if (!TextUtils.isEmpty(str)) {
-            try {
-                final ComponentName componentName = ComponentName.unflattenFromString(str);
-                final Intent intent = new Intent(SERVICE_INTERFACE).setComponent(componentName);
-                final ServiceInfo si = context.getPackageManager()
-                        .getServiceInfo(intent.getComponent(), 0);
-                final String permission = si == null ? null : si.permission;
-                if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) {
-                    return componentName;
-                }
-                Slog.w(LOG_TAG, String.format(
-                        "Service %s should require %s permission. Found %s permission",
-                        intent.getComponent().flattenToString(),
-                        Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
-                        si.permission));
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.w(LOG_TAG, String.format("Service %s not found", str));
-            }
-        } else {
+        final String packageName = context.getPackageManager().getSystemTextClassifierPackageName();
+        if (TextUtils.isEmpty(packageName)) {
             Slog.d(LOG_TAG, "No configured system TextClassifierService");
+            return null;
         }
+
+        final Intent intent = new Intent(SERVICE_INTERFACE).setPackage(packageName);
+
+        final ResolveInfo ri = context.getPackageManager().resolveService(intent,
+                PackageManager.MATCH_SYSTEM_ONLY);
+
+        if ((ri == null) || (ri.serviceInfo == null)) {
+            Slog.w(LOG_TAG, String.format("Package or service not found in package %s",
+                    packageName));
+            return null;
+        }
+        final ServiceInfo si = ri.serviceInfo;
+
+        final String permission = si.permission;
+        if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) {
+            return si.getComponentName();
+        }
+        Slog.w(LOG_TAG, String.format(
+                "Service %s should require %s permission. Found %s permission",
+                si.getComponentName(),
+                Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE,
+                si.permission));
         return null;
     }
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3a527b5..7cf9971 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3810,9 +3810,15 @@
     <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
         android:protectionLevel="signature|development|instant|appop" />
 
-    <!-- @hide Allows system components to access all app shortcuts. -->
+    <!-- @SystemApi Allows to access all app shortcuts.
+         @hide -->
     <permission android:name="android.permission.ACCESS_SHORTCUTS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|textClassifier" />
+
+    <!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
+         @hide -->
+    <permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS"
+        android:protectionLevel="signature|textClassifier" />
 
     <!-- @SystemApi Allows an application to read the runtime profiles of other apps.
          @hide <p>Not for use by third-party applications. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 287f296..97f6784 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -173,10 +173,10 @@
          values listed below. If no protectionLevel is defined for a custom
          permission, the system assigns the default ("normal").
          <p>Each protection level consists of a base permission type and zero or
-         more flags:
+         more flags. Use the following functions to extract those.
          <pre>
-         int basePermissionType = protectionLevel & {@link android.content.pm.PermissionInfo#PROTECTION_MASK_BASE};
-         int permissionFlags = protectionLevel & {@link android.content.pm.PermissionInfo#PROTECTION_MASK_FLAGS};
+         int basePermissionType = permissionInfo.getProtection();
+         int permissionFlags = permissionInfo.getProtectionFlags();
          </pre>
          -->
     <attr name="protectionLevel">
@@ -265,6 +265,9 @@
         <!-- Additional flag from base permission type: this permission can be granted to
              privileged apps in vendor partition. -->
         <flag name="vendorPrivileged" value="0x8000" />
+        <!-- Additional flag from base permission type: this permission can be automatically
+            granted to the system default text classifier -->
+        <flag name="textClassifier" value="0x10000" />
     </attr>
 
     <!-- Flags indicating more context for a permission group. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1f4425f..d0c07dc 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3150,14 +3150,14 @@
     -->
     <string name="config_defaultAutofillService" translatable="false"></string>
 
-    <!-- The component name, flattened to a string, for the default system textclassifier service.
+    <!-- The package name for the default system textclassifier service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          (e.g. com.android.textclassifier/.TextClassifierServiceImpl).
          If no textclassifier service with the specified name exists on the device (or if this is
          set to empty string), a default textclassifier will be loaded in the calling app's process.
          See android.view.textclassifier.TextClassificationManager.
     -->
-    <string name="config_defaultTextClassifierService" translatable="false"></string>
+    <string name="config_defaultTextClassifierPackage" translatable="false"></string>
 
     <!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
     <bool name="config_useDefaultFocusHighlight">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b69ded8..150354b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3105,7 +3105,7 @@
   <java-symbol type="string" name="notification_channel_heavy_weight_app" />
   <java-symbol type="string" name="notification_channel_system_changes" />
   <java-symbol type="string" name="config_defaultAutofillService" />
-  <java-symbol type="string" name="config_defaultTextClassifierService" />
+  <java-symbol type="string" name="config_defaultTextClassifierPackage" />
 
   <java-symbol type="string" name="notification_channel_foreground_service" />
   <java-symbol type="string" name="foreground_service_app_in_background" />