Merge "disable virtio-balloon by default for VirtualMachineRawConfig" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
index b79e346..a0fca82 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
@@ -76,6 +76,7 @@
     private SharedPathJson[] sharedPath;
     private DisplayJson display;
     private GpuJson gpu;
+    private boolean auto_memory_balloon;
 
     /** Parses JSON file at jsonPath */
     static ConfigJson from(Context context, Path jsonPath) {
@@ -145,7 +146,8 @@
                 .setBootloaderPath(bootloader)
                 .setKernelPath(kernel)
                 .setInitrdPath(initrd)
-                .useNetwork(network);
+                .useNetwork(network)
+                .useAutoMemoryBalloon(auto_memory_balloon);
 
         if (input != null) {
             builder.useTouch(input.touchscreen)
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActiveAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActiveAdapter.kt
new file mode 100644
index 0000000..c46effa
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActiveAdapter.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.materialswitch.MaterialSwitch
+
+class SettingsPortForwardingActiveAdapter(private val mPortsStateManager: PortsStateManager) :
+    SettingsPortForwardingBaseAdapter<SettingsPortForwardingActiveAdapter.ViewHolder>() {
+
+    override fun getItems(): ArrayList<SettingsPortForwardingItem> {
+        val enabledPorts = mPortsStateManager.getEnabledPorts()
+        return mPortsStateManager
+            .getActivePorts()
+            .map { SettingsPortForwardingItem(it, enabledPorts.contains(it)) }
+            .toCollection(ArrayList())
+    }
+
+    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+        val enabledSwitch: MaterialSwitch =
+            view.findViewById(R.id.settings_port_forwarding_active_item_enabled_switch)
+        val port: TextView = view.findViewById(R.id.settings_port_forwarding_active_item_port)
+    }
+
+    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
+        val view =
+            LayoutInflater.from(viewGroup.context)
+                .inflate(R.layout.settings_port_forwarding_active_item, viewGroup, false)
+        return ViewHolder(view)
+    }
+
+    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
+        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 ->
+            mPortsStateManager.updateEnabledPort(port, isChecked)
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
index d64c267..83a8d05 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -22,27 +22,50 @@
 
 class SettingsPortForwardingActivity : AppCompatActivity() {
     private lateinit var mPortsStateManager: PortsStateManager
-    private lateinit var mAdapter: SettingsPortForwardingAdapter
+    private lateinit var mPortsStateListener: Listener
+    private lateinit var mActivePortsAdapter: SettingsPortForwardingActiveAdapter
+    private lateinit var mInactivePortsAdapter: SettingsPortForwardingInactiveAdapter
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.settings_port_forwarding)
 
         mPortsStateManager = PortsStateManager.getInstance(this)
-        mAdapter = SettingsPortForwardingAdapter(mPortsStateManager)
 
-        val recyclerView: RecyclerView = findViewById(R.id.settings_port_forwarding_recycler_view)
-        recyclerView.layoutManager = LinearLayoutManager(this)
-        recyclerView.adapter = mAdapter
+        mActivePortsAdapter = SettingsPortForwardingActiveAdapter(mPortsStateManager)
+        val activeRecyclerView: RecyclerView =
+            findViewById(R.id.settings_port_forwarding_active_recycler_view)
+        activeRecyclerView.layoutManager = LinearLayoutManager(this)
+        activeRecyclerView.adapter = mActivePortsAdapter
+
+        mInactivePortsAdapter = SettingsPortForwardingInactiveAdapter(mPortsStateManager, this)
+        val inactiveRecyclerView: RecyclerView =
+            findViewById(R.id.settings_port_forwarding_inactive_recycler_view)
+        inactiveRecyclerView.layoutManager = LinearLayoutManager(this)
+        inactiveRecyclerView.adapter = mInactivePortsAdapter
+
+        mPortsStateListener = Listener()
+    }
+
+    private fun refreshAdapters() {
+        mActivePortsAdapter.refreshItems()
+        mInactivePortsAdapter.refreshItems()
     }
 
     override fun onResume() {
         super.onResume()
-        mAdapter.registerPortsStateListener()
+        mPortsStateManager.registerListener(mPortsStateListener)
+        refreshAdapters()
     }
 
     override fun onPause() {
-        mAdapter.unregisterPortsStateListener()
+        mPortsStateManager.unregisterListener(mPortsStateListener)
         super.onPause()
     }
