Merge Android 14 QPR2 to AOSP main

Bug: 319669529
Merged-In: I72c966ba72fb7d1171a886b286fc97f4aaf3d43c
Change-Id: Ibd86951ebe1e6cb232aa540940dc9cc7f8e69ca8
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 17a31d4..35a11fd 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -9,7 +9,10 @@
       ]
     },
     {
-      "name": "libtextclassifier_tests"
+      "name": "libtextclassifier_tests-tplus"
+    },
+    {
+      "name": "libtextclassifier_tests-sminus"
     },
     {
       "name": "libtextclassifier_java_tests"
@@ -36,7 +39,10 @@
       ]
     },
     {
-      "name": "libtextclassifier_tests"
+      "name": "libtextclassifier_tests-tplus"
+    },
+    {
+      "name": "libtextclassifier_tests-sminus"
     },
     {
       "name": "libtextclassifier_java_tests"
@@ -47,13 +53,25 @@
   ],
   "mainline-presubmit": [
     {
+      "name": "TextClassifierNotificationTests[com.google.android.extservices_tplus.apex]"
+    },
+    {
+      "name": "TextClassifierServiceTest[com.google.android.extservices_tplus.apex]"
+    },
+    {
+      "name": "libtextclassifier_tests-tplus[com.google.android.extservices_tplus.apex]"
+    },
+    {
+      "name": "libtextclassifier_java_tests[com.google.android.extservices_tplus.apex]"
+    },
+    {
       "name": "TextClassifierNotificationTests[com.google.android.extservices.apex]"
     },
     {
       "name": "TextClassifierServiceTest[com.google.android.extservices.apex]"
     },
     {
-      "name": "libtextclassifier_tests[com.google.android.extservices.apex]"
+      "name": "libtextclassifier_tests-sminus[com.google.android.extservices.apex]"
     },
     {
       "name": "libtextclassifier_java_tests[com.google.android.extservices.apex]"
diff --git a/java/src/com/android/textclassifier/DefaultTextClassifierService.java b/java/src/com/android/textclassifier/DefaultTextClassifierService.java
index d57af5e..d34f860 100644
--- a/java/src/com/android/textclassifier/DefaultTextClassifierService.java
+++ b/java/src/com/android/textclassifier/DefaultTextClassifierService.java
@@ -316,7 +316,7 @@
 
     @Override
     public TextClassifierSettings createTextClassifierSettings() {
-      return new TextClassifierSettings();
+      return new TextClassifierSettings(getContext());
     }
 
     @Override
diff --git a/java/src/com/android/textclassifier/common/TextClassifierSettings.java b/java/src/com/android/textclassifier/common/TextClassifierSettings.java
index d0ea917..6de8fe2 100644
--- a/java/src/com/android/textclassifier/common/TextClassifierSettings.java
+++ b/java/src/com/android/textclassifier/common/TextClassifierSettings.java
@@ -18,6 +18,8 @@
 
 import static java.util.concurrent.TimeUnit.HOURS;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.text.TextUtils;
@@ -55,15 +57,20 @@
 
   /** Whether the user language profile feature is enabled. */
   private static final String USER_LANGUAGE_PROFILE_ENABLED = "user_language_profile_enabled";
+
   /** Max length of text that suggestSelection can accept. */
   @VisibleForTesting
   static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH = "suggest_selection_max_range_length";
+
   /** Max length of text that classifyText can accept. */
   private static final String CLASSIFY_TEXT_MAX_RANGE_LENGTH = "classify_text_max_range_length";
+
   /** Max length of text that generateLinks can accept. */
   private static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length";
+
   /** Sampling rate for generateLinks logging. */
   private static final String GENERATE_LINKS_LOG_SAMPLE_RATE = "generate_links_log_sample_rate";
+
   /**
    * Extra count that is added to some languages, e.g. system languages, when deducing the frequent
    * languages in {@link
@@ -75,52 +82,65 @@
    * hint is not given.
    */
   @VisibleForTesting static final String ENTITY_LIST_DEFAULT = "entity_list_default";
+
   /**
    * A colon(:) separated string that specifies the default entities types for generateLinks when
    * the text is in a not editable UI widget.
    */
   private static final String ENTITY_LIST_NOT_EDITABLE = "entity_list_not_editable";
+
   /**
    * A colon(:) separated string that specifies the default entities types for generateLinks when
    * the text is in an editable UI widget.
    */
   private static final String ENTITY_LIST_EDITABLE = "entity_list_editable";
+
   /**
    * A colon(:) separated string that specifies the default action types for
    * suggestConversationActions when the suggestions are used in an app.
    */
   private static final String IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT =
       "in_app_conversation_action_types_default";
+
   /**
    * A colon(:) separated string that specifies the default action types for
    * suggestConversationActions when the suggestions are used in a notification.
    */
   private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT =
       "notification_conversation_action_types_default";
+
   /** Threshold to accept a suggested language from LangID model. */
   @VisibleForTesting static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override";
+
   /** Whether to enable {@link com.android.textclassifier.intent.TemplateIntentFactory}. */
   @VisibleForTesting
   static final String TEMPLATE_INTENT_FACTORY_ENABLED = "template_intent_factory_enabled";
+
   /** Whether to enable "translate" action in classifyText. */
   private static final String TRANSLATE_IN_CLASSIFICATION_ENABLED =
       "translate_in_classification_enabled";
+
   /**
    * Whether to detect the languages of the text in request by using langId for the native model.
    */
   private static final String DETECT_LANGUAGES_FROM_TEXT_ENABLED =
       "detect_languages_from_text_enabled";
+
   /** Whether to use models downloaded by config updater. */
   private static final String CONFIG_UPDATER_MODEL_ENABLED = "config_updater_model_enabled";
+
   /** Whether to enable model downloading with ModelDownloadManager */
   @VisibleForTesting
   public static final String MODEL_DOWNLOAD_MANAGER_ENABLED = "model_download_manager_enabled";
+
   /** Type of network to download model manifest. A String value of androidx.work.NetworkType. */
   private static final String MANIFEST_DOWNLOAD_REQUIRED_NETWORK_TYPE =
       "manifest_download_required_network_type";
+
   /** Max attempts allowed for a single ModelDownloader downloading task. */
   @VisibleForTesting
   static final String MODEL_DOWNLOAD_WORKER_MAX_ATTEMPTS = "model_download_worker_max_attempts";
+
   /** Max attempts allowed for a certain manifest url. */
   @VisibleForTesting
   public static final String MANIFEST_DOWNLOAD_MAX_ATTEMPTS = "manifest_download_max_attempts";
@@ -181,6 +201,7 @@
    * @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference.
    */
   @VisibleForTesting static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings";
+
   /** Default threshold to translate the language of the context the user selects */
   private static final String TRANSLATE_ACTION_THRESHOLD = "translate_action_threshold";
 
@@ -216,6 +237,7 @@
           ConversationAction.TYPE_VIEW_MAP,
           TYPE_ADD_CONTACT,
           TYPE_COPY);
+
   /**
    * < 0 : Not set. Use value from LangId model. 0 - 1: Override value in LangId model.
    *
@@ -236,6 +258,7 @@
   private static final int MANIFEST_DOWNLOAD_MAX_ATTEMPTS_DEFAULT = 3;
   private static final long MODEL_DOWNLOAD_BACKOFF_DELAY_IN_MILLIS_DEFAULT = HOURS.toMillis(1);
   private static final boolean MANIFEST_DOWNLOAD_REQUIRES_DEVICE_IDLE_DEFAULT = false;
+  private static final boolean MANIFEST_DOWNLOAD_REQUIRES_CHARGING_WEAR_DEFAULT = true;
   private static final boolean MANIFEST_DOWNLOAD_REQUIRES_CHARGING_DEFAULT = false;
   private static final boolean MULTI_LANGUAGE_SUPPORT_ENABLED_DEFAULT = false;
   private static final int MULTI_LANGUAGE_MODELS_LIMIT_DEFAULT = 2;
@@ -247,6 +270,7 @@
   private static final String MANIFEST_URL_DEFAULT = "";
   private static final String TESTING_LOCALE_LIST_OVERRIDE_DEFAULT = "";
   private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f};
+
   /**
    * Sampling rate for API logging. For example, 100 means there is a 0.01 chance that the API call
    * is the logged.
@@ -327,14 +351,18 @@
       };
 
   private final IDeviceConfig deviceConfig;
+  private final boolean isWear;
 
-  public TextClassifierSettings() {
-    this(DEFAULT_DEVICE_CONFIG);
+  public TextClassifierSettings(Context context) {
+    this(
+        DEFAULT_DEVICE_CONFIG,
+        context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
   }
 
   @VisibleForTesting
-  public TextClassifierSettings(IDeviceConfig deviceConfig) {
+  public TextClassifierSettings(IDeviceConfig deviceConfig, boolean isWear) {
     this.deviceConfig = deviceConfig;
+    this.isWear = isWear;
   }
 
   public int getSuggestSelectionMaxRangeLength() {
@@ -461,7 +489,9 @@
     return deviceConfig.getBoolean(
         NAMESPACE,
         MANIFEST_DOWNLOAD_REQUIRES_CHARGING,
-        MANIFEST_DOWNLOAD_REQUIRES_CHARGING_DEFAULT);
+        isWear
+            ? MANIFEST_DOWNLOAD_REQUIRES_CHARGING_WEAR_DEFAULT
+            : MANIFEST_DOWNLOAD_REQUIRES_CHARGING_DEFAULT);
   }
 
   /* Gets a list of models urls that should not be used. Usually used for a quick rollback.  */
@@ -496,6 +526,7 @@
     return deviceConfig.getInt(
         NAMESPACE, MULTI_ANNOTATOR_CACHE_SIZE, MULTI_ANNOTATOR_CACHE_SIZE_DEFAULT);
   }
