Merge "Add use_fec: false to microdroid partitions" into main
diff --git a/android/TerminalApp/.gitignore b/android/TerminalApp/.gitignore
index e81da29..e69de29 100644
--- a/android/TerminalApp/.gitignore
+++ b/android/TerminalApp/.gitignore
@@ -1,2 +0,0 @@
-assets/*
-!assets/.gitkeep
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 733a72b..4bb9703 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -8,6 +8,7 @@
         "java/**/*.java",
         "java/**/*.kt",
     ],
+    asset_dirs: ["assets"],
     resource_dirs: ["res"],
     static_libs: [
         "androidx-constraintlayout_constraintlayout",
diff --git a/android/TerminalApp/assets/js/ctrl_key_handler.js b/android/TerminalApp/assets/js/ctrl_key_handler.js
new file mode 100644
index 0000000..de901fc
--- /dev/null
+++ b/android/TerminalApp/assets/js/ctrl_key_handler.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+// keyCode 229 means composing text, so get the last character in
+// e.target.value.
+// keycode 64(@)-95(_) is mapped to a ctrl code
+// keycode 97(A)-122(Z) is converted to a small letter, and mapped to ctrl code
+window.term.attachCustomKeyEventHandler((e) => {
+  if (window.ctrl) {
+    keyCode = e.keyCode;
+    if (keyCode === 229) {
+      keyCode = e.target.value.charAt(e.target.selectionStart - 1).charCodeAt();
+    }
+    if (64 <= keyCode && keyCode <= 95) {
+      input = String.fromCharCode(keyCode - 64);
+    } else if (97 <= keyCode && keyCode <= 122) {
+      input = String.fromCharCode(keyCode - 96);
+    } else {
+      return true;
+    }
+    if (e.type === 'keyup') {
+      window.term.input(input);
+      e.target.value = e.target.value.slice(0, -1);
+      window.ctrl = false;
+    }
+    return false;
+  } else {
+    return true;
+  }
+});
+})();
\ No newline at end of file
diff --git a/android/TerminalApp/assets/js/enable_ctrl_key.js b/android/TerminalApp/assets/js/enable_ctrl_key.js
new file mode 100644
index 0000000..4aedcfe
--- /dev/null
+++ b/android/TerminalApp/assets/js/enable_ctrl_key.js
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+window.ctrl = true;
+})();
\ No newline at end of file
diff --git a/android/TerminalApp/assets/js/touch_to_mouse_handler.js b/android/TerminalApp/assets/js/touch_to_mouse_handler.js
new file mode 100644
index 0000000..fce03d6
--- /dev/null
+++ b/android/TerminalApp/assets/js/touch_to_mouse_handler.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+// TODO(b/375326606): consider contribution on
+// upstream(https://github.com/xtermjs/xterm.js/issues/3727)
+let convertTouchToMouse = false;
+function touchHandler(event) {
+  const contextmenuByTouch =
+      event.type === 'contextmenu' && event.pointerType === 'touch';
+  // Only proceed for long touches (contextmenu) or when converting touch to
+  // mouse
+  if (!contextmenuByTouch && !convertTouchToMouse) {
+    return;
+  }
+
+  const touch = event.changedTouches ? event.changedTouches[0] : event;
+
+  let type;
+  switch (event.type) {
+    case 'contextmenu':
+      convertTouchToMouse = true;
+      type = 'mousedown';
+      break;
+    case 'touchmove':
+      type = 'mousemove';
+      break;
+    case 'touchend':
+      convertTouchToMouse = false;
+      type = 'mouseup';
+      break;
+    default:
+      convertTouchToMouse = false;
+      return;
+  }
+
+  const simulatedEvent = new MouseEvent(type, {
+    bubbles: true,
+    cancelable: true,
+    view: window,
+    detail: 1,
+    screenX: touch.screenX,
+    screenY: touch.screenY,
+    clientX: touch.clientX,
+    clientY: touch.clientY,
+    button: 0,  // left click
+  });
+
+  touch.target.dispatchEvent(simulatedEvent);
+
+  // Prevent default behavior for touch events (except contextmenu)
+  if (event.type !== 'contextmenu') {
+    event.preventDefault();
+    event.stopPropagation();
+  }
+}
+const eventOptions = {
+  capture: true,
+  passive: false
+};
+document.addEventListener('touchstart', touchHandler, eventOptions);
+document.addEventListener('touchmove', touchHandler, eventOptions);
+document.addEventListener('touchend', touchHandler, eventOptions);
+document.addEventListener('touchcancel', touchHandler, eventOptions);
+document.addEventListener('contextmenu', touchHandler, eventOptions);
+})();
\ No newline at end of file
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
index 93b0b0c..d167da3 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
@@ -19,7 +19,6 @@
 import static com.android.virtualization.terminal.MainActivity.TAG;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.util.Log;
 
 import androidx.annotation.Keep;