+
+    private inner class Listener : PortsStateManager.Listener {
+        override fun onPortsStateUpdated(oldActivePorts: Set<Int>, newActivePorts: Set<Int>) {
+            refreshAdapters()
+        }
+    }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt
deleted file mode 100644
index 8282910..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingAdapter.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.SortedList
-import androidx.recyclerview.widget.SortedListAdapterCallback
-import com.google.android.material.materialswitch.MaterialSwitch
-
-class SettingsPortForwardingAdapter(private val mPortsStateManager: PortsStateManager) :
-    RecyclerView.Adapter<SettingsPortForwardingAdapter.ViewHolder>() {
-
-    private var mItems: SortedList<SettingsPortForwardingItem>
-    private val mPortsStateListener: Listener
-
-    init {
-        mItems =
-            SortedList(
-                SettingsPortForwardingItem::class.java,
-                object : SortedListAdapterCallback<SettingsPortForwardingItem>(this) {
-                    override fun compare(
-                        o1: SettingsPortForwardingItem,
-                        o2: SettingsPortForwardingItem,
-                    ): Int {
-                        return o1.port - o2.port
-                    }
-
-                    override fun areContentsTheSame(
-                        o1: SettingsPortForwardingItem,
-                        o2: SettingsPortForwardingItem,
-                    ): Boolean {
-                        return o1.port == o2.port && o1.enabled == o2.enabled
-                    }
-
-                    override fun areItemsTheSame(
-                        o1: SettingsPortForwardingItem,
-                        o2: SettingsPortForwardingItem,
-                    ): Boolean {
-                        return o1.port == o2.port
-                    }
-                },
-            )
-        mItems.addAll(getCurrentSettingsPortForwardingItem())
-        mPortsStateListener = Listener()
-    }
-
-    fun registerPortsStateListener() {
-        mPortsStateManager.registerListener(mPortsStateListener)
-        mItems.replaceAll(getCurrentSettingsPortForwardingItem())
-    }
-
-    fun unregisterPortsStateListener() {
-        mPortsStateManager.unregisterListener(mPortsStateListener)
-    }
-
-    private fun getCurrentSettingsPortForwardingItem(): ArrayList<SettingsPortForwardingItem> {
-        val enabledPorts = mPortsStateManager.getEnabledPorts()
-        return mPortsStateManager
-            .getActivePorts()
-            .map { SettingsPortForwardingItem(it, enabledPorts.contains(it)) }
-            .toCollection(ArrayList())
-    }
-
-    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
-        val enabledSwitch: MaterialSwitch =
-            view.findViewById(R.id.settings_port_forwarding_item_enabled_switch)
-        val port: TextView = view.findViewById(R.id.settings_port_forwarding_item_port)
-    }
-
-    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
-        val view =
-            LayoutInflater.from(viewGroup.context)
-                .inflate(R.layout.settings_port_forwarding_item, viewGroup, false)
-        return ViewHolder(view)
-    }
-
-    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
-        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 ->
-            mPortsStateManager.updateEnabledPort(port, isChecked)
-        }
-    }
-
-    override fun getItemCount() = mItems.size()
-
-    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/SettingsPortForwardingBaseAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingBaseAdapter.kt
new file mode 100644
index 0000000..4595372
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingBaseAdapter.kt
@@ -0,0 +1,62 @@
+/*
+ * 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 androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.SortedList
+import androidx.recyclerview.widget.SortedListAdapterCallback
+
+abstract class SettingsPortForwardingBaseAdapter<T : RecyclerView.ViewHolder>() :
+    RecyclerView.Adapter<T>() {
+    var mItems: SortedList<SettingsPortForwardingItem>
+
+    init {
+        mItems =
+            SortedList(
+                SettingsPortForwardingItem::class.java,
+                object : SortedListAdapterCallback<SettingsPortForwardingItem>(this) {
+                    override fun compare(
+                        o1: SettingsPortForwardingItem,
+                        o2: SettingsPortForwardingItem,
+                    ): Int {
+                        return o1.port - o2.port
+                    }
+
+                    override fun areContentsTheSame(
+                        o1: SettingsPortForwardingItem,
+                        o2: SettingsPortForwardingItem,
+                    ): Boolean {
+                        return o1.port == o2.port && o1.enabled == o2.enabled
+                    }
+
+                    override fun areItemsTheSame(
+                        o1: SettingsPortForwardingItem,
+                        o2: SettingsPortForwardingItem,
+                    ): Boolean {
+                        return o1.port == o2.port
+                    }
+                },
+            )
+    }
+
+    override fun getItemCount() = mItems.size()
+
+    abstract fun getItems(): ArrayList<SettingsPortForwardingItem>
+
+    fun refreshItems() {
+        mItems.replaceAll(getItems())
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
new file mode 100644
index 0000000..ee0bee5
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+
+class SettingsPortForwardingInactiveAdapter(
+    private val mPortsStateManager: PortsStateManager,
+    private val mContext: Context,
+) : SettingsPortForwardingBaseAdapter<SettingsPortForwardingInactiveAdapter.ViewHolder>() {
+
+    override fun getItems(): ArrayList<SettingsPortForwardingItem> {
+        return mPortsStateManager
+            .getEnabledPorts()
+            .subtract(mPortsStateManager.getActivePorts())
+            .map { SettingsPortForwardingItem(it, true) }
+            .toCollection(ArrayList())
+    }
+
+    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+        val closeButton: ImageButton =
+            view.findViewById(R.id.settings_port_forwarding_active_item_close_button)
+        val port: TextView = view.findViewById(R.id.settings_port_forwarding_inactive_item_port)
+    }
+
+    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
+        val view =
+            LayoutInflater.from(viewGroup.context)
+                .inflate(R.layout.settings_port_forwarding_inactive_item, viewGroup, false)
+        return ViewHolder(view)
+    }
+
+    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
+        val port = mItems[position].port
+        viewHolder.port.text = port.toString()
+        viewHolder.closeButton.contentDescription =
+            mContext.getString(
+                R.string.settings_port_forwarding_other_enabled_port_close_button,
+                port,
+            )
+        viewHolder.closeButton.setOnClickListener { _ ->
+            mPortsStateManager.updateEnabledPort(port, false)
+        }
+    }
+}
diff --git a/android/TerminalApp/res/drawable/ic_close.xml b/android/TerminalApp/res/drawable/ic_close.xml
new file mode 100644
index 0000000..e21c19c
--- /dev/null
+++ b/android/TerminalApp/res/drawable/ic_close.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--  Copyright 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
+</vector>
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding.xml b/android/TerminalApp/res/layout/settings_port_forwarding.xml
index 2d21962..77b9bf7 100644
--- a/android/TerminalApp/res/layout/settings_port_forwarding.xml
+++ b/android/TerminalApp/res/layout/settings_port_forwarding.xml
@@ -31,9 +31,33 @@
         android:hyphenationFrequency="full"
         android:layout_marginBottom="24dp"/>
 
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:text="@string/settings_port_forwarding_active_ports_title"
+        android:textSize="24sp"
+        android:hyphenationFrequency="full"
+        android:layout_marginBottom="24dp"/>
+
     <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/settings_port_forwarding_recycler_view"
+        android:id="@+id/settings_port_forwarding_active_recycler_view"
         android:layout_marginHorizontal="16dp"
+        android:layout_marginBottom="24dp"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:text="@string/settings_port_forwarding_other_enabled_ports_title"
+        android:textSize="24sp"
+        android:hyphenationFrequency="full"
+        android:layout_marginBottom="24dp"/>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/settings_port_forwarding_inactive_recycler_view"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginBottom="24dp"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
 </LinearLayout>
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding_item.xml b/android/TerminalApp/res/layout/settings_port_forwarding_active_item.xml
similarity index 81%
rename from android/TerminalApp/res/layout/settings_port_forwarding_item.xml
rename to android/TerminalApp/res/layout/settings_port_forwarding_active_item.xml
index 8a57b41..2a74146 100644
--- a/android/TerminalApp/res/layout/settings_port_forwarding_item.xml
+++ b/android/TerminalApp/res/layout/settings_port_forwarding_active_item.xml
@@ -19,21 +19,26 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:minHeight="48dp"
     app:layout_constraintCircleRadius="@dimen/material_emphasis_medium">
 
     <TextView
-        android:id="@+id/settings_port_forwarding_item_port"
+        android:id="@+id/settings_port_forwarding_active_item_port"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
+        android:textSize="16sp"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="8dp"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"/>
 
     <com.google.android.material.materialswitch.MaterialSwitch
-        android:id="@+id/settings_port_forwarding_item_enabled_switch"
+        android:id="@+id/settings_port_forwarding_active_item_enabled_switch"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent" />
 
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding_item.xml b/android/TerminalApp/res/layout/settings_port_forwarding_inactive_item.xml
similarity index 74%
copy from android/TerminalApp/res/layout/settings_port_forwarding_item.xml
copy to android/TerminalApp/res/layout/settings_port_forwarding_inactive_item.xml
index 8a57b41..127b152 100644
--- a/android/TerminalApp/res/layout/settings_port_forwarding_item.xml
+++ b/android/TerminalApp/res/layout/settings_port_forwarding_inactive_item.xml
@@ -19,21 +19,28 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:minHeight="48dp"
     app:layout_constraintCircleRadius="@dimen/material_emphasis_medium">
 
     <TextView
-        android:id="@+id/settings_port_forwarding_item_port"
+        android:id="@+id/settings_port_forwarding_inactive_item_port"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
+        android:textSize="16sp"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="8dp"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"/>
 
-    <com.google.android.material.materialswitch.MaterialSwitch
-        android:id="@+id/settings_port_forwarding_item_enabled_switch"
+    <ImageButton
+        android:id="@+id/settings_port_forwarding_active_item_close_button"
+        android:src="@drawable/ic_close"
+        android:background="@android:color/transparent"
+        android:contentDescription="@null"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index d21ded1..9cb6e4d 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -89,6 +89,8 @@
     <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>
+    <!-- Description of close button for other enabled ports. Used for talkback. [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_other_enabled_port_close_button">Delete <xliff:g id="port_number" example="8000">%d</xliff:g></string>
 
     <!-- Dialog title for enabling a new port [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_dialog_title">Allow a new port</string>
diff --git a/android/virtmgr/Android.bp b/android/virtmgr/Android.bp
index ad63995..3883c34 100644
--- a/android/virtmgr/Android.bp
+++ b/android/virtmgr/Android.bp
@@ -69,7 +69,7 @@
         "liblibfdt",
         "libfsfdt",
         "libhypervisor_props",
-        "libzerocopy",
+        "libzerocopy-0.7.35",
         "libuuid",
         // TODO(b/202115393) stabilize the interface
         "packagemanager_aidl-rust",
diff --git a/android/virtualizationservice/vfio_handler/Android.bp b/android/virtualizationservice/vfio_handler/Android.bp
index 3635cf1..fec61f1 100644
--- a/android/virtualizationservice/vfio_handler/Android.bp
+++ b/android/virtualizationservice/vfio_handler/Android.bp
@@ -28,7 +28,7 @@
         "liblog_rust",
         "libnix",
         "librustutils",
-        "libzerocopy",
+        "libzerocopy-0.7.35",
     ],
     apex_available: ["com.android.virt"],
 }
diff --git a/guest/apkdmverity/Android.bp b/guest/apkdmverity/Android.bp
index 0cb8ca1..64dde3e 100644
--- a/guest/apkdmverity/Android.bp
+++ b/guest/apkdmverity/Android.bp
@@ -22,7 +22,7 @@
         "libnum_traits",
         "libscopeguard",
         "libuuid",
-        "libzerocopy",
+        "libzerocopy-0.7.35",
     ],
     proc_macros: ["libnum_derive"],
     multilib: {
diff --git a/guest/microdroid_manager/src/vm_secret.rs b/guest/microdroid_manager/src/vm_secret.rs
index 1ad2d88..5cc90ff 100644
--- a/guest/microdroid_manager/src/vm_secret.rs
+++ b/guest/microdroid_manager/src/vm_secret.rs
@@ -36,6 +36,8 @@
 use secretkeeper_comm::data_types::error::SecretkeeperError;
 use std::fs;
 use zeroize::Zeroizing;
+use std::sync::Mutex;
+use std::sync::Arc;
 
 const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
 const AUTHORITY_HASH: i64 = -4670549;
@@ -98,27 +100,20 @@
 
         let explicit_dice = OwnedDiceArtifactsWithExplicitKey::from_owned_artifacts(dice_artifacts)
             .context("Failed to get Dice artifacts in explicit key format")?;
-        // For pVM, skp_secret are stored in Secretkeeper. For non-protected it is all 0s.
+        let session = SkVmSession::new(vm_service, &explicit_dice)?;
+        let id = super::get_instance_id()?.ok_or(anyhow!("Missing instance_id"))?;
+        let explicit_dice_chain = explicit_dice
+            .explicit_key_dice_chain()
+            .ok_or(anyhow!("Missing explicit dice chain, this is unusual"))?;
+        let policy = sealing_policy(explicit_dice_chain)
+            .map_err(|e| anyhow!("Failed to build a sealing_policy: {e}"))?;
         let mut skp_secret = Zeroizing::new([0u8; SECRET_SIZE]);
-        if super::is_strict_boot() {
-            let sk_service = get_secretkeeper_service(vm_service)?;
-            let mut session =
-                SkSession::new(sk_service, &explicit_dice, Some(get_secretkeeper_identity()?))?;
-            let id = super::get_instance_id()?.ok_or(anyhow!("Missing instance_id"))?;
-            let explicit_dice_chain = explicit_dice
-                .explicit_key_dice_chain()
-                .ok_or(anyhow!("Missing explicit dice chain, this is unusual"))?;
-            let policy = sealing_policy(explicit_dice_chain)
-                .map_err(|e| anyhow!("Failed to build a sealing_policy: {e}"))?;
-            if let Some(secret) = get_secret(&mut session, id, Some(policy.clone()))? {
-                *skp_secret = secret;
-            } else {
-                log::warn!(
-                    "No entry found in Secretkeeper for this VM instance, creating new secret."
-                );
-                *skp_secret = rand::random();
-                store_secret(&mut session, id, skp_secret.clone(), policy)?;
-            }
+        if let Some(secret) = session.get_secret(id, Some(policy.clone()))? {
+            *skp_secret = secret
+        } else {
+            log::warn!("No entry found in Secretkeeper for this VM instance, creating new secret.");
+            *skp_secret = rand::random();
+            session.store_secret(id, skp_secret.clone(), policy)?;
         }
         Ok(Self::V2 {
             dice_artifacts: explicit_dice,
@@ -231,48 +226,67 @@
         .map_err(|e| format!("DicePolicy construction failed {e:?}"))
 }
 
-fn store_secret(
-    session: &mut SkSession,
-    id: [u8; ID_SIZE],
-    secret: Zeroizing<[u8; SECRET_SIZE]>,
-    sealing_policy: Vec<u8>,
-) -> Result<()> {
-    let store_request = StoreSecretRequest { id: Id(id), secret: Secret(*secret), sealing_policy };
-    log::info!("Secretkeeper operation: {:?}", store_request);
+// The secure session between VM & Secretkeeper
+struct SkVmSession(Arc<Mutex<SkSession>>);
+impl SkVmSession {
+    fn new(
+        vm_service: &Strong<dyn IVirtualMachineService>,
+        dice: &OwnedDiceArtifactsWithExplicitKey,
+    ) -> Result<Self> {
+        let secretkeeper_proxy = get_secretkeeper_service(vm_service)?;
+        let secure_session =
+            SkSession::new(secretkeeper_proxy, dice, Some(get_secretkeeper_identity()?))?;
+        let secure_session = Arc::new(Mutex::new(secure_session));
+        Ok(Self(secure_session))
+    }
 
-    let store_request = store_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
-    let store_response = session.secret_management_request(&store_request)?;
-    let store_response = ResponsePacket::from_slice(&store_response).map_err(anyhow_err)?;
-    let response_type = store_response.response_type().map_err(anyhow_err)?;
-    ensure!(
-        response_type == ResponseType::Success,
-        "Secretkeeper store failed with error: {:?}",
-        *SecretkeeperError::deserialize_from_packet(store_response).map_err(anyhow_err)?
-    );
-    Ok(())
-}
+    fn store_secret(
+        &self,
+        id: [u8; ID_SIZE],
+        secret: Zeroizing<[u8; SECRET_SIZE]>,
+        sealing_policy: Vec<u8>,
+    ) -> Result<()> {
+        let store_request =
+            StoreSecretRequest { id: Id(id), secret: Secret(*secret), sealing_policy };
+        log::info!("Secretkeeper operation: {:?}", store_request);
 
-fn get_secret(
-    session: &mut SkSession,
-    id: [u8; ID_SIZE],
-    updated_sealing_policy: Option<Vec<u8>>,
-) -> Result<Option<[u8; SECRET_SIZE]>> {
-    let get_request = GetSecretRequest { id: Id(id), updated_sealing_policy };
-    log::info!("Secretkeeper operation: {:?}", get_request);
-    let get_request = get_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
-    let get_response = session.secret_management_request(&get_request)?;
-    let get_response = ResponsePacket::from_slice(&get_response).map_err(anyhow_err)?;
-    let response_type = get_response.response_type().map_err(anyhow_err)?;
-    if response_type == ResponseType::Success {
-        let get_response =
-            *GetSecretResponse::deserialize_from_packet(get_response).map_err(anyhow_err)?;
-        Ok(Some(get_response.secret.0))
-    } else {
-        let error = SecretkeeperError::deserialize_from_packet(get_response).map_err(anyhow_err)?;
-        if *error == SecretkeeperError::EntryNotFound {
-            return Ok(None);
+        let store_request = store_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
+        let session = &mut *self.0.lock().unwrap();
+        let store_response = session.secret_management_request(&store_request)?;
+        let store_response = ResponsePacket::from_slice(&store_response).map_err(anyhow_err)?;
+        let response_type = store_response.response_type().map_err(anyhow_err)?;
+        ensure!(
+            response_type == ResponseType::Success,
+            "Secretkeeper store failed with error: {:?}",
+            *SecretkeeperError::deserialize_from_packet(store_response).map_err(anyhow_err)?
+        );
+        Ok(())
+    }
+
+    fn get_secret(
+        &self,
+        id: [u8; ID_SIZE],
+        updated_sealing_policy: Option<Vec<u8>>,
+    ) -> Result<Option<[u8; SECRET_SIZE]>> {
+        let get_request = GetSecretRequest { id: Id(id), updated_sealing_policy };
+        log::info!("Secretkeeper operation: {:?}", get_request);
+        let get_request = get_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
+        let session = &mut *self.0.lock().unwrap();
+        let get_response = session.secret_management_request(&get_request)?;
+        let get_response = ResponsePacket::from_slice(&get_response).map_err(anyhow_err)?;
+        let response_type = get_response.response_type().map_err(anyhow_err)?;
+        if response_type == ResponseType::Success {
+            let get_response =
+                *GetSecretResponse::deserialize_from_packet(get_response).map_err(anyhow_err)?;
+            Ok(Some(get_response.secret.0))
+        } else {
+            let error =
+                SecretkeeperError::deserialize_from_packet(get_response).map_err(anyhow_err)?;
+            if *error == SecretkeeperError::EntryNotFound {
+                return Ok(None);
+            }
+            Err(anyhow!("Secretkeeper get failed: {error:?}"))
         }
-        Err(anyhow!("Secretkeeper get failed: {error:?}"))
     }
 }
 
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index 51f7802..23755cf 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -32,7 +32,7 @@
         "libuuid_nostd",
         "libvirtio_drivers",
         "libvmbase",
-        "libzerocopy_nostd",
+        "libzerocopy-0.7.35_nostd",
         "libzeroize_nostd",
     ],
 }
@@ -77,7 +77,7 @@
         "liblibfdt",
         "liblog_rust",
         "libpvmfw_fdt_template",
-        "libzerocopy",
+        "libzerocopy-0.7.35",
     ],
     data: [
         ":test_pvmfw_devices_vm_dtbo",
@@ -119,7 +119,7 @@
         "libdiced_open_dice_nostd",
         "libpvmfw_avb_nostd",
         "libdiced_sample_inputs_nostd",
-        "libzerocopy_nostd",
+        "libzerocopy-0.7.35_nostd",
         "libhex",
     ],
     static_libs: ["libopen_dice_clear_memory"],
diff --git a/guest/pvmfw/src/entry.rs b/guest/pvmfw/src/entry.rs
index 7c46515..862fb1d 100644
--- a/guest/pvmfw/src/entry.rs
+++ b/guest/pvmfw/src/entry.rs
@@ -15,10 +15,9 @@
 //! Low-level entry and exit points of pvmfw.
 
 use crate::config;
-use crate::memory;
+use crate::memory::MemorySlices;
 use core::arch::asm;
 use core::mem::size_of;
-use core::ops::Range;
 use core::slice;
 use log::error;
 use log::warn;
@@ -88,14 +87,14 @@
 
     let reboot_reason = match main_wrapper(fdt_address, payload_start, payload_size) {
         Err(r) => r,
-        Ok((next_stage, bcc)) => match next_stage {
-            NextStage::LinuxBootWithUart(ep) => jump_to_payload(fdt_address, ep, bcc),
+        Ok((next_stage, slices)) => match next_stage {
+            NextStage::LinuxBootWithUart(ep) => jump_to_payload(ep, &slices),
             NextStage::LinuxBoot(ep) => {
                 if let Err(e) = unshare_uart() {
                     error!("Failed to unmap UART: {e}");
                     RebootReason::InternalError
                 } else {
-                    jump_to_payload(fdt_address, ep, bcc)
+                    jump_to_payload(ep, &slices)
                 }
             }
         },
@@ -112,11 +111,11 @@
 ///
 /// Provide the abstractions necessary for start() to abort the pVM boot and for main() to run with
 /// the assumption that its environment has been properly configured.
-fn main_wrapper(
+fn main_wrapper<'a>(
     fdt: usize,
     payload: usize,
     payload_size: usize,
-) -> Result<(NextStage, Range<usize>), RebootReason> {
+) -> Result<(NextStage, MemorySlices<'a>), RebootReason> {
     // Limitations in this function:
     // - only access MMIO once (and while) it has been mapped and configured
     // - only perform logging once the logger has been initialized
@@ -136,7 +135,7 @@
 
     let config_entries = appended.get_entries();
 
-    let slices = memory::MemorySlices::new(fdt, payload, payload_size)?;
+    let mut slices = MemorySlices::new(fdt, payload, payload_size)?;
 
     // This wrapper allows main() to be blissfully ignorant of platform details.
     let (next_bcc, debuggable_payload) = crate::main(
@@ -148,6 +147,7 @@
         config_entries.vm_dtbo,
         config_entries.vm_ref_dt,
     )?;
+    slices.add_dice_chain(next_bcc);
     // Keep UART MMIO_GUARD-ed for debuggable payloads, to enable earlycon.
     let keep_uart = cfg!(debuggable_vms_improvements) && debuggable_payload;
 
@@ -162,7 +162,7 @@
 
     let next_stage = select_next_stage(slices.kernel, keep_uart);
 
-    Ok((next_stage, next_bcc))
+    Ok((next_stage, slices))
 }
 
 fn select_next_stage(kernel: &[u8], keep_uart: bool) -> NextStage {
@@ -173,7 +173,16 @@
     }
 }
 
-fn jump_to_payload(fdt_address: usize, payload_start: usize, bcc: Range<usize>) -> ! {
+fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! {
+    let fdt_address = slices.fdt.as_ptr() as usize;
+    let bcc = slices
+        .dice_chain
+        .map(|slice| {
+            let r = slice.as_ptr_range();
+            (r.start as usize)..(r.end as usize)
+        })
+        .expect("Missing DICE chain");
+
     deactivate_dynamic_page_tables();
 
     const ASM_STP_ALIGN: usize = size_of::<u64>() * 2;
@@ -313,7 +322,7 @@
             eh_stack_end = in(reg) u64::try_from(eh_stack.end.0).unwrap(),
             dcache_line_size = in(reg) u64::try_from(min_dcache_line_size()).unwrap(),
             in("x0") u64::try_from(fdt_address).unwrap(),
-            in("x30") u64::try_from(payload_start).unwrap(),
+            in("x30") u64::try_from(entrypoint).unwrap(),
             options(noreturn),
         );
     };
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index d04db06..a28a039 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -40,7 +40,6 @@
 use alloc::borrow::Cow;
 use alloc::boxed::Box;
 use bssl_avf::Digester;
-use core::ops::Range;
 use cstr::cstr;
 use diced_open_dice::{bcc_handover_parse, DiceArtifacts, DiceContext, Hidden, VM_KEY_ALGORITHM};
 use libfdt::{Fdt, FdtNode};
@@ -54,7 +53,7 @@
 use vmbase::rand;
 use vmbase::virtio::pci;
 
-fn main(
+fn main<'a>(
     untrusted_fdt: &mut Fdt,
     signed_kernel: &[u8],
     ramdisk: Option<&[u8]>,
@@ -62,7 +61,7 @@
     mut debug_policy: Option<&[u8]>,
     vm_dtbo: Option<&mut [u8]>,
     vm_ref_dt: Option<&[u8]>,
-) -> Result<(Range<usize>, bool), RebootReason> {
+) -> Result<(&'a [u8], bool), RebootReason> {
     info!("pVM firmware");
     debug!("FDT: {:?}", untrusted_fdt.as_ptr());
     debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
@@ -201,13 +200,7 @@
     })?;
 
     info!("Starting payload...");
-
-    let bcc_range = {
-        let r = next_bcc.as_ptr_range();
-        (r.start as usize)..(r.end as usize)
-    };
-
-    Ok((bcc_range, debuggable))
+    Ok((next_bcc, debuggable))
 }
 
 // Get the "salt" which is one of the input for DICE derivation.
diff --git a/guest/pvmfw/src/memory.rs b/guest/pvmfw/src/memory.rs
index d2f63b5..a663008 100644
--- a/guest/pvmfw/src/memory.rs
+++ b/guest/pvmfw/src/memory.rs
@@ -31,6 +31,7 @@
     pub fdt: &'a mut libfdt::Fdt,
     pub kernel: &'a [u8],
     pub ramdisk: Option<&'a [u8]>,
+    pub dice_chain: Option<&'a [u8]>,
 }
 
 impl<'a> MemorySlices<'a> {
@@ -111,6 +112,12 @@
             None
         };
 
-        Ok(Self { fdt: untrusted_fdt, kernel, ramdisk })
+        let dice_chain = None;
+
+        Ok(Self { fdt: untrusted_fdt, kernel, ramdisk, dice_chain })
+    }
+
+    pub fn add_dice_chain(&mut self, dice_chain: &'a [u8]) {
+        self.dice_chain = Some(dice_chain)
     }
 }
diff --git a/libs/devicemapper/Android.bp b/libs/devicemapper/Android.bp
index 5332469..17727f1 100644
--- a/libs/devicemapper/Android.bp
+++ b/libs/devicemapper/Android.bp
@@ -16,7 +16,7 @@
         "libhex",
         "libnix",
         "libuuid",
-        "libzerocopy",
+        "libzerocopy-0.7.35",
     ],
     multilib: {
         lib32: {
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
index 6c4c599..4508573 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -621,8 +621,8 @@
                 && this.mVmOutputCaptured == other.mVmOutputCaptured
                 && this.mVmConsoleInputSupported == other.mVmConsoleInputSupported
                 && this.mConnectVmConsole == other.mConnectVmConsole
-                && this.mConsoleInputDevice == other.mConsoleInputDevice
                 && (this.mVendorDiskImage == null) == (other.mVendorDiskImage == null)
+                && Objects.equals(this.mConsoleInputDevice, other.mConsoleInputDevice)
                 && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
                 && Objects.equals(this.mPayloadBinaryName, other.mPayloadBinaryName)
                 && Objects.equals(this.mPackageName, other.mPackageName)
diff --git a/libs/libavf/libavf.map.txt b/libs/libavf/libavf.map.txt
index 05a5b35..ecb4cc9 100644
--- a/libs/libavf/libavf.map.txt
+++ b/libs/libavf/libavf.map.txt
@@ -1,4 +1,4 @@
-LIBAVF { # introduced=36
+LIBAVF {
   global:
     AVirtualMachineRawConfig_create; # apex llndk
     AVirtualMachineRawConfig_destroy; # apex llndk
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 09f288d..1e24ff4 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -38,7 +38,7 @@
         "libcstr",
         "liblibfdt_bindgen",
         "libstatic_assertions",
-        "libzerocopy_nostd",
+        "libzerocopy-0.7.35_nostd",
     ],
 }
 
diff --git a/libs/libvmbase/Android.bp b/libs/libvmbase/Android.bp
index 3088633..7bcdc1d 100644
--- a/libs/libvmbase/Android.bp
+++ b/libs/libvmbase/Android.bp
@@ -91,7 +91,7 @@
         "libtinyvec_nostd",
         "libuuid_nostd",
         "libvirtio_drivers",
-        "libzerocopy_nostd",
+        "libzerocopy-0.7.35_nostd",
         "libzeroize_nostd",
     ],
     whole_static_libs: [