+
   /**
    * Gets all language variants and associated manifest url configured for a specific ModelType.
    *
diff --git a/java/src/com/android/textclassifier/downloader/DownloadedModelManagerImpl.java b/java/src/com/android/textclassifier/downloader/DownloadedModelManagerImpl.java
index 9bdfb5e..8e60c15 100644
--- a/java/src/com/android/textclassifier/downloader/DownloadedModelManagerImpl.java
+++ b/java/src/com/android/textclassifier/downloader/DownloadedModelManagerImpl.java
@@ -77,7 +77,8 @@
                 .build();
         File modelDownloaderDir = new File(context.getFilesDir(), DOWNLOAD_SUB_DIR_NAME);
         instance =
-            new DownloadedModelManagerImpl(db, modelDownloaderDir, new TextClassifierSettings());
+            new DownloadedModelManagerImpl(
+                db, modelDownloaderDir, new TextClassifierSettings(context));
       }
       return instance;
     }
diff --git a/java/src/com/android/textclassifier/downloader/ModelDownloadWorker.java b/java/src/com/android/textclassifier/downloader/ModelDownloadWorker.java
index ff04aea..a71cdef 100644
--- a/java/src/com/android/textclassifier/downloader/ModelDownloadWorker.java
+++ b/java/src/com/android/textclassifier/downloader/ModelDownloadWorker.java
@@ -78,7 +78,7 @@
     this.executorService = TextClassifierServiceExecutors.getDownloaderExecutor();
     this.downloader = new ModelDownloaderImpl(context, executorService);
     this.downloadedModelManager = DownloadedModelManagerImpl.getInstance(context);
-    this.settings = new TextClassifierSettings();
+    this.settings = new TextClassifierSettings(context);
     this.pendingDownloads = new ArrayMap<>();
     this.manifestsToDownload = null;
 
@@ -359,6 +359,7 @@
       return manfiestDownloadFuture;
     }
   }
+
   // Download a model and register it into Model table.
   private ListenableFuture<Void> downloadModel(ModelManifest.Model modelInfo) {
     String modelUrl = modelInfo.getUrl();
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/DefaultTextClassifierServiceTest.java b/java/tests/instrumentation/src/com/android/textclassifier/DefaultTextClassifierServiceTest.java
index ddab8bd..ff99126 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/DefaultTextClassifierServiceTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/DefaultTextClassifierServiceTest.java
@@ -279,7 +279,7 @@
 
     @Override
     public TextClassifierSettings createTextClassifierSettings() {
-      return new TextClassifierSettings();
+      return new TextClassifierSettings(getContext());
     }
 
     @Override
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/ModelFileManagerImplTest.java b/java/tests/instrumentation/src/com/android/textclassifier/ModelFileManagerImplTest.java
index 0e40515..f465bf3 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/ModelFileManagerImplTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/ModelFileManagerImplTest.java
@@ -83,7 +83,7 @@
         new File(ApplicationProvider.getApplicationContext().getCacheDir(), "rootTestDir");
     rootTestDir.mkdirs();
     Context context = ApplicationProvider.getApplicationContext();
-    settings = new TextClassifierSettings(deviceConfig);
+    settings = new TextClassifierSettings(deviceConfig, /* isWear= */ false);
     modelDownloadManager =
         new ModelDownloadManager(
             context,
@@ -135,7 +135,7 @@
 
     ModelFile bestModelFile =
         modelFileManager.findBestModelFile(
-            MODEL_TYPE, /* localePreferences= */ null, /*detectedLocales=*/ null);
+            MODEL_TYPE, /* localePreferences= */ null, /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(newerModelFile);
   }
 