@@ -34,22 +33,13 @@
 
 import io.grpc.stub.StreamObserver;
 
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
 final class DebianServiceImpl extends DebianServiceGrpc.DebianServiceImplBase {
-    private static final String PREFERENCE_FILE_KEY =
-            "com.android.virtualization.terminal.PREFERENCE_FILE_KEY";
-    private static final String PREFERENCE_FORWARDING_PORTS = "PREFERENCE_FORWARDING_PORTS";
-    private static final String PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX =
-            "PREFERENCE_FORWARDING_PORT_IS_ENABLED_";
-
     private final Context mContext;
-    private final SharedPreferences mSharedPref;
-    private final String mPreferenceForwardingPorts;
-    private final String mPreferenceForwardingPortIsEnabled;
-    private SharedPreferences.OnSharedPreferenceChangeListener mPortForwardingListener;
+    private final PortsStateManager mPortsStateManager;
+    private PortsStateManager.Listener mPortsStateListener;
     private final DebianServiceCallback mCallback;
 
     static {
@@ -60,12 +50,7 @@
         super();
         mCallback = callback;
         mContext = context;
-        mSharedPref =
-                mContext.getSharedPreferences(
-                        mContext.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
-        mPreferenceForwardingPorts = mContext.getString(R.string.preference_forwarding_ports);
-        mPreferenceForwardingPortIsEnabled =
-                mContext.getString(R.string.preference_forwarding_port_is_enabled);
+        mPortsStateManager = PortsStateManager.getInstance(mContext);
     }
 
     @Override
@@ -73,23 +58,7 @@
             ReportVmActivePortsRequest request,
             StreamObserver<ReportVmActivePortsResponse> responseObserver) {
         Log.d(TAG, "reportVmActivePorts: " + request.toString());
-
-        Set<String> prevPorts =
-                mSharedPref.getStringSet(mPreferenceForwardingPorts, Collections.emptySet());
-        SharedPreferences.Editor editor = mSharedPref.edit();
-        Set<String> ports = new HashSet<>();
-        for (int port : request.getPortsList()) {
-            ports.add(Integer.toString(port));
-            if (!mSharedPref.contains(
-                    mPreferenceForwardingPortIsEnabled + Integer.toString(port))) {
-                editor.putBoolean(
-                        mPreferenceForwardingPortIsEnabled + Integer.toString(port), false);
-            }
-        }
-        editor.putStringSet(mPreferenceForwardingPorts, ports);
-        editor.apply();
-        mCallback.onActivePortsChanged(prevPorts, ports);
-
+        mPortsStateManager.updateActivePorts(new HashSet<>(request.getPortsList()));
         ReportVmActivePortsResponse reply =
                 ReportVmActivePortsResponse.newBuilder().setSuccess(true).build();
         responseObserver.onNext(reply);
@@ -110,18 +79,15 @@
     public void openForwardingRequestQueue(
             QueueOpeningRequest request, StreamObserver<ForwardingRequestItem> responseObserver) {
         Log.d(TAG, "OpenForwardingRequestQueue");
-        mPortForwardingListener =
-                new SharedPreferences.OnSharedPreferenceChangeListener() {
+        mPortsStateListener =
+                new PortsStateManager.Listener() {
                     @Override
-                    public void onSharedPreferenceChanged(
-                            SharedPreferences sharedPreferences, String key) {
-                        if (key.startsWith(mPreferenceForwardingPortIsEnabled)
-                                || key.equals(mPreferenceForwardingPorts)) {
-                            updateListeningPorts();
-                        }
+                    public void onPortsStateUpdated(
+                            Set<Integer> oldActivePorts, Set<Integer> newActivePorts) {
+                        updateListeningPorts();
                     }
                 };
-        mSharedPref.registerOnSharedPreferenceChangeListener(mPortForwardingListener);
+        mPortsStateManager.registerListener(mPortsStateListener);
         updateListeningPorts();
         runForwarderHost(request.getCid(), new ForwarderHostCallback(responseObserver));
         responseObserver.onCompleted();
@@ -151,31 +117,26 @@
 
     void killForwarderHost() {
         Log.d(TAG, "Stopping port forwarding");
-        if (mPortForwardingListener != null) {
-            mSharedPref.unregisterOnSharedPreferenceChangeListener(mPortForwardingListener);
-            terminateForwarderHost();
+        if (mPortsStateListener != null) {
+            mPortsStateManager.unregisterListener(mPortsStateListener);
+            mPortsStateListener = null;
         }
+        terminateForwarderHost();
     }
 
     private static native void updateListeningPorts(int[] ports);
 
     private void updateListeningPorts() {
+        Set<Integer> activePorts = mPortsStateManager.getActivePorts();
+        Set<Integer> enabledPorts = mPortsStateManager.getEnabledPorts();
         updateListeningPorts(
-                mSharedPref
-                        .getStringSet(mPreferenceForwardingPorts, Collections.emptySet())
-                        .stream()
-                        .filter(
-                                port ->
-                                        mSharedPref.getBoolean(
-                                                mPreferenceForwardingPortIsEnabled + port, false))
-                        .map(Integer::valueOf)
+                activePorts.stream()
+                        .filter(port -> enabledPorts.contains(port))
                         .mapToInt(Integer::intValue)
                         .toArray());
     }
 
     protected interface DebianServiceCallback {
         void onIpAddressAvailable(String ipAddr);
-
-        void onActivePortsChanged(Set<String> oldPorts, Set<String> newPorts);
     }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index bfa425d..cfb1cbe 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -80,7 +80,7 @@
     private InstalledImage mImage;
     private X509Certificate[] mCertificates;
     private PrivateKey mPrivateKey;
-    private WebView mWebView;
+    private TerminalView mTerminalView;
     private AccessibilityManager mAccessibilityManager;
     private ConditionVariable mBootCompleted = new ConditionVariable();
     private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
@@ -113,12 +113,12 @@
 
         MaterialToolbar toolbar = (MaterialToolbar) findViewById(R.id.toolbar);
         setSupportActionBar(toolbar);
-        mWebView = (WebView) findViewById(R.id.webview);
-        mWebView.getSettings().setDatabaseEnabled(true);
-        mWebView.getSettings().setDomStorageEnabled(true);
-        mWebView.getSettings().setJavaScriptEnabled(true);
-        mWebView.getSettings().setCacheMode(LOAD_NO_CACHE);
-        mWebView.setWebChromeClient(new WebChromeClient());
+        mTerminalView = (TerminalView) findViewById(R.id.webview);
+        mTerminalView.getSettings().setDatabaseEnabled(true);
+        mTerminalView.getSettings().setDomStorageEnabled(true);
+        mTerminalView.getSettings().setJavaScriptEnabled(true);
+        mTerminalView.getSettings().setCacheMode(LOAD_NO_CACHE);
+        mTerminalView.setWebChromeClient(new WebChromeClient());
 
         setupModifierKeys();
 
@@ -173,16 +173,16 @@
         findViewById(R.id.btn_ctrl)
                 .setOnClickListener(
                         (v) -> {
-                            mWebView.evaluateJavascript(TerminalView.CTRL_KEY_HANDLER, null);
-                            mWebView.evaluateJavascript(TerminalView.ENABLE_CTRL_KEY, null);
+                            mTerminalView.mapCtrlKey();
+                            mTerminalView.enableCtrlKey();
                         });
 
         View.OnClickListener modifierButtonClickListener =
                 v -> {
                     if (BTN_KEY_CODE_MAP.containsKey(v.getId())) {
                         int keyCode = BTN_KEY_CODE_MAP.get(v.getId());
-                        mWebView.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
-                        mWebView.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+                        mTerminalView.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+                        mTerminalView.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
                     }
                 };
 
@@ -246,7 +246,7 @@
 
     private void connectToTerminalService() {
         Log.i(TAG, "URL=" + getTerminalServiceUrl().toString());
-        mWebView.setWebViewClient(
+        mTerminalView.setWebViewClient(
                 new WebViewClient() {
                     private boolean mLoadFailed = false;
                     private long mRequestId = 0;
@@ -300,8 +300,7 @@
                                                     .setVisibility(View.VISIBLE);
                                             mBootCompleted.open();
                                             updateModifierKeysVisibility();
-                                            mWebView.evaluateJavascript(
-                                                    TerminalView.TOUCH_TO_MOUSE_HANDLER, null);
+                                            mTerminalView.mapTouchToMouseEvent();
                                         }
                                     }
                                 });
@@ -328,7 +327,9 @@
                         () -> {
                             waitUntilVmStarts();
                             runOnUiThread(
-                                    () -> mWebView.loadUrl(getTerminalServiceUrl().toString()));
+                                    () ->
+                                            mTerminalView.loadUrl(
+                                                    getTerminalServiceUrl().toString()));
                         })
                 .start();
     }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java b/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java
index 2f728ba..0d70ab9 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.drawable.Icon;
-import android.util.Log;
 
 import java.util.HashSet;
 import java.util.Locale;
@@ -45,35 +44,39 @@
     private final Context mContext;
     private final NotificationManager mNotificationManager;
     private final BroadcastReceiver mReceiver;
+    private final PortsStateManager mPortsStateManager;
+    private final PortsStateManager.Listener mPortsStateListener;
 
     public PortNotifier(Context context) {
         mContext = context;
         mNotificationManager = mContext.getSystemService(NotificationManager.class);
         mReceiver = new PortForwardingRequestReceiver();
 
+        mPortsStateManager = PortsStateManager.getInstance(mContext);
+        mPortsStateListener =
+                new PortsStateManager.Listener() {
+                    @Override
+                    public void onPortsStateUpdated(
+                            Set<Integer> oldActivePorts, Set<Integer> newActivePorts) {
+                        Set<Integer> union = new HashSet<>(oldActivePorts);
+                        union.addAll(newActivePorts);
+                        for (int port : union) {
+                            if (!oldActivePorts.contains(port)) {
+                                showNotificationFor(port);
+                            } else if (!newActivePorts.contains(port)) {
+                                discardNotificationFor(port);
+                            }
+                        }
+                    }
+                };
+        mPortsStateManager.registerListener(mPortsStateListener);
+
         IntentFilter intentFilter = new IntentFilter(ACTION_PORT_FORWARDING);
         mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
     }
 
-    public void onActivePortsChanged(Set<String> oldPorts, Set<String> newPorts) {
-        Set<String> union = new HashSet<>(oldPorts);
-        union.addAll(newPorts);
-        for (String portStr : union) {
-            try {
-                int port = Integer.parseInt(portStr);
-                if (!oldPorts.contains(portStr)) {
-                    showNotificationFor(port);
-                } else if (!newPorts.contains(portStr)) {
-                    discardNotificationFor(port);
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Failed to parse port: " + portStr);
-                throw e;
-            }
-        }
-    }
-
     public void stop() {
+        mPortsStateManager.unregisterListener(mPortsStateListener);
         mContext.unregisterReceiver(mReceiver);
     }
 
@@ -134,16 +137,9 @@
         }
 
         private void performActionPortForwarding(Context context, Intent intent) {
-            String prefKey = context.getString(R.string.preference_file_key);
             int port = intent.getIntExtra(KEY_PORT, 0);
-            String key = context.getString(R.string.preference_forwarding_port_is_enabled) + port;
             boolean enabled = intent.getBooleanExtra(KEY_ENABLED, false);
-
-            context.getSharedPreferences(prefKey, Context.MODE_PRIVATE)
-                    .edit()
-                    .putBoolean(key, enabled)
-                    .apply();
-
+            mPortsStateManager.updateEnabledPort(port, enabled);
             discardNotificationFor(port);
         }
     }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.java b/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.java
new file mode 100644
index 0000000..56ecd96
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.terminal;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * PortsStateManager is responsible for communicating with shared preferences and managing state of
+ * ports.
+ */
+public class PortsStateManager {
+    private static final String PREFS_NAME = ".PORTS";
+    private static final int FLAG_ENABLED = 1;
+
+    private static PortsStateManager mInstance;
+    private final Object mLock = new Object();
+
+    private final SharedPreferences mSharedPref;
+
+    @GuardedBy("mLock")
+    private Set<Integer> mActivePorts;
+
+    @GuardedBy("mLock")
+    private final Set<Integer> mEnabledPorts;
+
+    @GuardedBy("mLock")
+    private final Set<Listener> mListeners;
+
+    private PortsStateManager(SharedPreferences sharedPref) {
+        mSharedPref = sharedPref;
+        mEnabledPorts =
+                mSharedPref.getAll().entrySet().stream()
+                        .filter(entry -> entry.getValue() instanceof Integer)
+                        .filter(entry -> ((int) entry.getValue() & FLAG_ENABLED) == FLAG_ENABLED)
+                        .map(entry -> entry.getKey())
+                        .filter(
+                                key -> {
+                                    try {
+                                        Integer.parseInt(key);
+                                        return true;
+                                    } catch (NumberFormatException e) {
+                                        return false;
+                                    }
+                                })
+                        .map(Integer::parseInt)
+                        .collect(Collectors.toSet());
+        mActivePorts = new HashSet<>();
+        mListeners = new HashSet<>();
+    }
+
+    static synchronized PortsStateManager getInstance(Context context) {
+        if (mInstance == null) {
+            SharedPreferences sharedPref =
+                    context.getSharedPreferences(
+                            context.getPackageName() + PREFS_NAME, Context.MODE_PRIVATE);
+            mInstance = new PortsStateManager(sharedPref);
+        }
+        return mInstance;
+    }
+
+    Set<Integer> getActivePorts() {
+        synchronized (mLock) {
+            return new HashSet<>(mActivePorts);
+        }
+    }
+
+    Set<Integer> getEnabledPorts() {
+        synchronized (mLock) {
+            return new HashSet<>(mEnabledPorts);
+        }
+    }
+
+    void updateActivePorts(Set<Integer> ports) {
+        Set<Integer> oldPorts;
+        synchronized (mLock) {
+            oldPorts = mActivePorts;
+            mActivePorts = ports;
+        }
+        notifyPortsStateUpdated(oldPorts, ports);
+    }
+
+    void updateEnabledPort(int port, boolean enabled) {
+        Set<Integer> activePorts;
+        synchronized (mLock) {
+            SharedPreferences.Editor editor = mSharedPref.edit();
+            editor.putInt(String.valueOf(port), enabled ? FLAG_ENABLED : 0);
+            editor.apply();
+            if (enabled) {
+                mEnabledPorts.add(port);
+            } else {
+                mEnabledPorts.remove(port);
+            }
+            activePorts = mActivePorts;
+        }
+        notifyPortsStateUpdated(activePorts, activePorts);
+    }
+
+    void registerListener(Listener listener) {
+        synchronized (mLock) {
+            mListeners.add(listener);
+        }
+    }
+
+    void unregisterListener(Listener listener) {
+        synchronized (mLock) {
+            mListeners.remove(listener);
+        }
+    }
+
+    private void notifyPortsStateUpdated(Set<Integer> oldActivePorts, Set<Integer> newActivePorts) {
+        Set<Listener> listeners;
+        synchronized (mLock) {
+            listeners = new HashSet<>(mListeners);
+        }
+        for (Listener listener : listeners) {
+            listener.onPortsStateUpdated(
+                    new HashSet<>(oldActivePorts), new HashSet<>(newActivePorts));
+        }
+    }
+
+    interface Listener {
+        default void onPortsStateUpdated(
+                Set<Integer> oldActivePorts, Set<Integer> newActivePorts) {}
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
index fe693c4..d64c267 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -15,25 +15,21 @@
  */
 package com.android.virtualization.terminal
 
-import android.content.Context
-import android.content.SharedPreferences
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 
 class SettingsPortForwardingActivity : AppCompatActivity() {
-    private lateinit var mSharedPref: SharedPreferences
+    private lateinit var mPortsStateManager: PortsStateManager
     private lateinit var mAdapter: SettingsPortForwardingAdapter
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.settings_port_forwarding)
 
-        mSharedPref =
-            this.getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE)
-
-        mAdapter = SettingsPortForwardingAdapter(mSharedPref, this)
+        mPortsStateManager = PortsStateManager.getInstance(this)
+        mAdapter = SettingsPortForwardingAdapter(mPortsStateManager)
 
         val recyclerView: RecyclerView = findViewById(R.id.settings_port_forwarding_recycler_view)
         recyclerView.layoutManager = LinearLayoutManager(this)
@@ -42,11 +38,11 @@
 
     override fun onResume() {
         super.onResume()
-        mSharedPref.registerOnSharedPreferenceChangeListener(mAdapter)
+        mAdapter.registerPortsStateListener()
     }
 
     override fun onPause() {
-        mSharedPref.unregisterOnSharedPreferenceChangeListener(mAdapter)
+        mAdapter.unregisterPortsStateListener()
         super.onPause()
     }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt
index afe985a..8282910 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt
@@ -15,8 +15,6 @@
  */
 package com.android.virtualization.terminal
 
-import android.content.Context
-import android.content.SharedPreferences
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -26,14 +24,11 @@
 import androidx.recyclerview.widget.SortedListAdapterCallback
 import com.google.android.material.materialswitch.MaterialSwitch
 
-class SettingsPortForwardingAdapter(
-    private val sharedPref: SharedPreferences?,
-    private val context: Context,
-) :
-    RecyclerView.Adapter<SettingsPortForwardingAdapter.ViewHolder>(),
-    SharedPreferences.OnSharedPreferenceChangeListener {
+class SettingsPortForwardingAdapter(private val mPortsStateManager: PortsStateManager) :
+    RecyclerView.Adapter<SettingsPortForwardingAdapter.ViewHolder>() {
 
     private var mItems: SortedList<SettingsPortForwardingItem>
+    private val mPortsStateListener: Listener
 
     init {
         mItems =
@@ -63,24 +58,24 @@
                 },
             )
         mItems.addAll(getCurrentSettingsPortForwardingItem())
+        mPortsStateListener = Listener()
+    }
+
+    fun registerPortsStateListener() {
+        mPortsStateManager.registerListener(mPortsStateListener)
+        mItems.replaceAll(getCurrentSettingsPortForwardingItem())
+    }
+
+    fun unregisterPortsStateListener() {
+        mPortsStateManager.unregisterListener(mPortsStateListener)
     }
 
     private fun getCurrentSettingsPortForwardingItem(): ArrayList<SettingsPortForwardingItem> {
-        val items = ArrayList<SettingsPortForwardingItem>()
-        val ports =
-            sharedPref!!.getStringSet(
-                context.getString(R.string.preference_forwarding_ports),
-                HashSet<String>(),
-            )
-        for (port in ports!!) {
-            val enabled =
-                sharedPref.getBoolean(
-                    context.getString(R.string.preference_forwarding_port_is_enabled) + port,
-                    false,
-                )
-            items.add(SettingsPortForwardingItem(port.toInt(), enabled))
-        }
-        return items
+        val enabledPorts = mPortsStateManager.getEnabledPorts()
+        return mPortsStateManager
+            .getActivePorts()
+            .map { SettingsPortForwardingItem(it, enabledPorts.contains(it)) }
+            .toCollection(ArrayList())
     }
 
     class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@@ -97,32 +92,19 @@
     }
 
     override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
-        viewHolder.port.text = mItems[position].port.toString()
+        val port = mItems[position].port
+        viewHolder.port.text = port.toString()
         viewHolder.enabledSwitch.contentDescription = viewHolder.port.text
         viewHolder.enabledSwitch.isChecked = mItems[position].enabled
         viewHolder.enabledSwitch.setOnCheckedChangeListener { _, isChecked ->
-            val sharedPref: SharedPreferences =
-                context.getSharedPreferences(
-                    context.getString(R.string.preference_file_key),
-                    Context.MODE_PRIVATE,
-                )
-            val editor = sharedPref.edit()
-            editor.putBoolean(
-                context.getString(R.string.preference_forwarding_port_is_enabled) +
-                    viewHolder.port.text,
-                isChecked,
-            )
-            editor.apply()
+            mPortsStateManager.updateEnabledPort(port, isChecked)
         }
     }
 
     override fun getItemCount() = mItems.size()
 
-    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
-        if (
-            key == context.getString(R.string.preference_forwarding_ports) ||
-                key!!.startsWith(context.getString(R.string.preference_forwarding_port_is_enabled))
-        ) {
+    private inner class Listener : PortsStateManager.Listener {
+        override fun onPortsStateUpdated(oldActivePorts: Set<Int>, newActivePorts: Set<Int>) {
             mItems.replaceAll(getCurrentSettingsPortForwardingItem())
         }
     }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
index 3f09e35..c57c4c0 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
@@ -38,6 +38,8 @@
 import android.view.inputmethod.InputConnection;
 import android.webkit.WebView;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.List;
 
 public class TerminalView extends WebView
@@ -46,105 +48,9 @@
     // arbitrarily set. We may want to adjust this in the future.
     private static final int TEXT_TOO_LONG_TO_ANNOUNCE = 200;
 
-    // keyCode 229 means composing text, so get the last character in e.target.value.
-    // keycode 64(@)-95(_) is mapped to a ctrl code
-    // keycode 97(A)-122(Z) is converted to a small letter, and mapped to ctrl code
-    public static final String CTRL_KEY_HANDLER =
-            """
-(function() {
-  window.term.attachCustomKeyEventHandler((e) => {
-      if (window.ctrl) {
-          keyCode = e.keyCode;
-          if (keyCode === 229) {
-              keyCode = e.target.value.charAt(e.target.selectionStart - 1).charCodeAt();
-          }
-          if (64 <= keyCode && keyCode <= 95) {
-              input = String.fromCharCode(keyCode - 64);
-          } else if (97 <= keyCode && keyCode <= 122) {
-              input = String.fromCharCode(keyCode - 96);
-          } else {
-              return true;
-          }
-          if (e.type === 'keyup') {
-              window.term.input(input);
-              e.target.value = e.target.value.slice(0, -1);
-              window.ctrl = false;
-          }
-          return false;
-      } else {
-          return true;
-      }
-  });
-})();
-""";
-    public static final String ENABLE_CTRL_KEY = "(function(){window.ctrl=true;})();";
-
-    // TODO(b/375326606): consider contribution on
-    // upstream(https://github.com/xtermjs/xterm.js/issues/3727)
-    public static final String TOUCH_TO_MOUSE_HANDLER =
-            """
-(function() {
-let convertTouchToMouse = false;
-function touchHandler(event) {
-  const contextmenuByTouch =
-      event.type === 'contextmenu' && event.pointerType === 'touch';
-  // Only proceed for long touches (contextmenu) or when converting touch to
-  // mouse
-  if (!contextmenuByTouch && !convertTouchToMouse) {
-    return;
-  }
-
-  const touch = event.changedTouches ? event.changedTouches[0] : event;
-
-  let type;
-  switch (event.type) {
-    case 'contextmenu':
-      convertTouchToMouse = true;
-      type = 'mousedown';
-      break;
-    case 'touchmove':
-      type = 'mousemove';
-      break;
-    case 'touchend':
-      convertTouchToMouse = false;
-      type = 'mouseup';
-      break;
-    default:
-      convertTouchToMouse = false;
-      return;
-  }
-
-  const simulatedEvent = new MouseEvent(type, {
-    bubbles: true,
-    cancelable: true,
-    view: window,
-    detail: 1,
-    screenX: touch.screenX,
-    screenY: touch.screenY,
-    clientX: touch.clientX,
-    clientY: touch.clientY,
-    button: 0,  // left click
-  });
-
-  touch.target.dispatchEvent(simulatedEvent);
-
-  // Prevent default behavior for touch events (except contextmenu)
-  if (event.type !== 'contextmenu') {
-    event.preventDefault();
-    event.stopPropagation();
-  }
-}
-const eventOptions = {
-  capture: true,
-  passive: false
-};
-document.addEventListener('touchstart', touchHandler, eventOptions);
-document.addEventListener('touchmove', touchHandler, eventOptions);
-document.addEventListener('touchend', touchHandler, eventOptions);
-document.addEventListener('touchcancel', touchHandler, eventOptions);
-document.addEventListener('contextmenu', touchHandler, eventOptions);
-})();
-""";
+    private final String CTRL_KEY_HANDLER;
+    private final String ENABLE_CTRL_KEY;
+    private final String TOUCH_TO_MOUSE_HANDLER;
 
     private final AccessibilityManager mA11yManager;
 
@@ -155,6 +61,32 @@
         mA11yManager.addTouchExplorationStateChangeListener(this);
         mA11yManager.addAccessibilityStateChangeListener(this);
         adjustToA11yStateChange();
+        try {
+            CTRL_KEY_HANDLER = readAssetAsString(context, "js/ctrl_key_handler.js");
+            ENABLE_CTRL_KEY = readAssetAsString(context, "js/enable_ctrl_key.js");
+            TOUCH_TO_MOUSE_HANDLER = readAssetAsString(context, "js/touch_to_mouse_handler.js");
+        } catch (IOException e) {
+            // It cannot happen
+            throw new IllegalArgumentException("cannot read code from asset", e);
+        }
+    }
+
+    private String readAssetAsString(Context context, String filePath) throws IOException {
+        try (InputStream is = context.getAssets().open(filePath)) {
+            return new String(is.readAllBytes());
+        }
+    }
+
+    public void mapTouchToMouseEvent() {
+        this.evaluateJavascript(TOUCH_TO_MOUSE_HANDLER, null);
+    }
+
+    public void mapCtrlKey() {
+        this.evaluateJavascript(CTRL_KEY_HANDLER, null);
+    }
+
+    public void enableCtrlKey() {
+        this.evaluateJavascript(ENABLE_CTRL_KEY, null);
     }
 
     @Override
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index c2d224a..a82c688 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -291,11 +291,6 @@
         mResultReceiver.send(VmLauncherService.RESULT_IPADDR, b);
     }
 
