Snap for 13264465 from 2f5a83c6ccb8c1c57f84fa28f43b0ccd7e9df584 to 25Q2-release

Change-Id: Ib7ec04b2b29bc34f3eea346a75867efb3b97c60b
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..347e497
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,2 @@
+IndentWidth: 4
+AllowShortIfStatementsOnASingleLine: WithoutElse
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index fd41ee6..c5b2762 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -38,6 +38,7 @@
 import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -87,6 +88,7 @@
 
     // The context is for the current user (system server)
     private final Context mContext;
+    private final Dependencies mDependencies;
     private final ConnectivityResources mResources;
     private final TelephonyManager mTelephonyManager;
     // The notification manager is created from a context for User.ALL, so notifications
@@ -96,7 +98,15 @@
     private final SparseIntArray mNotificationTypeMap;
 
     public NetworkNotificationManager(@NonNull final Context c, @NonNull final TelephonyManager t) {
+        this(c, t, new Dependencies());
+    }
+
+    @VisibleForTesting
+    protected NetworkNotificationManager(@NonNull final Context c,
+            @NonNull final TelephonyManager t,
+            @NonNull Dependencies dependencies) {
         mContext = c;
+        mDependencies = dependencies;
         mTelephonyManager = t;
         mNotificationManager =
                 (NotificationManager) c.createContextAsUser(UserHandle.ALL, 0 /* flags */)
@@ -106,6 +116,13 @@
     }
 
     @VisibleForTesting
+    protected static class Dependencies {
+        public BidiFormatter getBidiFormatter() {
+            return BidiFormatter.getInstance();
+        }
+    }
+
+    @VisibleForTesting
     protected static int approximateTransportType(NetworkAgentInfo nai) {
         return nai.isVPN() ? TRANSPORT_VPN : getFirstTransportType(nai);
     }
@@ -174,7 +191,7 @@
                 name = extraInfo;
             } else {
                 final String ssid = WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid());
-                name = ssid == null ? "" : ssid;
+                name = ssid == null ? "" : mDependencies.getBidiFormatter().unicodeWrap(ssid);
             }
             // Only notify for Internet-capable networks.
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
index 60285a8..1db5765 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectivityDiagnosticsCollector.kt
@@ -36,6 +36,7 @@
 import android.net.wifi.WifiManager
 import android.os.Build
 import android.os.ParcelFileDescriptor
+import android.os.ParcelFileDescriptor.AutoCloseInputStream
 import android.telephony.TelephonyManager
 import android.telephony.TelephonyManager.SIM_STATE_UNKNOWN
 import android.util.Log
@@ -46,6 +47,7 @@
 import java.io.CharArrayWriter
 import java.io.File
 import java.io.FileReader
+import java.io.InputStream
 import java.io.OutputStream
 import java.io.OutputStreamWriter
 import java.io.PrintWriter
@@ -438,13 +440,44 @@
      * <p>The output will be collected immediately, and exported to a test artifact file when the
      * test ends.
      * @param cmd The command to run. Stdout of the command will be collected.