@@ -149,7 +149,7 @@
 
     ModelFile bestModelFile =
         modelFileManager.findBestModelFile(
-            MODEL_TYPE, new LocaleList(DEFAULT_LOCALE), /*detectedLocales=*/ null);
+            MODEL_TYPE, new LocaleList(DEFAULT_LOCALE), /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(languageDependentModelFile);
   }
 
@@ -162,7 +162,7 @@
 
     ModelFile bestModelFile =
         modelFileManager.findBestModelFile(
-            MODEL_TYPE, new LocaleList(DEFAULT_LOCALE), /*detectedLocales=*/ null);
+            MODEL_TYPE, new LocaleList(DEFAULT_LOCALE), /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(languageIndependentModelFile);
   }
 
@@ -175,7 +175,7 @@
 
     ModelFile bestModelFile =
         modelFileManager.findBestModelFile(
-            MODEL_TYPE, new LocaleList(DEFAULT_LOCALE), /*detectedLocales=*/ null);
+            MODEL_TYPE, new LocaleList(DEFAULT_LOCALE), /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(matchButOlderModel);
   }
 
@@ -189,7 +189,7 @@
 
     ModelFile bestModelFile =
         modelFileManager.findBestModelFile(
-            MODEL_TYPE, LocaleList.forLanguageTags("zh-hk"), /*detectedLocales=*/ null);
+            MODEL_TYPE, LocaleList.forLanguageTags("zh-hk"), /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(languageDependentModelFile);
   }
 