-    @Override
-    public void onActivePortsChanged(Set<String> oldPorts, Set<String> newPorts) {
-        mPortNotifier.onActivePortsChanged(oldPorts, newPorts);
-    }
-
     public static void stop(Context context) {
         Intent i = getMyIntent(context);
         context.stopService(i);
diff --git a/android/TerminalApp/res/values/config.xml b/android/TerminalApp/res/values/config.xml
index 6440ee6..713e1a5 100644
--- a/android/TerminalApp/res/values/config.xml
+++ b/android/TerminalApp/res/values/config.xml
@@ -17,8 +17,6 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="preference_file_key" translatable="false">com.android.virtualization.terminal.PREFERENCE_FILE_KEY</string>
     <string name="preference_disk_size_key" translatable="false">PREFERENCE_DISK_SIZE_KEY</string>
-    <string name="preference_forwarding_ports" translatable="false">PREFERENCE_FORWARDING_PORTS</string>
-    <string name="preference_forwarding_port_is_enabled" translatable="false">PREFERENCE_FORWARDING_PORT_IS_ENABLED_</string>
 
     <bool name="terminal_portrait_only">true</bool>
 </resources>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 44d88a2..884e5f0 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -81,17 +81,31 @@
     <!-- Dialog confirmation button for restarting the terminal [CHAR LIMIT=16] -->
     <string name="settings_disk_resize_resize_confirm_dialog_confirm">Confirm</string>
 
-    <!-- Settings menu title for 'port forwarding' [CHAR LIMIT=none] -->
-    <string name="settings_port_forwarding_title">Port forwarding</string>
-    <!-- Settings menu subtitle for 'port forwarding' [CHAR LIMIT=none] -->
-    <string name="settings_port_forwarding_sub_title">Configure port forwarding</string>
-    <!-- Notification title for new port forwarding [CHAR LIMIT=none] -->
+    <!-- Settings menu title for port forwarding [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_title">Port control</string>
+    <!-- Settings menu subtitle for port forwarding [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_sub_title">Allow/deny listening ports</string>
+    <!-- Title for active ports setting in port forwarding [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_active_ports_title">Listening ports</string>
+    <!-- Title for other enabled ports setting in port forwarding [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_other_enabled_ports_title">Saved allowed ports</string>
+
+    <!-- Dialog title for enabling a new port [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_dialog_title">Allow a new port</string>
+    <!-- Dialog hint for enabling a new port [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_dialog_textview_hint">Enter a new port number</string>
+    <!-- Dialog save action for enabling a new port [CHAR LIMIT=16] -->
+    <string name="settings_port_forwarding_dialog_save">Save</string>
+    <!-- Dialog cancel action for enabling a new port [CHAR LIMIT=16] -->
+    <string name="settings_port_forwarding_dialog_cancel">Cancel</string>
+
+    <!-- Notification title for a new active port [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_notification_title">Terminal is requesting to open a new port</string>
-    <!-- Notification content for new port forwarding [CHAR LIMIT=none] -->
+    <!-- Notification content for a new active port [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_notification_content">Port requested: <xliff:g id="port_number" example="8080">%d</xliff:g></string>
-    <!-- Notification action accept [CHAR LIMIT=none] -->
+    <!-- Notification action accept [CHAR LIMIT=16] -->
     <string name="settings_port_forwarding_notification_accept">Accept</string>
-    <!-- Notification action deny [CHAR LIMIT=none] -->
+    <!-- Notification action deny [CHAR LIMIT=16] -->
     <string name="settings_port_forwarding_notification_deny">Deny</string>
 
     <!-- Settings menu title for recoverying image [CHAR LIMIT=none] -->
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index c2f7663..0f81f3d 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -53,6 +53,7 @@
 };
 use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{BnSecretkeeper, ISecretkeeper};
 use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::SecretId::SecretId;
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::PublicKey::PublicKey;
 use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
     Arc::Arc as AuthgraphArc, IAuthGraphKeyExchange::IAuthGraphKeyExchange,
     IAuthGraphKeyExchange::BnAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
@@ -888,16 +889,33 @@
         .context("Failed to extract vendor hashtree digest")
         .or_service_specific_exception(-1)?;
 
-    let trusted_props = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
+    let vendor_hashtree_digest = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
         info!(
             "Passing vendor hashtree digest to pvmfw. This will be rejected if it doesn't \
                 match the trusted digest in the pvmfw config, causing the VM to fail to start."
         );
-        vec![(cstr!("vendor_hashtree_descriptor_root_digest"), vendor_hashtree_digest.as_slice())]
+        Some((cstr!("vendor_hashtree_descriptor_root_digest"), vendor_hashtree_digest.as_slice()))
     } else {
-        vec![]
+        None
     };
 
+    let key_material;
+    let secretkeeper_public_key = if is_secretkeeper_supported() {
+        let sk: Strong<dyn ISecretkeeper> = binder::wait_for_interface(SECRETKEEPER_IDENTIFIER)?;
+        if sk.getInterfaceVersion()? >= 2 {
+            let PublicKey { keyMaterial } = sk.getSecretkeeperIdentity()?;
+            key_material = keyMaterial;
+            Some((cstr!("secretkeeper_public_key"), key_material.as_slice()))
+        } else {
+            None
+        }
+    } else {
+        None
+    };
+
+    let trusted_props: Vec<(&CStr, &[u8])> =
+        vec![vendor_hashtree_digest, secretkeeper_public_key].into_iter().flatten().collect();
+
     let instance_id;
     let mut untrusted_props = Vec::with_capacity(2);
     if cfg!(llpvm_changes) {
@@ -2042,6 +2060,14 @@
     fn deleteAll(&self) -> binder::Result<()> {
         self.0.deleteAll()
     }
+
+    fn getSecretkeeperIdentity(&self) -> binder::Result<PublicKey> {
+        // SecretkeeperProxy is really a RPC binder service for PVM (It is called by
+        // MicrodroidManager). PVMs do not & must not (for security reason) rely on
+        // getSecretKeeperIdentity, so we throw an exception if someone attempts to
+        // use this API from the proxy.
+        Err(ExceptionCode::SECURITY.into())
+    }
 }
 
 struct AuthGraphKeyExchangeProxy(Strong<dyn IAuthGraphKeyExchange>);
diff --git a/android/virtualizationservice/aidl/Android.bp b/android/virtualizationservice/aidl/Android.bp
index 79a9d40..db7be71 100644
--- a/android/virtualizationservice/aidl/Android.bp
+++ b/android/virtualizationservice/aidl/Android.bp
@@ -111,7 +111,7 @@
     name: "android.system.virtualmachineservice",
     srcs: ["android/system/virtualmachineservice/**/*.aidl"],
     imports: [
-        "android.hardware.security.secretkeeper-V1",
+        "android.hardware.security.secretkeeper-V2",
         "android.system.virtualizationcommon",
     ],
     unstable: true,
diff --git a/android/virtualizationservice/src/maintenance.rs b/android/virtualizationservice/src/maintenance.rs
index 8e04075..87ba412 100644
--- a/android/virtualizationservice/src/maintenance.rs
+++ b/android/virtualizationservice/src/maintenance.rs
@@ -297,7 +297,9 @@
 mod tests {
     use super::*;
     use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph;
-    use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper;
+    use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::{
+        self, PublicKey::PublicKey,
+    };
     use authgraph::IAuthGraphKeyExchange::IAuthGraphKeyExchange;
     use secretkeeper::ISecretkeeper::BnSecretkeeper;
     use std::sync::{Arc, Mutex};
@@ -335,6 +337,10 @@
             self.history.lock().unwrap().push(SkOp::DeleteAll);
             Ok(())
         }
+
+        fn getSecretkeeperIdentity(&self) -> binder::Result<PublicKey> {
+            unimplemented!()
+        }
     }
     impl binder::Interface for FakeSk {}
 
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 7231a7c..9eb478b 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -10,15 +10,14 @@
 	echo "Builds a debian image and save it to FILE. [sudo is required]"
 	echo "Options:"
 	echo "-h         Print usage and this help message and exit."
-	echo "-a ARCH    Architecture of the image [default is aarch64]"
+	echo "-a ARCH    Architecture of the image [default is host arch: $(uname -m)]"
 	echo "-r         Release mode build"
-	echo "-w         Save temp work directory (for debugging)"
+	echo "-w         Save temp work directory [for debugging]"
 }
 
 check_sudo() {
 	if [ "$EUID" -ne 0 ]; then
-		echo "Please run as root."
-		exit
+		echo "Please run as root." ; exit 1
 	fi
 }
 
@@ -26,17 +25,10 @@
 	while getopts "a:hrw" option; do
 		case ${option} in
 			h)
-				show_help
-				exit;;
+				show_help ; exit
+				;;
 			a)
-				if [[ "$OPTARG" != "aarch64" && "$OPTARG" != "x86_64" ]]; then
-					echo "Invalid architecture: $OPTARG"
-					exit
-				fi
 				arch="$OPTARG"
-				if [[ "$arch" == "x86_64" ]]; then
-					debian_arch="amd64"
-				fi
 				;;
 			r)
 				mode=release
@@ -45,11 +37,21 @@
 				save_workdir=1
 				;;
 			*)
-				echo "Invalid option: $OPTARG"
-				exit
+				echo "Invalid option: $OPTARG" ; exit 1
 				;;
 		esac
 	done
+	case "$arch" in
+		aarch64)
+			debian_arch="arm64"
+			;;
+		x86_64)
+			debian_arch="amd64"
+			;;
+		*)
+			echo "Invalid architecture: $arch" ; exit 1
+			;;
+	esac
 	if [[ "${*:$OPTIND:1}" ]]; then
 		built_image="${*:$OPTIND:1}"
 	fi