-     * @param shell The shell to run the command in.
+     * @param shell The shell to run the command in, for example "sh".
+     * @param exceptionContext An exception to write a stacktrace to the dump for context.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    fun collectCommandOutput(
+        cmd: String,
+        shell: String,
+        exceptionContext: Throwable? = null
+    ) = collectCommandOutput(cmd, exceptionContext) { c, outputProcessor ->
+        runCommandInShell(c, shell, outputProcessor)
+    }
+
+    /**
+     * Add the output of a command to the test data dump.
+     *
+     * <p>The output will be collected immediately, and exported to a test artifact file when the
+     * test ends.
+     *
+     * <p>Note this does not support shell pipes, redirections, or quoted arguments. See the S+
+     * overload if that is needed.
+     * @param cmd The command to run. Stdout of the command will be collected.
      * @param exceptionContext An exception to write a stacktrace to the dump for context.
      */
     fun collectCommandOutput(
         cmd: String,
-        shell: String = "sh",
         exceptionContext: Throwable? = null
+    ) = collectCommandOutput(cmd, exceptionContext) { c, outputProcessor ->
+        AutoCloseInputStream(
+            InstrumentationRegistry.getInstrumentation().uiAutomation.executeShellCommand(c)
+        ).use {
+            outputProcessor(it)
+        }
+    }
+
+    private fun collectCommandOutput(
+        cmd: String,
+        exceptionContext: Throwable? = null,
+        commandRunner: (String, (InputStream) -> Unit) -> Unit
     ) {
         Log.i(TAG, "Collecting '$cmd' for test artifacts")
         PrintWriter(buffer).let {
@@ -453,7 +486,7 @@
             it.flush()
         }
 
-        runCommandInShell(cmd, shell) { stdout, _ ->
+        commandRunner(cmd) { stdout ->
             stdout.copyTo(buffer)
         }
     }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ShellUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ShellUtil.kt
index fadc2ab..2b74036 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ShellUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ShellUtil.kt
@@ -19,8 +19,10 @@
 package com.android.testutils
 
 import android.app.UiAutomation
+import android.os.Build
 import android.os.ParcelFileDescriptor.AutoCloseInputStream
 import android.os.ParcelFileDescriptor.AutoCloseOutputStream
+import androidx.annotation.RequiresApi
 import androidx.test.platform.app.InstrumentationRegistry
 import java.io.InputStream
 
@@ -37,18 +39,17 @@
  *                        when this function returns.
  * @return Result of [outputProcessor].
  */
+@RequiresApi(Build.VERSION_CODES.S) // executeShellCommandRw is 31+
 fun <T> runCommandInShell(
     cmd: String,
     shell: String = "sh",
-    outputProcessor: (InputStream, InputStream) -> T,
+    outputProcessor: (InputStream) -> T,
 ): T {
-    val (stdout, stdin, stderr) = InstrumentationRegistry.getInstrumentation().uiAutomation
-        .executeShellCommandRwe(shell)
+    val (stdout, stdin) = InstrumentationRegistry.getInstrumentation().uiAutomation
+        .executeShellCommandRw(shell)
     AutoCloseOutputStream(stdin).bufferedWriter().use { it.write(cmd) }
     AutoCloseInputStream(stdout).use { outStream ->
-        AutoCloseInputStream(stderr).use { errStream ->
-            return outputProcessor(outStream, errStream)
-        }
+        return outputProcessor(outStream)
     }
 }
 
@@ -57,10 +58,11 @@
  *
  * Overload of [runCommandInShell] that reads and returns stdout as String.
  */
+@RequiresApi(Build.VERSION_CODES.S)
 fun runCommandInShell(
     cmd: String,
     shell: String = "sh",
-) = runCommandInShell(cmd, shell) { stdout, _ ->
+) = runCommandInShell(cmd, shell) { stdout ->
     stdout.reader().use { it.readText() }
 }
 
@@ -70,6 +72,7 @@
  * This is generally only usable on devices on which [DeviceInfoUtils.isDebuggable] is true.
  * @see runCommandInShell
  */
+@RequiresApi(Build.VERSION_CODES.S)
 fun runCommandInRootShell(
     cmd: String
 ) = runCommandInShell(cmd, shell = "su root sh")
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 727db58..bcdc4c5 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -27,6 +27,7 @@
 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.SIGN_IN;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
@@ -61,6 +62,7 @@
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.testing.PollingCheck;
+import android.text.BidiFormatter;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.widget.TextView;
@@ -95,13 +97,14 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkNotificationManagerTest {
 
-    private static final String TEST_SSID = "Test SSID";
+    private static final String TEST_SSID = "?Test SSID+";
     private static final String TEST_EXTRA_INFO = "extra";
     private static final int TEST_NOTIF_ID = 101;
     private static final String TEST_NOTIF_TAG = NetworkNotificationManager.tagFor(TEST_NOTIF_ID);
@@ -166,6 +169,7 @@
     @Mock NetworkAgentInfo mBluetoothNai;
     @Mock NetworkInfo mNetworkInfo;
     @Mock NetworkInfo mEmptyNetworkInfo;
+    @Mock NetworkNotificationManager.Dependencies mDependencies;
     ArgumentCaptor<Notification> mCaptor;
 
     NetworkNotificationManager mManager;
@@ -192,6 +196,7 @@
         doReturn(asUserCtx).when(mCtx).createContextAsUser(eq(UserHandle.ALL), anyInt());
         doReturn(mNotificationManager).when(mCtx)
                 .getSystemService(eq(Context.NOTIFICATION_SERVICE));
+        doReturn(BidiFormatter.getInstance(Locale.US)).when(mDependencies).getBidiFormatter();
         doReturn(TEST_EXTRA_INFO).when(mNetworkInfo).getExtraInfo();
         ConnectivityResources.setResourcesContextForTest(mCtx);
         doReturn(0xFF607D8B).when(mResources).getColor(anyInt(), any());
@@ -209,7 +214,7 @@
             .thenReturn(transportNames);
         when(mResources.getBoolean(R.bool.config_autoCancelNetworkNotifications)).thenReturn(true);
 
-        mManager = new NetworkNotificationManager(mCtx, mTelephonyManager);
+        mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mDependencies);
     }
 
     @After
@@ -535,6 +540,26 @@
     }
 
     @Test
+    public void testNotificationText_NoInternet_WithSsid() {
+        doReturn(null).when(mNetworkInfo).getExtraInfo();
+        doNotificationTextTest(NO_INTERNET,
+                R.string.wifi_no_internet, TEST_SSID,
+                R.string.wifi_no_internet_detailed);
+    }
+
+    @Test
+    public void testNotificationText_NoInternet_WithRtlSsid() {
+        final BidiFormatter formatter = BidiFormatter.getInstance(Locale.forLanguageTag("ar"));
+        final String wrappedString = formatter.unicodeWrap(TEST_SSID);
+        doReturn(formatter).when(mDependencies).getBidiFormatter();
+        doReturn(null).when(mNetworkInfo).getExtraInfo();
+        assertNotEquals(TEST_SSID, wrappedString);
+        doNotificationTextTest(NO_INTERNET,
+                R.string.wifi_no_internet, wrappedString,
+                R.string.wifi_no_internet_detailed);
+    }
+
+    @Test
     public void testNotificationText_Partial() {
         doNotificationTextTest(PARTIAL_CONNECTIVITY,
                 R.string.network_partial_connectivity, TEST_EXTRA_INFO,
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
index 5be8f49..6165afa 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkManagerTest.java
@@ -43,6 +43,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ThreadNetworkManagerTest {
+    private static final String THREAD_NETWORK_FEATURE = "android.hardware.thread_network";
+
     @Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -64,7 +66,7 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public void getManager_hasThreadFeatureOnVOrHigher_returnsNonNull() {
-        assumeTrue(mPackageManager.hasSystemFeature("android.hardware.thread_network"));
+        assumeTrue(mPackageManager.hasSystemFeature(THREAD_NETWORK_FEATURE));
 
         assertThat(mManager).isNotNull();
     }
@@ -81,8 +83,9 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
     @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    public void getManager_onUAndTv_returnsNonNull() {
+    public void getManager_onUAndTvWithThreadFeature_returnsNonNull() {
         assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+        assumeTrue(mPackageManager.hasSystemFeature(THREAD_NETWORK_FEATURE));
 
         assertThat(mManager).isNotNull();
     }