@@ -203,7 +203,7 @@
 
     ModelFile bestModelFile =
         modelFileManager.findBestModelFile(
-            MODEL_TYPE, LocaleList.forLanguageTags("zh"), /*detectedLocales=*/ null);
+            MODEL_TYPE, LocaleList.forLanguageTags("zh"), /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(languageDependentModelFile);
   }
 
@@ -217,7 +217,7 @@
 
     ModelFile bestModelFile =
         modelFileManager.findBestModelFile(
-            MODEL_TYPE, LocaleList.forLanguageTags("zh"), /*detectedLocales=*/ null);
+            MODEL_TYPE, LocaleList.forLanguageTags("zh"), /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(languageIndependentModelFile);
   }
 
@@ -250,7 +250,7 @@
         modelFileManager.findBestModelFile(
             MODEL_TYPE,
             new LocaleList(Locale.forLanguageTag("en"), Locale.forLanguageTag("zh-hk")),
-            /*detectedLocales=*/ null);
+            /* detectedLocales= */ null);
     assertThat(bestModelFile).isEqualTo(languageIndependentModelFile);
   }
 
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/TextClassifierImplTest.java b/java/tests/instrumentation/src/com/android/textclassifier/TextClassifierImplTest.java
index 89d405a..8a4487d 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/TextClassifierImplTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/TextClassifierImplTest.java
@@ -93,7 +93,7 @@
             .setAppLabel(FakeContextBuilder.DEFAULT_COMPONENT.getPackageName(), "Test app")
             .build();
     this.deviceConfig = new TestingDeviceConfig();
-    this.settings = new TextClassifierSettings(deviceConfig);
+    this.settings = new TextClassifierSettings(deviceConfig, /* isWear= */ false);
     this.annotatorModelCache = new LruCache<>(2);
     this.classifier =
         new TextClassifierImpl(context, settings, modelFileManager, annotatorModelCache);
@@ -177,7 +177,7 @@
     String suggested = "http://www.android.com";
     int startIndex = text.indexOf(suggested);
     TextSelection.Request request =
-        new TextSelection.Request.Builder(text, startIndex, /*endIndex=*/ startIndex + 1)
+        new TextSelection.Request.Builder(text, startIndex, /* endIndex= */ startIndex + 1)
             .setIncludeTextClassification(true)
             .build();
 