@@ -217,7 +219,7 @@
 }
 
 clean_up() {
-	[ "$save_workdir" -eq 0 ] || rm -rf "${workdir}"
+	[ "$save_workdir" -eq 1 ] || rm -rf "${workdir}"
 }
 
 set -e
@@ -230,8 +232,7 @@
 debian_version=bookworm
 config_space=${debian_cloud_image}/config_space/${debian_version}
 resources_dir=${debian_cloud_image}/src/debian_cloud_images/resources
-arch=aarch64
-debian_arch=arm64
+arch="$(uname -m)"
 mode=debug
 save_workdir=0
 
diff --git a/build/debian/build_in_container.sh b/build/debian/build_in_container.sh
index 7fd4c00..5028b74 100755
--- a/build/debian/build_in_container.sh
+++ b/build/debian/build_in_container.sh
@@ -1,35 +1,56 @@
 #!/bin/bash
 
-if [ -z "$ANDROID_BUILD_TOP" ]; then echo "forgot to source build/envsetup.sh?" && exit 1; fi
+show_help() {
+  echo "Usage: sudo $0 [OPTION]..."
+  echo "Builds a debian image and save it to image.raw."
+  echo "Options:"
+  echo "-h         Print usage and this help message and exit."
+  echo "-a ARCH    Architecture of the image [default is host arch: $(uname -m)]"
+  echo "-r         Release mode build"
+  echo "-s         Leave a shell open [default: only if the build fails]"
+  echo "-w         Save temp work directory in the container [for debugging]"
+}
 
