diff options
-rw-r--r-- | core/api/current.txt | 2 | ||||
-rw-r--r-- | core/api/system-current.txt | 15 | ||||
-rw-r--r-- | core/api/system-lint-baseline.txt | 2 | ||||
-rw-r--r-- | core/java/android/view/textclassifier/TextClassificationManager.java | 30 | ||||
-rw-r--r-- | core/java/android/view/textclassifier/TextClassifier.java | 52 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 11 | ||||
-rw-r--r-- | core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java | 31 | ||||
-rw-r--r-- | packages/Shell/AndroidManifest.xml | 4 |
8 files changed, 140 insertions, 7 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index e871c7383843..bbdcd6de9cb0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -58366,6 +58366,7 @@ package android.view.textclassifier { method @NonNull @WorkerThread public default android.view.textclassifier.TextSelection suggestSelection(@NonNull android.view.textclassifier.TextSelection.Request); method @NonNull @WorkerThread public default android.view.textclassifier.TextSelection suggestSelection(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @Nullable android.os.LocaleList); field public static final String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER"; + field @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") public static final String EXTRA_TEXT_ORIGIN_PACKAGE = "android.view.textclassifier.extra.TEXT_ORIGIN_PACKAGE"; field public static final String HINT_TEXT_IS_EDITABLE = "android.text_is_editable"; field public static final String HINT_TEXT_IS_NOT_EDITABLE = "android.text_is_not_editable"; field public static final android.view.textclassifier.TextClassifier NO_OP; @@ -58375,6 +58376,7 @@ package android.view.textclassifier { field public static final String TYPE_EMAIL = "email"; field public static final String TYPE_FLIGHT_NUMBER = "flight"; field public static final String TYPE_OTHER = "other"; + field @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") public static final String TYPE_OTP = "otp"; field public static final String TYPE_PHONE = "phone"; field public static final String TYPE_UNKNOWN = ""; field public static final String TYPE_URL = "url"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1c185899c2bb..36f3519f426d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -26,6 +26,7 @@ package android { field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS"; field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE"; field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER"; + field @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") public static final String ACCESS_TEXT_CLASSIFIER_BY_TYPE = "android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"; field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO"; field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER"; field public static final String ACCESS_TV_SHARED_FILTER = "android.permission.ACCESS_TV_SHARED_FILTER"; @@ -19136,6 +19137,20 @@ package android.view.inputmethod { } +package android.view.textclassifier { + + public final class TextClassificationManager { + method @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE) public android.view.textclassifier.TextClassifier getClassifier(int); + } + + public interface TextClassifier { + field @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") public static final int CLASSIFIER_TYPE_ANDROID_DEFAULT = 2; // 0x2 + field @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") public static final int CLASSIFIER_TYPE_DEVICE_DEFAULT = 1; // 0x1 + field @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") public static final int CLASSIFIER_TYPE_SELF_PROVIDED = 0; // 0x0 + } + +} + package android.view.translation { public final class TranslationCapability implements android.os.Parcelable { diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 7c43891f13f2..3b9ef959e797 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -505,6 +505,8 @@ DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface FlaggedApiLiteral: android.Manifest.permission#ACCESS_LAST_KNOWN_CELL_ID: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES). +FlaggedApiLiteral: android.Manifest.permission#ACCESS_TEXT_CLASSIFIER_BY_TYPE: + @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED). FlaggedApiLiteral: android.Manifest.permission#BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_HEALTH_CONNECT_BACKUP_RESTORE_PERMISSION_ENABLED). FlaggedApiLiteral: android.Manifest.permission#BIND_VERIFICATION_AGENT: diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index b606340b77a7..b9293242e4ff 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -16,13 +16,20 @@ package android.view.textclassifier; +import static android.Manifest.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; import android.os.ServiceManager; +import android.permission.flags.Flags; import android.view.textclassifier.TextClassifier.TextClassifierType; import com.android.internal.annotations.GuardedBy; @@ -115,6 +122,29 @@ public final class TextClassificationManager { } } + /** + * Returns a specific type of text classifier. + * If the specified text classifier cannot be found, this returns {@link TextClassifier#NO_OP}. + * <p> + * + * @see TextClassifier#CLASSIFIER_TYPE_SELF_PROVIDED + * @see TextClassifier#CLASSIFIER_TYPE_DEVICE_DEFAULT + * @see TextClassifier#CLASSIFIER_TYPE_ANDROID_DEFAULT + * @hide + */ + @SystemApi + @NonNull + @FlaggedApi(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) + @RequiresPermission(ACCESS_TEXT_CLASSIFIER_BY_TYPE) + public TextClassifier getClassifier(@TextClassifierType int type) { + if (mContext.checkCallingOrSelfPermission(ACCESS_TEXT_CLASSIFIER_BY_TYPE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "Caller does not have permission " + ACCESS_TEXT_CLASSIFIER_BY_TYPE); + } + return getTextClassifier(type); + } + private TextClassificationConstants getSettings() { synchronized (mLock) { if (mSettings == null) { diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index ef5004536354..59afdac1bfd7 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -16,16 +16,20 @@ package android.view.textclassifier; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.annotation.WorkerThread; import android.os.LocaleList; import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; +import android.permission.flags.Flags; import android.text.Spannable; import android.text.SpannableString; import android.text.style.URLSpan; @@ -63,11 +67,6 @@ public interface TextClassifier { /** @hide */ String LOG_TAG = "androidtc"; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {LOCAL, SYSTEM, DEFAULT_SYSTEM}) - @interface TextClassifierType {} // TODO: Expose as system APIs. /** Specifies a TextClassifier that runs locally in the app's process. @hide */ int LOCAL = 0; /** Specifies a TextClassifier that runs in the system process and serves all apps. @hide */ @@ -76,8 +75,33 @@ public interface TextClassifier { int DEFAULT_SYSTEM = 2; /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {CLASSIFIER_TYPE_SELF_PROVIDED, CLASSIFIER_TYPE_DEVICE_DEFAULT, + CLASSIFIER_TYPE_ANDROID_DEFAULT}) + @interface TextClassifierType { + } + /** Specifies a TextClassifier that runs locally in the app's process. @hide */ + @FlaggedApi(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) + @SystemApi + int CLASSIFIER_TYPE_SELF_PROVIDED = LOCAL; + /** + * Specifies a TextClassifier that is set as the default on this particular device. This may be + * the same as CLASSIFIER_TYPE_DEVICE_DEFAULT, unless set otherwise by the device manufacturer. + * @hide + */ + @FlaggedApi(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) + @SystemApi + int CLASSIFIER_TYPE_DEVICE_DEFAULT = SYSTEM; + /** Specifies the TextClassifier that is provided by Android. @hide */ + @FlaggedApi(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) + @SystemApi + int CLASSIFIER_TYPE_ANDROID_DEFAULT = DEFAULT_SYSTEM; + + /** @hide */ + @SuppressLint("SwitchIntDef") static String typeToString(@TextClassifierType int type) { - switch (type) { + int unflaggedType = type; + switch (unflaggedType) { case LOCAL: return "Local"; case SYSTEM: @@ -108,6 +132,9 @@ public interface TextClassifier { String TYPE_DATE_TIME = "datetime"; /** Flight number in IATA format. */ String TYPE_FLIGHT_NUMBER = "flight"; + /** Onetime password. */ + @FlaggedApi(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) + String TYPE_OTP = "otp"; /** * Word that users may be interested to look up for meaning. * @hide @@ -126,7 +153,8 @@ public interface TextClassifier { TYPE_DATE, TYPE_DATE_TIME, TYPE_FLIGHT_NUMBER, - TYPE_DICTIONARY + TYPE_DICTIONARY, + TYPE_OTP }) @interface EntityType {} @@ -198,6 +226,16 @@ public interface TextClassifier { String EXTRA_FROM_TEXT_CLASSIFIER = "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER"; /** + * Extra specifying the package name of the app from which the text to be classified originated. + * + * For example, a notification assistant might use TextClassifier, but the notification + * content could originate from a different app. This key allows you to provide + * the package name of that source app. + */ + @FlaggedApi(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) + String EXTRA_TEXT_ORIGIN_PACKAGE = "android.view.textclassifier.extra.TEXT_ORIGIN_PACKAGE"; + + /** * Returns suggested text selection start and end indices, recognized entity types, and their * associated confidence scores. The entity types are ordered from highest to lowest scoring. * diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6b0569041edd..c82978d88ea0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8792,6 +8792,17 @@ android:protectionLevel="signature|privileged|vendorPrivileged" android:featureFlag="android.media.tv.flags.kids_mode_tvdb_sharing"/> + <!-- @SystemApi + @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") + This permission is required to access the specific text classifier you need from the + TextClassificationManager. + <p>Protection level: signature|role + @hide + --> + <permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE" + android:protectionLevel="signature|role" + android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java index 402b92a3f2a2..26806b143629 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java @@ -16,16 +16,23 @@ package android.view.textclassifier; +import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; + import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; +import android.Manifest; import android.content.Context; +import android.permission.flags.Flags; +import android.platform.test.annotations.RequiresFlagsEnabled; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -61,4 +68,28 @@ public class TextClassificationManagerTest { assertThat(mTcm.getTextClassifier(TextClassifier.SYSTEM)) .isInstanceOf(SystemTextClassifier.class); } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_TEXT_CLASSIFIER_CHOICE_API_ENABLED) + public void testGetClassifier() { + Assume.assumeTrue(Flags.textClassifierChoiceApiEnabled()); + assertThrows(SecurityException.class, + () -> mTcm.getClassifier(TextClassifier.CLASSIFIER_TYPE_DEVICE_DEFAULT)); + assertThrows(SecurityException.class, + () -> mTcm.getClassifier(TextClassifier.CLASSIFIER_TYPE_ANDROID_DEFAULT)); + assertThrows(SecurityException.class, + () -> mTcm.getClassifier(TextClassifier.CLASSIFIER_TYPE_SELF_PROVIDED)); + + runWithShellPermissionIdentity(() -> { + assertThat( + mTcm.getClassifier(TextClassifier.CLASSIFIER_TYPE_DEVICE_DEFAULT)).isInstanceOf( + SystemTextClassifier.class); + assertThat(mTcm.getClassifier( + TextClassifier.CLASSIFIER_TYPE_ANDROID_DEFAULT)).isInstanceOf( + SystemTextClassifier.class); + assertThat(mTcm.getClassifier( + TextClassifier.CLASSIFIER_TYPE_SELF_PROVIDED)).isSameInstanceAs( + TextClassifier.NO_OP); + }, Manifest.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE); + } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index fa6e2dbe02f3..65f487765c1e 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -989,6 +989,10 @@ <uses-permission android:name="android.permission.health.READ_SKIN_TEMPERATURE" android:featureFlag="android.permission.flags.replace_body_sensor_permission_enabled"/> + <!-- Permission for TestClassifier tests to get access to classifier by type --> + <uses-permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE" + android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" |