Merge "Use statically assigned IP for the VM" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 84c3eee..1fabf8d 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -25,11 +25,13 @@
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.webkit.WebChromeClient;
+import android.webkit.WebResourceError;
+import android.webkit.WebResourceRequest;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.appcompat.app.AppCompatActivity;
@@ -43,13 +45,20 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
 
 public class MainActivity extends AppCompatActivity
         implements VmLauncherServices.VmLauncherServiceCallback,
                 AccessibilityManager.TouchExplorationStateChangeListener {
+
     private static final String TAG = "VmTerminalApp";
-    private String mVmIpAddr;
+    private static final String VM_ADDR = "192.168.0.2";
+    private static final int TTYD_PORT = 7681;
     private WebView mWebView;
+    private AccessibilityManager mAccessibilityManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -78,21 +87,73 @@
         mWebView.getSettings().setDomStorageEnabled(true);
         mWebView.getSettings().setJavaScriptEnabled(true);
         mWebView.setWebChromeClient(new WebChromeClient());
+
+        mAccessibilityManager = getSystemService(AccessibilityManager.class);
+        mAccessibilityManager.addTouchExplorationStateChangeListener(this);
+
+        connectToTerminalService();
+    }
+
+    private URL getTerminalServiceUrl() {
+        boolean needsAccessibility = mAccessibilityManager.isTouchExplorationEnabled();
+        String file = "/";
+        String query = needsAccessibility ? "?screenReaderMode=true" : "";
+
+        try {
+            return new URL("http", VM_ADDR, TTYD_PORT, file + query);
+        } catch (MalformedURLException e) {
+            // this cannot happen
+            return null;
+        }
+    }
+
+    private void connectToTerminalService() {
+        Log.i(TAG, "URL=" + getTerminalServiceUrl().toString());
         mWebView.setWebViewClient(
                 new WebViewClient() {
                     @Override
-                    public boolean shouldOverrideUrlLoading(WebView view, String url) {
-                        view.loadUrl(url);
-                        return true;
+                    public boolean shouldOverrideUrlLoading(
+                            WebView view, WebResourceRequest request) {
+                        return false;
+                    }
+
+                    @Override
+                    public void onReceivedError(
+                            WebView view, WebResourceRequest request, WebResourceError error) {
+                        switch (error.getErrorCode()) {
+                            case WebViewClient.ERROR_CONNECT:
+                            case WebViewClient.ERROR_HOST_LOOKUP:
+                                view.reload();
+                                return;
+                            default:
+                                String url = request.getUrl().toString();
+                                CharSequence msg = error.getDescription();
+                                Log.e(TAG, "Failed to load " + url + ": " + msg);
+                        }
                     }
 
                     @Override
                     public void onPageFinished(WebView view, String url) {
-                        android.os.Trace.endAsyncSection("executeTerminal", 0);
+                        URL loadedUrl = null;
+                        try {
+                            loadedUrl = new URL(url);
+                        } catch (MalformedURLException e) {
+                            // cannot happen.
+                        }
+                        Log.i(TAG, "on page finished. URL=" + loadedUrl);
+                        if (getTerminalServiceUrl().toString().equals(url)) {
+                            android.os.Trace.endAsyncSection("executeTerminal", 0);
+                            view.setVisibility(View.VISIBLE);
+                        }
                     }
                 });
-
-        getSystemService(AccessibilityManager.class).addTouchExplorationStateChangeListener(this);
+        new Thread(
+                        () -> {
+                            waitUntilVmStarts();
+                            runOnUiThread(
+                                    () -> mWebView.loadUrl(getTerminalServiceUrl().toString()));
+                        })
+                .start();
     }
 
     private void diskResize(Context context, long sizeInBytes) throws IOException {
@@ -179,6 +240,22 @@
         }
     }
 