@@ -194,7 +194,7 @@
   public void testSuggestSelection_notIncludeTextClassification() throws IOException {
     String text = "Visit http://www.android.com for more information";
     TextSelection.Request request =
-        new TextSelection.Request.Builder(text, /*startIndex=*/ 0, /*endIndex=*/ 4)
+        new TextSelection.Request.Builder(text, /* startIndex= */ 0, /* endIndex= */ 4)
             .setIncludeTextClassification(false)
             .build();
 
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/common/TextClassifierSettingsTest.java b/java/tests/instrumentation/src/com/android/textclassifier/common/TextClassifierSettingsTest.java
index 17aef84..2f936e4 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/common/TextClassifierSettingsTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/common/TextClassifierSettingsTest.java
@@ -139,7 +139,7 @@
   private static void assertSettings(
       Map<String, String> keyValueMap, Consumer<TextClassifierSettings> settingsConsumer) {
     TestingDeviceConfig deviceConfig = new TestingDeviceConfig();
-    TextClassifierSettings settings = new TextClassifierSettings(deviceConfig);
+    TextClassifierSettings settings = new TextClassifierSettings(deviceConfig, /* isWear= */ false);
     for (String key : keyValueMap.keySet()) {
       deviceConfig.setConfig(key, keyValueMap.get(key));
     }
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/downloader/DownloadedModelManagerImplTest.java b/java/tests/instrumentation/src/com/android/textclassifier/downloader/DownloadedModelManagerImplTest.java
index 5ff4d89..7adbf6d 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/downloader/DownloadedModelManagerImplTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/downloader/DownloadedModelManagerImplTest.java
@@ -52,7 +52,7 @@
     modelDownloaderDir = new File(context.getFilesDir(), "test_dir");
     modelDownloaderDir.mkdirs();
     deviceConfig = new TestingDeviceConfig();
-    settings = new TextClassifierSettings(deviceConfig);
+    settings = new TextClassifierSettings(deviceConfig, /* isWear= */ false);
     db = Room.inMemoryDatabaseBuilder(context, DownloadedModelDatabase.class).build();
     downloadedModelManagerImpl =
         DownloadedModelManagerImpl.getInstanceForTesting(db, modelDownloaderDir, settings);
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/downloader/LocaleUtilsTest.java b/java/tests/instrumentation/src/com/android/textclassifier/downloader/LocaleUtilsTest.java
index a553c51..1350868 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/downloader/LocaleUtilsTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/downloader/LocaleUtilsTest.java
@@ -39,7 +39,7 @@
   @Before
   public void setUp() {
     deviceConfig = new TestingDeviceConfig();
-    settings = new TextClassifierSettings(deviceConfig);
+    settings = new TextClassifierSettings(deviceConfig, /* isWear= */ false);
   }
 
   @Test
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadManagerTest.java b/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadManagerTest.java
index 9e11c09..6128100 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadManagerTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadManagerTest.java
@@ -83,7 +83,7 @@
             ModelDownloadWorker.class,
             () -> workManager,
             downloadedModelManager,
-            new TextClassifierSettings(deviceConfig),
+            new TextClassifierSettings(deviceConfig, /* isWear= */ false),
             MoreExecutors.newDirectExecutorService());
     this.downloadManagerWithBadWorkManager =
         new ModelDownloadManager(
@@ -93,7 +93,7 @@
               throw new IllegalStateException("WorkManager may fail!");
             },
             downloadedModelManager,
-            new TextClassifierSettings(deviceConfig),
+            new TextClassifierSettings(deviceConfig, /* isWear= */ false),
             MoreExecutors.newDirectExecutorService());
 
     setDefaultLocalesRule.set(DEFAULT_LOCALE_LIST);
diff --git a/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadWorkerTest.java b/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadWorkerTest.java
index 3646934..7f50f83 100644
--- a/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadWorkerTest.java
+++ b/java/tests/instrumentation/src/com/android/textclassifier/downloader/ModelDownloadWorkerTest.java
@@ -157,7 +157,7 @@
 
     Context context = ApplicationProvider.getApplicationContext();
     this.deviceConfig = new TestingDeviceConfig();
-    this.settings = new TextClassifierSettings(deviceConfig);
+    this.settings = new TextClassifierSettings(deviceConfig, /* isWear= */ false);
     this.modelDownloaderDir = new File(context.getCacheDir(), "downloaded");
     this.modelDownloaderDir.mkdirs();
     this.modelFile = new File(modelDownloaderDir, "test.model");
diff --git a/native/Android.bp b/native/Android.bp
index 5287894..4f14b4f 100644
--- a/native/Android.bp
+++ b/native/Android.bp
@@ -31,6 +31,7 @@
         "com.android.btservices",
         "com.android.neuralnetworks",
         "test_com.android.neuralnetworks",
+        "com.android.ondevicepersonalization",
     ],
     min_sdk_version: "apex_inherit",
     sdk_version: "current",