-arch=aarch64
+arch="$(uname -m)"
 release_flag=
 save_workdir_flag=
+shell_condition="||"
 
-while getopts "a:rw" option; do
+while getopts "a:rsw" option; do
   case ${option} in
     a)
-      if [[ "$OPTARG" != "aarch64" && "$OPTARG" != "x86_64" ]]; then
-        echo "Invalid architecture: $OPTARG"
-        exit
-      fi
       arch="$OPTARG"
       ;;
+    h)
+      show_help ; exit
+      ;;
     r)
       release_flag="-r"
       ;;
+    s)
+      shell_condition=";"
+      ;;
     w)
       save_workdir_flag="-w"
       ;;
     *)
-      echo "Invalid option: $OPTARG"
-      exit
+      echo "Invalid option: $OPTARG" ; exit 1
       ;;
   esac
 done
 
+if [[ "$arch" != "aarch64" && "$arch" != "x86_64" ]]; then
+  echo "Invalid architecture: $arch" ; exit 1
+fi
+
+if [ -z "$ANDROID_BUILD_TOP" ] ; then
+  echo '`ANDROID_BUILD_TOP` is undefined.'
+  echo 'Please `lunch` an Android target, or manually set the variable.'
+  exit 1
+fi
+
 docker run --privileged -it -v /dev:/dev \
   -v "$ANDROID_BUILD_TOP/packages/modules/Virtualization:/root/Virtualization" \
   --workdir /root/Virtualization/build/debian \
   ubuntu:22.04 \