+    private static void waitUntilVmStarts() {
+        InetAddress addr = null;
+        try {
+            addr = InetAddress.getByName(VM_ADDR);
+        } catch (UnknownHostException e) {
+            // this can never happen.
+        }
+        try {
+            while (!addr.isReachable(10000)) {}
+        } catch (IOException e) {
+            // give up on network error
+            throw new RuntimeException(e);
+        }
+        return;
+    }
+
     @Override
     protected void onDestroy() {
         getSystemService(AccessibilityManager.class).removeTouchExplorationStateChangeListener(this);
@@ -186,23 +263,6 @@
         super.onDestroy();
     }
 
-    private void gotoTerminalURL() {
-        if (mVmIpAddr == null) {
-            Log.d(TAG, "ip addr is not set yet");
-            return;
-        }
-
-        boolean isTouchExplorationEnabled =
-                getSystemService(AccessibilityManager.class).isTouchExplorationEnabled();
-
-        String url =
-                "http://"
-                        + mVmIpAddr
-                        + ":7681/"
-                        + (isTouchExplorationEnabled ? "?screenReaderMode=true" : "");
-        runOnUiThread(() -> mWebView.loadUrl(url));
-    }
-
     @Override
     public void onVmStart() {
         Log.i(TAG, "onVmStart()");
@@ -224,9 +284,7 @@
 
     @Override
     public void onIpAddrAvailable(String ipAddr) {
-        mVmIpAddr = ipAddr;
-        ((TextView) findViewById(R.id.ip_addr_textview)).setText(mVmIpAddr);
-        gotoTerminalURL();
+        // TODO: remove this
     }
 
     @Override
@@ -241,10 +299,11 @@
         if (id == R.id.copy_ip_addr) {
             // TODO(b/340126051): remove this menu item when port forwarding is supported.
             getSystemService(ClipboardManager.class)
-                    .setPrimaryClip(ClipData.newPlainText("A VM's IP address", mVmIpAddr));
+                    .setPrimaryClip(ClipData.newPlainText("A VM's IP address", VM_ADDR));
             return true;
         } else if (id == R.id.stop_vm) {
             VmLauncherServices.stopVmLauncherService(this);
+            mWebView.setVisibility(View.INVISIBLE);
             return true;
 
         } else if (id == R.id.menu_item_settings) {
@@ -257,6 +316,6 @@
 
     @Override
     public void onTouchExplorationStateChanged(boolean enabled) {
-        gotoTerminalURL();
+        connectToTerminalService();
     }
 }
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
index f786a0f..736b45a 100644
--- a/android/TerminalApp/res/layout/activity_headless.xml
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -20,6 +20,7 @@
       android:id="@+id/webview"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
-      android:layout_marginBottom="5dp" />
+      android:layout_marginBottom="5dp"
+      android:visibility="invisible"/>
 
 </LinearLayout>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index c3a3348..821a2ef 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -17,9 +17,9 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name">Terminal</string>
-    <string name="vm_creation_message">Virtual machine is booting. Please wait.</string>
-    <string name="vm_stop_message">Virtual machine is stopped. Exiting.</string>
-    <string name="vm_error_message">Virtual machine crashed. Exiting.</string>
+    <string name="vm_creation_message">Preparing terminal.</string>
+    <string name="vm_stop_message">Stopping terminal.</string>
+    <string name="vm_error_message">Terminal crashed.</string>
 
     <string name="settings_disk_resize_title">Disk Resize</string>
     <string name="settings_disk_resize_sub_title">Resize / Rootfs</string>
diff --git a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
index 241eef4..998389b 100644
--- a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
+++ b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.LinkAddress;
 import android.net.TetheringManager;
 import android.net.TetheringManager.StartTetheringCallback;
 import android.net.TetheringManager.TetheringRequest;
@@ -157,8 +158,11 @@
 
         @Override
         public void enableVmTethering() {
+            LinkAddress local = new LinkAddress("192.168.0.1/24");
+            LinkAddress client = new LinkAddress("192.168.0.2/24");
             final TetheringRequest tr =
                     new TetheringRequest.Builder(TetheringManager.TETHERING_VIRTUAL)
+                            .setStaticIpv4Addresses(local, client)
                             .setConnectivityScope(TetheringManager.CONNECTIVITY_SCOPE_GLOBAL)
                             .build();