diff --git a/private/app_neverallows.te b/private/app_neverallows.te
index 30acf8729929c86fde5a9f084d2a618d26a3be96..7936147341a651cf8c2d28ea070c6258c59e959e 100644
--- a/private/app_neverallows.te
+++ b/private/app_neverallows.te
@@ -51,6 +51,15 @@ neverallow {
   -runas_app
 } { app_data_file privapp_data_file }:file execute_no_trans;
 
+# Do not allow untrusted apps to invoke dex2oat. This was historically required
+# by ART for compiling secondary dex files but has been removed in Q.
+# Exempt legacy apps (targetApi<=28) for compatibility.
+neverallow {
+  all_untrusted_apps
+  -untrusted_app_25
+  -untrusted_app_27
+} dex2oat_exec:file no_x_file_perms;
+
 # Do not allow untrusted apps to be assigned mlstrustedsubject.
 # This would undermine the per-user isolation model being
 # enforced via levelFrom=user in seapp_contexts and the mls
diff --git a/private/untrusted_app_25.te b/private/untrusted_app_25.te
index d264aaf161a27f044b99d9059d9af57e244437f8..2db9c4b1a9fe0e2f1eb6f440241c3ac16321ca37 100644
--- a/private/untrusted_app_25.te
+++ b/private/untrusted_app_25.te
@@ -49,3 +49,9 @@ allow untrusted_app_25 { apk_data_file app_data_file asec_public_file }:file exe
 # for targetApi<=25. This is also allowed for targetAPIs 26, 27,
 # and 28 in untrusted_app_27.te.
 allow untrusted_app_25 app_data_file:file execute_no_trans;
+
+# The ability to invoke dex2oat. Historically required by ART, now only
+# allowed for targetApi<=28 for compat reasons.
+allow untrusted_app_25 dex2oat_exec:file rx_file_perms;
+auditallow untrusted_app_25 dex2oat_exec:file rx_file_perms;
+
diff --git a/private/untrusted_app_27.te b/private/untrusted_app_27.te
index 7b9060d639a637910167a82649613139106f9579..c828f64868d951a4c47f1e65916b50acf22eaabc 100644
--- a/private/untrusted_app_27.te
+++ b/private/untrusted_app_27.te
@@ -30,3 +30,9 @@ bluetooth_domain(untrusted_app_27)
 # The ability to call exec() on files in the apps home directories
 # for targetApi 26, 27, and 28.
 allow untrusted_app_27 app_data_file:file execute_no_trans;
+
+# The ability to invoke dex2oat. Historically required by ART, now only
+# allowed for targetApi<=28 for compat reasons.
+allow untrusted_app_27 dex2oat_exec:file rx_file_perms;
+auditallow untrusted_app_27 dex2oat_exec:file rx_file_perms;
+
diff --git a/public/app.te b/public/app.te
index 8b62967af03dfe49641802fbc1a48d9e31dbdcd2..40dee5dcdf510e1d8d839ddacf0e928e51dca995 100644
--- a/public/app.te
+++ b/public/app.te
@@ -119,9 +119,6 @@ r_dir_file(appdomain, vendor_framework_file)
 allow appdomain vendor_public_lib_file:dir r_dir_perms;
 allow appdomain vendor_public_lib_file:file { execute read open getattr map };
 
-# Execute dex2oat when apps call dexclassloader
-allow appdomain dex2oat_exec:file rx_file_perms;
-
 # Read/write wallpaper file (opened by system).
 allow appdomain wallpaper_file:file { getattr read write map };