-  bash -c "/root/Virtualization/build/debian/build.sh -a $arch $release_flag $save_workdir_flag || bash"
+  bash -c "/root/Virtualization/build/debian/build.sh -a $arch $release_flag $save_workdir_flag $shell_condition bash"
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
index 130e691..43f0338 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
@@ -5,7 +5,7 @@
 cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
 sudo losetup -D
 grep vmx /proc/cpuinfo || true
-sudo ./build.sh -r
+sudo ./build.sh -r -a aarch64
 sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
 mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
 sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
index 50ded7b..22ac595 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
@@ -5,8 +5,7 @@
 cd "${KOKORO_ARTIFACTS_DIR}/git/avf/build/debian/"
 sudo losetup -D
 grep vmx /proc/cpuinfo || true
-sudo ./build.sh -a x86_64 -r
+sudo ./build.sh -r -a x86_64
 sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
-
 mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
 sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 4241c47..f799fb1 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -156,7 +156,7 @@
         "--allowlist-var=DICE_INLINE_CONFIG_SIZE",
         "--allowlist-var=DICE_PRIVATE_KEY_SEED_SIZE",
         "--allowlist-var=DICE_ID_SIZE",
-        "--allowlist-var=DICE_PRIVATE_KEY_SIZE",
+        "--allowlist-var=DICE_PRIVATE_KEY_BUFFER_SIZE",
     ],
 }
 