@@ -71,6 +72,7 @@
         "test_com.android.neuralnetworks",
         "com.android.extservices",
         "com.android.adservices",
+        "com.android.ondevicepersonalization",
     ],
 }
 
@@ -251,7 +253,7 @@
 // libtextclassifier_tests
 // -----------------------
 cc_test {
-    name: "libtextclassifier_tests",
+    name: "libtextclassifier_tests-tplus",
     defaults: ["libtextclassifier_defaults"],
 
     test_suites: ["general-tests", "mts-extservices"],
@@ -277,10 +279,59 @@
         "libtextclassifier_fbgen_utils_lua_utils_tests",
     ],
 
-    compile_multilib: "prefer32",
-
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
     // A workaround for code coverage. See b/166040889#comment23
     sdk_variant_only: true,
+    test_config: "AndroidTest-tplus.xml",
+}
+
+cc_test {
+    name: "libtextclassifier_tests-sminus",
+    defaults: ["libtextclassifier_defaults"],
+
+    test_suites: ["general-tests", "mts-extservices"],
+
+    data: [
+        "**/test_data/*",
+        "**/*.bfbs",
+    ],
+
+    srcs: ["**/*.cc"],
+    exclude_srcs: [":libtextclassifier_java_test_sources"],
+
+    header_libs: ["jni_headers"],
+
+    static_libs: [
+        "libgmock_ndk",
+        "libgtest_ndk_c++",
+        "libbase_ndk",
+    ],
+
+    generated_headers: [
+        "libtextclassifier_fbgen_utils_flatbuffers_flatbuffers_test",
+        "libtextclassifier_fbgen_utils_lua_utils_tests",
+    ],
+
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    // A workaround for code coverage. See b/166040889#comment23
+    sdk_variant_only: true,
+    test_config: "AndroidTest-sminus.xml",
 }
 
 // ------------------------------------
diff --git a/native/AndroidTest.xml b/native/AndroidTest-sminus.xml
similarity index 69%
copy from native/AndroidTest.xml
copy to native/AndroidTest-sminus.xml
index 11893f5..b4c8628 100644
--- a/native/AndroidTest.xml
+++ b/native/AndroidTest-sminus.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- 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.
@@ -13,14 +13,19 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Config for libtextclassifier_tests">
+<configuration description="Config for libtextclassifier_tests-sminus">
     <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.extservices.apex" />
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="mts" />
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
-        <option name="push" value="libtextclassifier_tests->/data/local/tmp/libtextclassifier_tests" />
+        <option name="push" value="libtextclassifier_tests-sminus->/data/local/tmp/libtextclassifier_tests-sminus" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
         <option name="push" value="actions->/data/local/tmp/actions" />
         <option name="push" value="annotator->/data/local/tmp/annotator" />
         <option name="push" value="utils->/data/local/tmp/utils" />
@@ -28,9 +33,15 @@
 
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="libtextclassifier_tests" />
+        <option name="module-name" value="libtextclassifier_tests-sminus" />
     </test>
 
+    <!-- Prevent test from running on Android T+ -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MaxSdkModuleController">
+        <option name="max-sdk-level" value="32"/>
+    </object>
+
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.extservices" />
     </object>
diff --git a/native/AndroidTest.xml b/native/AndroidTest-tplus.xml
similarity index 71%
rename from native/AndroidTest.xml
rename to native/AndroidTest-tplus.xml
index 11893f5..ab2749c 100644
--- a/native/AndroidTest.xml
+++ b/native/AndroidTest-tplus.xml
@@ -13,14 +13,19 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Config for libtextclassifier_tests">
-    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.extservices.apex" />
+<configuration description="Config for libtextclassifier_tests-tplus">
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.extservices_tplus.apex" />
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="mts" />
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
-        <option name="push" value="libtextclassifier_tests->/data/local/tmp/libtextclassifier_tests" />
+        <option name="push" value="libtextclassifier_tests-tplus->/data/local/tmp/libtextclassifier_tests-tplus" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
         <option name="push" value="actions->/data/local/tmp/actions" />
         <option name="push" value="annotator->/data/local/tmp/annotator" />
         <option name="push" value="utils->/data/local/tmp/utils" />
@@ -28,9 +33,13 @@
 
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="libtextclassifier_tests" />
+        <option name="module-name" value="libtextclassifier_tests-tplus" />
     </test>
 
+    <!-- Prevent tests from running on Android S- -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController"/>
+
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.extservices" />
     </object>