diff --git a/libs/dice/open_dice/src/dice.rs b/libs/dice/open_dice/src/dice.rs
index 206769c..6c7d48d 100644
--- a/libs/dice/open_dice/src/dice.rs
+++ b/libs/dice/open_dice/src/dice.rs
@@ -21,7 +21,7 @@
 use open_dice_cbor_bindgen::{
     DiceConfigType, DiceDeriveCdiCertificateId, DiceDeriveCdiPrivateKeySeed, DiceInputValues,
     DiceMainFlow, DICE_CDI_SIZE, DICE_HASH_SIZE, DICE_HIDDEN_SIZE, DICE_ID_SIZE,
-    DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_SEED_SIZE, DICE_PRIVATE_KEY_SIZE,
+    DICE_INLINE_CONFIG_SIZE, DICE_PRIVATE_KEY_BUFFER_SIZE, DICE_PRIVATE_KEY_SEED_SIZE,
 };
 #[cfg(feature = "multialg")]
 use open_dice_cbor_bindgen::{DiceContext_, DiceKeyAlgorithm};
@@ -41,7 +41,7 @@
 /// The size of a private key seed.
 pub const PRIVATE_KEY_SEED_SIZE: usize = DICE_PRIVATE_KEY_SEED_SIZE as usize;
 /// The size of a private key.
-pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_SIZE as usize;
+pub const PRIVATE_KEY_SIZE: usize = DICE_PRIVATE_KEY_BUFFER_SIZE as usize;
 /// The size of an ID.
 pub const ID_SIZE: usize = DICE_ID_SIZE as usize;