Merge "[test][dice] Add security_version to sample VM DICE chain" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.java
deleted file mode 100644
index d6ca1e6..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.virtualization.terminal;
-
-import android.Manifest;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-public abstract class BaseActivity extends AppCompatActivity {
-    private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        NotificationManager notificationManager = getSystemService(NotificationManager.class);
-        if (notificationManager.getNotificationChannel(this.getPackageName()) == null) {
-            NotificationChannel channel =
-                    new NotificationChannel(
-                            this.getPackageName(),
-                            getString(R.string.app_name),
-                            NotificationManager.IMPORTANCE_DEFAULT);
-            notificationManager.createNotificationChannel(channel);
-        }
-
-        if (!(this instanceof ErrorActivity)) {
-            Thread currentThread = Thread.currentThread();
-            if (!(currentThread.getUncaughtExceptionHandler()
-                    instanceof TerminalExceptionHandler)) {
-                currentThread.setUncaughtExceptionHandler(
-                        new TerminalExceptionHandler(getApplicationContext()));
-            }
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        if (getApplicationContext().checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            requestPermissions(
-                    new String[] {Manifest.permission.POST_NOTIFICATIONS},
-                    POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE);
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.kt
new file mode 100644
index 0000000..e7ac8d9
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/BaseActivity.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+package com.android.virtualization.terminal
+
+import android.Manifest
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+
+abstract class BaseActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val notificationManager =
+            getSystemService<NotificationManager>(NotificationManager::class.java)
+        if (notificationManager.getNotificationChannel(this.packageName) == null) {
+            val channel =
+                NotificationChannel(
+                    this.packageName,
+                    getString(R.string.app_name),
+                    NotificationManager.IMPORTANCE_HIGH,
+                )
+            notificationManager.createNotificationChannel(channel)
+        }
+
+        if (this !is ErrorActivity) {
+            val currentThread = Thread.currentThread()
+            if (currentThread.uncaughtExceptionHandler !is TerminalExceptionHandler) {
+                currentThread.uncaughtExceptionHandler =
+                    TerminalExceptionHandler(applicationContext)
+            }
+        }
+    }
+
+    public override fun onResume() {
+        super.onResume()
+
+        if (
+            applicationContext.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) !=
+                PackageManager.PERMISSION_GRANTED
+        ) {
+            requestPermissions(
+                arrayOf<String>(Manifest.permission.POST_NOTIFICATIONS),
+                POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE,
+            )
+        }
+    }
+
+    companion object {
+        private const val POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java b/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java
deleted file mode 100644
index e3d1a67..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.virtualization.terminal;
-
-import static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.content.Context;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-import android.util.Base64;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-
-public class CertificateUtils {
-    private static final String ALIAS = "ttyd";
-
-    public static KeyStore.PrivateKeyEntry createOrGetKey() {
-        try {
-            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-
-            if (!ks.containsAlias(ALIAS)) {
-                Log.d(TAG, "there is no keypair, will generate it");
-                createKey();
-            } else if (!(ks.getCertificate(ALIAS) instanceof X509Certificate)) {
-                Log.d(TAG, "certificate isn't X509Certificate or it is invalid");
-                createKey();
-            } else {
-                try {
-                    ((X509Certificate) ks.getCertificate(ALIAS)).checkValidity();
-                } catch (CertificateExpiredException | CertificateNotYetValidException e) {
-                    Log.d(TAG, "certificate is invalid", e);
-                    createKey();
-                }
-            }
-            return ((KeyStore.PrivateKeyEntry) ks.getEntry(ALIAS, null));
-        } catch (Exception e) {
-            throw new RuntimeException("cannot generate or get key", e);
-        }
-    }
-
-    private static void createKey()
-            throws NoSuchAlgorithmException,
-                    NoSuchProviderException,
-                    InvalidAlgorithmParameterException {
-        KeyPairGenerator kpg =
-                KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
-        kpg.initialize(
-                new KeyGenParameterSpec.Builder(
-                                ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
-                        .build());
-
-        kpg.generateKeyPair();
-    }
-
-    public static void writeCertificateToFile(Context context, Certificate cert) {
-        String certFileName = "ca.crt";
-        File certFile = new File(context.getFilesDir(), certFileName);
-        try (FileOutputStream writer = new FileOutputStream(certFile)) {
-            String cert_begin = "-----BEGIN CERTIFICATE-----\n";
-            String end_cert = "-----END CERTIFICATE-----\n";
-            String output =
-                    cert_begin
-                            + Base64.encodeToString(cert.getEncoded(), Base64.DEFAULT)
-                                    .replaceAll("(.{64})", "$1\n")
-                            + end_cert;
-            writer.write(output.getBytes());
-        } catch (IOException | CertificateEncodingException e) {
-            throw new RuntimeException("cannot write certs", e);
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.kt b/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.kt
new file mode 100644
index 0000000..ee8a8f8
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+package com.android.virtualization.terminal
+
+import android.content.Context
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import android.util.Base64
+import android.util.Log
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.lang.Exception
+import java.lang.RuntimeException
+import java.security.InvalidAlgorithmParameterException
+import java.security.KeyPairGenerator
+import java.security.KeyStore
+import java.security.NoSuchAlgorithmException
+import java.security.NoSuchProviderException
+import java.security.cert.Certificate
+import java.security.cert.CertificateEncodingException
+import java.security.cert.CertificateExpiredException
+import java.security.cert.CertificateNotYetValidException
+import java.security.cert.X509Certificate
+
+object CertificateUtils {
+    private const val ALIAS = "ttyd"
+
+    fun createOrGetKey(): KeyStore.PrivateKeyEntry {
+        try {
+            val ks = KeyStore.getInstance("AndroidKeyStore")
+            ks.load(null)
+
+            if (!ks.containsAlias(ALIAS)) {
+                Log.d(MainActivity.TAG, "there is no keypair, will generate it")
+                createKey()
+            } else if (ks.getCertificate(ALIAS) !is X509Certificate) {
+                Log.d(MainActivity.TAG, "certificate isn't X509Certificate or it is invalid")
+                createKey()
+            } else {
+                try {
+                    (ks.getCertificate(ALIAS) as X509Certificate).checkValidity()
+                } catch (e: CertificateExpiredException) {
+                    Log.d(MainActivity.TAG, "certificate is invalid", e)
+                    createKey()
+                } catch (e: CertificateNotYetValidException) {
+                    Log.d(MainActivity.TAG, "certificate is invalid", e)
+                    createKey()
+                }
+            }
+            return ks.getEntry(ALIAS, null) as KeyStore.PrivateKeyEntry
+        } catch (e: Exception) {
+            throw RuntimeException("cannot generate or get key", e)
+        }
+    }
+
+    @Throws(
+        NoSuchAlgorithmException::class,
+        NoSuchProviderException::class,
+        InvalidAlgorithmParameterException::class,
+    )
+    private fun createKey() {
+        val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
+        kpg.initialize(
+            KeyGenParameterSpec.Builder(
+                    ALIAS,
+                    KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY,
+                )
+                .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+                .build()
+        )
+
+        kpg.generateKeyPair()
+    }
+
+    fun writeCertificateToFile(context: Context, cert: Certificate) {
+        val certFile = File(context.getFilesDir(), "ca.crt")
+        try {
+            FileOutputStream(certFile).use { writer ->
+                val certBegin = "-----BEGIN CERTIFICATE-----\n"
+                val certEnd = "-----END CERTIFICATE-----\n"
+                val output =
+                    (certBegin +
+                        Base64.encodeToString(cert.encoded, Base64.DEFAULT)
+                            .replace("(.{64})".toRegex(), "$1\n") +
+                        certEnd)
+                writer.write(output.toByteArray())
+            }
+        } catch (e: IOException) {
+            throw RuntimeException("cannot write certs", e)
+        } catch (e: CertificateEncodingException) {
+            throw RuntimeException("cannot write certs", e)
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
deleted file mode 100644
index a0fca82..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
+++ /dev/null
@@ -1,354 +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.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.graphics.Rect;
-import android.os.Environment;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.Disk;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.Partition;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.SharedPath;
-import android.util.DisplayMetrics;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import com.google.gson.Gson;
-import com.google.gson.annotations.SerializedName;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.Reader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/** This class and its inner classes model vm_config.json. */
-class ConfigJson {
-    private static final boolean DEBUG = true;
-
-    private ConfigJson() {}
-
-    @SerializedName("protected")
-    private boolean isProtected;
-
-    private String name;
-    private String cpu_topology;
-    private String platform_version;
-    private int memory_mib = 1024;
-    private String console_input_device;
-    private String bootloader;
-    private String kernel;
-    private String initrd;
-    private String params;
-    private boolean debuggable;
-    private boolean console_out;
-    private boolean connect_console;
-    private boolean network;
-    private InputJson input;
-    private AudioJson audio;
-    private DiskJson[] disks;
-    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) {
-        try (FileReader fileReader = new FileReader(jsonPath.toFile())) {
-            String content = replaceKeywords(fileReader, context);
-            return new Gson().fromJson(content, ConfigJson.class);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to parse " + jsonPath, e);
-        }
-    }
-
-    private static String replaceKeywords(Reader r, Context context) throws IOException {
-        Map<String, String> rules = new HashMap<>();
-        rules.put("\\$PAYLOAD_DIR", InstalledImage.getDefault(context).getInstallDir().toString());
-        rules.put("\\$USER_ID", String.valueOf(context.getUserId()));
-        rules.put("\\$PACKAGE_NAME", context.getPackageName());
-        rules.put("\\$APP_DATA_DIR", context.getDataDir().toString());
-
-        try (BufferedReader br = new BufferedReader(r)) {
-            return br.lines()
-                    .map(
-                            line -> {
-                                for (Map.Entry<String, String> rule : rules.entrySet()) {
-                                    line = line.replaceAll(rule.getKey(), rule.getValue());
-                                }
-                                return line;
-                            })
-                    .collect(Collectors.joining("\n"));
-        }
-    }
-
-    private int getCpuTopology() {
-        switch (cpu_topology) {
-            case "one_cpu":
-                return VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
-            case "match_host":
-                return VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
-            default:
-                throw new RuntimeException("invalid cpu topology: " + cpu_topology);
-        }
-    }
-
-    private int getDebugLevel() {
-        return debuggable
-                ? VirtualMachineConfig.DEBUG_LEVEL_FULL
-                : VirtualMachineConfig.DEBUG_LEVEL_NONE;
-    }
-
-    /** Converts this parsed JSON into VirtualMachieConfig Builder */
-    VirtualMachineConfig.Builder toConfigBuilder(Context context) {
-        return new VirtualMachineConfig.Builder(context)
-                .setProtectedVm(isProtected)
-                .setMemoryBytes((long) memory_mib * 1024 * 1024)
-                .setConsoleInputDevice(console_input_device)
-                .setCpuTopology(getCpuTopology())
-                .setCustomImageConfig(toCustomImageConfigBuilder(context).build())
-                .setDebugLevel(getDebugLevel())
-                .setVmOutputCaptured(console_out)
-                .setConnectVmConsole(connect_console);
-    }
-
-    VirtualMachineCustomImageConfig.Builder toCustomImageConfigBuilder(Context context) {
-        VirtualMachineCustomImageConfig.Builder builder =
-                new VirtualMachineCustomImageConfig.Builder();
-
-        builder.setName(name)
-                .setBootloaderPath(bootloader)
-                .setKernelPath(kernel)
-                .setInitrdPath(initrd)
-                .useNetwork(network)
-                .useAutoMemoryBalloon(auto_memory_balloon);
-
-        if (input != null) {
-            builder.useTouch(input.touchscreen)
-                    .useKeyboard(input.keyboard)
-                    .useMouse(input.mouse)
-                    .useTrackpad(input.trackpad)
-                    .useSwitches(input.switches);
-        }
-
-        if (audio != null) {
-            builder.setAudioConfig(audio.toConfig());
-        }
-
-        if (display != null) {
-            builder.setDisplayConfig(display.toConfig(context));
-        }
-
-        if (gpu != null) {
-            builder.setGpuConfig(gpu.toConfig());
-        }
-
-        if (params != null) {
-            Arrays.stream(params.split(" ")).forEach(builder::addParam);
-        }
-
-        if (disks != null) {
-            Arrays.stream(disks).map(d -> d.toConfig()).forEach(builder::addDisk);
-        }
-
-        if (sharedPath != null) {
-            Arrays.stream(sharedPath)
-                    .map(d -> d.toConfig(context))
-                    .filter(Objects::nonNull)
-                    .forEach(builder::addSharedPath);
-        }
-        return builder;
-    }
-
-    private static class SharedPathJson {
-        private SharedPathJson() {}
-
-        private String sharedPath;
-        private static final int GUEST_UID = 1000;
-        private static final int GUEST_GID = 100;
-
-        private SharedPath toConfig(Context context) {
-            try {
-                int terminalUid = getTerminalUid(context);
-                if (sharedPath.contains("emulated")) {
-                    if (Environment.isExternalStorageManager()) {
-                        int currentUserId = context.getUserId();
-                        String path = sharedPath + "/" + currentUserId + "/Download";
-                        return new SharedPath(
-                                path,
-                                terminalUid,
-                                terminalUid,
-                                GUEST_UID,
-                                GUEST_GID,
-                                0007,
-                                "android",
-                                "android",
-                                false, /* app domain is set to false so that crosvm is spin up as child of virtmgr */
-                                "");
-                    }
-                    return null;
-                }
-                Path socketPath = context.getFilesDir().toPath().resolve("internal.virtiofs");
-                Files.deleteIfExists(socketPath);
-                return new SharedPath(
-                        sharedPath,
-                        terminalUid,
-                        terminalUid,
-                        0,
-                        0,
-                        0007,
-                        "internal",
-                        "internal",
-                        true, /* app domain is set to true so that crosvm is spin up from app context */
-                        socketPath.toString());
-            } catch (NameNotFoundException | IOException e) {
-                return null;
-            }
-        }
-
-        private int getTerminalUid(Context context) throws NameNotFoundException {
-            return context.getPackageManager()
-                    .getPackageUidAsUser(context.getPackageName(), context.getUserId());
-        }
-    }
-
-    private static class InputJson {
-        private InputJson() {}
-
-        private boolean touchscreen;
-        private boolean keyboard;
-        private boolean mouse;
-        private boolean switches;
-        private boolean trackpad;
-    }
-
-    private static class AudioJson {
-        private AudioJson() {}
-
-        private boolean microphone;
-        private boolean speaker;
-
-        private AudioConfig toConfig() {
-            return new AudioConfig.Builder()
-                    .setUseMicrophone(microphone)
-                    .setUseSpeaker(speaker)
-                    .build();
-        }
-    }
-
-    private static class DiskJson {
-        private DiskJson() {}
-
-        private boolean writable;
-        private String image;
-        private PartitionJson[] partitions;
-
-        private Disk toConfig() {
-            Disk d = writable ? Disk.RWDisk(image) : Disk.RODisk(image);
-            for (PartitionJson pj : partitions) {
-                boolean writable = this.writable && pj.writable;
-                d.addPartition(new Partition(pj.label, pj.path, writable, pj.guid));
-            }
-            return d;
-        }
-    }
-
-    private static class PartitionJson {
-        private PartitionJson() {}
-
-        private boolean writable;
-        private String label;
-        private String path;
-        private String guid;
-    }
-
-    private static class DisplayJson {
-        private DisplayJson() {}
-
-        private float scale;
-        private int refresh_rate;
-        private int width_pixels;
-        private int height_pixels;
-
-        private DisplayConfig toConfig(Context context) {
-            WindowManager wm = context.getSystemService(WindowManager.class);
-            WindowMetrics metrics = wm.getCurrentWindowMetrics();
-            Rect dispBounds = metrics.getBounds();
-
-            int width = width_pixels > 0 ? width_pixels : dispBounds.right;
-            int height = height_pixels > 0 ? height_pixels : dispBounds.bottom;
-
-            int dpi = (int) (DisplayMetrics.DENSITY_DEFAULT * metrics.getDensity());
-            if (scale > 0.0f) {
-                dpi = (int) (dpi * scale);
-            }
-
-            int refreshRate = (int) context.getDisplay().getRefreshRate();
-            if (this.refresh_rate != 0) {
-                refreshRate = this.refresh_rate;
-            }
-
-            return new DisplayConfig.Builder()
-                    .setWidth(width)
-                    .setHeight(height)
-                    .setHorizontalDpi(dpi)
-                    .setVerticalDpi(dpi)
-                    .setRefreshRate(refreshRate)
-                    .build();
-        }
-    }
-
-    private static class GpuJson {
-        private GpuJson() {}
-
-        private String backend;
-        private String pci_address;
-        private String renderer_features;
-        private boolean renderer_use_egl = true;
-        private boolean renderer_use_gles = true;
-        private boolean renderer_use_glx = false;
-        private boolean renderer_use_surfaceless = true;
-        private boolean renderer_use_vulkan = false;
-        private String[] context_types;
-
-        private GpuConfig toConfig() {
-            return new GpuConfig.Builder()
-                    .setBackend(backend)
-                    .setPciAddress(pci_address)
-                    .setRendererFeatures(renderer_features)
-                    .setRendererUseEgl(renderer_use_egl)
-                    .setRendererUseGles(renderer_use_gles)
-                    .setRendererUseGlx(renderer_use_glx)
-                    .setRendererUseSurfaceless(renderer_use_surfaceless)
-                    .setRendererUseVulkan(renderer_use_vulkan)
-                    .setContextTypes(context_types)
-                    .build();
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.kt
new file mode 100644
index 0000000..1fd58cd
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.kt
@@ -0,0 +1,333 @@
+/*
+ * 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.pm.PackageManager
+import android.os.Environment
+import android.system.virtualmachine.VirtualMachineConfig
+import android.system.virtualmachine.VirtualMachineCustomImageConfig
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import com.android.virtualization.terminal.ConfigJson.AudioJson
+import com.android.virtualization.terminal.ConfigJson.DiskJson
+import com.android.virtualization.terminal.ConfigJson.DisplayJson
+import com.android.virtualization.terminal.ConfigJson.GpuJson
+import com.android.virtualization.terminal.ConfigJson.InputJson
+import com.android.virtualization.terminal.ConfigJson.PartitionJson
+import com.android.virtualization.terminal.ConfigJson.SharedPathJson
+import com.android.virtualization.terminal.InstalledImage.Companion.getDefault
+import com.google.gson.Gson
+import com.google.gson.annotations.SerializedName
+import java.io.BufferedReader
+import java.io.FileReader
+import java.io.IOException
+import java.io.Reader
+import java.lang.Exception
+import java.lang.RuntimeException
+import java.nio.file.Files
+import java.nio.file.Path
+
+/** This class and its inner classes model vm_config.json. */
+internal data class ConfigJson(
+    @SerializedName("protected") private val isProtected: Boolean,
+    private val name: String?,
+    private val cpu_topology: String?,
+    private val platform_version: String?,
+    private val memory_mib: Int = 1024,
+    private val console_input_device: String?,
+    private val bootloader: String?,
+    private val kernel: String?,
+    private val initrd: String?,
+    private val params: String?,
+    private val debuggable: Boolean,
+    private val console_out: Boolean,
+    private val connect_console: Boolean,
+    private val network: Boolean,
+    private val input: InputJson?,
+    private val audio: AudioJson?,
+    private val disks: Array<DiskJson>?,
+    private val sharedPath: Array<SharedPathJson>?,
+    private val display: DisplayJson?,
+    private val gpu: GpuJson?,
+    private val auto_memory_balloon: Boolean,
+) {
+    private fun getCpuTopology(): Int {
+        return when (cpu_topology) {
+            "one_cpu" -> VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU
+            "match_host" -> VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST
+            else -> throw RuntimeException("invalid cpu topology: $cpu_topology")
+        }
+    }
+
+    private fun getDebugLevel(): Int {
+        return if (debuggable) VirtualMachineConfig.DEBUG_LEVEL_FULL
+        else VirtualMachineConfig.DEBUG_LEVEL_NONE
+    }
+
+    /** Converts this parsed JSON into VirtualMachineConfig Builder */
+    fun toConfigBuilder(context: Context): VirtualMachineConfig.Builder {
+        return VirtualMachineConfig.Builder(context)
+            .setProtectedVm(isProtected)
+            .setMemoryBytes(memory_mib.toLong() * 1024 * 1024)
+            .setConsoleInputDevice(console_input_device)
+            .setCpuTopology(getCpuTopology())
+            .setCustomImageConfig(toCustomImageConfigBuilder(context).build())
+            .setDebugLevel(getDebugLevel())
+            .setVmOutputCaptured(console_out)
+            .setConnectVmConsole(connect_console)
+    }
+
+    fun toCustomImageConfigBuilder(context: Context): VirtualMachineCustomImageConfig.Builder {
+        val builder = VirtualMachineCustomImageConfig.Builder()
+
+        builder
+            .setName(name)
+            .setBootloaderPath(bootloader)
+            .setKernelPath(kernel)
+            .setInitrdPath(initrd)
+            .useNetwork(network)
+            .useAutoMemoryBalloon(auto_memory_balloon)
+
+        if (input != null) {
+            builder
+                .useTouch(input.touchscreen)
+                .useKeyboard(input.keyboard)
+                .useMouse(input.mouse)
+                .useTrackpad(input.trackpad)
+                .useSwitches(input.switches)
+        }
+
+        if (audio != null) {
+            builder.setAudioConfig(audio.toConfig())
+        }
+
+        if (display != null) {
+            builder.setDisplayConfig(display.toConfig(context))
+        }
+
+        if (gpu != null) {
+            builder.setGpuConfig(gpu.toConfig())
+        }
+
+        params?.split(" ".toRegex())?.filter { it.isNotEmpty() }?.forEach { builder.addParam(it) }
+
+        disks?.forEach { builder.addDisk(it.toConfig()) }
+
+        sharedPath?.mapNotNull { it.toConfig(context) }?.forEach { builder.addSharedPath(it) }
+
+        return builder
+    }
+
+    internal data class SharedPathJson(private val sharedPath: String?) {
+        fun toConfig(context: Context): VirtualMachineCustomImageConfig.SharedPath? {
+            try {
+                val terminalUid = getTerminalUid(context)
+                if (sharedPath?.contains("emulated") == true) {
+                    if (Environment.isExternalStorageManager()) {
+                        val currentUserId = context.userId
+                        val path = "$sharedPath/$currentUserId/Download"
+                        return VirtualMachineCustomImageConfig.SharedPath(
+                            path,
+                            terminalUid,
+                            terminalUid,
+                            GUEST_UID,
+                            GUEST_GID,
+                            7,
+                            "android",
+                            "android",
+                            false, /* app domain is set to false so that crosvm is spin up as child of virtmgr */
+                            "",
+                        )
+                    }
+                    return null
+                }
+                val socketPath = context.getFilesDir().toPath().resolve("internal.virtiofs")
+                Files.deleteIfExists(socketPath)
+                return VirtualMachineCustomImageConfig.SharedPath(
+                    sharedPath,
+                    terminalUid,
+                    terminalUid,
+                    0,
+                    0,
+                    7,
+                    "internal",
+                    "internal",
+                    true, /* app domain is set to true so that crosvm is spin up from app context */
+                    socketPath.toString(),
+                )
+            } catch (e: PackageManager.NameNotFoundException) {
+                return null
+            } catch (e: IOException) {
+                return null
+            }
+        }
+
+        @Throws(PackageManager.NameNotFoundException::class)
+        fun getTerminalUid(context: Context): Int {
+            return context
+                .getPackageManager()
+                .getPackageUidAsUser(context.getPackageName(), context.userId)
+        }
+
+        companion object {
+            private const val GUEST_UID = 1000
+            private const val GUEST_GID = 100
+        }
+    }
+
+    internal data class InputJson(
+        val touchscreen: Boolean,
+        val keyboard: Boolean,
+        val mouse: Boolean,
+        val switches: Boolean,
+        val trackpad: Boolean,
+    )
+
+    internal data class AudioJson(private val microphone: Boolean, private val speaker: Boolean) {
+
+        fun toConfig(): VirtualMachineCustomImageConfig.AudioConfig {
+            return VirtualMachineCustomImageConfig.AudioConfig.Builder()
+                .setUseMicrophone(microphone)
+                .setUseSpeaker(speaker)
+                .build()
+        }
+    }
+
+    internal data class DiskJson(
+        private val writable: Boolean,
+        private val image: String?,
+        private val partitions: Array<PartitionJson>?,
+    ) {
+        fun toConfig(): VirtualMachineCustomImageConfig.Disk {
+            val d =
+                if (writable) VirtualMachineCustomImageConfig.Disk.RWDisk(image)
+                else VirtualMachineCustomImageConfig.Disk.RODisk(image)
+            partitions?.forEach {
+                val writable = this.writable && it.writable
+                d.addPartition(
+                    VirtualMachineCustomImageConfig.Partition(it.label, it.path, writable, it.guid)
+                )
+            }
+            return d
+        }
+    }
+
+    internal data class PartitionJson(
+        val writable: Boolean,
+        val label: String?,
+        val path: String?,
+        val guid: String?,
+    )
+
+    internal data class DisplayJson(
+        private val scale: Float = 0f,
+        private val refresh_rate: Int = 0,
+        private val width_pixels: Int = 0,
+        private val height_pixels: Int = 0,
+    ) {
+        fun toConfig(context: Context): VirtualMachineCustomImageConfig.DisplayConfig {
+            val wm = context.getSystemService<WindowManager>(WindowManager::class.java)
+            val metrics = wm.currentWindowMetrics
+            val dispBounds = metrics.bounds
+
+            val width = if (width_pixels > 0) width_pixels else dispBounds.right
+            val height = if (height_pixels > 0) height_pixels else dispBounds.bottom
+
+            var dpi = (DisplayMetrics.DENSITY_DEFAULT * metrics.density).toInt()
+            if (scale > 0.0f) {
+                dpi = (dpi * scale).toInt()
+            }
+
+            var refreshRate = context.display.refreshRate.toInt()
+            if (this.refresh_rate != 0) {
+                refreshRate = this.refresh_rate
+            }
+
+            return VirtualMachineCustomImageConfig.DisplayConfig.Builder()
+                .setWidth(width)
+                .setHeight(height)
+                .setHorizontalDpi(dpi)
+                .setVerticalDpi(dpi)
+                .setRefreshRate(refreshRate)
+                .build()
+        }
+    }
+
+    internal data class GpuJson(
+        private val backend: String?,
+        private val pci_address: String?,
+        private val renderer_features: String?,
+        // TODO: GSON actaully ignores the default values
+        private val renderer_use_egl: Boolean = true,
+        private val renderer_use_gles: Boolean = true,
+        private val renderer_use_glx: Boolean = false,
+        private val renderer_use_surfaceless: Boolean = true,
+        private val renderer_use_vulkan: Boolean = false,
+        private val context_types: Array<String>?,
+    ) {
+        fun toConfig(): VirtualMachineCustomImageConfig.GpuConfig {
+            return VirtualMachineCustomImageConfig.GpuConfig.Builder()
+                .setBackend(backend)
+                .setPciAddress(pci_address)
+                .setRendererFeatures(renderer_features)
+                .setRendererUseEgl(renderer_use_egl)
+                .setRendererUseGles(renderer_use_gles)
+                .setRendererUseGlx(renderer_use_glx)
+                .setRendererUseSurfaceless(renderer_use_surfaceless)
+                .setRendererUseVulkan(renderer_use_vulkan)
+                .setContextTypes(context_types)
+                .build()
+        }
+    }
+
+    companion object {
+        private const val DEBUG = true
+
+        /** Parses JSON file at jsonPath */
+        fun from(context: Context, jsonPath: Path): ConfigJson {
+            try {
+                FileReader(jsonPath.toFile()).use { fileReader ->
+                    val content = replaceKeywords(fileReader, context)
+                    return Gson().fromJson<ConfigJson?>(content, ConfigJson::class.java)
+                }
+            } catch (e: Exception) {
+                throw RuntimeException("Failed to parse $jsonPath", e)
+            }
+        }
+
+        @Throws(IOException::class)
+        private fun replaceKeywords(r: Reader, context: Context): String {
+            val rules: Map<String, String> =
+                mapOf(
+                    "\\\$PAYLOAD_DIR" to getDefault(context).installDir.toString(),
+                    "\\\$USER_ID" to context.userId.toString(),
+                    "\\\$PACKAGE_NAME" to context.getPackageName(),
+                    "\\\$APP_DATA_DIR" to context.getDataDir().toString(),
+                )
+
+            return BufferedReader(r).useLines { lines ->
+                lines
+                    .map { line ->
+                        rules.entries.fold(line) { acc, rule ->
+                            acc.replace(rule.key.toRegex(), rule.value)
+                        }
+                    }
+                    .joinToString("\n")
+            }
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
deleted file mode 100644
index 9cf6093..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
+++ /dev/null
@@ -1,167 +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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.Keep;
-
-import com.android.virtualization.terminal.proto.DebianServiceGrpc;
-import com.android.virtualization.terminal.proto.ForwardingRequestItem;
-import com.android.virtualization.terminal.proto.IpAddr;
-import com.android.virtualization.terminal.proto.QueueOpeningRequest;
-import com.android.virtualization.terminal.proto.ReportVmActivePortsRequest;
-import com.android.virtualization.terminal.proto.ReportVmActivePortsResponse;
-import com.android.virtualization.terminal.proto.ReportVmIpAddrResponse;
-import com.android.virtualization.terminal.proto.ShutdownQueueOpeningRequest;
-import com.android.virtualization.terminal.proto.ShutdownRequestItem;
-
-import io.grpc.stub.StreamObserver;
-
-import java.util.HashSet;
-import java.util.Set;
-
-final class DebianServiceImpl extends DebianServiceGrpc.DebianServiceImplBase {
-    private final Context mContext;
-    private final PortsStateManager mPortsStateManager;
-    private PortsStateManager.Listener mPortsStateListener;
-    private final DebianServiceCallback mCallback;
-    private Runnable mShutdownRunnable;
-
-    static {
-        System.loadLibrary("forwarder_host_jni");
-    }
-
-    DebianServiceImpl(Context context, DebianServiceCallback callback) {
-        super();
-        mCallback = callback;
-        mContext = context;
-        mPortsStateManager = PortsStateManager.getInstance(mContext);
-    }
-
-    @Override
-    public void reportVmActivePorts(
-            ReportVmActivePortsRequest request,
-            StreamObserver<ReportVmActivePortsResponse> responseObserver) {
-        Log.d(TAG, "reportVmActivePorts: " + request.toString());
-        mPortsStateManager.updateActivePorts(new HashSet<>(request.getPortsList()));
-        ReportVmActivePortsResponse reply =
-                ReportVmActivePortsResponse.newBuilder().setSuccess(true).build();
-        responseObserver.onNext(reply);
-        responseObserver.onCompleted();
-    }
-
-    @Override
-    public void reportVmIpAddr(
-            IpAddr request, StreamObserver<ReportVmIpAddrResponse> responseObserver) {
-        Log.d(TAG, "reportVmIpAddr: " + request.toString());
-        mCallback.onIpAddressAvailable(request.getAddr());
-        ReportVmIpAddrResponse reply = ReportVmIpAddrResponse.newBuilder().setSuccess(true).build();
-        responseObserver.onNext(reply);
-        responseObserver.onCompleted();
-    }
-
-    @Override
-    public void openForwardingRequestQueue(
-            QueueOpeningRequest request, StreamObserver<ForwardingRequestItem> responseObserver) {
-        Log.d(TAG, "OpenForwardingRequestQueue");
-        mPortsStateListener =
-                new PortsStateManager.Listener() {
-                    @Override
-                    public void onPortsStateUpdated(
-                            Set<Integer> oldActivePorts, Set<Integer> newActivePorts) {
-                        updateListeningPorts();
-                    }
-                };
-        mPortsStateManager.registerListener(mPortsStateListener);
-        updateListeningPorts();
-        runForwarderHost(request.getCid(), new ForwarderHostCallback(responseObserver));
-        responseObserver.onCompleted();
-    }
-
-    public boolean shutdownDebian() {
-        if (mShutdownRunnable == null) {
-            Log.d(TAG, "mShutdownRunnable is not ready.");
-            return false;
-        }
-        mShutdownRunnable.run();
-        return true;
-    }
-
-    @Override
-    public void openShutdownRequestQueue(
-            ShutdownQueueOpeningRequest request,
-            StreamObserver<ShutdownRequestItem> responseObserver) {
-        Log.d(TAG, "openShutdownRequestQueue");
-        mShutdownRunnable =
-                () -> {
-                    responseObserver.onNext(ShutdownRequestItem.newBuilder().build());
-                    responseObserver.onCompleted();
-                    mShutdownRunnable = null;
-                };
-    }
-
-    @Keep
-    private static class ForwarderHostCallback {
-        private StreamObserver<ForwardingRequestItem> mResponseObserver;
-
-        ForwarderHostCallback(StreamObserver<ForwardingRequestItem> responseObserver) {
-            mResponseObserver = responseObserver;
-        }
-
-        private void onForwardingRequestReceived(int guestTcpPort, int vsockPort) {
-            ForwardingRequestItem item =
-                    ForwardingRequestItem.newBuilder()
-                            .setGuestTcpPort(guestTcpPort)
-                            .setVsockPort(vsockPort)
-                            .build();
-            mResponseObserver.onNext(item);
-        }
-    }
-
-    private static native void runForwarderHost(int cid, ForwarderHostCallback callback);
-
-    private static native void terminateForwarderHost();
-
-    void killForwarderHost() {
-        Log.d(TAG, "Stopping port forwarding");
-        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(
-                activePorts.stream()
-                        .filter(port -> enabledPorts.contains(port))
-                        .mapToInt(Integer::intValue)
-                        .toArray());
-    }
-
-    protected interface DebianServiceCallback {
-        void onIpAddressAvailable(String ipAddr);
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
new file mode 100644
index 0000000..887ae02
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.util.Log
+import androidx.annotation.Keep
+import com.android.virtualization.terminal.DebianServiceImpl.ForwarderHostCallback
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import com.android.virtualization.terminal.PortsStateManager.Companion.getInstance
+import com.android.virtualization.terminal.proto.DebianServiceGrpc.DebianServiceImplBase
+import com.android.virtualization.terminal.proto.ForwardingRequestItem
+import com.android.virtualization.terminal.proto.QueueOpeningRequest
+import com.android.virtualization.terminal.proto.ReportVmActivePortsRequest
+import com.android.virtualization.terminal.proto.ReportVmActivePortsResponse
+import com.android.virtualization.terminal.proto.ShutdownQueueOpeningRequest
+import com.android.virtualization.terminal.proto.ShutdownRequestItem
+import io.grpc.stub.StreamObserver
+
+internal class DebianServiceImpl(context: Context) : DebianServiceImplBase() {
+    private val portsStateManager: PortsStateManager = getInstance(context)
+    private var portsStateListener: PortsStateManager.Listener? = null
+    private var shutdownRunnable: Runnable? = null
+
+    override fun reportVmActivePorts(
+        request: ReportVmActivePortsRequest,
+        responseObserver: StreamObserver<ReportVmActivePortsResponse?>,
+    ) {
+        portsStateManager.updateActivePorts(request.portsList)
+        Log.d(TAG, "reportVmActivePorts: " + portsStateManager.getActivePorts())
+        val reply = ReportVmActivePortsResponse.newBuilder().setSuccess(true).build()
+        responseObserver.onNext(reply)
+        responseObserver.onCompleted()
+    }
+
+    override fun openForwardingRequestQueue(
+        request: QueueOpeningRequest,
+        responseObserver: StreamObserver<ForwardingRequestItem?>,
+    ) {
+        Log.d(TAG, "OpenForwardingRequestQueue")
+        portsStateListener =
+            object : PortsStateManager.Listener {
+                override fun onPortsStateUpdated(
+                    oldActivePorts: Set<Int>,
+                    newActivePorts: Set<Int>,
+                ) {
+                    updateListeningPorts()
+                }
+            }
+        portsStateManager.registerListener(portsStateListener!!)
+        updateListeningPorts()
+        runForwarderHost(request.cid, ForwarderHostCallback(responseObserver))
+        responseObserver.onCompleted()
+    }
+
+    fun shutdownDebian(): Boolean {
+        if (shutdownRunnable == null) {
+            Log.d(TAG, "mShutdownRunnable is not ready.")
+            return false
+        }
+        shutdownRunnable!!.run()
+        return true
+    }
+
+    override fun openShutdownRequestQueue(
+        request: ShutdownQueueOpeningRequest?,
+        responseObserver: StreamObserver<ShutdownRequestItem?>,
+    ) {
+        Log.d(TAG, "openShutdownRequestQueue")
+        shutdownRunnable = Runnable {
+            responseObserver.onNext(ShutdownRequestItem.newBuilder().build())
+            responseObserver.onCompleted()
+            shutdownRunnable = null
+        }
+    }
+
+    @Keep
+    private class ForwarderHostCallback(
+        private val responseObserver: StreamObserver<ForwardingRequestItem?>
+    ) {
+
+        fun onForwardingRequestReceived(guestTcpPort: Int, vsockPort: Int) {
+            val item =
+                ForwardingRequestItem.newBuilder()
+                    .setGuestTcpPort(guestTcpPort)
+                    .setVsockPort(vsockPort)
+                    .build()
+            responseObserver.onNext(item)
+        }
+    }
+
+    fun killForwarderHost() {
+        Log.d(TAG, "Stopping port forwarding")
+        if (portsStateListener != null) {
+            portsStateManager.unregisterListener(portsStateListener!!)
+            portsStateListener = null
+        }
+        terminateForwarderHost()
+    }
+
+    private fun updateListeningPorts() {
+        val activePorts: Set<Int> = portsStateManager.getActivePorts()
+        val enabledPorts: Set<Int> = portsStateManager.getEnabledPorts()
+        updateListeningPorts(activePorts.filter { enabledPorts.contains(it) }.toIntArray())
+    }
+
+    companion object {
+        init {
+            System.loadLibrary("forwarder_host_jni")
+        }
+
+        @JvmStatic private external fun runForwarderHost(cid: Int, callback: ForwarderHostCallback?)
+
+        @JvmStatic private external fun terminateForwarderHost()
+
+        @JvmStatic private external fun updateListeningPorts(ports: IntArray?)
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
deleted file mode 100644
index 7099f22..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.virtualization.terminal;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-public class ErrorActivity extends BaseActivity {
-    private static final String EXTRA_CAUSE = "cause";
-
-    public static void start(Context context, Exception e) {
-        Intent intent = new Intent(context, ErrorActivity.class);
-        intent.putExtra(EXTRA_CAUSE, e);
-        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
-        context.startActivity(intent);
-    }
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.activity_error);
-
-        View button = findViewById(R.id.recovery);
-        button.setOnClickListener((event) -> launchRecoveryActivity());
-    }
-
-    @Override
-    protected void onNewIntent(@NonNull Intent intent) {
-        super.onNewIntent(intent);
-        setIntent(intent);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        Intent intent = getIntent();
-        Exception e = intent.getParcelableExtra(EXTRA_CAUSE, Exception.class);
-        TextView cause = findViewById(R.id.cause);
-        if (e != null) {
-            String stackTrace = getStackTrace(e);
-            cause.setText(getString(R.string.error_code, stackTrace));
-        } else {
-            cause.setText(null);
-        }
-    }
-
-    private void launchRecoveryActivity() {
-        Intent intent = new Intent(this, SettingsRecoveryActivity.class);
-        startActivity(intent);
-    }
-
-    private static String getStackTrace(Exception e) {
-        try (StringWriter sWriter = new StringWriter();
-                PrintWriter pWriter = new PrintWriter(sWriter)) {
-            e.printStackTrace(pWriter);
-            return sWriter.toString();
-        } catch (IOException ex) {
-            // This shall never happen
-            throw new RuntimeException(ex);
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.kt
new file mode 100644
index 0000000..0a1090d
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+package com.android.virtualization.terminal
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import java.io.IOException
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.lang.Exception
+import java.lang.RuntimeException
+
+class ErrorActivity : BaseActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(R.layout.activity_error)
+
+        val button = findViewById<View>(R.id.recovery)
+        button.setOnClickListener(View.OnClickListener { _ -> launchRecoveryActivity() })
+    }
+
+    override fun onNewIntent(intent: Intent) {
+        super.onNewIntent(intent)
+        setIntent(intent)
+    }
+
+    override fun onResume() {
+        super.onResume()
+
+        val intent = getIntent()
+        val e = intent.getParcelableExtra<Exception?>(EXTRA_CAUSE, Exception::class.java)
+        val cause = findViewById<TextView>(R.id.cause)
+        cause.text = e?.let { getString(R.string.error_code, getStackTrace(it)) }
+    }
+
+    private fun launchRecoveryActivity() {
+        val intent = Intent(this, SettingsRecoveryActivity::class.java)
+        startActivity(intent)
+    }
+
+    companion object {
+        private const val EXTRA_CAUSE = "cause"
+
+        fun start(context: Context, e: Exception) {
+            val intent = Intent(context, ErrorActivity::class.java)
+            intent.putExtra(EXTRA_CAUSE, e)
+            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+            context.startActivity(intent)
+        }
+
+        private fun getStackTrace(e: Exception): String? {
+            try {
+                StringWriter().use { sWriter ->
+                    PrintWriter(sWriter).use { pWriter ->
+                        e.printStackTrace(pWriter)
+                        return sWriter.toString()
+                    }
+                }
+            } catch (ex: IOException) {
+                // This shall never happen
+                throw RuntimeException(ex)
+            }
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java
deleted file mode 100644
index 7f14179..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java
+++ /dev/null
@@ -1,175 +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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.os.Build;
-import android.os.Environment;
-import android.util.Log;
-
-import org.apache.commons.compress.archivers.ArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.Arrays;
-import java.util.function.Function;
-
-/**
- * ImageArchive models the archive file (images.tar.gz) where VM payload files are in. This class
- * provides methods for handling the archive file, most importantly installing it.
- */
-class ImageArchive {
-    private static final String DIR_IN_SDCARD = "linux";
-    private static final String ARCHIVE_NAME = "images.tar.gz";
-    private static final String BUILD_TAG = "latest"; // TODO: use actual tag name
-    private static final String HOST_URL = "https://dl.google.com/android/ferrochrome/" + BUILD_TAG;
-
-    // Only one can be non-null
-    private final URL mUrl;
-    private final Path mPath;
-
-    private ImageArchive(URL url) {
-        mUrl = url;
-        mPath = null;
-    }
-
-    private ImageArchive(Path path) {
-        mUrl = null;
-        mPath = path;
-    }
-
-    public static Path getSdcardPathForTesting() {
-        return Environment.getExternalStoragePublicDirectory(DIR_IN_SDCARD).toPath();
-    }
-
-    /** Creates ImageArchive which is located in the sdcard. This archive is for testing only. */
-    public static ImageArchive fromSdCard() {
-        Path file = getSdcardPathForTesting().resolve(ARCHIVE_NAME);
-        return new ImageArchive(file);
-    }
-
-    /** Creates ImageArchive which is hosted in the Google server. This is the official archive. */
-    public static ImageArchive fromInternet() {
-        String arch = Arrays.asList(Build.SUPPORTED_ABIS).contains("x86_64") ? "x86_64" : "aarch64";
-        try {
-            URL url = new URL(HOST_URL + "/" + arch + "/" + ARCHIVE_NAME);
-            return new ImageArchive(url);
-        } catch (MalformedURLException e) {
-            // cannot happen
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Creates ImageArchive from either SdCard or Internet. SdCard is used only when the build is
-     * debuggable and the file actually exists.
-     */
-    public static ImageArchive getDefault() {
-        ImageArchive archive = fromSdCard();
-        if (Build.isDebuggable() && archive.exists()) {
-            return archive;
-        } else {
-            return fromInternet();
-        }
-    }
-
-    /** Tests if ImageArchive exists on the medium. */
-    public boolean exists() {
-        if (mPath != null) {
-            return Files.exists(mPath);
-        } else {
-            // TODO
-            return true;
-        }
-    }
-
-    /** Returns size of the archive in bytes */
-    public long getSize() throws IOException {
-        if (!exists()) {
-            throw new IllegalStateException("Cannot get size of non existing archive");
-        }
-        if (mPath != null) {
-            return Files.size(mPath);
-        } else {
-            HttpURLConnection conn = null;
-            try {
-                conn = (HttpURLConnection) mUrl.openConnection();
-                conn.setRequestMethod("HEAD");
-                conn.getInputStream();
-                return conn.getContentLength();
-            } finally {
-                if (conn != null) {
-                    conn.disconnect();
-                }
-            }
-        }
-    }
-
-    private InputStream getInputStream(Function<InputStream, InputStream> filter)
-            throws IOException {
-        InputStream is = mPath != null ? new FileInputStream(mPath.toFile()) : mUrl.openStream();
-        BufferedInputStream bufStream = new BufferedInputStream(is);
-        return filter == null ? bufStream : filter.apply(bufStream);
-    }
-
-    /**
-     * Installs this ImageArchive to a directory pointed by path. filter can be supplied to provide
-     * an additional input stream which will be used during the installation.
-     */
-    public void installTo(Path dir, Function<InputStream, InputStream> filter) throws IOException {
-        String source = mPath != null ? mPath.toString() : mUrl.toString();
-        Log.d(TAG, "Installing. source: " + source + ", destination: " + dir.toString());
-        try (InputStream stream = getInputStream(filter);
-                GzipCompressorInputStream gzStream = new GzipCompressorInputStream(stream);
-                TarArchiveInputStream tarStream = new TarArchiveInputStream(gzStream)) {
-
-            Files.createDirectories(dir);
-            ArchiveEntry entry;
-            while ((entry = tarStream.getNextEntry()) != null) {
-                Path to = dir.resolve(entry.getName());
-                if (Files.isDirectory(to)) {
-                    Files.createDirectories(to);
-                    continue;
-                }
-                Files.copy(tarStream, to, StandardCopyOption.REPLACE_EXISTING);
-            }
-        }
-        commitInstallationAt(dir);
-    }
-
-    private void commitInstallationAt(Path dir) throws IOException {
-        // To save storage, delete the source archive on the disk.
-        if (mPath != null) {
-            Files.deleteIfExists(mPath);
-        }
-
-        // Mark the completion
-        Path marker = dir.resolve(InstalledImage.MARKER_FILENAME);
-        Files.createFile(marker);
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
new file mode 100644
index 0000000..017ff89
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
@@ -0,0 +1,183 @@
+/*
+ * 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.os.Build
+import android.os.Environment
+import android.util.Log
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.io.BufferedInputStream
+import java.io.FileInputStream
+import java.io.IOException
+import java.io.InputStream
+import java.lang.RuntimeException
+import java.net.HttpURLConnection
+import java.net.MalformedURLException
+import java.net.URL
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardCopyOption
+import java.util.function.Function
+import org.apache.commons.compress.archivers.ArchiveEntry
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
+
+/**
+ * ImageArchive models the archive file (images.tar.gz) where VM payload files are in. This class
+ * provides methods for handling the archive file, most importantly installing it.
+ */
+internal class ImageArchive {
+    // Only one can be non-null
+    private sealed class Source<out A, out B>
+
+    private data class UrlSource<out Url>(val value: Url) : Source<Url, Nothing>()
+
+    private data class PathSource<out Path>(val value: Path) : Source<Nothing, Path>()
+
+    private val source: Source<URL, Path>
+
+    private constructor(url: URL) {
+        source = UrlSource(url)
+    }
+
+    private constructor(path: Path) {
+        source = PathSource(path)
+    }
+
+    /** Tests if ImageArchive exists on the medium. */
+    fun exists(): Boolean {
+        return when (source) {
+            is UrlSource -> true
+            is PathSource -> Files.exists(source.value)
+        }
+    }
+
+    /** Returns size of the archive in bytes */
+    @Throws(IOException::class)
+    fun getSize(): Long {
+        check(exists()) { "Cannot get size of non existing archive" }
+        return when (source) {
+            is UrlSource -> {
+                val conn = source.value.openConnection() as HttpURLConnection
+                try {
+                    conn.requestMethod = "HEAD"
+                    conn.getInputStream()
+                    return conn.contentLength.toLong()
+                } finally {
+                    conn.disconnect()
+                }
+            }
+            is PathSource -> Files.size(source.value)
+        }
+    }
+
+    @Throws(IOException::class)
+    private fun getInputStream(filter: Function<InputStream, InputStream>?): InputStream? {
+        val bufStream =
+            BufferedInputStream(
+                when (source) {
+                    is UrlSource -> source.value.openStream()
+                    is PathSource -> FileInputStream(source.value.toFile())
+                }
+            )
+        return filter?.apply(bufStream) ?: bufStream
+    }
+
+    /**
+     * Installs this ImageArchive to a directory pointed by path. filter can be supplied to provide
+     * an additional input stream which will be used during the installation.
+     */
+    @Throws(IOException::class)
+    fun installTo(dir: Path, filter: Function<InputStream, InputStream>?) {
+        val source =
+            when (source) {
+                is PathSource -> source.value.toString()
+                is UrlSource -> source.value.toString()
+            }
+        Log.d(TAG, "Installing. source: $source, destination: $dir")
+        TarArchiveInputStream(GzipCompressorInputStream(getInputStream(filter))).use { tarStream ->
+            Files.createDirectories(dir)
+            var entry: ArchiveEntry?
+            while ((tarStream.nextEntry.also { entry = it }) != null) {
+                val to = dir.resolve(entry!!.getName())
+                if (Files.isDirectory(to)) {
+                    Files.createDirectories(to)
+                    continue
+                }
+                Files.copy(tarStream, to, StandardCopyOption.REPLACE_EXISTING)
+            }
+        }
+        commitInstallationAt(dir)
+    }
+
+    @Throws(IOException::class)
+    private fun commitInstallationAt(dir: Path) {
+        // To save storage, delete the source archive on the disk.
+        if (source is PathSource) {
+            Files.deleteIfExists(source.value)
+        }
+
+        // Mark the completion
+        val marker = dir.resolve(InstalledImage.MARKER_FILENAME)
+        Files.createFile(marker)
+    }
+
+    companion object {
+        private const val DIR_IN_SDCARD = "linux"
+        private const val ARCHIVE_NAME = "images.tar.gz"
+        private const val BUILD_TAG = "latest" // TODO: use actual tag name
+        private const val HOST_URL = "https://dl.google.com/android/ferrochrome/$BUILD_TAG"
+
+        fun getSdcardPathForTesting(): Path {
+            return Environment.getExternalStoragePublicDirectory(DIR_IN_SDCARD).toPath()
+        }
+
+        /**
+         * Creates ImageArchive which is located in the sdcard. This archive is for testing only.
+         */
+        fun fromSdCard(): ImageArchive {
+            return ImageArchive(getSdcardPathForTesting().resolve(ARCHIVE_NAME))
+        }
+
+        /**
+         * Creates ImageArchive which is hosted in the Google server. This is the official archive.
+         */
+        fun fromInternet(): ImageArchive {
+            val arch =
+                if (listOf<String?>(*Build.SUPPORTED_ABIS).contains("x86_64")) "x86_64"
+                else "aarch64"
+            try {
+                return ImageArchive(URL("$HOST_URL/$arch/$ARCHIVE_NAME"))
+            } catch (e: MalformedURLException) {
+                // cannot happen
+                throw RuntimeException(e)
+            }
+        }
+
+        /**
+         * Creates ImageArchive from either SdCard or Internet. SdCard is used only when the build
+         * is debuggable and the file actually exists.
+         */
+        fun getDefault(): ImageArchive {
+            val archive = fromSdCard()
+            return if (Build.isDebuggable() && archive.exists()) {
+                archive
+            } else {
+                fromInternet()
+            }
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.java
deleted file mode 100644
index 318f49a..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.java
+++ /dev/null
@@ -1,212 +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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.content.Context;
-import android.os.FileUtils;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.FileDescriptor;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-
-/** Collection of files that consist of a VM image. */
-class InstalledImage {
-    private static final String INSTALL_DIRNAME = "linux";
-    private static final String ROOTFS_FILENAME = "root_part";
-    private static final String BACKUP_FILENAME = "root_part_backup";
-    private static final String CONFIG_FILENAME = "vm_config.json";
-    private static final String BUILD_ID_FILENAME = "build_id";
-    static final String MARKER_FILENAME = "completed";
-
-    public static final long RESIZE_STEP_BYTES = 4 << 20; // 4 MiB
-
-    private final Path mDir;
-    private final Path mRootPartition;
-    private final Path mBackup;
-    private final Path mConfig;
-    private final Path mMarker;
-    private String mBuildId;
-
-    /** Returns InstalledImage for a given app context */
-    public static InstalledImage getDefault(Context context) {
-        Path installDir = context.getFilesDir().toPath().resolve(INSTALL_DIRNAME);
-        return new InstalledImage(installDir);
-    }
-
-    private InstalledImage(Path dir) {
-        mDir = dir;
-        mRootPartition = dir.resolve(ROOTFS_FILENAME);
-        mBackup = dir.resolve(BACKUP_FILENAME);
-        mConfig = dir.resolve(CONFIG_FILENAME);
-        mMarker = dir.resolve(MARKER_FILENAME);
-    }
-
-    public Path getInstallDir() {
-        return mDir;
-    }
-
-    /** Tests if this InstalledImage is actually installed. */
-    public boolean isInstalled() {
-        return Files.exists(mMarker);
-    }
-
-    /** Fully understalls this InstalledImage by deleting everything. */
-    public void uninstallFully() throws IOException {
-        FileUtils.deleteContentsAndDir(mDir.toFile());
-    }
-
-    /** Returns the path to the VM config file. */
-    public Path getConfigPath() {
-        return mConfig;
-    }
-
-    /** Returns the build ID of the installed image */
-    public String getBuildId() {
-        if (mBuildId == null) {
-            mBuildId = readBuildId();
-        }
-        return mBuildId;
-    }
-
-    private String readBuildId() {
-        Path file = mDir.resolve(BUILD_ID_FILENAME);
-        if (!Files.exists(file)) {
-            return "<no build id>";
-        }
-        try (BufferedReader r = new BufferedReader(new FileReader(file.toFile()))) {
-            return r.readLine();
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to read build ID", e);
-        }
-    }
-
-    public Path uninstallAndBackup() throws IOException {
-        Files.delete(mMarker);
-        Files.move(mRootPartition, mBackup, StandardCopyOption.REPLACE_EXISTING);
-        return mBackup;
-    }
-
-    public Path getBackupFile() {
-        return mBackup;
-    }
-
-    public boolean hasBackup() {
-        return Files.exists(mBackup);
-    }
-
-    public void deleteBackup() throws IOException {
-        Files.deleteIfExists(mBackup);
-    }
-
-    public long getSize() throws IOException {
-        return Files.size(mRootPartition);
-    }
-
-    public long getSmallestSizePossible() throws IOException {
-        runE2fsck(mRootPartition);
-        String p = mRootPartition.toAbsolutePath().toString();
-        String result = runCommand("/system/bin/resize2fs", "-P", p);
-        // The return value is the number of 4k block
-        try {
-            long minSize =
-                    Long.parseLong(result.lines().toArray(String[]::new)[1].substring(42))
-                            * 4
-                            * 1024;
-            return roundUp(minSize);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "Failed to parse min size, p=" + p + ", result=" + result);
-            throw new IOException(e);
-        }
-    }
-
-    public long resize(long desiredSize) throws IOException {
-        desiredSize = roundUp(desiredSize);
-        final long curSize = getSize();
-
-        if (desiredSize == curSize) {
-            return desiredSize;
-        }
-
-        runE2fsck(mRootPartition);
-        if (desiredSize > curSize) {
-            allocateSpace(mRootPartition, desiredSize);
-        }
-        resizeFilesystem(mRootPartition, desiredSize);
-        return getSize();
-    }
-
-    private static void allocateSpace(Path path, long sizeInBytes) throws IOException {
-        try {
-            RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw");
-            FileDescriptor fd = raf.getFD();
-            Os.posix_fallocate(fd, 0, sizeInBytes);
-            raf.close();
-            Log.d(TAG, "Allocated space to: " + sizeInBytes + " bytes");
-        } catch (ErrnoException e) {
-            Log.e(TAG, "Failed to allocate space", e);
-            throw new IOException("Failed to allocate space", e);
-        }
-    }
-
-    private static void runE2fsck(Path path) throws IOException {
-        String p = path.toAbsolutePath().toString();
-        runCommand("/system/bin/e2fsck", "-y", "-f", p);
-        Log.d(TAG, "e2fsck completed: " + path);
-    }
-
-    private static void resizeFilesystem(Path path, long sizeInBytes) throws IOException {
-        long sizeInMB = sizeInBytes / (1024 * 1024);
-        if (sizeInMB == 0) {
-            Log.e(TAG, "Invalid size: " + sizeInBytes + " bytes");
-            throw new IllegalArgumentException("Size cannot be zero MB");
-        }
-        String sizeArg = sizeInMB + "M";
-        String p = path.toAbsolutePath().toString();
-        runCommand("/system/bin/resize2fs", p, sizeArg);
-        Log.d(TAG, "resize2fs completed: " + path + ", size: " + sizeArg);
-    }
-
-    private static String runCommand(String... command) throws IOException {
-        try {
-            Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
-            process.waitFor();
-            String result = new String(process.getInputStream().readAllBytes());
-            if (process.exitValue() != 0) {
-                Log.w(TAG, "Process returned with error, command=" + String.join(" ", command)
-                    + ", exitValue=" + process.exitValue() + ", result=" + result);
-            }
-            return result;
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new IOException("Command interrupted", e);
-        }
-    }
-
-    private static long roundUp(long bytes) {
-        // Round up every diskSizeStep MB
-        return (long) Math.ceil(((double) bytes) / RESIZE_STEP_BYTES) * RESIZE_STEP_BYTES;
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
new file mode 100644
index 0000000..7acc5f3
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.os.FileUtils
+import android.system.ErrnoException
+import android.system.Os
+import android.util.Log
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.io.BufferedReader
+import java.io.FileReader
+import java.io.IOException
+import java.io.RandomAccessFile
+import java.lang.IllegalArgumentException
+import java.lang.NumberFormatException
+import java.lang.RuntimeException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardCopyOption
+import kotlin.math.ceil
+
+/** Collection of files that consist of a VM image. */
+public class InstalledImage private constructor(val installDir: Path) {
+    private val rootPartition: Path = installDir.resolve(ROOTFS_FILENAME)
+    val backupFile: Path = installDir.resolve(BACKUP_FILENAME)
+
+    /** The path to the VM config file. */
+    val configPath: Path = installDir.resolve(CONFIG_FILENAME)
+    private val marker: Path = installDir.resolve(MARKER_FILENAME)
+    /** The build ID of the installed image */
+    val buildId: String by lazy { readBuildId() }
+
+    /** Tests if this InstalledImage is actually installed. */
+    fun isInstalled(): Boolean {
+        return Files.exists(marker)
+    }
+
+    /** Fully uninstall this InstalledImage by deleting everything. */
+    @Throws(IOException::class)
+    fun uninstallFully() {
+        FileUtils.deleteContentsAndDir(installDir.toFile())
+    }
+
+    private fun readBuildId(): String {
+        val file = installDir.resolve(BUILD_ID_FILENAME)
+        if (!Files.exists(file)) {
+            return "<no build id>"
+        }
+        try {
+            BufferedReader(FileReader(file.toFile())).use { r ->
+                return r.readLine()
+            }
+        } catch (e: IOException) {
+            throw RuntimeException("Failed to read build ID", e)
+        }
+    }
+
+    @Throws(IOException::class)
+    fun uninstallAndBackup(): Path {
+        Files.delete(marker)
+        Files.move(rootPartition, backupFile, StandardCopyOption.REPLACE_EXISTING)
+        return backupFile
+    }
+
+    fun hasBackup(): Boolean {
+        return Files.exists(backupFile)
+    }
+
+    @Throws(IOException::class)
+    fun deleteBackup() {
+        Files.deleteIfExists(backupFile)
+    }
+
+    @Throws(IOException::class)
+    fun getSize(): Long {
+        return Files.size(rootPartition)
+    }
+
+    @Throws(IOException::class)
+    fun getSmallestSizePossible(): Long {
+        runE2fsck(rootPartition)
+        val p: String = rootPartition.toAbsolutePath().toString()
+        val result = runCommand("/system/bin/resize2fs", "-P", p)
+        val regex = "Estimated minimum size of the filesystem: ([0-9]+)".toRegex()
+        val matchResult = result.lines().firstNotNullOfOrNull { regex.find(it) }
+        if (matchResult != null) {
+            try {
+                val size = matchResult.groupValues[1].toLong()
+                // The return value is the number of 4k block
+                return roundUp(size * 4 * 1024)
+            } catch (e: NumberFormatException) {
+                // cannot happen
+            }
+        }
+        val msg = "Failed to get min size, p=$p, result=$result"
+        Log.e(TAG, msg)
+        throw RuntimeException(msg)
+    }
+
+    @Throws(IOException::class)
+    fun resize(desiredSize: Long): Long {
+        val roundedUpDesiredSize = roundUp(desiredSize)
+        val curSize = getSize()
+
+        if (roundedUpDesiredSize == curSize) {
+            return roundedUpDesiredSize
+        }
+
+        runE2fsck(rootPartition)
+        if (roundedUpDesiredSize > curSize) {
+            allocateSpace(rootPartition, roundedUpDesiredSize)
+        }
+        resizeFilesystem(rootPartition, roundedUpDesiredSize)
+        return getSize()
+    }
+
+    companion object {
+        private const val INSTALL_DIRNAME = "linux"
+        private const val ROOTFS_FILENAME = "root_part"
+        private const val BACKUP_FILENAME = "root_part_backup"
+        private const val CONFIG_FILENAME = "vm_config.json"
+        private const val BUILD_ID_FILENAME = "build_id"
+        const val MARKER_FILENAME: String = "completed"
+
+        const val RESIZE_STEP_BYTES: Long = 4 shl 20 // 4 MiB
+
+        /** Returns InstalledImage for a given app context */
+        fun getDefault(context: Context): InstalledImage {
+            val installDir = context.getFilesDir().toPath().resolve(INSTALL_DIRNAME)
+            return InstalledImage(installDir)
+        }
+
+        @Throws(IOException::class)
+        private fun allocateSpace(path: Path, sizeInBytes: Long) {
+            try {
+                val raf = RandomAccessFile(path.toFile(), "rw")
+                val fd = raf.fd
+                Os.posix_fallocate(fd, 0, sizeInBytes)
+                raf.close()
+                Log.d(TAG, "Allocated space to: $sizeInBytes bytes")
+            } catch (e: ErrnoException) {
+                Log.e(TAG, "Failed to allocate space", e)
+                throw IOException("Failed to allocate space", e)
+            }
+        }
+
+        @Throws(IOException::class)
+        private fun runE2fsck(path: Path) {
+            val p: String = path.toAbsolutePath().toString()
+            runCommand("/system/bin/e2fsck", "-y", "-f", p)
+            Log.d(TAG, "e2fsck completed: $path")
+        }
+
+        @Throws(IOException::class)
+        private fun resizeFilesystem(path: Path, sizeInBytes: Long) {
+            val sizeInMB = sizeInBytes / (1024 * 1024)
+            if (sizeInMB == 0L) {
+                Log.e(TAG, "Invalid size: $sizeInBytes bytes")
+                throw IllegalArgumentException("Size cannot be zero MB")
+            }
+            val sizeArg = sizeInMB.toString() + "M"
+            val p: String = path.toAbsolutePath().toString()
+            runCommand("/system/bin/resize2fs", p, sizeArg)
+            Log.d(TAG, "resize2fs completed: $path, size: $sizeArg")
+        }
+
+        @Throws(IOException::class)
+        private fun runCommand(vararg command: String): String {
+            try {
+                val process = ProcessBuilder(*command).redirectErrorStream(true).start()
+                process.waitFor()
+                val result = String(process.inputStream.readAllBytes())
+                if (process.exitValue() != 0) {
+                    Log.w(
+                        TAG,
+                        "Process returned with error, command=${listOf(*command).joinToString(" ")}," +
+                            "exitValue=${process.exitValue()}, result=$result",
+                    )
+                }
+                return result
+            } catch (e: InterruptedException) {
+                Thread.currentThread().interrupt()
+                throw IOException("Command interrupted", e)
+            }
+        }
+
+        private fun roundUp(bytes: Long): Long {
+            // Round up every diskSizeStep MB
+            return ceil((bytes.toDouble()) / RESIZE_STEP_BYTES).toLong() * RESIZE_STEP_BYTES
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
deleted file mode 100644
index 1c62572..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.virtualization.terminal;
-
-import static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.annotation.MainThread;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.FileUtils;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.text.format.Formatter;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.android.material.progressindicator.LinearProgressIndicator;
-import com.google.android.material.snackbar.Snackbar;
-
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-
-public class InstallerActivity extends BaseActivity {
-    private static final long ESTIMATED_IMG_SIZE_BYTES = FileUtils.parseSize("550MB");
-
-    private CheckBox mWaitForWifiCheckbox;
-    private TextView mInstallButton;
-
-    private IInstallerService mService;
-    private ServiceConnection mInstallerServiceConnection;
-    private InstallProgressListener mInstallProgressListener;
-    private boolean mInstallRequested;
-    private ConditionVariable mInstallCompleted = new ConditionVariable();
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setResult(RESULT_CANCELED);
-
-        mInstallProgressListener = new InstallProgressListener(this);
-
-        setContentView(R.layout.activity_installer);
-        updateSizeEstimation(ESTIMATED_IMG_SIZE_BYTES);
-        measureImageSizeAndUpdateDescription();
-
-        mWaitForWifiCheckbox = (CheckBox) findViewById(R.id.installer_wait_for_wifi_checkbox);
-        mInstallButton = (TextView) findViewById(R.id.installer_install_button);
-
-        mInstallButton.setOnClickListener(
-                (event) -> {
-                    requestInstall();
-                });
-
-        Intent intent = new Intent(this, InstallerService.class);
-        mInstallerServiceConnection = new InstallerServiceConnection(this);
-        if (!bindService(intent, mInstallerServiceConnection, Context.BIND_AUTO_CREATE)) {
-            handleInternalError(new Exception("Failed to connect to installer service"));
-        }
-    }
-
-    private void updateSizeEstimation(long est) {
-        String desc =
-                getString(
-                        R.string.installer_desc_text_format,
-                        Formatter.formatShortFileSize(this, est));
-        runOnUiThread(
-                () -> {
-                    TextView view = (TextView) findViewById(R.id.installer_desc);
-                    view.setText(desc);
-                });
-    }
-
-    private void measureImageSizeAndUpdateDescription() {
-        new Thread(
-                        () -> {
-                            long est;
-                            try {
-                                est = ImageArchive.getDefault().getSize();
-                            } catch (IOException e) {
-                                Log.w(TAG, "Failed to measure image size.", e);
-                                return;
-                            }
-                            updateSizeEstimation(est);
-                        })
-                .start();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        if (Build.isDebuggable() && ImageArchive.fromSdCard().exists()) {
-            showSnackbar("Auto installing", Snackbar.LENGTH_LONG);
-            requestInstall();
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mInstallerServiceConnection != null) {
-            unbindService(mInstallerServiceConnection);
-            mInstallerServiceConnection = null;
-        }
-
-        super.onDestroy();
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BUTTON_START) {
-            requestInstall();
-            return true;
-        }
-        return super.onKeyUp(keyCode, event);
-    }
-
-    @VisibleForTesting
-    public boolean waitForInstallCompleted(long timeoutMillis) {
-        return mInstallCompleted.block(timeoutMillis);
-    }
-
-    private void showSnackbar(String message, int length) {
-        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), message, length);
-        snackbar.setAnchorView(mWaitForWifiCheckbox);
-        snackbar.show();
-    }
-
-    public void handleInternalError(Exception e) {
-        if (Build.isDebuggable()) {
-            showSnackbar(
-                    e.getMessage() + ". File a bugreport to go/ferrochrome-bug",
-                    Snackbar.LENGTH_INDEFINITE);
-        }
-        Log.e(TAG, "Internal error", e);
-        finishWithResult(RESULT_CANCELED);
-    }
-
-    private void finishWithResult(int resultCode) {
-        if (resultCode == RESULT_OK) {
-            mInstallCompleted.open();
-        }
-        setResult(resultCode);
-        finish();
-    }
-
-    private void setInstallEnabled(boolean enable) {
-        mInstallButton.setEnabled(enable);
-        mWaitForWifiCheckbox.setEnabled(enable);
-        LinearProgressIndicator progressBar = findViewById(R.id.installer_progress);
-        if (enable) {
-            progressBar.setVisibility(View.INVISIBLE);
-        } else {
-            progressBar.setVisibility(View.VISIBLE);
-        }
-
-        int resId =
-                enable
-                        ? R.string.installer_install_button_enabled_text
-                        : R.string.installer_install_button_disabled_text;
-        mInstallButton.setText(getString(resId));
-    }
-
-    @MainThread
-    private void requestInstall() {
-        setInstallEnabled(/* enable= */ false);
-
-        if (mService != null) {
-            try {
-                mService.requestInstall(mWaitForWifiCheckbox.isChecked());
-            } catch (RemoteException e) {
-                handleInternalError(e);
-            }
-        } else {
-            Log.d(TAG, "requestInstall() is called, but not yet connected");
-            mInstallRequested = true;
-        }
-    }
-
-    @MainThread
-    public void handleInstallerServiceConnected() {
-        try {
-            mService.setProgressListener(mInstallProgressListener);
-            if (mService.isInstalled()) {
-                // Finishing this activity will trigger MainActivity::onResume(),
-                // and VM will be started from there.
-                finishWithResult(RESULT_OK);
-                return;
-            }
-
-            if (mInstallRequested) {
-                requestInstall();
-            } else if (mService.isInstalling()) {
-                setInstallEnabled(false);
-            }
-        } catch (RemoteException e) {
-            handleInternalError(e);
-        }
-    }
-
-    @MainThread
-    public void handleInstallerServiceDisconnected() {
-        handleInternalError(new Exception("InstallerService is destroyed while in use"));
-    }
-
-    @MainThread
-    private void handleInstallError(String displayText) {
-        showSnackbar(displayText, Snackbar.LENGTH_LONG);
-        setInstallEnabled(true);
-    }
-
-    private static class InstallProgressListener extends IInstallProgressListener.Stub {
-        private final WeakReference<InstallerActivity> mActivity;
-
-        InstallProgressListener(InstallerActivity activity) {
-            mActivity = new WeakReference<>(activity);
-        }
-
-        @Override
-        public void onCompleted() {
-            InstallerActivity activity = mActivity.get();
-            if (activity == null) {
-                // Ignore incoming connection or disconnection after activity is destroyed.
-                return;
-            }
-
-            // MainActivity will be resume and handle rest of progress.
-            activity.finishWithResult(RESULT_OK);
-        }
-
-        @Override
-        public void onError(String displayText) {
-            InstallerActivity context = mActivity.get();
-            if (context == null) {
-                // Ignore incoming connection or disconnection after activity is destroyed.
-                return;
-            }
-
-            context.runOnUiThread(
-                    () -> {
-                        InstallerActivity activity = mActivity.get();
-                        if (activity == null) {
-                            // Ignore incoming connection or disconnection after activity is
-                            // destroyed.
-                            return;
-                        }
-
-                        activity.handleInstallError(displayText);
-                    });
-        }
-    }
-
-    @MainThread
-    public static final class InstallerServiceConnection implements ServiceConnection {
-        private final WeakReference<InstallerActivity> mActivity;
-
-        InstallerServiceConnection(InstallerActivity activity) {
-            mActivity = new WeakReference<>(activity);
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            InstallerActivity activity = mActivity.get();
-            if (activity == null || activity.mInstallerServiceConnection == null) {
-                // Ignore incoming connection or disconnection after activity is destroyed.
-                return;
-            }
-            if (service == null) {
-                activity.handleInternalError(new Exception("service shouldn't be null"));
-            }
-
-            activity.mService = IInstallerService.Stub.asInterface(service);
-            activity.handleInstallerServiceConnected();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            InstallerActivity activity = mActivity.get();
-            if (activity == null || activity.mInstallerServiceConnection == null) {
-                // Ignore incoming connection or disconnection after activity is destroyed.
-                return;
-            }
-
-            if (activity.mInstallerServiceConnection != null) {
-                activity.unbindService(activity.mInstallerServiceConnection);
-                activity.mInstallerServiceConnection = null;
-            }
-            activity.handleInstallerServiceDisconnected();
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.kt
new file mode 100644
index 0000000..ec4a8c4
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.kt
@@ -0,0 +1,288 @@
+/*
+ * 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.
+ */
+package com.android.virtualization.terminal
+
+import android.annotation.MainThread
+import android.content.ComponentName
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Build
+import android.os.Bundle
+import android.os.ConditionVariable
+import android.os.FileUtils
+import android.os.IBinder
+import android.os.RemoteException
+import android.text.format.Formatter
+import android.util.Log
+import android.view.KeyEvent
+import android.view.View
+import android.widget.CheckBox
+import android.widget.TextView
+import com.android.internal.annotations.VisibleForTesting
+import com.android.virtualization.terminal.ImageArchive.Companion.fromSdCard
+import com.android.virtualization.terminal.ImageArchive.Companion.getDefault
+import com.android.virtualization.terminal.InstallerActivity.InstallProgressListener
+import com.android.virtualization.terminal.InstallerActivity.InstallerServiceConnection
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import com.google.android.material.progressindicator.LinearProgressIndicator
+import com.google.android.material.snackbar.Snackbar
+import java.io.IOException
+import java.lang.Exception
+import java.lang.ref.WeakReference
+
+public class InstallerActivity : BaseActivity() {
+    private lateinit var waitForWifiCheckbox: CheckBox
+    private lateinit var installButton: TextView
+
+    private var service: IInstallerService? = null
+    private var installerServiceConnection: ServiceConnection? = null
+    private lateinit var installProgressListener: InstallProgressListener
+    private var installRequested = false
+    private val installCompleted = ConditionVariable()
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setResult(RESULT_CANCELED)
+
+        installProgressListener = InstallProgressListener(this)
+
+        setContentView(R.layout.activity_installer)
+        updateSizeEstimation(ESTIMATED_IMG_SIZE_BYTES)
+        measureImageSizeAndUpdateDescription()
+
+        waitForWifiCheckbox = findViewById<CheckBox>(R.id.installer_wait_for_wifi_checkbox)
+        installButton = findViewById<TextView>(R.id.installer_install_button)
+
+        installButton.setOnClickListener(View.OnClickListener { requestInstall() })
+
+        val intent = Intent(this, InstallerService::class.java)
+        installerServiceConnection = InstallerServiceConnection(this)
+        if (!bindService(intent, installerServiceConnection!!, BIND_AUTO_CREATE)) {
+            handleInternalError(Exception("Failed to connect to installer service"))
+        }
+    }
+
+    private fun updateSizeEstimation(est: Long) {
+        val desc =
+            getString(R.string.installer_desc_text_format, Formatter.formatShortFileSize(this, est))
+        runOnUiThread {
+            val view = findViewById<TextView>(R.id.installer_desc)
+            view.text = desc
+        }
+    }
+
+    private fun measureImageSizeAndUpdateDescription() {
+        Thread {
+                val est: Long =
+                    try {
+                        getDefault().getSize()
+                    } catch (e: IOException) {
+                        Log.w(TAG, "Failed to measure image size.", e)
+                        return@Thread
+                    }
+                updateSizeEstimation(est)
+            }
+            .start()
+    }
+
+    override fun onResume() {
+        super.onResume()
+
+        if (Build.isDebuggable() && fromSdCard().exists()) {
+            showSnackBar("Auto installing", Snackbar.LENGTH_LONG)
+            requestInstall()
+        }
+    }
+
+    public override fun onDestroy() {
+        if (installerServiceConnection != null) {
+            unbindService(installerServiceConnection!!)
+            installerServiceConnection = null
+        }
+
+        super.onDestroy()
+    }
+
+    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
+        if (keyCode == KeyEvent.KEYCODE_BUTTON_START) {
+            requestInstall()
+            return true
+        }
+        return super.onKeyUp(keyCode, event)
+    }
+
+    @VisibleForTesting
+    public fun waitForInstallCompleted(timeoutMillis: Long): Boolean {
+        return installCompleted.block(timeoutMillis)
+    }
+
+    private fun showSnackBar(message: String, length: Int) {
+        val snackBar = Snackbar.make(findViewById<View>(android.R.id.content), message, length)
+        snackBar.anchorView = waitForWifiCheckbox
+        snackBar.show()
+    }
+
+    fun handleInternalError(e: Exception) {
+        if (Build.isDebuggable()) {
+            showSnackBar(
+                e.message + ". File a bugreport to go/ferrochrome-bug",
+                Snackbar.LENGTH_INDEFINITE,
+            )
+        }
+        Log.e(TAG, "Internal error", e)
+        finishWithResult(RESULT_CANCELED)
+    }
+
+    private fun finishWithResult(resultCode: Int) {
+        if (resultCode == RESULT_OK) {
+            installCompleted.open()
+        }
+        setResult(resultCode)
+        finish()
+    }
+
+    private fun setInstallEnabled(enabled: Boolean) {
+        installButton.setEnabled(enabled)
+        waitForWifiCheckbox.setEnabled(enabled)
+        val progressBar = findViewById<LinearProgressIndicator>(R.id.installer_progress)
+        progressBar.visibility = if (enabled) View.INVISIBLE else View.VISIBLE
+
+        val resId =
+            if (enabled) R.string.installer_install_button_enabled_text
+            else R.string.installer_install_button_disabled_text
+        installButton.text = getString(resId)
+    }
+
+    @MainThread
+    private fun requestInstall() {
+        setInstallEnabled(/* enabled= */ false)
+
+        if (service != null) {
+            try {
+                service!!.requestInstall(waitForWifiCheckbox.isChecked)
+            } catch (e: RemoteException) {
+                handleInternalError(e)
+            }
+        } else {
+            Log.d(TAG, "requestInstall() is called, but not yet connected")
+            installRequested = true
+        }
+    }
+
+    @MainThread
+    fun handleInstallerServiceConnected() {
+        try {
+            service!!.setProgressListener(installProgressListener)
+            if (service!!.isInstalled()) {
+                // Finishing this activity will trigger MainActivity::onResume(),
+                // and VM will be started from there.
+                finishWithResult(RESULT_OK)
+                return
+            }
+
+            if (installRequested) {
+                requestInstall()
+            } else if (service!!.isInstalling()) {
+                setInstallEnabled(false)
+            }
+        } catch (e: RemoteException) {
+            handleInternalError(e)
+        }
+    }
+
+    @MainThread
+    fun handleInstallerServiceDisconnected() {
+        handleInternalError(Exception("InstallerService is destroyed while in use"))
+    }
+
+    @MainThread
+    private fun handleInstallError(displayText: String) {
+        showSnackBar(displayText, Snackbar.LENGTH_LONG)
+        setInstallEnabled(true)
+    }
+
+    private class InstallProgressListener(activity: InstallerActivity) :
+        IInstallProgressListener.Stub() {
+        private val activity: WeakReference<InstallerActivity> =
+            WeakReference<InstallerActivity>(activity)
+
+        override fun onCompleted() {
+            val activity = activity.get()
+            if (activity == null) {
+                // Ignore incoming connection or disconnection after activity is destroyed.
+                return
+            }
+
+            // MainActivity will be resume and handle rest of progress.
+            activity.finishWithResult(RESULT_OK)
+        }
+
+        override fun onError(displayText: String) {
+            val context = activity.get()
+            if (context == null) {
+                // Ignore incoming connection or disconnection after activity is destroyed.
+                return
+            }
+
+            context.runOnUiThread {
+                val activity = activity.get()
+                if (activity == null) {
+                    // Ignore incoming connection or disconnection after activity is
+                    // destroyed.
+                    return@runOnUiThread
+                }
+                activity.handleInstallError(displayText)
+            }
+        }
+    }
+
+    @MainThread
+    class InstallerServiceConnection internal constructor(activity: InstallerActivity) :
+        ServiceConnection {
+        private val activity: WeakReference<InstallerActivity> =
+            WeakReference<InstallerActivity>(activity)
+
+        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+            val activity = activity.get()
+            if (activity == null || activity.installerServiceConnection == null) {
+                // Ignore incoming connection or disconnection after activity is destroyed.
+                return
+            }
+            if (service == null) {
+                activity.handleInternalError(Exception("service shouldn't be null"))
+            }
+
+            activity.service = IInstallerService.Stub.asInterface(service)
+            activity.handleInstallerServiceConnected()
+        }
+
+        override fun onServiceDisconnected(name: ComponentName?) {
+            val activity = activity.get()
+            if (activity == null || activity.installerServiceConnection == null) {
+                // Ignore incoming connection or disconnection after activity is destroyed.
+                return
+            }
+
+            activity.unbindService(activity.installerServiceConnection!!)
+            activity.installerServiceConnection = null
+            activity.handleInstallerServiceDisconnected()
+        }
+    }
+
+    companion object {
+        private val ESTIMATED_IMG_SIZE_BYTES = FileUtils.parseSize("550MB")
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
deleted file mode 100644
index 66ab414..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.virtualization.terminal;
-
-import static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.ServiceInfo;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.os.Build;
-import android.os.IBinder;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.nio.file.Path;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class InstallerService extends Service {
-    private static final int NOTIFICATION_ID = 1313; // any unique number among notifications
-
-    private final Object mLock = new Object();
-
-    private Notification mNotification;
-
-    @GuardedBy("mLock")
-    private boolean mIsInstalling;
-
-    @GuardedBy("mLock")
-    private boolean mHasWifi;
-
-    @GuardedBy("mLock")
-    private IInstallProgressListener mListener;
-
-    private ExecutorService mExecutorService;
-    private ConnectivityManager mConnectivityManager;
-    private MyNetworkCallback mNetworkCallback;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        Intent intent = new Intent(this, MainActivity.class);
-        PendingIntent pendingIntent =
-                PendingIntent.getActivity(
-                        this, /* requestCode= */ 0, intent, PendingIntent.FLAG_IMMUTABLE);
-        mNotification =
-                new Notification.Builder(this, this.getPackageName())
-                        .setSilent(true)
-                        .setSmallIcon(R.drawable.ic_launcher_foreground)
-                        .setContentTitle(getString(R.string.installer_notif_title_text))
-                        .setContentText(getString(R.string.installer_notif_desc_text))
-                        .setOngoing(true)
-                        .setContentIntent(pendingIntent)
-                        .build();
-
-        mExecutorService =
-                Executors.newSingleThreadExecutor(
-                        new TerminalThreadFactory(getApplicationContext()));
-
-        mConnectivityManager = getSystemService(ConnectivityManager.class);
-        Network defaultNetwork = mConnectivityManager.getBoundNetworkForProcess();
-        if (defaultNetwork != null) {
-            NetworkCapabilities capability =
-                    mConnectivityManager.getNetworkCapabilities(defaultNetwork);
-            if (capability != null) {
-                mHasWifi = capability.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
-            }
-        }
-        mNetworkCallback = new MyNetworkCallback();
-        mConnectivityManager.registerDefaultNetworkCallback(mNetworkCallback);
-    }
-
-    @Nullable
-    @Override
-    public IBinder onBind(Intent intent) {
-        return new InstallerServiceImpl(this);
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        super.onStartCommand(intent, flags, startId);
-
-        Log.d(TAG, "Starting service ...");
-
-        return START_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-
-        Log.d(TAG, "Service is destroyed");
-        if (mExecutorService != null) {
-            mExecutorService.shutdown();
-        }
-        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-    }
-
-    private void requestInstall(boolean isWifiOnly) {
-        synchronized (mLock) {
-            if (mIsInstalling) {
-                Log.i(TAG, "already installing..");
-                return;
-            } else {
-                Log.i(TAG, "installing..");
-                mIsInstalling = true;
-            }
-        }
-
-        // Make service to be long running, even after unbind() when InstallerActivity is destroyed
-        // The service will still be destroyed if task is remove.
-        startService(new Intent(this, InstallerService.class));
-        startForeground(
-                NOTIFICATION_ID, mNotification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
-
-        mExecutorService.execute(
-                () -> {
-                    boolean success = downloadFromSdcard() || downloadFromUrl(isWifiOnly);
-                    stopForeground(STOP_FOREGROUND_REMOVE);
-
-                    synchronized (mLock) {
-                        mIsInstalling = false;
-                    }
-                    if (success) {
-                        notifyCompleted();
-                    }
-                });
-    }
-
-    private boolean downloadFromSdcard() {
-        ImageArchive archive = ImageArchive.fromSdCard();
-
-        // Installing from sdcard is preferred, but only supported only in debuggable build.
-        if (Build.isDebuggable() && archive.exists()) {
-            Log.i(TAG, "trying to install /sdcard/linux/images.tar.gz");
-
-            Path dest = InstalledImage.getDefault(this).getInstallDir();
-            try {
-                archive.installTo(dest, null);
-                Log.i(TAG, "image is installed from /sdcard/linux/images.tar.gz");
-                return true;
-            } catch (IOException e) {
-                Log.i(TAG, "Failed to install /sdcard/linux/images.tar.gz", e);
-            }
-        } else {
-            Log.i(TAG, "Non-debuggable build doesn't support installation from /sdcard/linux");
-        }
-        return false;
-    }
-
-    private boolean checkForWifiOnly(boolean isWifiOnly) {
-        if (!isWifiOnly) {
-            return true;
-        }
-        synchronized (mLock) {
-            return mHasWifi;
-        }
-    }
-
-    // TODO(b/374015561): Support pause/resume download
-    private boolean downloadFromUrl(boolean isWifiOnly) {
-        if (!checkForWifiOnly(isWifiOnly)) {
-            Log.e(TAG, "Install isn't started because Wifi isn't available");
-            notifyError(getString(R.string.installer_error_no_wifi));
-            return false;
-        }
-
-        Path dest = InstalledImage.getDefault(this).getInstallDir();
-        try {
-            ImageArchive.fromInternet()
-                    .installTo(
-                            dest,
-                            is -> {
-                                WifiCheckInputStream filter = new WifiCheckInputStream(is);
-                                filter.setWifiOnly(isWifiOnly);
-                                return filter;
-                            });
-        } catch (WifiCheckInputStream.NoWifiException e) {
-            Log.e(TAG, "Install failed because of Wi-Fi is gone");
-            notifyError(getString(R.string.installer_error_no_wifi));
-            return false;
-        } catch (UnknownHostException | SocketException e) {
-            // Log.e() doesn't print stack trace for UnknownHostException
-            Log.e(TAG, "Install failed: " + e.getMessage(), e);
-            notifyError(getString(R.string.installer_error_network));
-            return false;
-        } catch (IOException e) {
-            Log.e(TAG, "Installation failed", e);
-            notifyError(getString(R.string.installer_error_unknown));
-            return false;
-        }
-        return true;
-    }
-
-    private void notifyError(String displayText) {
-        IInstallProgressListener listener;
-        synchronized (mLock) {
-            listener = mListener;
-        }
-
-        try {
-            listener.onError(displayText);
-        } catch (Exception e) {
-            // ignore. Activity may not exist.
-        }
-    }
-
-    private void notifyCompleted() {
-        IInstallProgressListener listener;
-        synchronized (mLock) {
-            listener = mListener;
-        }
-
-        try {
-            listener.onCompleted();
-        } catch (Exception e) {
-            // ignore. Activity may not exist.
-        }
-    }
-
-    private static final class InstallerServiceImpl extends IInstallerService.Stub {
-        // Holds weak reference to avoid Context leak
-        private final WeakReference<InstallerService> mService;
-
-        public InstallerServiceImpl(InstallerService service) {
-            mService = new WeakReference<>(service);
-        }
-
-        private InstallerService ensureServiceConnected() throws RuntimeException {
-            InstallerService service = mService.get();
-            if (service == null) {
-                throw new RuntimeException(
-                        "Internal error: Installer service is being accessed after destroyed");
-            }
-            return service;
-        }
-
-        @Override
-        public void requestInstall(boolean isWifiOnly) {
-            InstallerService service = ensureServiceConnected();
-            synchronized (service.mLock) {
-                service.requestInstall(isWifiOnly);
-            }
-        }
-
-        @Override
-        public void setProgressListener(IInstallProgressListener listener) {
-            InstallerService service = ensureServiceConnected();
-            synchronized (service.mLock) {
-                service.mListener = listener;
-            }
-        }
-
-        @Override
-        public boolean isInstalling() {
-            InstallerService service = ensureServiceConnected();
-            synchronized (service.mLock) {
-                return service.mIsInstalling;
-            }
-        }
-
-        @Override
-        public boolean isInstalled() {
-            InstallerService service = ensureServiceConnected();
-            synchronized (service.mLock) {
-                return !service.mIsInstalling && InstalledImage.getDefault(service).isInstalled();
-            }
-        }
-    }
-
-    private final class WifiCheckInputStream extends InputStream {
-        private static final int READ_BYTES = 1024;
-
-        private final InputStream mInputStream;
-        private boolean mIsWifiOnly;
-
-        public WifiCheckInputStream(InputStream is) {
-            super();
-            mInputStream = is;
-        }
-
-        public void setWifiOnly(boolean isWifiOnly) {
-            mIsWifiOnly = isWifiOnly;
-        }
-
-        @Override
-        public int read(byte[] buf, int offset, int numToRead) throws IOException {
-            int totalRead = 0;
-            while (numToRead > 0) {
-                if (!checkForWifiOnly(mIsWifiOnly)) {
-                    throw new NoWifiException();
-                }
-                int read =
-                        mInputStream.read(buf, offset + totalRead, Math.min(READ_BYTES, numToRead));
-                if (read <= 0) {
-                    break;
-                }
-                totalRead += read;
-                numToRead -= read;
-            }
-            return totalRead;
-        }
-
-        @Override
-        public int read() throws IOException {
-            if (!checkForWifiOnly(mIsWifiOnly)) {
-                throw new NoWifiException();
-            }
-            return mInputStream.read();
-        }
-
-        private static final class NoWifiException extends SocketException {
-            // empty
-        }
-    }
-
-    private final class MyNetworkCallback extends ConnectivityManager.NetworkCallback {
-        @Override
-        public void onCapabilitiesChanged(
-                @NonNull Network network, @NonNull NetworkCapabilities capability) {
-            synchronized (mLock) {
-                mHasWifi = capability.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
-            }
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
new file mode 100644
index 0000000..423d66b
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
@@ -0,0 +1,333 @@
+/*
+ * 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.
+ */
+package com.android.virtualization.terminal
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.content.pm.ServiceInfo
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.os.Build
+import android.os.IBinder
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.virtualization.terminal.ImageArchive.Companion.fromInternet
+import com.android.virtualization.terminal.ImageArchive.Companion.fromSdCard
+import com.android.virtualization.terminal.InstalledImage.Companion.getDefault
+import com.android.virtualization.terminal.InstallerService.InstallerServiceImpl
+import com.android.virtualization.terminal.InstallerService.WifiCheckInputStream.NoWifiException
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.io.IOException
+import java.io.InputStream
+import java.lang.Exception
+import java.lang.RuntimeException
+import java.lang.ref.WeakReference
+import java.net.SocketException
+import java.net.UnknownHostException
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import kotlin.math.min
+
+class InstallerService : Service() {
+    private val lock = Any()
+
+    private lateinit var notification: Notification
+
+    @GuardedBy("lock") private var isInstalling = false
+
+    @GuardedBy("lock") private var hasWifi = false
+
+    @GuardedBy("lock") private var listener: IInstallProgressListener? = null
+
+    private lateinit var executorService: ExecutorService
+    private lateinit var connectivityManager: ConnectivityManager
+    private lateinit var networkCallback: MyNetworkCallback
+
+    override fun onCreate() {
+        super.onCreate()
+
+        val intent = Intent(this, MainActivity::class.java)
+        val pendingIntent =
+            PendingIntent.getActivity(
+                this,
+                /* requestCode= */ 0,
+                intent,
+                PendingIntent.FLAG_IMMUTABLE,
+            )
+        notification =
+            Notification.Builder(this, this.packageName)
+                .setSilent(true)
+                .setSmallIcon(R.drawable.ic_launcher_foreground)
+                .setContentTitle(getString(R.string.installer_notif_title_text))
+                .setContentText(getString(R.string.installer_notif_desc_text))
+                .setOngoing(true)
+                .setContentIntent(pendingIntent)
+                .build()
+
+        executorService =
+            Executors.newSingleThreadExecutor(TerminalThreadFactory(applicationContext))
+
+        connectivityManager = getSystemService<ConnectivityManager>(ConnectivityManager::class.java)
+        val defaultNetwork = connectivityManager.boundNetworkForProcess
+        if (defaultNetwork != null) {
+            val capability = connectivityManager.getNetworkCapabilities(defaultNetwork)
+            if (capability != null) {
+                hasWifi = capability.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+            }
+        }
+        networkCallback = MyNetworkCallback()
+        connectivityManager.registerDefaultNetworkCallback(networkCallback)
+    }
+
+    override fun onBind(intent: Intent?): IBinder? {
+        return InstallerServiceImpl(this)
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        super.onStartCommand(intent, flags, startId)
+
+        Log.d(TAG, "Starting service ...")
+
+        return START_STICKY
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        Log.d(TAG, "Service is destroyed")
+        executorService.shutdown()
+        connectivityManager.unregisterNetworkCallback(networkCallback)
+    }
+
+    private fun requestInstall(isWifiOnly: Boolean) {
+        synchronized(lock) {
+            if (isInstalling) {
+                Log.i(TAG, "already installing..")
+                return
+            } else {
+                Log.i(TAG, "installing..")
+                isInstalling = true
+            }
+        }
+
+        // Make service to be long running, even after unbind() when InstallerActivity is destroyed
+        // The service will still be destroyed if task is remove.
+        startService(Intent(this, InstallerService::class.java))
+        startForeground(
+            NOTIFICATION_ID,
+            notification,
+            ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
+        )
+
+        executorService.execute(
+            Runnable {
+                val success = downloadFromSdcard() || downloadFromUrl(isWifiOnly)
+                stopForeground(STOP_FOREGROUND_REMOVE)
+
+                synchronized(lock) { isInstalling = false }
+                if (success) {
+                    notifyCompleted()
+                }
+            }
+        )
+    }
+
+    private fun downloadFromSdcard(): Boolean {
+        val archive = fromSdCard()
+
+        // Installing from sdcard is preferred, but only supported only in debuggable build.
+        if (Build.isDebuggable() && archive.exists()) {
+            Log.i(TAG, "trying to install /sdcard/linux/images.tar.gz")
+
+            val dest = getDefault(this).installDir
+            try {
+                archive.installTo(dest, null)
+                Log.i(TAG, "image is installed from /sdcard/linux/images.tar.gz")
+                return true
+            } catch (e: IOException) {
+                Log.i(TAG, "Failed to install /sdcard/linux/images.tar.gz", e)
+            }
+        } else {
+            Log.i(TAG, "Non-debuggable build doesn't support installation from /sdcard/linux")
+        }
+        return false
+    }
+
+    private fun checkForWifiOnly(isWifiOnly: Boolean): Boolean {
+        if (!isWifiOnly) {
+            return true
+        }
+        synchronized(lock) {
+            return hasWifi
+        }
+    }
+
+    // TODO(b/374015561): Support pause/resume download
+    private fun downloadFromUrl(isWifiOnly: Boolean): Boolean {
+        if (!checkForWifiOnly(isWifiOnly)) {
+            Log.e(TAG, "Install isn't started because Wifi isn't available")
+            notifyError(getString(R.string.installer_error_no_wifi))
+            return false
+        }
+
+        val dest = getDefault(this).installDir
+        try {
+            fromInternet().installTo(dest) {
+                val filter = WifiCheckInputStream(it)
+                filter.setWifiOnly(isWifiOnly)
+                filter
+            }
+        } catch (e: NoWifiException) {
+            Log.e(TAG, "Install failed because of Wi-Fi is gone")
+            notifyError(getString(R.string.installer_error_no_wifi))
+            return false
+        } catch (e: UnknownHostException) {
+            // Log.e() doesn't print stack trace for UnknownHostException
+            Log.e(TAG, "Install failed: " + e.message, e)
+            notifyError(getString(R.string.installer_error_network))
+            return false
+        } catch (e: SocketException) {
+            Log.e(TAG, "Install failed: " + e.message, e)
+            notifyError(getString(R.string.installer_error_network))
+            return false
+        } catch (e: IOException) {
+            Log.e(TAG, "Installation failed", e)
+            notifyError(getString(R.string.installer_error_unknown))
+            return false
+        }
+        return true
+    }
+
+    private fun notifyError(displayText: String?) {
+        var listener: IInstallProgressListener
+        synchronized(lock) { listener = this@InstallerService.listener!! }
+
+        try {
+            listener.onError(displayText)
+        } catch (e: Exception) {
+            // ignore. Activity may not exist.
+        }
+    }
+
+    private fun notifyCompleted() {
+        var listener: IInstallProgressListener
+        synchronized(lock) { listener = this@InstallerService.listener!! }
+
+        try {
+            listener.onCompleted()
+        } catch (e: Exception) {
+            // ignore. Activity may not exist.
+        }
+    }
+
+    private class InstallerServiceImpl(service: InstallerService?) : IInstallerService.Stub() {
+        // Holds weak reference to avoid Context leak
+        private val mService: WeakReference<InstallerService> =
+            WeakReference<InstallerService>(service)
+
+        @Throws(RuntimeException::class)
+        fun ensureServiceConnected(): InstallerService {
+            val service: InstallerService? = mService.get()
+            if (service == null) {
+                throw RuntimeException(
+                    "Internal error: Installer service is being accessed after destroyed"
+                )
+            }
+            return service
+        }
+
+        override fun requestInstall(isWifiOnly: Boolean) {
+            val service = ensureServiceConnected()
+            synchronized(service.lock) { service.requestInstall(isWifiOnly) }
+        }
+
+        override fun setProgressListener(listener: IInstallProgressListener) {
+            val service = ensureServiceConnected()
+            synchronized(service.lock) { service.listener = listener }
+        }
+
+        override fun isInstalling(): Boolean {
+            val service = ensureServiceConnected()
+            synchronized(service.lock) {
+                return service.isInstalling
+            }
+        }
+
+        override fun isInstalled(): Boolean {
+            val service = ensureServiceConnected()
+            synchronized(service.lock) {
+                return !service.isInstalling && getDefault(service).isInstalled()
+            }
+        }
+    }
+
+    private inner class WifiCheckInputStream(private val inputStream: InputStream) : InputStream() {
+        private var isWifiOnly = false
+
+        fun setWifiOnly(isWifiOnly: Boolean) {
+            this@WifiCheckInputStream.isWifiOnly = isWifiOnly
+        }
+
+        @Throws(IOException::class)
+        override fun read(buf: ByteArray?, offset: Int, numToRead: Int): Int {
+            var remaining = numToRead
+            var totalRead = 0
+            while (remaining > 0) {
+                if (!checkForWifiOnly(isWifiOnly)) {
+                    throw NoWifiException()
+                }
+                val read =
+                    this@WifiCheckInputStream.inputStream.read(
+                        buf,
+                        offset + totalRead,
+                        min(READ_BYTES, remaining),
+                    )
+                if (read <= 0) {
+                    break
+                }
+                totalRead += read
+                remaining -= read
+            }
+            return totalRead
+        }
+
+        @Throws(IOException::class)
+        override fun read(): Int {
+            if (!checkForWifiOnly(isWifiOnly)) {
+                throw NoWifiException()
+            }
+            return this@WifiCheckInputStream.inputStream.read()
+        }
+
+        inner class NoWifiException : SocketException()
+    }
+
+    private inner class MyNetworkCallback : ConnectivityManager.NetworkCallback() {
+        override fun onCapabilitiesChanged(network: Network, capability: NetworkCapabilities) {
+            synchronized(lock) {
+                hasWifi = capability.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+            }
+        }
+    }
+
+    companion object {
+        private const val NOTIFICATION_ID = 1313 // any unique number among notifications
+        private const val READ_BYTES = 1024
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.java b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
deleted file mode 100644
index 2c0149e..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
+++ /dev/null
@@ -1,87 +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.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.util.Log;
-
-import libcore.io.Streams;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.concurrent.ExecutorService;
-
-/**
- * Forwards VM's console output to a file on the Android side, and VM's log output to Android logd.
- */
-class Logger {
-    private Logger() {}
-
-    static void setup(VirtualMachine vm, Path path, ExecutorService executor) {
-        if (vm.getConfig().getDebugLevel() != VirtualMachineConfig.DEBUG_LEVEL_FULL) {
-            return;
-        }
-
-        try {
-            InputStream console = vm.getConsoleOutput();
-            OutputStream file = Files.newOutputStream(path, StandardOpenOption.CREATE);
-            executor.submit(() -> Streams.copy(console, new LineBufferedOutputStream(file)));
-
-            InputStream log = vm.getLogOutput();
-            executor.submit(() -> writeToLogd(log, vm.getName()));
-        } catch (VirtualMachineException | IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static boolean writeToLogd(InputStream input, String vmName) throws IOException {
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
-        String line;
-        while ((line = reader.readLine()) != null && !Thread.interrupted()) {
-            Log.d(vmName, line);
-        }
-        // TODO: find out why javac complains when the return type of this method is void. It
-        // (incorrectly?) thinks that IOException should be caught inside the lambda.
-        return true;
-    }
-
-    private static class LineBufferedOutputStream extends BufferedOutputStream {
-        LineBufferedOutputStream(OutputStream out) {
-            super(out);
-        }
-
-        @Override
-        public void write(byte[] buf, int off, int len) throws IOException {
-            super.write(buf, off, len);
-            for (int i = 0; i < len; ++i) {
-                if (buf[off + i] == '\n') {
-                    flush();
-                    break;
-                }
-            }
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
new file mode 100644
index 0000000..547f1a7
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.system.virtualmachine.VirtualMachine
+import android.system.virtualmachine.VirtualMachineConfig
+import android.system.virtualmachine.VirtualMachineException
+import android.util.Log
+import com.android.virtualization.terminal.Logger.LineBufferedOutputStream
+import java.io.BufferedOutputStream
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.io.OutputStream
+import java.lang.RuntimeException
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption
+import java.util.concurrent.ExecutorService
+import libcore.io.Streams
+
+/**
+ * Forwards VM's console output to a file on the Android side, and VM's log output to Android logd.
+ */
+internal object Logger {
+    fun setup(vm: VirtualMachine, path: Path, executor: ExecutorService) {
+        if (vm.config.debugLevel != VirtualMachineConfig.DEBUG_LEVEL_FULL) {
+            return
+        }
+
+        try {
+            val console = vm.getConsoleOutput()
+            val file = Files.newOutputStream(path, StandardOpenOption.CREATE)
+            executor.submit<Int?> {
+                console.use { console ->
+                    LineBufferedOutputStream(file).use { fileOutput ->
+                        Streams.copy(console, fileOutput)
+                    }
+                }
+            }
+
+            val log = vm.getLogOutput()
+            executor.submit<Unit> { log.use { writeToLogd(it, vm.name) } }
+        } catch (e: VirtualMachineException) {
+            throw RuntimeException(e)
+        } catch (e: IOException) {
+            throw RuntimeException(e)
+        }
+    }
+
+    @Throws(IOException::class)
+    private fun writeToLogd(input: InputStream?, vmName: String?) {
+        val reader = BufferedReader(InputStreamReader(input))
+        reader
+            .useLines { lines -> lines.takeWhile { !Thread.interrupted() } }
+            .forEach { Log.d(vmName, it) }
+    }
+
+    private class LineBufferedOutputStream(out: OutputStream?) : BufferedOutputStream(out) {
+        @Throws(IOException::class)
+        override fun write(buf: ByteArray, off: Int, len: Int) {
+            super.write(buf, off, len)
+            (0 until len).firstOrNull { buf[off + it] == '\n'.code.toByte() }?.let { flush() }
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
deleted file mode 100644
index 016af83..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ /dev/null
@@ -1,559 +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 static android.webkit.WebSettings.LOAD_NO_CACHE;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Icon;
-import android.graphics.fonts.FontStyle;
-import android.net.Uri;
-import android.net.http.SslError;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.Environment;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
-import android.webkit.ClientCertRequest;
-import android.webkit.SslErrorHandler;
-import android.webkit.WebChromeClient;
-import android.webkit.WebResourceError;
-import android.webkit.WebResourceRequest;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-
-import androidx.activity.result.ActivityResult;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.microdroid.test.common.DeviceProperties;
-
-import com.google.android.material.appbar.MaterialToolbar;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.security.KeyStore;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class MainActivity extends BaseActivity
-        implements VmLauncherService.VmLauncherServiceCallback, AccessibilityStateChangeListener {
-    static final String TAG = "VmTerminalApp";
-    static final String KEY_DISK_SIZE = "disk_size";
-    private static final String VM_ADDR = "192.168.0.2";
-    private static final int TTYD_PORT = 7681;
-    private static final int TERMINAL_CONNECTION_TIMEOUT_MS;
-    private static final int REQUEST_CODE_INSTALLER = 0x33;
-    private static final int FONT_SIZE_DEFAULT = 13;
-
-    static {
-        DeviceProperties prop = DeviceProperties.create(SystemProperties::get);
-        if (prop.isCuttlefish() || prop.isGoldfish()) {
-            TERMINAL_CONNECTION_TIMEOUT_MS = 180_000; // 3 minutes
-        } else {
-            TERMINAL_CONNECTION_TIMEOUT_MS = 20_000; // 20 sec
-        }
-    }
-
-    private ExecutorService mExecutorService;
-    private InstalledImage mImage;
-    private X509Certificate[] mCertificates;
-    private PrivateKey mPrivateKey;
-    private TerminalView mTerminalView;
-    private AccessibilityManager mAccessibilityManager;
-    private ConditionVariable mBootCompleted = new ConditionVariable();
-    private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
-    private ActivityResultLauncher<Intent> mManageExternalStorageActivityResultLauncher;
-    private static final Map<Integer, Integer> BTN_KEY_CODE_MAP =
-            Map.ofEntries(
-                    Map.entry(R.id.btn_tab, KeyEvent.KEYCODE_TAB),
-                    // Alt key sends ESC keycode
-                    Map.entry(R.id.btn_alt, KeyEvent.KEYCODE_ESCAPE),
-                    Map.entry(R.id.btn_esc, KeyEvent.KEYCODE_ESCAPE),
-                    Map.entry(R.id.btn_left, KeyEvent.KEYCODE_DPAD_LEFT),
-                    Map.entry(R.id.btn_right, KeyEvent.KEYCODE_DPAD_RIGHT),
-                    Map.entry(R.id.btn_up, KeyEvent.KEYCODE_DPAD_UP),
-                    Map.entry(R.id.btn_down, KeyEvent.KEYCODE_DPAD_DOWN),
-                    Map.entry(R.id.btn_home, KeyEvent.KEYCODE_MOVE_HOME),
-                    Map.entry(R.id.btn_end, KeyEvent.KEYCODE_MOVE_END),
-                    Map.entry(R.id.btn_pgup, KeyEvent.KEYCODE_PAGE_UP),
-                    Map.entry(R.id.btn_pgdn, KeyEvent.KEYCODE_PAGE_DOWN));
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        lockOrientationIfNecessary();
-
-        mImage = InstalledImage.getDefault(this);
-
-        boolean launchInstaller = installIfNecessary();
-
-        setContentView(R.layout.activity_headless);
-
-        MaterialToolbar toolbar = (MaterialToolbar) findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
-        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();
-
-        mAccessibilityManager = getSystemService(AccessibilityManager.class);
-        mAccessibilityManager.addAccessibilityStateChangeListener(this);
-
-        readClientCertificate();
-
-        mManageExternalStorageActivityResultLauncher =
-                registerForActivityResult(
-                        new ActivityResultContracts.StartActivityForResult(),
-                        (ActivityResult result) -> {
-                            startVm();
-                        });
-        getWindow()
-                .getDecorView()
-                .getRootView()
-                .setOnApplyWindowInsetsListener(
-                        (v, insets) -> {
-                            updateModifierKeysVisibility();
-                            return insets;
-                        });
-
-        mExecutorService =
-                Executors.newSingleThreadExecutor(
-                        new TerminalThreadFactory(getApplicationContext()));
-
-        // if installer is launched, it will be handled in onActivityResult
-        if (!launchInstaller) {
-            if (!Environment.isExternalStorageManager()) {
-                requestStoragePermissions(this, mManageExternalStorageActivityResultLauncher);
-            } else {
-                startVm();
-            }
-        }
-    }
-
-    private void lockOrientationIfNecessary() {
-        boolean hasHwQwertyKeyboard =
-                getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY;
-        if (hasHwQwertyKeyboard) {
-            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-        } else if (getResources().getBoolean(R.bool.terminal_portrait_only)) {
-            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        }
-    }
-
-    @Override
-    public void onConfigurationChanged(@NonNull Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        lockOrientationIfNecessary();
-        updateModifierKeysVisibility();
-    }
-
-    private void setupModifierKeys() {
-        // Only ctrl key is special, it communicates with xtermjs to modify key event with ctrl key
-        findViewById(R.id.btn_ctrl)
-                .setOnClickListener(
-                        (v) -> {
-                            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());
-                        mTerminalView.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
-                        mTerminalView.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
-                    }
-                };
-
-        for (int btn : BTN_KEY_CODE_MAP.keySet()) {
-            View v = findViewById(btn);
-            if (v != null) {
-                v.setOnClickListener(modifierButtonClickListener);
-            }
-        }
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        if (Build.isDebuggable() && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
-            if (event.getAction() == KeyEvent.ACTION_UP) {
-                ErrorActivity.start(this, new Exception("Debug: KeyEvent.KEYCODE_UNKNOWN"));
-            }
-            return true;
-        }
-        return super.dispatchKeyEvent(event);
-    }
-
-    private void requestStoragePermissions(
-            Context context, ActivityResultLauncher<Intent> activityResultLauncher) {
-        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
-        Uri uri = Uri.fromParts("package", context.getPackageName(), null);
-        intent.setData(uri);
-        activityResultLauncher.launch(intent);
-    }
-
-    private URL getTerminalServiceUrl() {
-        Configuration config = getResources().getConfiguration();
-
-        String query =
-                "?fontSize="
-                        + (int) (config.fontScale * FONT_SIZE_DEFAULT)
-                        + "&fontWeight="
-                        + (FontStyle.FONT_WEIGHT_NORMAL + config.fontWeightAdjustment)
-                        + "&fontWeightBold="
-                        + (FontStyle.FONT_WEIGHT_BOLD + config.fontWeightAdjustment)
-                        + "&screenReaderMode="
-                        + mAccessibilityManager.isEnabled()
-                        + "&titleFixed="
-                        + getString(R.string.app_name);
-
-        try {
-            return new URL("https", VM_ADDR, TTYD_PORT, "/" + query);
-        } catch (MalformedURLException e) {
-            // this cannot happen
-            return null;
-        }
-    }
-
-    private void readClientCertificate() {
-        KeyStore.PrivateKeyEntry pke = CertificateUtils.createOrGetKey();
-        CertificateUtils.writeCertificateToFile(this, pke.getCertificate());
-        mPrivateKey = pke.getPrivateKey();
-        mCertificates = new X509Certificate[1];
-        mCertificates[0] = (X509Certificate) pke.getCertificate();
-    }
-
-    private void connectToTerminalService() {
-        Log.i(TAG, "URL=" + getTerminalServiceUrl().toString());
-        mTerminalView.setWebViewClient(
-                new WebViewClient() {
-                    private boolean mLoadFailed = false;
-                    private long mRequestId = 0;
-
-                    @Override
-                    public boolean shouldOverrideUrlLoading(
-                            WebView view, WebResourceRequest request) {
-                        return false;
-                    }
-
-                    @Override
-                    public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                        mLoadFailed = false;
-                    }
-
-                    @Override
-                    public void onReceivedError(
-                            WebView view, WebResourceRequest request, WebResourceError error) {
-                        mLoadFailed = true;
-                        switch (error.getErrorCode()) {
-                            case WebViewClient.ERROR_CONNECT:
-                            case WebViewClient.ERROR_HOST_LOOKUP:
-                            case WebViewClient.ERROR_FAILED_SSL_HANDSHAKE:
-                            case WebViewClient.ERROR_TIMEOUT:
-                                view.reload();
-                                return;
-                            default:
-                                String url = request.getUrl().toString();
-                                CharSequence msg = error.getDescription();
-                                Log.e(TAG, "Failed to load " + url + ": " + msg);
-                        }
-                    }
-
-                    @Override
-                    public void onPageFinished(WebView view, String url) {
-                        if (mLoadFailed) {
-                            return;
-                        }
-
-                        mRequestId++;
-                        view.postVisualStateCallback(
-                                mRequestId,
-                                new WebView.VisualStateCallback() {
-                                    @Override
-                                    public void onComplete(long requestId) {
-                                        if (requestId == mRequestId) {
-                                            android.os.Trace.endAsyncSection("executeTerminal", 0);
-                                            findViewById(R.id.boot_progress)
-                                                    .setVisibility(View.GONE);
-                                            findViewById(R.id.webview_container)
-                                                    .setVisibility(View.VISIBLE);
-                                            mBootCompleted.open();
-                                            updateModifierKeysVisibility();
-                                            mTerminalView.mapTouchToMouseEvent();
-                                        }
-                                    }
-                                });
-                    }
-
-                    @Override
-                    public void onReceivedClientCertRequest(
-                            WebView view, ClientCertRequest request) {
-                        if (mPrivateKey != null && mCertificates != null) {
-                            request.proceed(mPrivateKey, mCertificates);
-                            return;
-                        }
-                        super.onReceivedClientCertRequest(view, request);
-                    }
-
-                    @Override
-                    public void onReceivedSslError(
-                            WebView view, SslErrorHandler handler, SslError error) {
-                        // ttyd uses self-signed certificate
-                        handler.proceed();
-                    }
-                });
-        mExecutorService.execute(
-                () -> {
-                    // TODO(b/376793781): Remove polling
-                    waitUntilVmStarts();
-                    runOnUiThread(() -> mTerminalView.loadUrl(getTerminalServiceUrl().toString()));
-                });
-    }
-
-    private static void waitUntilVmStarts() {
-        InetAddress addr = null;
-        try {
-            addr = InetAddress.getByName(VM_ADDR);
-        } catch (UnknownHostException e) {
-            // this can never happen.
-        }
-        long startTime = SystemClock.elapsedRealtime();
-        while (true) {
-            int remainingTime =
-                    TERMINAL_CONNECTION_TIMEOUT_MS
-                            - (int) (SystemClock.elapsedRealtime() - startTime);
-
-            if (Thread.interrupted()) {
-                Log.d(TAG, "the waiting thread is interrupted");
-                return;
-            }
-            if (remainingTime <= 0) {
-                throw new RuntimeException("Connection to terminal timeout");
-            }
-            try {
-                // Note: this quits immediately if VM is unreachable.
-                if (addr.isReachable(remainingTime)) {
-                    return;
-                }
-            } catch (IOException e) {
-                // give up on network error
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        if (mExecutorService != null) {
-            mExecutorService.shutdown();
-        }
-
-        getSystemService(AccessibilityManager.class).removeAccessibilityStateChangeListener(this);
-        VmLauncherService.stop(this);
-        super.onDestroy();
-    }
-
-    @Override
-    public void onVmStart() {
-        Log.i(TAG, "onVmStart()");
-    }
-
-    @Override
-    public void onVmStop() {
-        Log.i(TAG, "onVmStop()");
-        finish();
-    }
-
-    @Override
-    public void onVmError() {
-        Log.i(TAG, "onVmError()");
-        // TODO: error cause is too simple.
-        ErrorActivity.start(this, new Exception("onVmError"));
-    }
-
-    @Override
-    public void onIpAddrAvailable(String ipAddr) {
-        // TODO: remove this
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        getMenuInflater().inflate(R.menu.main_menu, menu);
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        int id = item.getItemId();
-        if (id == R.id.menu_item_settings) {
-            Intent intent = new Intent(this, SettingsActivity.class);
-            this.startActivity(intent);
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public void onAccessibilityStateChanged(boolean enabled) {
-        connectToTerminalService();
-    }
-
-    private void updateModifierKeysVisibility() {
-        boolean imeShown =
-                getWindow().getDecorView().getRootWindowInsets().isVisible(WindowInsets.Type.ime());
-        boolean hasHwQwertyKeyboard =
-                getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY;
-        boolean showModifierKeys = imeShown && !hasHwQwertyKeyboard;
-
-        View modifierKeys = findViewById(R.id.modifier_keys);
-        modifierKeys.setVisibility(showModifierKeys ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-
-        if (requestCode == REQUEST_CODE_INSTALLER) {
-            if (resultCode != RESULT_OK) {
-                Log.e(TAG, "Failed to start VM. Installer returned error.");
-                finish();
-            }
-            if (!Environment.isExternalStorageManager()) {
-                requestStoragePermissions(this, mManageExternalStorageActivityResultLauncher);
-            } else {
-                startVm();
-            }
-        }
-    }
-
-    private boolean installIfNecessary() {
-        // If payload from external storage exists(only for debuggable build) or there is no
-        // installed image, launch installer activity.
-        if (!mImage.isInstalled()) {
-            Intent intent = new Intent(this, InstallerActivity.class);
-            startActivityForResult(intent, REQUEST_CODE_INSTALLER);
-            return true;
-        }
-        return false;
-    }
-
-    private void startVm() {
-        InstalledImage image = InstalledImage.getDefault(this);
-        if (!image.isInstalled()) {
-            return;
-        }
-
-        resizeDiskIfNecessary(image);
-
-        Intent tapIntent = new Intent(this, MainActivity.class);
-        tapIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        PendingIntent tapPendingIntent =
-                PendingIntent.getActivity(this, 0, tapIntent, PendingIntent.FLAG_IMMUTABLE);
-
-        Intent settingsIntent = new Intent(this, SettingsActivity.class);
-        settingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        PendingIntent settingsPendingIntent = PendingIntent.getActivity(this, 0, settingsIntent,
-                PendingIntent.FLAG_IMMUTABLE);
-
-        Intent stopIntent = new Intent();
-        stopIntent.setClass(this, VmLauncherService.class);
-        stopIntent.setAction(VmLauncherService.ACTION_STOP_VM_LAUNCHER_SERVICE);
-        PendingIntent stopPendingIntent =
-                PendingIntent.getService(
-                        this,
-                        0,
-                        stopIntent,
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        Icon icon = Icon.createWithResource(getResources(), R.drawable.ic_launcher_foreground);
-        Notification notification =
-                new Notification.Builder(this, this.getPackageName())
-                        .setSilent(true)
-                        .setSmallIcon(R.drawable.ic_launcher_foreground)
-                        .setContentTitle(
-                                getResources().getString(R.string.service_notification_title))
-                        .setContentText(
-                                getResources().getString(R.string.service_notification_content))
-                        .setContentIntent(tapPendingIntent)
-                        .setOngoing(true)
-                        .addAction(
-                                new Notification.Action.Builder(
-                                                icon,
-                                                getResources()
-                                                        .getString(
-                                                                R.string
-                                                                        .service_notification_settings),
-                                                settingsPendingIntent)
-                                        .build())
-                        .addAction(
-                                new Notification.Action.Builder(
-                                                icon,
-                                                getResources()
-                                                        .getString(
-                                                                R.string
-                                                                        .service_notification_quit_action),
-                                                stopPendingIntent)
-                                        .build())
-                        .build();
-
-        android.os.Trace.beginAsyncSection("executeTerminal", 0);
-        VmLauncherService.run(this, this, notification);
-        connectToTerminalService();
-    }
-
-    @VisibleForTesting
-    public boolean waitForBootCompleted(long timeoutMillis) {
-        return mBootCompleted.block(timeoutMillis);
-    }
-
-    private void resizeDiskIfNecessary(InstalledImage image) {
-        try {
-            // TODO(b/382190982): Show snackbar message instead when it's recoverable.
-            image.resize(getIntent().getLongExtra(KEY_DISK_SIZE, image.getSize()));
-        } catch (IOException e) {
-            ErrorActivity.start(this, new Exception("Failed to resize disk", e));
-            return;
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
new file mode 100644
index 0000000..bf2f573
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
@@ -0,0 +1,492 @@
+/*
+ * 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.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.graphics.fonts.FontStyle
+import android.net.Uri
+import android.net.http.SslError
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdServiceInfo
+import android.os.Build
+import android.os.Bundle
+import android.os.ConditionVariable
+import android.os.Environment
+import android.os.SystemProperties
+import android.os.Trace
+import android.provider.Settings
+import android.util.Log
+import android.view.KeyEvent
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityManager
+import android.webkit.ClientCertRequest
+import android.webkit.SslErrorHandler
+import android.webkit.WebChromeClient
+import android.webkit.WebResourceError
+import android.webkit.WebResourceRequest
+import android.webkit.WebSettings
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultCallback
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import com.android.internal.annotations.VisibleForTesting
+import com.android.microdroid.test.common.DeviceProperties
+import com.android.virtualization.terminal.CertificateUtils.createOrGetKey
+import com.android.virtualization.terminal.CertificateUtils.writeCertificateToFile
+import com.android.virtualization.terminal.ErrorActivity.Companion.start
+import com.android.virtualization.terminal.InstalledImage.Companion.getDefault
+import com.android.virtualization.terminal.VmLauncherService.Companion.run
+import com.android.virtualization.terminal.VmLauncherService.Companion.stop
+import com.android.virtualization.terminal.VmLauncherService.VmLauncherServiceCallback
+import com.google.android.material.appbar.MaterialToolbar
+import java.io.IOException
+import java.lang.Exception
+import java.net.MalformedURLException
+import java.net.URL
+import java.security.PrivateKey
+import java.security.cert.X509Certificate
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+public class MainActivity :
+    BaseActivity(),
+    VmLauncherServiceCallback,
+    AccessibilityManager.AccessibilityStateChangeListener {
+    private lateinit var executorService: ExecutorService
+    private lateinit var image: InstalledImage
+    private var certificates: Array<X509Certificate>? = null
+    private var privateKey: PrivateKey? = null
+    private lateinit var terminalContainer: ViewGroup
+    private lateinit var terminalView: TerminalView
+    private lateinit var accessibilityManager: AccessibilityManager
+    private val bootCompleted = ConditionVariable()
+    private lateinit var manageExternalStorageActivityResultLauncher: ActivityResultLauncher<Intent>
+    private lateinit var modifierKeysController: ModifierKeysController
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        lockOrientationIfNecessary()
+
+        image = getDefault(this)
+
+        val launchInstaller = installIfNecessary()
+
+        setContentView(R.layout.activity_headless)
+
+        val toolbar = findViewById<MaterialToolbar>(R.id.toolbar)
+        setSupportActionBar(toolbar)
+        terminalView = findViewById<TerminalView>(R.id.webview)
+        terminalView.getSettings().setDomStorageEnabled(true)
+        terminalView.getSettings().setJavaScriptEnabled(true)
+        terminalView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE)
+        terminalView.setWebChromeClient(WebChromeClient())
+
+        terminalContainer = terminalView.parent as ViewGroup
+
+        modifierKeysController = ModifierKeysController(this, terminalView, terminalContainer)
+
+        accessibilityManager =
+            getSystemService<AccessibilityManager>(AccessibilityManager::class.java)
+        accessibilityManager.addAccessibilityStateChangeListener(this)
+
+        readClientCertificate()
+
+        manageExternalStorageActivityResultLauncher =
+            registerForActivityResult<Intent, ActivityResult>(
+                StartActivityForResult(),
+                ActivityResultCallback { startVm() },
+            )
+        executorService =
+            Executors.newSingleThreadExecutor(TerminalThreadFactory(applicationContext))
+
+        // if installer is launched, it will be handled in onActivityResult
+        if (!launchInstaller) {
+            if (!Environment.isExternalStorageManager()) {
+                requestStoragePermissions(this, manageExternalStorageActivityResultLauncher)
+            } else {
+                startVm()
+            }
+        }
+    }
+
+    private fun lockOrientationIfNecessary() {
+        val hasHwQwertyKeyboard = resources.configuration.keyboard == Configuration.KEYBOARD_QWERTY
+        if (hasHwQwertyKeyboard) {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
+        } else if (resources.getBoolean(R.bool.terminal_portrait_only)) {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+        }
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        lockOrientationIfNecessary()
+        modifierKeysController.update()
+    }
+
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (Build.isDebuggable() && event.keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+            if (event.action == KeyEvent.ACTION_UP) {
+                start(this, Exception("Debug: KeyEvent.KEYCODE_UNKNOWN"))
+            }
+            return true
+        }
+        return super.dispatchKeyEvent(event)
+    }
+
+    private fun requestStoragePermissions(
+        context: Context,
+        activityResultLauncher: ActivityResultLauncher<Intent>,
+    ) {
+        val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
+        val uri = Uri.fromParts("package", context.getPackageName(), null)
+        intent.setData(uri)
+        activityResultLauncher.launch(intent)
+    }
+
+    private fun getTerminalServiceUrl(ipAddress: String?, port: Int): URL? {
+        val config = resources.configuration
+
+        val query =
+            ("?fontSize=" +
+                (config.fontScale * FONT_SIZE_DEFAULT).toInt() +
+                "&fontWeight=" +
+                (FontStyle.FONT_WEIGHT_NORMAL + config.fontWeightAdjustment) +
+                "&fontWeightBold=" +
+                (FontStyle.FONT_WEIGHT_BOLD + config.fontWeightAdjustment) +
+                "&screenReaderMode=" +
+                accessibilityManager.isEnabled +
+                "&titleFixed=" +
+                getString(R.string.app_name))
+
+        try {
+            return URL("https", ipAddress, port, "/$query")
+        } catch (e: MalformedURLException) {
+            // this cannot happen
+            return null
+        }
+    }
+
+    private fun readClientCertificate() {
+        val pke = createOrGetKey()
+        writeCertificateToFile(this, pke.certificate)
+        privateKey = pke.privateKey
+        certificates = arrayOf<X509Certificate>(pke.certificate as X509Certificate)
+    }
+
+    private fun connectToTerminalService() {
+        terminalView.setWebViewClient(
+            object : WebViewClient() {
+                private var loadFailed = false
+                private var requestId: Long = 0
+
+                override fun shouldOverrideUrlLoading(
+                    view: WebView?,
+                    request: WebResourceRequest?,
+                ): Boolean {
+                    return false
+                }
+
+                override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
+                    loadFailed = false
+                }
+
+                override fun onReceivedError(
+                    view: WebView,
+                    request: WebResourceRequest,
+                    error: WebResourceError,
+                ) {
+                    loadFailed = true
+                    when (error.getErrorCode()) {
+                        ERROR_CONNECT,
+                        ERROR_HOST_LOOKUP,
+                        ERROR_FAILED_SSL_HANDSHAKE,
+                        ERROR_TIMEOUT -> {
+                            view.reload()
+                            return
+                        }
+
+                        else -> {
+                            val url: String? = request.getUrl().toString()
+                            val msg = error.getDescription()
+                            Log.e(TAG, "Failed to load $url: $msg")
+                        }
+                    }
+                }
+
+                override fun onPageFinished(view: WebView, url: String?) {
+                    if (loadFailed) {
+                        return
+                    }
+
+                    requestId++
+                    view.postVisualStateCallback(
+                        requestId,
+                        object : WebView.VisualStateCallback() {
+                            override fun onComplete(completedRequestId: Long) {
+                                if (completedRequestId == requestId) {
+                                    Trace.endAsyncSection("executeTerminal", 0)
+                                    findViewById<View?>(R.id.boot_progress).visibility = View.GONE
+                                    terminalContainer.visibility = View.VISIBLE
+                                    bootCompleted.open()
+                                    modifierKeysController.update()
+                                    terminalView.mapTouchToMouseEvent()
+                                }
+                            }
+                        },
+                    )
+                }
+
+                override fun onReceivedClientCertRequest(
+                    view: WebView?,
+                    request: ClientCertRequest,
+                ) {
+                    if (privateKey != null && certificates != null) {
+                        request.proceed(privateKey, certificates)
+                        return
+                    }
+                    super.onReceivedClientCertRequest(view, request)
+                }
+
+                override fun onReceivedSslError(
+                    view: WebView?,
+                    handler: SslErrorHandler,
+                    error: SslError?,
+                ) {
+                    // ttyd uses self-signed certificate
+                    handler.proceed()
+                }
+            }
+        )
+
+        // TODO: refactor this block as a method
+        val nsdManager = getSystemService<NsdManager>(NsdManager::class.java)
+        val info = NsdServiceInfo()
+        info.serviceType = "_http._tcp"
+        info.serviceName = "ttyd"
+        nsdManager.registerServiceInfoCallback(
+            info,
+            executorService,
+            object : NsdManager.ServiceInfoCallback {
+                override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) {}
+
+                override fun onServiceInfoCallbackUnregistered() {}
+
+                override fun onServiceLost() {}
+
+                override fun onServiceUpdated(info: NsdServiceInfo) {
+                    nsdManager.unregisterServiceInfoCallback(this)
+
+                    Log.i(TAG, "Service found: $info")
+                    val ipAddress = info.hostAddresses[0].hostAddress
+                    val port = info.port
+                    val url = getTerminalServiceUrl(ipAddress, port)
+                    runOnUiThread(Runnable { terminalView.loadUrl(url.toString()) })
+                }
+            },
+        )
+    }
+
+    override fun onDestroy() {
+        executorService.shutdown()
+        getSystemService<AccessibilityManager>(AccessibilityManager::class.java)
+            .removeAccessibilityStateChangeListener(this)
+        stop(this)
+        super.onDestroy()
+    }
+
+    override fun onVmStart() {
+        Log.i(TAG, "onVmStart()")
+    }
+
+    override fun onVmStop() {
+        Log.i(TAG, "onVmStop()")
+        finish()
+    }
+
+    override fun onVmError() {
+        Log.i(TAG, "onVmError()")
+        // TODO: error cause is too simple.
+        start(this, Exception("onVmError"))
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+        menuInflater.inflate(R.menu.main_menu, menu)
+        return true
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        val id = item.getItemId()
+        if (id == R.id.menu_item_settings) {
+            val intent = Intent(this, SettingsActivity::class.java)
+            this.startActivity(intent)
+            return true
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
+    override fun onAccessibilityStateChanged(enabled: Boolean) {
+        connectToTerminalService()
+    }
+
+    private val installerLauncher =
+        registerForActivityResult(StartActivityForResult()) { result ->
+            val resultCode = result.resultCode
+            if (resultCode != RESULT_OK) {
+                Log.e(TAG, "Failed to start VM. Installer returned error.")
+                finish()
+            }
+            if (!Environment.isExternalStorageManager()) {
+                requestStoragePermissions(this, manageExternalStorageActivityResultLauncher)
+            } else {
+                startVm()
+            }
+        }
+
+    private fun installIfNecessary(): Boolean {
+        // If payload from external storage exists(only for debuggable build) or there is no
+        // installed image, launch installer activity.
+        if (!image.isInstalled()) {
+            val intent = Intent(this, InstallerActivity::class.java)
+            installerLauncher.launch(intent)
+            return true
+        }
+        return false
+    }
+
+    private fun startVm() {
+        val image = getDefault(this)
+        if (!image.isInstalled()) {
+            return
+        }
+
+        resizeDiskIfNecessary(image)
+
+        val tapIntent = Intent(this, MainActivity::class.java)
+        tapIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        val tapPendingIntent =
+            PendingIntent.getActivity(this, 0, tapIntent, PendingIntent.FLAG_IMMUTABLE)
+
+        val settingsIntent = Intent(this, SettingsActivity::class.java)
+        settingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        val settingsPendingIntent =
+            PendingIntent.getActivity(this, 0, settingsIntent, PendingIntent.FLAG_IMMUTABLE)
+
+        val stopIntent = Intent()
+        stopIntent.setClass(this, VmLauncherService::class.java)
+        stopIntent.setAction(VmLauncherService.ACTION_STOP_VM_LAUNCHER_SERVICE)
+        val stopPendingIntent =
+            PendingIntent.getService(
+                this,
+                0,
+                stopIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
+            )
+        val icon = Icon.createWithResource(resources, R.drawable.ic_launcher_foreground)
+        val notification: Notification =
+            Notification.Builder(this, this.packageName)
+                .setSilent(true)
+                .setSmallIcon(R.drawable.ic_launcher_foreground)
+                .setContentTitle(resources.getString(R.string.service_notification_title))
+                .setContentText(resources.getString(R.string.service_notification_content))
+                .setContentIntent(tapPendingIntent)
+                .setOngoing(true)
+                .addAction(
+                    Notification.Action.Builder(
+                            icon,
+                            resources.getString(R.string.service_notification_settings),
+                            settingsPendingIntent,
+                        )
+                        .build()
+                )
+                .addAction(
+                    Notification.Action.Builder(
+                            icon,
+                            resources.getString(R.string.service_notification_quit_action),
+                            stopPendingIntent,
+                        )
+                        .build()
+                )
+                .build()
+
+        Trace.beginAsyncSection("executeTerminal", 0)
+        run(this, this, notification)
+        connectToTerminalService()
+    }
+
+    @VisibleForTesting
+    public fun waitForBootCompleted(timeoutMillis: Long): Boolean {
+        return bootCompleted.block(timeoutMillis)
+    }
+
+    private fun resizeDiskIfNecessary(image: InstalledImage) {
+        try {
+            // TODO(b/382190982): Show snackbar message instead when it's recoverable.
+            image.resize(intent.getLongExtra(KEY_DISK_SIZE, image.getSize()))
+        } catch (e: IOException) {
+            start(this, Exception("Failed to resize disk", e))
+            return
+        }
+    }
+
+    companion object {
+        const val TAG: String = "VmTerminalApp"
+        const val KEY_DISK_SIZE: String = "disk_size"
+        private val TERMINAL_CONNECTION_TIMEOUT_MS: Int
+        private const val REQUEST_CODE_INSTALLER = 0x33
+        private const val FONT_SIZE_DEFAULT = 13
+
+        init {
+            val prop =
+                DeviceProperties.create(
+                    DeviceProperties.PropertyGetter { key: String -> SystemProperties.get(key) }
+                )
+            TERMINAL_CONNECTION_TIMEOUT_MS =
+                if (prop.isCuttlefish() || prop.isGoldfish()) {
+                    180000 // 3 minutes
+                } else {
+                    20000 // 20 sec
+                }
+        }
+
+        private val BTN_KEY_CODE_MAP =
+            mapOf(
+                R.id.btn_tab to KeyEvent.KEYCODE_TAB, // Alt key sends ESC keycode
+                R.id.btn_alt to KeyEvent.KEYCODE_ESCAPE,
+                R.id.btn_esc to KeyEvent.KEYCODE_ESCAPE,
+                R.id.btn_left to KeyEvent.KEYCODE_DPAD_LEFT,
+                R.id.btn_right to KeyEvent.KEYCODE_DPAD_RIGHT,
+                R.id.btn_up to KeyEvent.KEYCODE_DPAD_UP,
+                R.id.btn_down to KeyEvent.KEYCODE_DPAD_DOWN,
+                R.id.btn_home to KeyEvent.KEYCODE_MOVE_HOME,
+                R.id.btn_end to KeyEvent.KEYCODE_MOVE_END,
+                R.id.btn_pgup to KeyEvent.KEYCODE_PAGE_UP,
+                R.id.btn_pgdn to KeyEvent.KEYCODE_PAGE_DOWN,
+            )
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ModifierKeysController.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ModifierKeysController.kt
new file mode 100644
index 0000000..f8f30f9
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ModifierKeysController.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2025 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.app.Activity
+import android.content.res.Configuration
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+
+class ModifierKeysController(
+    val activity: Activity,
+    val terminalView: TerminalView,
+    val parent: ViewGroup,
+) {
+    private val window = activity.window
+    private val keysSingleLine: View
+    private val keysDoubleLine: View
+
+    private var keysInSingleLine: Boolean = false
+
+    init {
+        // Prepare the two modifier keys layout, but don't add them yet because we don't know which
+        // layout will be needed.
+        val layout = LayoutInflater.from(activity)
+        keysSingleLine = layout.inflate(R.layout.modifier_keys_singleline, parent, false)
+        keysDoubleLine = layout.inflate(R.layout.modifier_keys_doubleline, parent, false)
+
+        addClickListeners(keysSingleLine)
+        addClickListeners(keysDoubleLine)
+
+        keysSingleLine.visibility = View.GONE
+        keysDoubleLine.visibility = View.GONE
+
+        // Setup for the update to be called when needed
+        window.decorView.rootView.setOnApplyWindowInsetsListener { _: View?, insets: WindowInsets ->
+            update()
+            insets
+        }
+
+        terminalView.setOnFocusChangeListener { _: View, _: Boolean -> update() }
+    }
+
+    private fun addClickListeners(keys: View) {
+        // Only ctrl key is special, it communicates with xtermjs to modify key event with ctrl key
+        keys
+            .findViewById<View>(R.id.btn_ctrl)
+            .setOnClickListener({
+                terminalView.mapCtrlKey()
+                terminalView.enableCtrlKey()
+            })
+
+        val listener =
+            View.OnClickListener { v: View ->
+                BTN_KEY_CODE_MAP[v.id]?.also { keyCode ->
+                    terminalView.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
+                    terminalView.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, keyCode))
+                }
+            }
+
+        for (btn in BTN_KEY_CODE_MAP.keys) {
+            keys.findViewById<View>(btn).setOnClickListener(listener)
+        }
+    }
+
+    fun update() {
+        // select single line or double line
+        val needSingleLine = needsKeysInSingleLine()
+        if (keysInSingleLine != needSingleLine) {
+            if (needSingleLine) {
+                parent.removeView(keysDoubleLine)
+                parent.addView(keysSingleLine)
+            } else {
+                parent.removeView(keysSingleLine)
+                parent.addView(keysDoubleLine)
+            }
+            keysInSingleLine = needSingleLine
+        }
+
+        // set visibility
+        val needShow = needToShowKeys()
+        val keys = if (keysInSingleLine) keysSingleLine else keysDoubleLine
+        keys.visibility = if (needShow) View.VISIBLE else View.GONE
+    }
+
+    // Modifier keys are required only when IME is shown and the HW qwerty keyboard is not present
+    private fun needToShowKeys(): Boolean {
+        val imeShown = activity.window.decorView.rootWindowInsets.isVisible(WindowInsets.Type.ime())
+        val hasFocus = terminalView.hasFocus()
+        val hasHwQwertyKeyboard =
+            activity.resources.configuration.keyboard == Configuration.KEYBOARD_QWERTY
+        return imeShown && hasFocus && !hasHwQwertyKeyboard
+    }
+
+    // If terminal's height is less than 30% of the screen height, we need to show modifier keys in
+    // a single line to save the vertical space
+    private fun needsKeysInSingleLine(): Boolean =
+        (terminalView.height / activity.window.decorView.height.toFloat()) < 0.3f
+
+    companion object {
+        private val BTN_KEY_CODE_MAP =
+            mapOf(
+                R.id.btn_tab to KeyEvent.KEYCODE_TAB, // Alt key sends ESC keycode
+                R.id.btn_alt to KeyEvent.KEYCODE_ESCAPE,
+                R.id.btn_esc to KeyEvent.KEYCODE_ESCAPE,
+                R.id.btn_left to KeyEvent.KEYCODE_DPAD_LEFT,
+                R.id.btn_right to KeyEvent.KEYCODE_DPAD_RIGHT,
+                R.id.btn_up to KeyEvent.KEYCODE_DPAD_UP,
+                R.id.btn_down to KeyEvent.KEYCODE_DPAD_DOWN,
+                R.id.btn_home to KeyEvent.KEYCODE_MOVE_HOME,
+                R.id.btn_end to KeyEvent.KEYCODE_MOVE_END,
+                R.id.btn_pgup to KeyEvent.KEYCODE_PAGE_UP,
+                R.id.btn_pgdn to KeyEvent.KEYCODE_PAGE_DOWN,
+            )
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java b/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java
deleted file mode 100644
index 0d70ab9..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java
+++ /dev/null
@@ -1,146 +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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.drawable.Icon;
-
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * PortNotifier is responsible for posting a notification when a new open port is detected. User can
- * enable or disable forwarding of the port in notification panel.
- */
-class PortNotifier {
-    private static final String ACTION_PORT_FORWARDING = "android.virtualization.PORT_FORWARDING";
-    private static final String KEY_PORT = "port";
-    private static final String KEY_ENABLED = "enabled";
-
-    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 stop() {
-        mPortsStateManager.unregisterListener(mPortsStateListener);
-        mContext.unregisterReceiver(mReceiver);
-    }
-
-    private String getString(int resId) {
-        return mContext.getString(resId);
-    }
-
-    private PendingIntent getPendingIntentFor(int port, boolean enabled) {
-        Intent intent = new Intent(ACTION_PORT_FORWARDING);
-        intent.setPackage(mContext.getPackageName());
-        intent.setIdentifier(String.format(Locale.ROOT, "%d_%b", port, enabled));
-        intent.putExtra(KEY_PORT, port);
-        intent.putExtra(KEY_ENABLED, enabled);
-        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
-    }
-
-    private void showNotificationFor(int port) {
-        Intent tapIntent = new Intent(mContext, SettingsPortForwardingActivity.class);
-        tapIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        PendingIntent tapPendingIntent =
-                PendingIntent.getActivity(mContext, 0, tapIntent, PendingIntent.FLAG_IMMUTABLE);
-
-        String title = getString(R.string.settings_port_forwarding_notification_title);
-        String content =
-                mContext.getString(R.string.settings_port_forwarding_notification_content, port);
-        String acceptText = getString(R.string.settings_port_forwarding_notification_accept);
-        String denyText = getString(R.string.settings_port_forwarding_notification_deny);
-        Icon icon = Icon.createWithResource(mContext, R.drawable.ic_launcher_foreground);
-
-        Action acceptAction =
-                new Action.Builder(icon, acceptText, getPendingIntentFor(port, true /* enabled */))
-                        .build();
-        Action denyAction =
-                new Action.Builder(icon, denyText, getPendingIntentFor(port, false /* enabled */))
-                        .build();
-        Notification notification =
-                new Notification.Builder(mContext, mContext.getPackageName())
-                        .setSmallIcon(R.drawable.ic_launcher_foreground)
-                        .setContentTitle(title)
-                        .setContentText(content)
-                        .setContentIntent(tapPendingIntent)
-                        .addAction(acceptAction)
-                        .addAction(denyAction)
-                        .build();
-        mNotificationManager.notify(TAG, port, notification);
-    }
-
-    private void discardNotificationFor(int port) {
-        mNotificationManager.cancel(TAG, port);
-    }
-
-    private final class PortForwardingRequestReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (ACTION_PORT_FORWARDING.equals(intent.getAction())) {
-                performActionPortForwarding(context, intent);
-            }
-        }
-
-        private void performActionPortForwarding(Context context, Intent intent) {
-            int port = intent.getIntExtra(KEY_PORT, 0);
-            boolean enabled = intent.getBooleanExtra(KEY_ENABLED, false);
-            mPortsStateManager.updateEnabledPort(port, enabled);
-            discardNotificationFor(port);
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.kt b/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.kt
new file mode 100644
index 0000000..7c48303
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.drawable.Icon
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.util.Locale
+
+/**
+ * PortNotifier is responsible for posting a notification when a new open port is detected. User can
+ * enable or disable forwarding of the port in notification panel.
+ */
+internal class PortNotifier(val context: Context) {
+    private val notificationManager: NotificationManager =
+        context.getSystemService<NotificationManager>(NotificationManager::class.java)
+    private val receiver: BroadcastReceiver =
+        PortForwardingRequestReceiver().also {
+            val intentFilter = IntentFilter(ACTION_PORT_FORWARDING)
+            context.registerReceiver(it, intentFilter, Context.RECEIVER_NOT_EXPORTED)
+        }
+    private val portsStateListener: PortsStateManager.Listener =
+        object : PortsStateManager.Listener {
+            override fun onPortsStateUpdated(oldActivePorts: Set<Int>, newActivePorts: Set<Int>) {
+                // added active ports
+                (newActivePorts - oldActivePorts).forEach { showNotificationFor(it) }
+                // removed active ports
+                (oldActivePorts - newActivePorts).forEach { discardNotificationFor(it) }
+            }
+        }
+    private val portsStateManager: PortsStateManager =
+        PortsStateManager.getInstance(context).also { it.registerListener(portsStateListener) }
+
+    fun stop() {
+        portsStateManager.unregisterListener(portsStateListener)
+        context.unregisterReceiver(receiver)
+    }
+
+    private fun getString(resId: Int): String {
+        return context.getString(resId)
+    }
+
+    private fun getPendingIntentFor(port: Int, enabled: Boolean): PendingIntent {
+        val intent = Intent(ACTION_PORT_FORWARDING)
+        intent.setPackage(context.getPackageName())
+        intent.setIdentifier(String.format(Locale.ROOT, "%d_%b", port, enabled))
+        intent.putExtra(KEY_PORT, port)
+        intent.putExtra(KEY_ENABLED, enabled)
+        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+    }
+
+    private fun showNotificationFor(port: Int) {
+        val tapIntent = Intent(context, SettingsPortForwardingActivity::class.java)
+        tapIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        val tapPendingIntent =
+            PendingIntent.getActivity(context, 0, tapIntent, PendingIntent.FLAG_IMMUTABLE)
+
+        val title = getString(R.string.settings_port_forwarding_notification_title)
+        val content =
+            context.getString(
+                R.string.settings_port_forwarding_notification_content,
+                port,
+                portsStateManager.getActivePortInfo(port)?.comm,
+            )
+        val acceptText = getString(R.string.settings_port_forwarding_notification_accept)
+        val denyText = getString(R.string.settings_port_forwarding_notification_deny)
+        val icon = Icon.createWithResource(context, R.drawable.ic_launcher_foreground)
+
+        val acceptAction: Notification.Action =
+            Notification.Action.Builder(
+                    icon,
+                    acceptText,
+                    getPendingIntentFor(port, true /* enabled */),
+                )
+                .build()
+        val denyAction: Notification.Action =
+            Notification.Action.Builder(
+                    icon,
+                    denyText,
+                    getPendingIntentFor(port, false /* enabled */),
+                )
+                .build()
+        val notification: Notification =
+            Notification.Builder(context, context.getPackageName())
+                .setSmallIcon(R.drawable.ic_launcher_foreground)
+                .setContentTitle(title)
+                .setContentText(content)
+                .setFullScreenIntent(tapPendingIntent, true)
+                .addAction(acceptAction)
+                .addAction(denyAction)
+                .setAutoCancel(true)
+                .build()
+        notificationManager.notify(TAG, port, notification)
+    }
+
+    private fun discardNotificationFor(port: Int) {
+        notificationManager.cancel(TAG, port)
+    }
+
+    private inner class PortForwardingRequestReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent) {
+            if (ACTION_PORT_FORWARDING == intent.action) {
+                performActionPortForwarding(intent)
+            }
+        }
+
+        fun performActionPortForwarding(intent: Intent) {
+            val port = intent.getIntExtra(KEY_PORT, 0)
+            val enabled = intent.getBooleanExtra(KEY_ENABLED, false)
+            portsStateManager.updateEnabledPort(port, enabled)
+            discardNotificationFor(port)
+        }
+    }
+
+    companion object {
+        private const val ACTION_PORT_FORWARDING = "android.virtualization.PORT_FORWARDING"
+        private const val KEY_PORT = "port"
+        private const val KEY_ENABLED = "enabled"
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.java b/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.java
deleted file mode 100644
index 5321d89..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.java
+++ /dev/null
@@ -1,158 +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.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 clearEnabledPorts() {
-        Set<Integer> activePorts;
-        synchronized (mLock) {
-            SharedPreferences.Editor editor = mSharedPref.edit();
-            editor.clear();
-            editor.apply();
-            mEnabledPorts.clear();
-            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/PortsStateManager.kt b/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.kt
new file mode 100644
index 0000000..20bccc2
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/PortsStateManager.kt
@@ -0,0 +1,137 @@
+/*
+ * 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 com.android.virtualization.terminal.proto.ActivePort
+import java.util.HashSet
+
+/**
+ * PortsStateManager is responsible for communicating with shared preferences and managing state of
+ * ports.
+ */
+class PortsStateManager private constructor(private val sharedPref: SharedPreferences) {
+    private val lock = Any()
+
+    @GuardedBy("lock") private val activePorts: MutableMap<Int, ActivePort> = hashMapOf()
+
+    @GuardedBy("lock")
+    private val enabledPorts: MutableSet<Int> =
+        sharedPref
+            .getAll()
+            .entries
+            .filterIsInstance<MutableMap.MutableEntry<String, Int>>()
+            .filter { it.value and FLAG_ENABLED == FLAG_ENABLED }
+            .map { it.key.toIntOrNull() }
+            .filterNotNull()
+            .toMutableSet()
+
+    @GuardedBy("lock") private val listeners: MutableSet<Listener> = hashSetOf()
+
+    fun getActivePorts(): Set<Int> {
+        synchronized(lock) {
+            return HashSet<Int>(activePorts.keys)
+        }
+    }
+
+    fun getActivePortInfo(port: Int): ActivePort? {
+        synchronized(lock) {
+            return activePorts[port]
+        }
+    }
+
+    fun getEnabledPorts(): Set<Int> {
+        synchronized(lock) {
+            return HashSet<Int>(enabledPorts)
+        }
+    }
+
+    fun updateActivePorts(ports: List<ActivePort>) {
+        val oldPorts = getActivePorts()
+        synchronized(lock) {
+            activePorts.clear()
+            activePorts.putAll(ports.associateBy { it.port })
+        }
+        notifyPortsStateUpdated(oldPorts, getActivePorts())
+    }
+
+    fun updateEnabledPort(port: Int, enabled: Boolean) {
+        synchronized(lock) {
+            val editor = sharedPref.edit()
+            editor.putInt(port.toString(), if (enabled) FLAG_ENABLED else 0)
+            editor.apply()
+            if (enabled) {
+                enabledPorts.add(port)
+            } else {
+                enabledPorts.remove(port)
+            }
+        }
+        notifyPortsStateUpdated(getActivePorts(), getActivePorts())
+    }
+
+    fun clearEnabledPorts() {
+        synchronized(lock) {
+            val editor = sharedPref.edit()
+            editor.clear()
+            editor.apply()
+            enabledPorts.clear()
+        }
+        notifyPortsStateUpdated(getActivePorts(), getActivePorts())
+    }
+
+    fun registerListener(listener: Listener) {
+        synchronized(lock) { listeners.add(listener) }
+    }
+
+    fun unregisterListener(listener: Listener) {
+        synchronized(lock) { listeners.remove(listener) }
+    }
+
+    // TODO: it notifies when both enabledPort and activePort are changed, but doesn't provide
+    // enabledPort's value change. Make this callback provide that information as well.
+    private fun notifyPortsStateUpdated(oldActivePorts: Set<Int>, newActivePorts: Set<Int>) {
+        synchronized(lock) { HashSet<Listener>(this@PortsStateManager.listeners) }
+            .forEach {
+                it.onPortsStateUpdated(HashSet<Int>(oldActivePorts), HashSet<Int>(newActivePorts))
+            }
+    }
+
+    interface Listener {
+        fun onPortsStateUpdated(oldActivePorts: Set<Int>, newActivePorts: Set<Int>) {}
+    }
+
+    companion object {
+        private const val PREFS_NAME = ".PORTS"
+        private const val FLAG_ENABLED = 1
+
+        private var instance: PortsStateManager? = null
+
+        @Synchronized
+        fun getInstance(context: Context): PortsStateManager {
+            if (instance == null) {
+                val sharedPref =
+                    context.getSharedPreferences(
+                        context.getPackageName() + PREFS_NAME,
+                        Context.MODE_PRIVATE,
+                    )
+                instance = PortsStateManager(sharedPref)
+            }
+            return instance!!
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
deleted file mode 100644
index 4094025..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
+++ /dev/null
@@ -1,115 +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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.content.Context;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineCallback;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.system.virtualmachine.VirtualMachineManager;
-import android.util.Log;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ForkJoinPool;
-
-/** Utility class for creating a VM and waiting for it to finish. */
-class Runner {
-    private final VirtualMachine mVirtualMachine;
-    private final Callback mCallback;
-
-    private Runner(VirtualMachine vm, Callback cb) {
-        mVirtualMachine = vm;
-        mCallback = cb;
-    }
-
-    /** Create a virtual machine of the given config, under the given context. */
-    static Runner create(Context context, VirtualMachineConfig config)
-            throws VirtualMachineException {
-        // context may already be the app context, but calling this again is not harmful.
-        // See b/359439878 on why vmm should be obtained from the app context.
-        Context appContext = context.getApplicationContext();
-        VirtualMachineManager vmm = appContext.getSystemService(VirtualMachineManager.class);
-        VirtualMachineCustomImageConfig customConfig = config.getCustomImageConfig();
-        if (customConfig == null) {
-            throw new RuntimeException("CustomImageConfig is missing");
-        }
-
-        String name = customConfig.getName();
-        if (name == null || name.isEmpty()) {
-            throw new RuntimeException("Virtual machine's name is missing in the config");
-        }
-
-        VirtualMachine vm = vmm.getOrCreate(name, config);
-        try {
-            vm.setConfig(config);
-        } catch (VirtualMachineException e) {
-            vmm.delete(name);
-            vm = vmm.create(name, config);
-            Log.w(TAG, "Re-creating virtual machine (" + name + ")", e);
-        }
-
-        Callback cb = new Callback();
-        vm.setCallback(ForkJoinPool.commonPool(), cb);
-        vm.run();
-        return new Runner(vm, cb);
-    }
-
-    /** Give access to the underlying VirtualMachine object. */
-    VirtualMachine getVm() {
-        return mVirtualMachine;
-    }
-
-    /** Get future about VM's exit status. */
-    CompletableFuture<Boolean> getExitStatus() {
-        return mCallback.mFinishedSuccessfully;
-    }
-
-    private static class Callback implements VirtualMachineCallback {
-        final CompletableFuture<Boolean> mFinishedSuccessfully = new CompletableFuture<>();
-
-        @Override
-        public void onPayloadStarted(VirtualMachine vm) {
-            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
-        }
-
-        @Override
-        public void onPayloadReady(VirtualMachine vm) {
-            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
-        }
-
-        @Override
-        public void onPayloadFinished(VirtualMachine vm, int exitCode) {
-            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
-        }
-
-        @Override
-        public void onError(VirtualMachine vm, int errorCode, String message) {
-            Log.e(TAG, "Error from VM. code: " + errorCode + " (" + message + ")");
-            mFinishedSuccessfully.complete(false);
-        }
-
-        @Override
-        public void onStopped(VirtualMachine vm, int reason) {
-            Log.d(TAG, "VM stopped. Reason: " + reason);
-            mFinishedSuccessfully.complete(true);
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Runner.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.kt
new file mode 100644
index 0000000..6454cbd
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.system.virtualmachine.VirtualMachine
+import android.system.virtualmachine.VirtualMachineCallback
+import android.system.virtualmachine.VirtualMachineConfig
+import android.system.virtualmachine.VirtualMachineException
+import android.system.virtualmachine.VirtualMachineManager
+import android.util.Log
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ForkJoinPool
+
+/** Utility class for creating a VM and waiting for it to finish. */
+internal class Runner private constructor(val vm: VirtualMachine?, callback: Callback) {
+    /** Get future about VM's exit status. */
+    val exitStatus = callback.finishedSuccessfully
+
+    private class Callback : VirtualMachineCallback {
+        val finishedSuccessfully: CompletableFuture<Boolean> = CompletableFuture<Boolean>()
+
+        override fun onPayloadStarted(vm: VirtualMachine) {
+            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+        }
+
+        override fun onPayloadReady(vm: VirtualMachine) {
+            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+        }
+
+        override fun onPayloadFinished(vm: VirtualMachine, exitCode: Int) {
+            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+        }
+
+        override fun onError(vm: VirtualMachine, errorCode: Int, message: String) {
+            Log.e(TAG, "Error from VM. code: $errorCode ($message)")
+            finishedSuccessfully.complete(false)
+        }
+
+        override fun onStopped(vm: VirtualMachine, reason: Int) {
+            Log.d(TAG, "VM stopped. Reason: $reason")
+            finishedSuccessfully.complete(true)
+        }
+    }
+
+    companion object {
+        /** Create a virtual machine of the given config, under the given context. */
+        @Throws(VirtualMachineException::class)
+        fun create(context: Context, config: VirtualMachineConfig): Runner {
+            // context may already be the app context, but calling this again is not harmful.
+            // See b/359439878 on why vmm should be obtained from the app context.
+            val appContext = context.getApplicationContext()
+            val vmm =
+                appContext.getSystemService<VirtualMachineManager>(
+                    VirtualMachineManager::class.java
+                )
+            val customConfig = config.customImageConfig
+            requireNotNull(customConfig) { "CustomImageConfig is missing" }
+
+            val name = customConfig.name
+            require(!name.isNullOrEmpty()) { "Virtual machine's name is missing in the config" }
+
+            var vm = vmm.getOrCreate(name, config)
+            try {
+                vm.config = config
+            } catch (e: VirtualMachineException) {
+                vmm.delete(name)
+                vm = vmm.create(name, config)
+                Log.w(TAG, "Re-creating virtual machine ($name)", e)
+            }
+
+            val cb = Callback()
+            vm.setCallback(ForkJoinPool.commonPool(), cb)
+            vm.run()
+            return Runner(vm, cb)
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActiveAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActiveAdapter.kt
index c46effa..7076084 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActiveAdapter.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActiveAdapter.kt
@@ -15,6 +15,7 @@
  */
 package com.android.virtualization.terminal
 
+import android.content.Context
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -22,12 +23,14 @@
 import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.materialswitch.MaterialSwitch
 
-class SettingsPortForwardingActiveAdapter(private val mPortsStateManager: PortsStateManager) :
-    SettingsPortForwardingBaseAdapter<SettingsPortForwardingActiveAdapter.ViewHolder>() {
+class SettingsPortForwardingActiveAdapter(
+    private val portsStateManager: PortsStateManager,
+    private val context: Context,
+) : SettingsPortForwardingBaseAdapter<SettingsPortForwardingActiveAdapter.ViewHolder>() {
 
     override fun getItems(): ArrayList<SettingsPortForwardingItem> {
-        val enabledPorts = mPortsStateManager.getEnabledPorts()
-        return mPortsStateManager
+        val enabledPorts = portsStateManager.getEnabledPorts()
+        return portsStateManager
             .getActivePorts()
             .map { SettingsPortForwardingItem(it, enabledPorts.contains(it)) }
             .toCollection(ArrayList())
@@ -47,12 +50,18 @@
     }
 
     override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
-        val port = mItems[position].port
-        viewHolder.port.text = port.toString()
+        val port = items[position].port
+        viewHolder.port.text =
+            context.getString(
+                R.string.settings_port_forwarding_active_ports_content,
+                port,
+                portsStateManager.getActivePortInfo(port)?.comm,
+            )
         viewHolder.enabledSwitch.contentDescription = viewHolder.port.text
-        viewHolder.enabledSwitch.isChecked = mItems[position].enabled
+        viewHolder.enabledSwitch.setOnCheckedChangeListener(null)
+        viewHolder.enabledSwitch.isChecked = items[position].enabled
         viewHolder.enabledSwitch.setOnCheckedChangeListener { _, isChecked ->
-            mPortsStateManager.updateEnabledPort(port, isChecked)
+            portsStateManager.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 27d6ce7..db3926d 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -31,30 +31,30 @@
 private const val PORT_RANGE_MAX: Int = 65535
 
 class SettingsPortForwardingActivity : AppCompatActivity() {
-    private lateinit var mPortsStateManager: PortsStateManager
-    private lateinit var mPortsStateListener: Listener
-    private lateinit var mActivePortsAdapter: SettingsPortForwardingActiveAdapter
-    private lateinit var mInactivePortsAdapter: SettingsPortForwardingInactiveAdapter
+    private lateinit var portsStateManager: PortsStateManager
+    private lateinit var portsStateListener: Listener
+    private lateinit var activePortsAdapter: SettingsPortForwardingActiveAdapter
+    private lateinit var inactivePortsAdapter: SettingsPortForwardingInactiveAdapter
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.settings_port_forwarding)
 
-        mPortsStateManager = PortsStateManager.getInstance(this)
+        portsStateManager = PortsStateManager.getInstance(this)
 
-        mActivePortsAdapter = SettingsPortForwardingActiveAdapter(mPortsStateManager)
+        activePortsAdapter = SettingsPortForwardingActiveAdapter(portsStateManager, this)
         val activeRecyclerView: RecyclerView =
             findViewById(R.id.settings_port_forwarding_active_recycler_view)
         activeRecyclerView.layoutManager = LinearLayoutManager(this)
-        activeRecyclerView.adapter = mActivePortsAdapter
+        activeRecyclerView.adapter = activePortsAdapter
 
-        mInactivePortsAdapter = SettingsPortForwardingInactiveAdapter(mPortsStateManager, this)
+        inactivePortsAdapter = SettingsPortForwardingInactiveAdapter(portsStateManager, this)
         val inactiveRecyclerView: RecyclerView =
             findViewById(R.id.settings_port_forwarding_inactive_recycler_view)
         inactiveRecyclerView.layoutManager = LinearLayoutManager(this)
-        inactiveRecyclerView.adapter = mInactivePortsAdapter
+        inactiveRecyclerView.adapter = inactivePortsAdapter
 
-        mPortsStateListener = Listener()
+        portsStateListener = Listener()
 
         val addButton = findViewById<ImageButton>(R.id.settings_port_forwarding_inactive_add_button)
         addButton.setOnClickListener {
@@ -71,7 +71,7 @@
                                 R.id.settings_port_forwarding_inactive_add_dialog_text
                             )!!
                         val port = editText.text.toString().toInt()
-                        mPortsStateManager.updateEnabledPort(port, true)
+                        portsStateManager.updateEnabledPort(port, true)
                     }
                     .setNegativeButton(R.string.settings_port_forwarding_dialog_cancel, null)
                     .create()
@@ -121,8 +121,8 @@
                             )
                             positiveButton.setEnabled(false)
                         } else if (
-                            mPortsStateManager.getActivePorts().contains(port) ||
-                                mPortsStateManager.getEnabledPorts().contains(port)
+                            portsStateManager.getActivePorts().contains(port) ||
+                                portsStateManager.getEnabledPorts().contains(port)
                         ) {
                             editText.setError(
                                 getString(
@@ -140,18 +140,20 @@
     }
 
     private fun refreshAdapters() {
-        mActivePortsAdapter.refreshItems()
-        mInactivePortsAdapter.refreshItems()
+        runOnUiThread {
+            activePortsAdapter.refreshItems()
+            inactivePortsAdapter.refreshItems()
+        }
     }
 
     override fun onResume() {
         super.onResume()
-        mPortsStateManager.registerListener(mPortsStateListener)
+        portsStateManager.registerListener(portsStateListener)
         refreshAdapters()
     }
 
     override fun onPause() {
-        mPortsStateManager.unregisterListener(mPortsStateListener)
+        portsStateManager.unregisterListener(portsStateListener)
         super.onPause()
     }
 
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingBaseAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingBaseAdapter.kt
index 4595372..5b8d022 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingBaseAdapter.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingBaseAdapter.kt
@@ -21,10 +21,10 @@
 
 abstract class SettingsPortForwardingBaseAdapter<T : RecyclerView.ViewHolder>() :
     RecyclerView.Adapter<T>() {
-    var mItems: SortedList<SettingsPortForwardingItem>
+    var items: SortedList<SettingsPortForwardingItem>
 
     init {
-        mItems =
+        items =
             SortedList(
                 SettingsPortForwardingItem::class.java,
                 object : SortedListAdapterCallback<SettingsPortForwardingItem>(this) {
@@ -52,11 +52,11 @@
             )
     }
 
-    override fun getItemCount() = mItems.size()
+    override fun getItemCount() = items.size()
 
     abstract fun getItems(): ArrayList<SettingsPortForwardingItem>
 
     fun refreshItems() {
-        mItems.replaceAll(getItems())
+        items.replaceAll(getItems())
     }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
index d572129..e1fe468 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
@@ -24,14 +24,14 @@
 import androidx.recyclerview.widget.RecyclerView
 
 class SettingsPortForwardingInactiveAdapter(
-    private val mPortsStateManager: PortsStateManager,
-    private val mContext: Context,
+    private val portsStateManager: PortsStateManager,
+    private val context: Context,
 ) : SettingsPortForwardingBaseAdapter<SettingsPortForwardingInactiveAdapter.ViewHolder>() {
 
     override fun getItems(): ArrayList<SettingsPortForwardingItem> {
-        return mPortsStateManager
+        return portsStateManager
             .getEnabledPorts()
-            .subtract(mPortsStateManager.getActivePorts())
+            .subtract(portsStateManager.getActivePorts())
             .map { SettingsPortForwardingItem(it, true) }
             .toCollection(ArrayList())
     }
@@ -50,15 +50,15 @@
     }
 
     override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
-        val port = mItems[position].port
+        val port = items[position].port
         viewHolder.port.text = port.toString()
         viewHolder.closeButton.contentDescription =
-            mContext.getString(
+            context.getString(
                 R.string.settings_port_forwarding_other_enabled_port_close_button,
                 port,
             )
         viewHolder.closeButton.setOnClickListener { _ ->
-            mPortsStateManager.updateEnabledPort(port, false)
+            portsStateManager.updateEnabledPort(port, false)
         }
     }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
index a4d43b8..319a53b 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
@@ -22,7 +22,7 @@
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.isVisible
 import androidx.lifecycle.lifecycleScope
-import com.android.virtualization.terminal.MainActivity.TAG
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
 import com.google.android.material.card.MaterialCardView
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.snackbar.Snackbar
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.java b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.java
deleted file mode 100644
index 4ab2b77..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.virtualization.terminal;
-
-import android.content.Context;
-import android.util.Log;
-
-public class TerminalExceptionHandler implements Thread.UncaughtExceptionHandler {
-    private static final String TAG = "TerminalExceptionHandler";
-
-    private final Context mContext;
-
-    public TerminalExceptionHandler(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public void uncaughtException(Thread thread, Throwable throwable) {
-        Exception exception;
-        if (throwable instanceof Exception) {
-            exception = (Exception) throwable;
-        } else {
-            exception = new Exception(throwable);
-        }
-        try {
-            ErrorActivity.start(mContext, exception);
-        } catch (Exception ex) {
-            Log.wtf(TAG, "Failed to launch error activity for an exception", exception);
-        }
-
-        thread.getDefaultUncaughtExceptionHandler().uncaughtException(thread, throwable);
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.kt b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.kt
new file mode 100644
index 0000000..3a8c444
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalExceptionHandler.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+package com.android.virtualization.terminal
+
+import android.content.Context
+import android.util.Log
+import java.lang.Exception
+
+class TerminalExceptionHandler(private val context: Context) : Thread.UncaughtExceptionHandler {
+
+    override fun uncaughtException(thread: Thread, throwable: Throwable) {
+        val exception = (throwable as? Exception) ?: Exception(throwable)
+        try {
+            ErrorActivity.start(context, exception)
+        } catch (_: Exception) {
+            Log.wtf(TAG, "Failed to launch error activity for an exception", exception)
+        }
+        Thread.getDefaultUncaughtExceptionHandler()?.uncaughtException(thread, throwable)
+    }
+
+    companion object {
+        private const val TAG = "TerminalExceptionHandler"
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.java b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.java
deleted file mode 100644
index 5ee535d..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.virtualization.terminal;
-
-import android.content.Context;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
-public class TerminalThreadFactory implements ThreadFactory {
-    private final Context mContext;
-
-    public TerminalThreadFactory(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public Thread newThread(Runnable r) {
-        Thread thread = Executors.defaultThreadFactory().newThread(r);
-        thread.setUncaughtExceptionHandler(new TerminalExceptionHandler(mContext));
-        return thread;
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.kt b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.kt
new file mode 100644
index 0000000..f8e909d
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalThreadFactory.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+package com.android.virtualization.terminal
+
+import android.content.Context
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+
+class TerminalThreadFactory(private val context: Context) : ThreadFactory {
+    override fun newThread(r: Runnable): Thread {
+        return Executors.defaultThreadFactory().newThread(r).also {
+            it.uncaughtExceptionHandler = TerminalExceptionHandler(context)
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
deleted file mode 100644
index 0ffc093..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
+++ /dev/null
@@ -1,313 +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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
-import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.inputmethod.EditorInfo;
-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
-        implements AccessibilityStateChangeListener, TouchExplorationStateChangeListener {
-    // Maximum length of texts the talk back announcements can be. This value is somewhat
-    // arbitrarily set. We may want to adjust this in the future.
-    private static final int TEXT_TOO_LONG_TO_ANNOUNCE = 200;
-
-    private final String CTRL_KEY_HANDLER;
-    private final String ENABLE_CTRL_KEY;
-    private final String TOUCH_TO_MOUSE_HANDLER;
-
-    private final AccessibilityManager mA11yManager;
-
-    public TerminalView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mA11yManager = context.getSystemService(AccessibilityManager.class);
-        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
-    public void onAccessibilityStateChanged(boolean enabled) {
-        Log.d(TAG, "accessibility " + enabled);
-        adjustToA11yStateChange();
-    }
-
-    @Override
-    public void onTouchExplorationStateChanged(boolean enabled) {
-        Log.d(TAG, "touch exploration " + enabled);
-        adjustToA11yStateChange();
-    }
-
-    private void adjustToA11yStateChange() {
-        if (!mA11yManager.isEnabled()) {
-            setFocusable(true);
-            return;
-        }
-
-        // When accessibility is on, the webview itself doesn't have to be focusable. The (virtual)
-        // edittext will be focusable to accept inputs. However, the webview has to be focusable for
-        // an accessibility purpose so that users can read the contents in it or scroll the view.
-        setFocusable(false);
-        setFocusableInTouchMode(true);
-    }
-
-    // AccessibilityEvents for WebView are sent directly from WebContentsAccessibilityImpl to the
-    // parent of WebView, without going through WebView. So, there's no WebView methods we can
-    // override to intercept the event handling process. To work around this, we attach an
-    // AccessibilityDelegate to the parent view where the events are sent to. And to guarantee that
-    // the parent view exists, wait until the WebView is attached to the window by when the parent
-    // must exist.
-    private final AccessibilityDelegate mA11yEventFilter =
-            new AccessibilityDelegate() {
-                @Override
-                public boolean onRequestSendAccessibilityEvent(
-                        ViewGroup host, View child, AccessibilityEvent e) {
-                    // We filter only the a11y events from the WebView
-                    if (child != TerminalView.this) {
-                        return super.onRequestSendAccessibilityEvent(host, child, e);
-                    }
-                    final int eventType = e.getEventType();
-                    switch (e.getEventType()) {
-                            // Skip reading texts that are too long. Right now, ttyd emits entire
-                            // text on the terminal to the live region, which is very annoying to
-                            // screen reader users.
-                        case AccessibilityEvent.TYPE_ANNOUNCEMENT:
-                            CharSequence text = e.getText().get(0); // there always is a text
-                            if (text.length() >= TEXT_TOO_LONG_TO_ANNOUNCE) {
-                                Log.i(TAG, "Announcement skipped because it's too long: " + text);
-                                return false;
-                            }
-                            break;
-                    }
-                    return super.onRequestSendAccessibilityEvent(host, child, e);
-                }
-            };
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (mA11yManager.isEnabled()) {
-            View parent = (View) getParent();
-            parent.setAccessibilityDelegate(mA11yEventFilter);
-        }
-    }
-
-    private final AccessibilityNodeProvider mA11yNodeProvider =
-            new AccessibilityNodeProvider() {
-
-                /** Returns the original NodeProvider that WebView implements. */
-                private AccessibilityNodeProvider getParent() {
-                    return TerminalView.super.getAccessibilityNodeProvider();
-                }
-
-                /** Convenience method for reading a string resource. */
-                private String getString(int resId) {
-                    return TerminalView.this.getContext().getResources().getString(resId);
-                }
-
-                /** Checks if NodeInfo renders an empty line in the terminal. */
-                private boolean isEmptyLine(AccessibilityNodeInfo info) {
-                    final CharSequence text = info.getText();
-                    // Node with no text is not consiered a line. ttyd emits at least one character,
-                    // which usually is NBSP.
-                    if (text == null) {
-                        return false;
-                    }
-                    for (int i = 0; i < text.length(); i++) {
-                        char c = text.charAt(i);
-                        // Note: don't use Characters.isWhitespace as it doesn't recognize NBSP as a
-                        // whitespace.
-                        if (!TextUtils.isWhitespace(c)) {
-                            return false;
-                        }
-                    }
-                    return true;
-                }
-
-                @Override
-                public AccessibilityNodeInfo createAccessibilityNodeInfo(int id) {
-                    AccessibilityNodeInfo info = getParent().createAccessibilityNodeInfo(id);
-                    if (info == null) {
-                        return null;
-                    }
-
-                    final String className = info.getClassName().toString();
-
-                    // By default all views except the cursor is not click-able. Other views are
-                    // read-only. This ensures that user is not navigated to non-clickable elements
-                    // when using switches.
-                    if (!"android.widget.EditText".equals(className)) {
-                        info.removeAction(AccessibilityAction.ACTION_CLICK);
-                    }
-
-                    switch (className) {
-                        case "android.webkit.WebView":
-                            // There are two NodeInfo objects of class name WebView. The one is the
-                            // real WebView whose ID is View.NO_ID as it's at the root of the
-                            // virtual view hierarchy. The second one is a virtual view for the
-                            // iframe. The latter one's text is set to the command that we give to
-                            // ttyd, which is "login -f droid ...". This is an impl detail which
-                            // doesn't have to be announced.  Replace the text with "Terminal
-                            // display".
-                            if (id != View.NO_ID) {
-                                info.setText(null);
-                                info.setContentDescription(getString(R.string.terminal_display));
-                                // b/376827536
-                                info.setHintText(getString(R.string.double_tap_to_edit_text));
-                            }
-
-                            // These two lines below are to prevent this WebView element from being
-                            // fousable by the screen reader, while allowing any other element in
-                            // the WebView to be focusable by the reader. In our case, the EditText
-                            // is a117_focusable.
-                            info.setScreenReaderFocusable(false);
-                            info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
-                            break;
-                        case "android.view.View":
-                            // Empty line was announced as "space" (via the NBSP character).
-                            // Localize the spoken text.
-                            if (isEmptyLine(info)) {
-                                info.setContentDescription(getString(R.string.empty_line));
-                                // b/376827536
-                                info.setHintText(getString(R.string.double_tap_to_edit_text));
-                            }
-                            break;
-                        case "android.widget.TextView":
-                            // There are several TextViews in the terminal, and one of them is an
-                            // invisible TextView which seems to be from the <div
-                            // class="live-region"> tag. Interestingly, its text is often populated
-                            // with the entire text on the screen. Silence this by forcibly setting
-                            // the text to null. Note that this TextView is identified by having a
-                            // zero width. This certainly is not elegant, but I couldn't find other
-                            // options.
-                            Rect rect = new Rect();
-                            info.getBoundsInScreen(rect);
-                            if (rect.width() == 0) {
-                                info.setText(null);
-                                info.setContentDescription(getString(R.string.empty_line));
-                            }
-                            info.setScreenReaderFocusable(false);
-                            break;
-                        case "android.widget.EditText":
-                            // This EditText is for the <textarea> accepting user input; the cursor.
-                            // ttyd name it as "Terminal input" but it's not i18n'ed. Override it
-                            // here for better i18n.
-                            info.setText(null);
-                            info.setHintText(getString(R.string.double_tap_to_edit_text));
-                            info.setContentDescription(getString(R.string.terminal_input));
-                            info.setScreenReaderFocusable(true);
-                            info.addAction(AccessibilityAction.ACTION_FOCUS);
-                            break;
-                    }
-                    return info;
-                }
-
-                @Override
-                public boolean performAction(int id, int action, Bundle arguments) {
-                    return getParent().performAction(id, action, arguments);
-                }
-
-                @Override
-                public void addExtraDataToAccessibilityNodeInfo(
-                        int virtualViewId,
-                        AccessibilityNodeInfo info,
-                        String extraDataKey,
-                        Bundle arguments) {
-                    getParent()
-                            .addExtraDataToAccessibilityNodeInfo(
-                                    virtualViewId, info, extraDataKey, arguments);
-                }
-
-                @Override
-                public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(
-                        String text, int virtualViewId) {
-                    return getParent().findAccessibilityNodeInfosByText(text, virtualViewId);
-                }
-
-                @Override
-                public AccessibilityNodeInfo findFocus(int focus) {
-                    return getParent().findFocus(focus);
-                }
-            };
-
-    @Override
-    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
-        AccessibilityNodeProvider p = super.getAccessibilityNodeProvider();
-        if (p != null && mA11yManager.isEnabled()) {
-            return mA11yNodeProvider;
-        }
-        return p;
-    }
-
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
-        if (outAttrs != null) {
-            outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
-        }
-        return inputConnection;
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.kt b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.kt
new file mode 100644
index 0000000..4d9a89d
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.graphics.Rect
+import android.os.Bundle
+import android.text.InputType
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeProvider
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import android.webkit.WebView
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.io.IOException
+
+class TerminalView(context: Context, attrs: AttributeSet?) :
+    WebView(context, attrs),
+    AccessibilityManager.AccessibilityStateChangeListener,
+    AccessibilityManager.TouchExplorationStateChangeListener {
+    private val ctrlKeyHandler: String = readAssetAsString(context, "js/ctrl_key_handler.js")
+    private val enableCtrlKey: String = readAssetAsString(context, "js/enable_ctrl_key.js")
+    private val touchToMouseHandler: String =
+        readAssetAsString(context, "js/touch_to_mouse_handler.js")
+    private val a11yManager =
+        context.getSystemService<AccessibilityManager>(AccessibilityManager::class.java).also {
+            it.addTouchExplorationStateChangeListener(this)
+            it.addAccessibilityStateChangeListener(this)
+        }
+
+    @Throws(IOException::class)
+    private fun readAssetAsString(context: Context, filePath: String): String {
+        return String(context.assets.open(filePath).readAllBytes())
+    }
+
+    fun mapTouchToMouseEvent() {
+        this.evaluateJavascript(touchToMouseHandler, null)
+    }
+
+    fun mapCtrlKey() {
+        this.evaluateJavascript(ctrlKeyHandler, null)
+    }
+
+    fun enableCtrlKey() {
+        this.evaluateJavascript(enableCtrlKey, null)
+    }
+
+    override fun onAccessibilityStateChanged(enabled: Boolean) {
+        Log.d(TAG, "accessibility $enabled")
+        adjustToA11yStateChange()
+    }
+
+    override fun onTouchExplorationStateChanged(enabled: Boolean) {
+        Log.d(TAG, "touch exploration $enabled")
+        adjustToA11yStateChange()
+    }
+
+    private fun adjustToA11yStateChange() {
+        if (!a11yManager.isEnabled) {
+            setFocusable(true)
+            return
+        }
+
+        // When accessibility is on, the webview itself doesn't have to be focusable. The (virtual)
+        // edittext will be focusable to accept inputs. However, the webview has to be focusable for
+        // an accessibility purpose so that users can read the contents in it or scroll the view.
+        setFocusable(false)
+        setFocusableInTouchMode(true)
+    }
+
+    // AccessibilityEvents for WebView are sent directly from WebContentsAccessibilityImpl to the
+    // parent of WebView, without going through WebView. So, there's no WebView methods we can
+    // override to intercept the event handling process. To work around this, we attach an
+    // AccessibilityDelegate to the parent view where the events are sent to. And to guarantee that
+    // the parent view exists, wait until the WebView is attached to the window by when the parent
+    // must exist.
+    private val a11yEventFilter: AccessibilityDelegate =
+        object : AccessibilityDelegate() {
+            override fun onRequestSendAccessibilityEvent(
+                host: ViewGroup,
+                child: View,
+                e: AccessibilityEvent,
+            ): Boolean {
+                // We filter only the a11y events from the WebView
+                if (child !== this@TerminalView) {
+                    return super.onRequestSendAccessibilityEvent(host, child, e)
+                }
+                when (e.eventType) {
+                    AccessibilityEvent.TYPE_ANNOUNCEMENT -> {
+                        val text = e.text[0] // there always is a text
+                        if (text.length >= TEXT_TOO_LONG_TO_ANNOUNCE) {
+                            Log.i(TAG, "Announcement skipped because it's too long: $text")
+                            return false
+                        }
+                    }
+                }
+                return super.onRequestSendAccessibilityEvent(host, child, e)
+            }
+        }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        if (a11yManager.isEnabled) {
+            val parent = getParent() as View
+            parent.setAccessibilityDelegate(a11yEventFilter)
+        }
+    }
+
+    private val a11yNodeProvider: AccessibilityNodeProvider =
+        object : AccessibilityNodeProvider() {
+            /** Returns the original NodeProvider that WebView implements. */
+            private fun getParent(): AccessibilityNodeProvider? {
+                return super@TerminalView.getAccessibilityNodeProvider()
+            }
+
+            /** Convenience method for reading a string resource. */
+            private fun getString(resId: Int): String {
+                return this@TerminalView.context.getResources().getString(resId)
+            }
+
+            /** Checks if NodeInfo renders an empty line in the terminal. */
+            private fun isEmptyLine(info: AccessibilityNodeInfo): Boolean {
+                // Node with no text is not considered a line. ttyd emits at least one character,
+                // which usually is NBSP.
+                // Note: don't use Characters.isWhitespace as it doesn't recognize NBSP as a
+                // whitespace.
+                return (info.getText()?.all { TextUtils.isWhitespace(it.code) }) == true
+            }
+
+            override fun createAccessibilityNodeInfo(id: Int): AccessibilityNodeInfo? {
+                val info: AccessibilityNodeInfo? = getParent()?.createAccessibilityNodeInfo(id)
+                if (info == null) {
+                    return null
+                }
+
+                val className = info.className.toString()
+
+                // By default all views except the cursor is not click-able. Other views are
+                // read-only. This ensures that user is not navigated to non-clickable elements
+                // when using switches.
+                if ("android.widget.EditText" != className) {
+                    info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)
+                }
+
+                when (className) {
+                    "android.webkit.WebView" -> {
+                        // There are two NodeInfo objects of class name WebView. The one is the
+                        // real WebView whose ID is View.NO_ID as it's at the root of the
+                        // virtual view hierarchy. The second one is a virtual view for the
+                        // iframe. The latter one's text is set to the command that we give to
+                        // ttyd, which is "login -f droid ...". This is an impl detail which
+                        // doesn't have to be announced.  Replace the text with "Terminal
+                        // display".
+                        if (id != NO_ID) {
+                            info.setText(null)
+                            info.setContentDescription(getString(R.string.terminal_display))
+                            // b/376827536
+                            info.setHintText(getString(R.string.double_tap_to_edit_text))
+                        }
+
+                        // These two lines below are to prevent this WebView element from being
+                        // focusable by the screen reader, while allowing any other element in
+                        // the WebView to be focusable by the reader. In our case, the EditText
+                        // is a117_focusable.
+                        info.isScreenReaderFocusable = false
+                        info.addAction(
+                            AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS
+                        )
+                    }
+
+                    "android.view.View" ->
+                        // Empty line was announced as "space" (via the NBSP character).
+                        // Localize the spoken text.
+                        if (isEmptyLine(info)) {
+                            info.setContentDescription(getString(R.string.empty_line))
+                            // b/376827536
+                            info.setHintText(getString(R.string.double_tap_to_edit_text))
+                        }
+
+                    "android.widget.TextView" -> {
+                        // There are several TextViews in the terminal, and one of them is an
+                        // invisible TextView which seems to be from the <div
+                        // class="live-region"> tag. Interestingly, its text is often populated
+                        // with the entire text on the screen. Silence this by forcibly setting
+                        // the text to null. Note that this TextView is identified by having a
+                        // zero width. This certainly is not elegant, but I couldn't find other
+                        // options.
+                        val rect = Rect()
+                        info.getBoundsInScreen(rect)
+                        if (rect.width() == 0) {
+                            info.setText(null)
+                            info.setContentDescription(getString(R.string.empty_line))
+                        }
+                        info.isScreenReaderFocusable = false
+                    }
+
+                    "android.widget.EditText" -> {
+                        // This EditText is for the <textarea> accepting user input; the cursor.
+                        // ttyd name it as "Terminal input" but it's not i18n'ed. Override it
+                        // here for better i18n.
+                        info.setText(null)
+                        info.setHintText(getString(R.string.double_tap_to_edit_text))
+                        info.setContentDescription(getString(R.string.terminal_input))
+                        info.isScreenReaderFocusable = true
+                        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_FOCUS)
+                    }
+                }
+                return info
+            }
+
+            override fun performAction(id: Int, action: Int, arguments: Bundle?): Boolean {
+                return getParent()?.performAction(id, action, arguments) == true
+            }
+
+            override fun addExtraDataToAccessibilityNodeInfo(
+                virtualViewId: Int,
+                info: AccessibilityNodeInfo?,
+                extraDataKey: String?,
+                arguments: Bundle?,
+            ) {
+                getParent()
+                    ?.addExtraDataToAccessibilityNodeInfo(
+                        virtualViewId,
+                        info,
+                        extraDataKey,
+                        arguments,
+                    )
+            }
+
+            override fun findAccessibilityNodeInfosByText(
+                text: String?,
+                virtualViewId: Int,
+            ): MutableList<AccessibilityNodeInfo?>? {
+                return getParent()?.findAccessibilityNodeInfosByText(text, virtualViewId)
+            }
+
+            override fun findFocus(focus: Int): AccessibilityNodeInfo? {
+                return getParent()?.findFocus(focus)
+            }
+        }
+
+    override fun getAccessibilityNodeProvider(): AccessibilityNodeProvider? {
+        val p = super.getAccessibilityNodeProvider()
+        if (p != null && a11yManager.isEnabled) {
+            return a11yNodeProvider
+        }
+        return p
+    }
+
+    override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection? {
+        val inputConnection = super.onCreateInputConnection(outAttrs)
+        if (outAttrs != null) {
+            outAttrs.inputType = outAttrs.inputType or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+        }
+        return inputConnection
+    }
+
+    companion object {
+        // Maximum length of texts the talk back announcements can be. This value is somewhat
+        // arbitrarily set. We may want to adjust this in the future.
+        private const val TEXT_TOO_LONG_TO_ANNOUNCE = 200
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
deleted file mode 100644
index f262f1f..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
+++ /dev/null
@@ -1,365 +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 static com.android.virtualization.terminal.MainActivity.TAG;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.ResultReceiver;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.Disk;
-import android.system.virtualmachine.VirtualMachineException;
-import android.util.Log;
-import android.widget.Toast;
-
-import io.grpc.Grpc;
-import io.grpc.InsecureServerCredentials;
-import io.grpc.Metadata;
-import io.grpc.Server;
-import io.grpc.ServerCall;
-import io.grpc.ServerCallHandler;
-import io.grpc.ServerInterceptor;
-import io.grpc.Status;
-import io.grpc.okhttp.OkHttpServerBuilder;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Objects;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class VmLauncherService extends Service implements DebianServiceImpl.DebianServiceCallback {
-    private static final String EXTRA_NOTIFICATION = "EXTRA_NOTIFICATION";
-    private static final String ACTION_START_VM_LAUNCHER_SERVICE =
-            "android.virtualization.START_VM_LAUNCHER_SERVICE";
-
-    public static final String ACTION_STOP_VM_LAUNCHER_SERVICE =
-            "android.virtualization.STOP_VM_LAUNCHER_SERVICE";
-
-    private static final int RESULT_START = 0;
-    private static final int RESULT_STOP = 1;
-    private static final int RESULT_ERROR = 2;
-    private static final int RESULT_IPADDR = 3;
-    private static final String KEY_VM_IP_ADDR = "ip_addr";
-
-    private ExecutorService mExecutorService;
-    private VirtualMachine mVirtualMachine;
-    private ResultReceiver mResultReceiver;
-    private Server mServer;
-    private DebianServiceImpl mDebianService;
-    private PortNotifier mPortNotifier;
-
-    private static Intent getMyIntent(Context context) {
-        return new Intent(context.getApplicationContext(), VmLauncherService.class);
-    }
-
-    public interface VmLauncherServiceCallback {
-        void onVmStart();
-
-        void onVmStop();
-
-        void onVmError();
-
-        void onIpAddrAvailable(String ipAddr);
-    }
-
-    public static void run(
-            Context context, VmLauncherServiceCallback callback, Notification notification) {
-        Intent i = getMyIntent(context);
-        if (i == null) {
-            return;
-        }
-        ResultReceiver resultReceiver =
-                new ResultReceiver(new Handler(Looper.myLooper())) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (callback == null) {
-                            return;
-                        }
-                        switch (resultCode) {
-                            case RESULT_START:
-                                callback.onVmStart();
-                                return;
-                            case RESULT_STOP:
-                                callback.onVmStop();
-                                return;
-                            case RESULT_ERROR:
-                                callback.onVmError();
-                                return;
-                            case RESULT_IPADDR:
-                                callback.onIpAddrAvailable(resultData.getString(KEY_VM_IP_ADDR));
-                                return;
-                        }
-                    }
-                };
-        i.putExtra(Intent.EXTRA_RESULT_RECEIVER, getResultReceiverForIntent(resultReceiver));
-        i.putExtra(VmLauncherService.EXTRA_NOTIFICATION, notification);
-        context.startForegroundService(i);
-    }
-
-    private static ResultReceiver getResultReceiverForIntent(ResultReceiver r) {
-        Parcel parcel = Parcel.obtain();
-        r.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        r = ResultReceiver.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
-        return r;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        if (Objects.equals(intent.getAction(), ACTION_STOP_VM_LAUNCHER_SERVICE)) {
-
-            if (mDebianService != null && mDebianService.shutdownDebian()) {
-                // During shutdown, change the notification content to indicate that it's closing
-                Notification notification = createNotificationForTerminalClose();
-                getSystemService(NotificationManager.class).notify(this.hashCode(), notification);
-            } else {
-                // If there is no Debian service or it fails to shutdown, just stop the service.
-                stopSelf();
-            }
-            return START_NOT_STICKY;
-        }
-        if (mVirtualMachine != null) {
-            Log.d(TAG, "VM instance is already started");
-            return START_NOT_STICKY;
-        }
-        mExecutorService =
-                Executors.newCachedThreadPool(new TerminalThreadFactory(getApplicationContext()));
-
-        InstalledImage image = InstalledImage.getDefault(this);
-        ConfigJson json = ConfigJson.from(this, image.getConfigPath());
-        VirtualMachineConfig.Builder configBuilder = json.toConfigBuilder(this);
-        VirtualMachineCustomImageConfig.Builder customImageConfigBuilder =
-                json.toCustomImageConfigBuilder(this);
-        if (overrideConfigIfNecessary(customImageConfigBuilder)) {
-            configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
-        }
-        VirtualMachineConfig config = configBuilder.build();
-
-        Runner runner;
-        try {
-            android.os.Trace.beginSection("vmCreate");
-            runner = Runner.create(this, config);
-            android.os.Trace.endSection();
-            android.os.Trace.beginAsyncSection("debianBoot", 0);
-        } catch (VirtualMachineException e) {
-            throw new RuntimeException("cannot create runner", e);
-        }
-        mVirtualMachine = runner.getVm();
-        mResultReceiver =
-                intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, ResultReceiver.class);
-
-        runner.getExitStatus()
-                .thenAcceptAsync(
-                        success -> {
-                            if (mResultReceiver != null) {
-                                mResultReceiver.send(success ? RESULT_STOP : RESULT_ERROR, null);
-                            }
-                            stopSelf();
-                        });
-        Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
-        Logger.setup(mVirtualMachine, logPath, mExecutorService);
-
-        Notification notification =
-                intent.getParcelableExtra(EXTRA_NOTIFICATION, Notification.class);
-
-        startForeground(this.hashCode(), notification);
-
-        mResultReceiver.send(RESULT_START, null);
-
-        mPortNotifier = new PortNotifier(this);
-        startDebianServer();
-
-        return START_NOT_STICKY;
-    }
-
-    private Notification createNotificationForTerminalClose() {
-        Intent stopIntent = new Intent();
-        stopIntent.setClass(this, VmLauncherService.class);
-        stopIntent.setAction(VmLauncherService.ACTION_STOP_VM_LAUNCHER_SERVICE);
-        PendingIntent stopPendingIntent =
-                PendingIntent.getService(
-                        this,
-                        0,
-                        stopIntent,
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        Icon icon = Icon.createWithResource(getResources(), R.drawable.ic_launcher_foreground);
-        String stopActionText =
-                getResources().getString(R.string.service_notification_force_quit_action);
-        String stopNotificationTitle =
-                getResources().getString(R.string.service_notification_close_title);
-        return new Notification.Builder(this, this.getPackageName())
-                .setSmallIcon(R.drawable.ic_launcher_foreground)
-                .setContentTitle(stopNotificationTitle)
-                .setOngoing(true)
-                .setSilent(true)
-                .addAction(
-                        new Notification.Action.Builder(icon, stopActionText, stopPendingIntent)
-                                .build())
-                .build();
-    }
-
-    private boolean overrideConfigIfNecessary(VirtualMachineCustomImageConfig.Builder builder) {
-        boolean changed = false;
-        // TODO: check if ANGLE is enabled for the app.
-        if (Files.exists(ImageArchive.getSdcardPathForTesting().resolve("virglrenderer"))) {
-            builder.setGpuConfig(
-                    new VirtualMachineCustomImageConfig.GpuConfig.Builder()
-                            .setBackend("virglrenderer")
-                            .setRendererUseEgl(true)
-                            .setRendererUseGles(true)
-                            .setRendererUseGlx(false)
-                            .setRendererUseSurfaceless(true)
-                            .setRendererUseVulkan(false)
-                            .setContextTypes(new String[] {"virgl2"})
-                            .build());
-            Toast.makeText(this, R.string.virgl_enabled, Toast.LENGTH_SHORT).show();
-            changed = true;
-        }
-
-        InstalledImage image = InstalledImage.getDefault(this);
-        if (image.hasBackup()) {
-            Path backup = image.getBackupFile();
-            builder.addDisk(Disk.RWDisk(backup.toString()));
-            changed = true;
-        }
-        return changed;
-    }
-
-    private void startDebianServer() {
-        ServerInterceptor interceptor =
-                new ServerInterceptor() {
-                    @Override
-                    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
-                            ServerCall<ReqT, RespT> call,
-                            Metadata headers,
-                            ServerCallHandler<ReqT, RespT> next) {
-                        // Refer to VirtualizationSystemService.TetheringService
-                        final String VM_STATIC_IP_ADDR = "192.168.0.2";
-                        InetSocketAddress remoteAddr =
-                                (InetSocketAddress)
-                                        call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
-
-                        if (remoteAddr != null
-                                && Objects.equals(
-                                        remoteAddr.getAddress().getHostAddress(),
-                                        VM_STATIC_IP_ADDR)) {
-                            // Allow the request only if it is from VM
-                            return next.startCall(call, headers);
-                        }
-                        Log.d(TAG, "blocked grpc request from " + remoteAddr);
-                        call.close(Status.Code.PERMISSION_DENIED.toStatus(), new Metadata());
-                        return new ServerCall.Listener<ReqT>() {};
-                    }
-                };
-        try {
-            // TODO(b/372666638): gRPC for java doesn't support vsock for now.
-            int port = 0;
-            mDebianService = new DebianServiceImpl(this, this);
-            mServer =
-                    OkHttpServerBuilder.forPort(port, InsecureServerCredentials.create())
-                            .intercept(interceptor)
-                            .addService(mDebianService)
-                            .build()
-                            .start();
-        } catch (IOException e) {
-            Log.d(TAG, "grpc server error", e);
-            return;
-        }
-
-        mExecutorService.execute(
-                () -> {
-                    // TODO(b/373533555): we can use mDNS for that.
-                    String debianServicePortFileName = "debian_service_port";
-                    File debianServicePortFile = new File(getFilesDir(), debianServicePortFileName);
-                    try (FileOutputStream writer = new FileOutputStream(debianServicePortFile)) {
-                        writer.write(String.valueOf(mServer.getPort()).getBytes());
-                    } catch (IOException e) {
-                        Log.d(TAG, "cannot write grpc port number", e);
-                    }
-                });
-    }
-
-    @Override
-    public void onIpAddressAvailable(String ipAddr) {
-        android.os.Trace.endAsyncSection("debianBoot", 0);
-        Bundle b = new Bundle();
-        b.putString(VmLauncherService.KEY_VM_IP_ADDR, ipAddr);
-        mResultReceiver.send(VmLauncherService.RESULT_IPADDR, b);
-    }
-
-    public static void stop(Context context) {
-        Intent i = getMyIntent(context);
-        i.setAction(VmLauncherService.ACTION_STOP_VM_LAUNCHER_SERVICE);
-        context.startService(i);
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mPortNotifier != null) {
-            mPortNotifier.stop();
-        }
-        getSystemService(NotificationManager.class).cancelAll();
-        stopDebianServer();
-        if (mVirtualMachine != null) {
-            if (mVirtualMachine.getStatus() == VirtualMachine.STATUS_RUNNING) {
-                try {
-                    mVirtualMachine.stop();
-                    stopForeground(STOP_FOREGROUND_REMOVE);
-                } catch (VirtualMachineException e) {
-                    Log.e(TAG, "failed to stop a VM instance", e);
-                }
-            }
-            mExecutorService.shutdownNow();
-            mExecutorService = null;
-            mVirtualMachine = null;
-        }
-        super.onDestroy();
-    }
-
-    private void stopDebianServer() {
-        if (mDebianService != null) {
-            mDebianService.killForwarderHost();
-        }
-        if (mServer != null) {
-            mServer.shutdown();
-        }
-    }
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
new file mode 100644
index 0000000..8c0368d
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -0,0 +1,353 @@
+/*
+ * 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.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdServiceInfo
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.Parcel
+import android.os.ResultReceiver
+import android.os.Trace
+import android.system.virtualmachine.VirtualMachine
+import android.system.virtualmachine.VirtualMachineCustomImageConfig
+import android.system.virtualmachine.VirtualMachineException
+import android.util.Log
+import android.widget.Toast
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import com.android.virtualization.terminal.Runner.Companion.create
+import com.android.virtualization.terminal.VmLauncherService.VmLauncherServiceCallback
+import io.grpc.Grpc
+import io.grpc.InsecureServerCredentials
+import io.grpc.Metadata
+import io.grpc.Server
+import io.grpc.ServerCall
+import io.grpc.ServerCallHandler
+import io.grpc.ServerInterceptor
+import io.grpc.Status
+import io.grpc.okhttp.OkHttpServerBuilder
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.lang.RuntimeException
+import java.net.InetSocketAddress
+import java.net.SocketAddress
+import java.nio.file.Files
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+class VmLauncherService : Service() {
+    // TODO: using lateinit for some fields to avoid null
+    private var executorService: ExecutorService? = null
+    private var virtualMachine: VirtualMachine? = null
+    private var resultReceiver: ResultReceiver? = null
+    private var server: Server? = null
+    private var debianService: DebianServiceImpl? = null
+    private var portNotifier: PortNotifier? = null
+
+    interface VmLauncherServiceCallback {
+        fun onVmStart()
+
+        fun onVmStop()
+
+        fun onVmError()
+    }
+
+    override fun onBind(intent: Intent?): IBinder? {
+        return null
+    }
+
+    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+        if (intent.action == ACTION_STOP_VM_LAUNCHER_SERVICE) {
+            if (debianService != null && debianService!!.shutdownDebian()) {
+                // During shutdown, change the notification content to indicate that it's closing
+                val notification = createNotificationForTerminalClose()
+                getSystemService<NotificationManager?>(NotificationManager::class.java)
+                    .notify(this.hashCode(), notification)
+            } else {
+                // If there is no Debian service or it fails to shutdown, just stop the service.
+                stopSelf()
+            }
+            return START_NOT_STICKY
+        }
+        if (virtualMachine != null) {
+            Log.d(TAG, "VM instance is already started")
+            return START_NOT_STICKY
+        }
+        executorService = Executors.newCachedThreadPool(TerminalThreadFactory(applicationContext))
+
+        val image = InstalledImage.getDefault(this)
+        val json = ConfigJson.from(this, image.configPath)
+        val configBuilder = json.toConfigBuilder(this)
+        val customImageConfigBuilder = json.toCustomImageConfigBuilder(this)
+        if (overrideConfigIfNecessary(customImageConfigBuilder)) {
+            configBuilder.setCustomImageConfig(customImageConfigBuilder.build())
+        }
+        val config = configBuilder.build()
+
+        Trace.beginSection("vmCreate")
+        val runner: Runner =
+            try {
+                create(this, config)
+            } catch (e: VirtualMachineException) {
+                throw RuntimeException("cannot create runner", e)
+            }
+        Trace.endSection()
+        Trace.beginAsyncSection("debianBoot", 0)
+
+        virtualMachine = runner.vm
+        resultReceiver =
+            intent.getParcelableExtra<ResultReceiver?>(
+                Intent.EXTRA_RESULT_RECEIVER,
+                ResultReceiver::class.java,
+            )
+
+        runner.exitStatus.thenAcceptAsync { success: Boolean ->
+            resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
+            stopSelf()
+        }
+        val logPath = getFileStreamPath(virtualMachine!!.name + ".log").toPath()
+        Logger.setup(virtualMachine!!, logPath, executorService!!)
+
+        val notification =
+            intent.getParcelableExtra<Notification?>(EXTRA_NOTIFICATION, Notification::class.java)
+
+        startForeground(this.hashCode(), notification)
+
+        resultReceiver!!.send(RESULT_START, null)
+
+        portNotifier = PortNotifier(this)
+
+        // TODO: dedup this part
+        val nsdManager = getSystemService<NsdManager?>(NsdManager::class.java)
+        val info = NsdServiceInfo()
+        info.serviceType = "_http._tcp"
+        info.serviceName = "ttyd"
+        nsdManager.registerServiceInfoCallback(
+            info,
+            executorService!!,
+            object : NsdManager.ServiceInfoCallback {
+                override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) {}
+
+                override fun onServiceInfoCallbackUnregistered() {}
+
+                override fun onServiceLost() {}
+
+                override fun onServiceUpdated(info: NsdServiceInfo) {
+                    nsdManager.unregisterServiceInfoCallback(this)
+                    Log.i(TAG, "Service found: $info")
+                    startDebianServer(info.hostAddresses[0].hostAddress)
+                }
+            },
+        )
+
+        return START_NOT_STICKY
+    }
+
+    private fun createNotificationForTerminalClose(): Notification {
+        val stopIntent = Intent()
+        stopIntent.setClass(this, VmLauncherService::class.java)
+        stopIntent.setAction(ACTION_STOP_VM_LAUNCHER_SERVICE)
+        val stopPendingIntent =
+            PendingIntent.getService(
+                this,
+                0,
+                stopIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
+            )
+        val icon = Icon.createWithResource(resources, R.drawable.ic_launcher_foreground)
+        val stopActionText: String? =
+            resources.getString(R.string.service_notification_force_quit_action)
+        val stopNotificationTitle: String? =
+            resources.getString(R.string.service_notification_close_title)
+        return Notification.Builder(this, this.packageName)
+            .setSmallIcon(R.drawable.ic_launcher_foreground)
+            .setContentTitle(stopNotificationTitle)
+            .setOngoing(true)
+            .setSilent(true)
+            .addAction(Notification.Action.Builder(icon, stopActionText, stopPendingIntent).build())
+            .build()
+    }
+
+    private fun overrideConfigIfNecessary(
+        builder: VirtualMachineCustomImageConfig.Builder
+    ): Boolean {
+        var changed = false
+        // TODO: check if ANGLE is enabled for the app.
+        if (Files.exists(ImageArchive.getSdcardPathForTesting().resolve("virglrenderer"))) {
+            builder.setGpuConfig(
+                VirtualMachineCustomImageConfig.GpuConfig.Builder()
+                    .setBackend("virglrenderer")
+                    .setRendererUseEgl(true)
+                    .setRendererUseGles(true)
+                    .setRendererUseGlx(false)
+                    .setRendererUseSurfaceless(true)
+                    .setRendererUseVulkan(false)
+                    .setContextTypes(arrayOf<String>("virgl2"))
+                    .build()
+            )
+            Toast.makeText(this, R.string.virgl_enabled, Toast.LENGTH_SHORT).show()
+            changed = true
+        }
+
+        val image = InstalledImage.getDefault(this)
+        if (image.hasBackup()) {
+            val backup = image.backupFile
+            builder.addDisk(VirtualMachineCustomImageConfig.Disk.RWDisk(backup.toString()))
+            changed = true
+        }
+        return changed
+    }
+
+    private fun startDebianServer(ipAddress: String?) {
+        val interceptor: ServerInterceptor =
+            object : ServerInterceptor {
+                override fun <ReqT, RespT> interceptCall(
+                    call: ServerCall<ReqT?, RespT?>,
+                    headers: Metadata?,
+                    next: ServerCallHandler<ReqT?, RespT?>,
+                ): ServerCall.Listener<ReqT?>? {
+                    val remoteAddr =
+                        call.attributes.get<SocketAddress?>(Grpc.TRANSPORT_ATTR_REMOTE_ADDR)
+                            as InetSocketAddress?
+
+                    if (remoteAddr?.address?.hostAddress == ipAddress) {
+                        // Allow the request only if it is from VM
+                        return next.startCall(call, headers)
+                    }
+                    Log.d(TAG, "blocked grpc request from $remoteAddr")
+                    call.close(Status.Code.PERMISSION_DENIED.toStatus(), Metadata())
+                    return object : ServerCall.Listener<ReqT?>() {}
+                }
+            }
+        try {
+            // TODO(b/372666638): gRPC for java doesn't support vsock for now.
+            val port = 0
+            debianService = DebianServiceImpl(this)
+            server =
+                OkHttpServerBuilder.forPort(port, InsecureServerCredentials.create())
+                    .intercept(interceptor)
+                    .addService(debianService)
+                    .build()
+                    .start()
+        } catch (e: IOException) {
+            Log.d(TAG, "grpc server error", e)
+            return
+        }
+
+        executorService!!.execute(
+            Runnable {
+                // TODO(b/373533555): we can use mDNS for that.
+                val debianServicePortFile = File(filesDir, "debian_service_port")
+                try {
+                    FileOutputStream(debianServicePortFile).use { writer ->
+                        writer.write(server!!.port.toString().toByteArray())
+                    }
+                } catch (e: IOException) {
+                    Log.d(TAG, "cannot write grpc port number", e)
+                }
+            }
+        )
+    }
+
+    override fun onDestroy() {
+        portNotifier?.stop()
+        getSystemService<NotificationManager?>(NotificationManager::class.java).cancelAll()
+        stopDebianServer()
+        if (virtualMachine != null) {
+            if (virtualMachine!!.getStatus() == VirtualMachine.STATUS_RUNNING) {
+                try {
+                    virtualMachine!!.stop()
+                    stopForeground(STOP_FOREGROUND_REMOVE)
+                } catch (e: VirtualMachineException) {
+                    Log.e(TAG, "failed to stop a VM instance", e)
+                }
+            }
+            executorService?.shutdownNow()
+            executorService = null
+            virtualMachine = null
+        }
+        super.onDestroy()
+    }
+
+    private fun stopDebianServer() {
+        debianService?.killForwarderHost()
+        server?.shutdown()
+    }
+
+    companion object {
+        private const val EXTRA_NOTIFICATION = "EXTRA_NOTIFICATION"
+        private const val ACTION_START_VM_LAUNCHER_SERVICE =
+            "android.virtualization.START_VM_LAUNCHER_SERVICE"
+
+        const val ACTION_STOP_VM_LAUNCHER_SERVICE: String =
+            "android.virtualization.STOP_VM_LAUNCHER_SERVICE"
+
+        private const val RESULT_START = 0
+        private const val RESULT_STOP = 1
+        private const val RESULT_ERROR = 2
+
+        private fun getMyIntent(context: Context): Intent {
+            return Intent(context.getApplicationContext(), VmLauncherService::class.java)
+        }
+
+        fun run(
+            context: Context,
+            callback: VmLauncherServiceCallback?,
+            notification: Notification?,
+        ) {
+            val i = getMyIntent(context)
+            val resultReceiver: ResultReceiver =
+                object : ResultReceiver(Handler(Looper.myLooper()!!)) {
+                    override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
+                        if (callback == null) {
+                            return
+                        }
+                        when (resultCode) {
+                            RESULT_START -> callback.onVmStart()
+                            RESULT_STOP -> callback.onVmStop()
+                            RESULT_ERROR -> callback.onVmError()
+                        }
+                    }
+                }
+            i.putExtra(Intent.EXTRA_RESULT_RECEIVER, getResultReceiverForIntent(resultReceiver))
+            i.putExtra(EXTRA_NOTIFICATION, notification)
+            context.startForegroundService(i)
+        }
+
+        private fun getResultReceiverForIntent(r: ResultReceiver): ResultReceiver {
+            val parcel = Parcel.obtain()
+            r.writeToParcel(parcel, 0)
+            parcel.setDataPosition(0)
+            return ResultReceiver.CREATOR.createFromParcel(parcel).also { parcel.recycle() }
+        }
+
+        fun stop(context: Context) {
+            val i = getMyIntent(context)
+            i.setAction(ACTION_STOP_VM_LAUNCHER_SERVICE)
+            context.startService(i)
+        }
+    }
+}
diff --git a/android/TerminalApp/res/layout/activity_error.xml b/android/TerminalApp/res/layout/activity_error.xml
index 054478f..c0409a7 100644
--- a/android/TerminalApp/res/layout/activity_error.xml
+++ b/android/TerminalApp/res/layout/activity_error.xml
@@ -51,7 +51,9 @@
         android:layout_marginTop="24dp"
         android:layout_marginHorizontal="60dp"
         android:layout_below="@id/desc"
-        android:textSize="14sp" />
+        android:textSize="14sp"
+        android:scrollbars="vertical|horizontal"
+        android:scrollHorizontally="true" />
 
     <Button
         android:id="@+id/recovery"
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
index b4a65cc..e18aa5c 100644
--- a/android/TerminalApp/res/layout/activity_headless.xml
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -48,7 +48,6 @@
                 android:layout_height="wrap_content"/>
         </LinearLayout>
         <LinearLayout
-            android:id="@+id/webview_container"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_marginBottom="5dp"
@@ -59,7 +58,6 @@
                 android:layout_width="match_parent"
                 android:layout_height="0dp"
                 android:layout_weight="1" />
-            <include layout="@layout/layout_modifier_keys" />
         </LinearLayout>
     </FrameLayout>
 
diff --git a/android/TerminalApp/res/layout/layout_modifier_keys.xml b/android/TerminalApp/res/layout/modifier_keys_doubleline.xml
similarity index 100%
rename from android/TerminalApp/res/layout/layout_modifier_keys.xml
rename to android/TerminalApp/res/layout/modifier_keys_doubleline.xml
diff --git a/android/TerminalApp/res/layout/modifier_keys_singleline.xml b/android/TerminalApp/res/layout/modifier_keys_singleline.xml
new file mode 100644
index 0000000..6ccf48f
--- /dev/null
+++ b/android/TerminalApp/res/layout/modifier_keys_singleline.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+<!--TODO(b/376813452): we might want tablet UI for that-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/modifier_keys"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal" >
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_esc"
+        android:textSize="10sp"
+        android:text="@string/btn_esc_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_tab"
+        android:textSize="10sp"
+        android:text="@string/btn_tab_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_ctrl"
+        android:textSize="10sp"
+        android:text="@string/btn_ctrl_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_alt"
+        android:textSize="10sp"
+        android:text="@string/btn_alt_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_home"
+        android:textSize="10sp"
+        android:text="@string/btn_home_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_end"
+        android:textSize="10sp"
+        android:text="@string/btn_end_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_left"
+        android:textSize="10sp"
+        android:text="@string/btn_left_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_down"
+        android:textSize="10sp"
+        android:text="@string/btn_down_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_up"
+        android:textSize="10sp"
+        android:text="@string/btn_up_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_right"
+        android:textSize="10sp"
+        android:text="@string/btn_right_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_pgdn"
+        android:textSize="10sp"
+        android:text="@string/btn_pgdn_text" />
+    <Button
+        style="@style/ModifierKeyStyle"
+        android:id="@+id/btn_pgup"
+        android:textSize="10sp"
+        android:text="@string/btn_pgup_text" />
+</LinearLayout>
diff --git a/android/TerminalApp/res/values-af/strings.xml b/android/TerminalApp/res/values-af/strings.xml
index af277a3..eae5281 100644
--- a/android/TerminalApp/res/values-af/strings.xml
+++ b/android/TerminalApp/res/values-af/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Poortkontrole"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Laat luisterpoorte toe of weier hulle"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Luisterpoorte"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Het toegelate poorte gestoor"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Het toegelate poorte gestoor"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Voeg by"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Vee <xliff:g id="PORT_NUMBER">%d</xliff:g> uit"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Laat ’n nuwe poort toe"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Voer ’n nuwe poortnommer in"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Stoor"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Kanselleer"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Voer ’n nommer in"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ongeldige poortnommer"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Poort bestaan reeds"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminaal probeer om ’n nuwe poort oop te maak"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Poort versoek: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Aanvaar"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Weier"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Herwin"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Verwyder rugsteundata"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Verwyder <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Onterugstelbare fout"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Kon nie ná ’n fout terugstel nie.\nJy kan probeer om die terminaal te herbegin of een van die herwinningopsies probeer.\nAs alle pogings misluk, vee alle data skoon deur Linux-terminaal in ontwikkelaaropsies aan/af te skakel."</string>
     <string name="error_code" msgid="3585291676855383649">"Foutkode: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Instellings"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminaal loop tans"</string>
diff --git a/android/TerminalApp/res/values-am/strings.xml b/android/TerminalApp/res/values-am/strings.xml
index 5eb23d1..f76eab8 100644
--- a/android/TerminalApp/res/values-am/strings.xml
+++ b/android/TerminalApp/res/values-am/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"የወደብ ቁጥጥር"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"የማዳመጫ ወደቦችን ይፍቀዱ/ይከልክሉ"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"የማዳመጫ ወደቦች"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"የተቀመጡ የሚፈቀዱ ወደቦች"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"አክል"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g>ን ሰርዝ"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"አዲስ ወደብ ይፍቀዱ"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"አዲስ የወደብ ቁጥር ያስገቡ"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"አስቀምጥ"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ይቅር"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"እባክዎ ቁጥር ያስገቡ"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"ልክ ያልሆነ የወደብ ቁጥር"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"ወደብ ቀድሞውኑ ይገኛል"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ተርሚናል አዲስ ወደብ ለመክፈት እየጠየቀ ነው"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"የተጠየቀ ወደብ፦ <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ተቀበል"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ከልክል"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"መልሶ ማግኘት"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ምትኬ ውሂብን አስወግድ"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> አስወግድ"</string>
     <string name="error_title" msgid="405150657301906598">"ሊመለስ የማይችል ስሕተት"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"ከስሕተት መልሶ ማግኘት አልተሳካም።\nተርሚናልን እንደገና ማስጀመርን መሞከር ወይም ከመልሶ ማግኛ አማራጮች አንዱን መሞከር ይችላሉ።\nሁሉም ሙከራዎች ካልተሳኩ Linux ተርሚናልን ከገንቢ አማራጮች ላይ በማብራት/ማጥፋት ሁሉንም ውሂብ ይጥረጉ።"</string>
     <string name="error_code" msgid="3585291676855383649">"የስሕተት ኮድ፦ <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ቅንብሮች"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ተርሚናል በመሄድ ላይ ነው"</string>
diff --git a/android/TerminalApp/res/values-ar/strings.xml b/android/TerminalApp/res/values-ar/strings.xml
index 5f029f5..c01460d 100644
--- a/android/TerminalApp/res/values-ar/strings.xml
+++ b/android/TerminalApp/res/values-ar/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"التحكّم في المنافذ"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"السماح بمنافذ الاستماع أو حظرها"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"منافذ الاستماع"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"المنافذ المسموح بها المحفوظة"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"المنافذ المسموح بها المحفوظة"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"إضافة"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"حذف <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"السماح بمنفذ جديد"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"إدخال رقم منفذ جديد"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"حفظ"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"إلغاء"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"يُرجى إدخال رقم"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"رقم المنفذ غير صالح"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"المنفذ متوفِّر حاليًا"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"تطلُب الوحدة الطرفية فتح منفذ جديد"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"المنفذ المطلوب: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"قبول"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"رفض"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"الاسترداد"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"إزالة بيانات النسخة الاحتياطية"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"إزالة بيانات <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"خطأ غير قابل للإصلاح"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"تعذَّر استرداد البيانات من خطأ.\nيمكنك محاولة إعادة تشغيل الوحدة الطرفية أو تجربة أحد خيارات استرداد الحساب.\nإذا فشلت كل المحاولات، يمكنك حجب كل البيانات من خلال تفعيل وحدة Linux الطرفية أو إيقافها من \"خيارات المطوّرين\"."</string>
     <string name="error_code" msgid="3585291676855383649">"رمز الخطأ: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"الإعدادات"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"الوحدة الطرفية قيد التشغيل"</string>
diff --git a/android/TerminalApp/res/values-as/strings.xml b/android/TerminalApp/res/values-as/strings.xml
index 29630b1..a773fad 100644
--- a/android/TerminalApp/res/values-as/strings.xml
+++ b/android/TerminalApp/res/values-as/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"প’ৰ্ট নিয়ন্ত্ৰণ"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"শুনা প’ৰ্টৰ অনুমতি দিয়ক/অস্বীকাৰ কৰক"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"শুনা প’ৰ্ট"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ছেভ কৰা অনুমোদিত প’ৰ্ট"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ছেভ কৰা অনুমোদিত প’ৰ্ট"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"যোগ দিয়ক"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> মচক"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"এটা নতুন প’ৰ্টৰ অনুমতি দিয়ক"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"এটা নতুন প’ৰ্ট নম্বৰ দিয়ক"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"ছেভ কৰক"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"বাতিল কৰক"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"অনুগ্ৰহ কৰি এটা নম্বৰ দিয়ক"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"অমান্য প’ৰ্টৰ নম্বৰ"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"প’ৰ্ট ইতিমধ্যে আছে"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"টাৰ্মিনেলটোৱে এটা নতুন প’ৰ্ট খুলিবলৈ অনুৰোধ কৰি আছে"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"প\'ৰ্ট অনুৰোধ কৰা হৈছে: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"গ্ৰহণ কৰক"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"অস্বীকাৰ কৰক"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"পুনৰুদ্ধাৰ"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"বেকআপ লোৱা ডেটা আঁতৰাওক"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> আঁতৰাওক"</string>
     <string name="error_title" msgid="405150657301906598">"পুনৰুদ্ধাৰ কৰিব নোৱৰা আসোঁৱাহ"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"এটা আসোঁৱাহৰ পৰা পুনৰুদ্ধাৰ কৰিব পৰা নগ’ল।\nআপুনি টাৰ্মিনেলটো ৰিষ্টাৰ্ট কৰি চাব পাৰে বা পুনৰুদ্ধাৰৰ বিকল্পসমূহৰ মাজৰ পৰা এটা ব্যৱহাৰ কৰি চাব পাৰে।\nযদি আটাইবোৰ প্ৰয়াস বিফল হয়, বিকাশকৰ্তাৰ বিকল্পসমূহৰ পৰা Linux টাৰ্মিনেল অন/অফ কৰি আটাইবোৰ ডেটা মচি পেলাওক।"</string>
     <string name="error_code" msgid="3585291676855383649">"আসোঁৱাহ ক’ড: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ছেটিং"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"টাৰ্মিনেলটো চলি আছে"</string>
diff --git a/android/TerminalApp/res/values-az/strings.xml b/android/TerminalApp/res/values-az/strings.xml
index 371d9ad..0e52500 100644
--- a/android/TerminalApp/res/values-az/strings.xml
+++ b/android/TerminalApp/res/values-az/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Port nəzarəti"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Dinləmə portlarına icazə verin/imtina edin"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Dinləmə portları"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Yadda saxlanılmış icazə verilən portlar"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Əlavə edin"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Silin: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Yeni porta icazə verin"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Yeni port nömrəsi daxil edin"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Yadda saxlayın"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Ləğv edin"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Nömrə daxil edin"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Yanlış port nömrəsi"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port artıq mövcuddur"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal yeni port açmağı tələb edir"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port tələb edildi: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Qəbul edin"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Rədd edin"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Bərpa"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Yedək datanı silin"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Silin: <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Düzəldilməyən xəta"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Xətanı bərpa edilmədi.\nTerminalı yenidən başlatmağa cəhd edə və ya bərpa seçimlərindən birini sınaya bilərsiniz.\nBütün cəhdlər uğursuz olarsa, Linux terminalını developer seçimlərindən yandırıb-söndürməklə bütün datanı silin."</string>
     <string name="error_code" msgid="3585291676855383649">"Xəta kodu: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Ayarlar"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal işləyir"</string>
diff --git a/android/TerminalApp/res/values-b+sr+Latn/strings.xml b/android/TerminalApp/res/values-b+sr+Latn/strings.xml
index 2dddc37..fdbd9c0 100644
--- a/android/TerminalApp/res/values-b+sr+Latn/strings.xml
+++ b/android/TerminalApp/res/values-b+sr+Latn/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kontrola porta"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Dozvolite ili zabranite portove za slušanje"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Portovi za slušanje"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Sačuvani dozvoljeni portovi"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Sačuvani dozvoljeni portovi"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Dodaj"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Izbriši <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Dozvolite novi port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Unesite novi broj porta"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Sačuvaj"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Otkaži"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Unesite broj"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Nevažeći broj porta"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port već postoji"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal traži da otvori novi port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Obavezan port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Prihvati"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Odbij"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Oporavak"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Uklonite rezervnu kopiju"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Uklonite <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Nepopravljiva greška"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Oporavak od greške nije uspeo.\nPokušajte da restartujete terminal ili isprobajte jednu od opcija za vraćanje.\nAko nijedan pokušaj ne uspe, obrišite sve podatke tako što ćete uključiti ili isključiti Linux terminal u opcijama za programere."</string>
     <string name="error_code" msgid="3585291676855383649">"Kôd greške: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Podešavanja"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal je aktivan"</string>
diff --git a/android/TerminalApp/res/values-be/strings.xml b/android/TerminalApp/res/values-be/strings.xml
index 26cae1c..77a6538 100644
--- a/android/TerminalApp/res/values-be/strings.xml
+++ b/android/TerminalApp/res/values-be/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Кіраванне портам"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Дазволіць (адмовіць) доступ да партоў праслухоўвання"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Парты праслухоўвання"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Захаваць дазволеныя парты"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Захаваць дазволеныя парты"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Дадаць"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Выдаліць порт <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Дазволіць доступ да новага порта"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Увядзіце нумар новага порта"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Захаваць"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Скасаваць"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Увядзіце нумар"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Няправільны нумар порта"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Порт ужо існуе"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Тэрмінал запытвае адкрыць новы порт"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Запытаны порт: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Прыняць"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Адмовіць"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Аднаўленне"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Выдаліць даныя рэзервовай копіі"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Выдаліць: <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Непапраўная памылка"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Не ўдалося аднавіць праграму пасля памылкі.\nПеразапусціце тэрмінал ці паспрабуйце скарыстаць адзін з варыянтаў аднаўлення.\nКалі гэтыя спробы не дадуць вынікаў, ачысціце ўсе даныя, уключыўшы (выключыўшы) тэрмінал Linux у параметрах распрацоўшчыка."</string>
     <string name="error_code" msgid="3585291676855383649">"Код памылкі: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Налады"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Тэрмінал запушчаны"</string>
diff --git a/android/TerminalApp/res/values-bg/strings.xml b/android/TerminalApp/res/values-bg/strings.xml
index 7ff9785..d9c16df 100644
--- a/android/TerminalApp/res/values-bg/strings.xml
+++ b/android/TerminalApp/res/values-bg/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Контрол на портовете"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Разрешаване/отхвърляне на портовете за слушане"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Портове за слушане"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Запазени разрешени портове"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Запазени разрешени портове"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Добавяне"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Изтриване на <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Разрешаване на нов порт"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Въведете номера на новия порт"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Запазване"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Отказ"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Моля, въведете номер."</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Невалиден номер на порт"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Портът вече съществува"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Терминалът заявява отварянето на нов порт"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Заявен порт: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Приемам"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Отказ"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Възстановя­ване"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Премахване на резервното копие на данните"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Премахване на <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Непоправима грешка"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Неуспешно възстановяване от грешка.\nМожете да рестартирате терминала или да изпробвате една от опциите за възстановяване.\nАко всички опити са неуспешни, изчистете всички данни, като включите/изключите терминала на Linux от опциите за програмисти."</string>
     <string name="error_code" msgid="3585291676855383649">"Код на грешката: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Настройки"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Терминалът работи"</string>
diff --git a/android/TerminalApp/res/values-bn/strings.xml b/android/TerminalApp/res/values-bn/strings.xml
index 982d98f..b25bc02 100644
--- a/android/TerminalApp/res/values-bn/strings.xml
+++ b/android/TerminalApp/res/values-bn/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"পোর্ট কন্ট্রোল"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"লিসিনিং পোর্টের অনুমতি দিন/অনুমতি দেবেন না"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"লিসিনিং পোর্ট"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"অনুমতি দেওয়া পোর্ট সেভ করা হয়েছে"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"যোগ করুন"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> মুছুন"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"নতুন পোর্টের অনুমতি দিন"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"নতুন পোর্ট নম্বর লিখুন"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"সেভ করুন"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"বাতিল করুন"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"কোনও নম্বর লিখুন"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"পোর্ট নম্বর ভুল আছে"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"পোর্ট আগে থেকেই রয়েছে"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"টার্মিনাল নতুন পোর্ট খোলার অনুরোধ করছে"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"পোর্ট ফরওয়ার্ড করা সম্পর্কে অনুরোধ করা হয়েছে: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"সম্মতি দিন"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"বাতিল করুন"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"আগের অবস্থায় ফেরানো"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ব্যাকআপ ডেটা সরান"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> সরান"</string>
     <string name="error_title" msgid="405150657301906598">"ডেটা ফিরিয়ে আনা যাবে না এমন সমস্যা"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"এই সমস্যার জন্য ডেটা আগের অবস্থায় ফেরানো যায়নি।\nআপনি টার্মিনাল রিস্টার্ট করে বা অ্যাকাউন্ট ফিরিয়ে আনার বিকল্পের মধ্যে কোনও একটি ব্যবহার করে দেখতে পারেন। \nসব প্রচেষ্টা ব্যর্থ হলে, ডেভেলপার বিকল্প থেকে Linux টার্মিনাল চালু/বন্ধ করার মাধ্যমে সব ডেটা ওয়াইপ করুন।"</string>
     <string name="error_code" msgid="3585291676855383649">"এরর কোড: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"সেটিংস"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"টার্মিনাল চলছে"</string>
diff --git a/android/TerminalApp/res/values-bs/strings.xml b/android/TerminalApp/res/values-bs/strings.xml
index 08180ef..8dcfe6f 100644
--- a/android/TerminalApp/res/values-bs/strings.xml
+++ b/android/TerminalApp/res/values-bs/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Upravljanje priključkom"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Dozvoli/odbij priključke za slušanje"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Priključci za slušanje"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Sačuvani dozvoljeni priključci"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
-    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Izbriši priključak <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Sačuvani dozvoljeni priključci"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Dodavanje"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Brisanje priključka <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Dozvolite novi priključak"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Unesite broj novog priključka"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Sačuvaj"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Otkaži"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Unesite broj"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Nevažeći broj priključka"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Priključak već postoji"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal zahtijeva otvaranje novog priključka"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Zatražen je priključak: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Prihvati"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Odbij"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Oporavak"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Ukloni podatke sigurnosne kopije"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Ukloni <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Nepopravljiva greška"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Oporavak od greške nije uspio.\nMožete ponovo pokrenuti terminal ili isprobati jednu od opcija za oporavak.\nAko svi pokušaji ne uspiju, uništite sve podatke uključivanjem/isključivanjem Linux terminala u opcijama za programere."</string>
     <string name="error_code" msgid="3585291676855383649">"Kȏd greške: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Postavke"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal je pokrenut"</string>
diff --git a/android/TerminalApp/res/values-ca/strings.xml b/android/TerminalApp/res/values-ca/strings.xml
index d8fb9eb..0737ffe 100644
--- a/android/TerminalApp/res/values-ca/strings.xml
+++ b/android/TerminalApp/res/values-ca/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Control de ports"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Permet o denega els ports d\'escolta"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Ports d\'escolta"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Ports permesos desats"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Afegeix"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Suprimeix <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Permet un port nou"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Introdueix un número de port nou"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Desa"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancel·la"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Introdueix un número"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"El número de port no és vàlid"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"El port ja existeix"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"El terminal està sol·licitant obrir un port nou"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port sol·licitat: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accepta"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Denega"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recuperació"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Suprimeix les dades de la còpia de seguretat"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Suprimeix <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Error irrecuperable"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"No s\'ha pogut recuperar després de l\'error.\nPots provar de reiniciar el terminal o provar una de les opcions de recuperació.\nSi tots els intents fallen, suprimeix totes les dades activant o desactivant el terminal de Linux des de les opcions per a desenvolupadors."</string>
     <string name="error_code" msgid="3585291676855383649">"Codi d\'error: <xliff:g id="ERROR_CODE">%s</xliff:g>."</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Configuració"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"El terminal s\'està executant"</string>
diff --git a/android/TerminalApp/res/values-cs/strings.xml b/android/TerminalApp/res/values-cs/strings.xml
index 872da07..2ab202e 100644
--- a/android/TerminalApp/res/values-cs/strings.xml
+++ b/android/TerminalApp/res/values-cs/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Ovládání portů"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Povolit/zakázat naslouchající porty"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Naslouchající porty"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Uložené povolené porty"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Uložené povolené porty"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Přidat"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Smazat port <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Povolení nového portu"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Zadejte nové číslo portu"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Uložit"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Zrušit"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Zadejte číslo"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Neplatné číslo portu"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port už existuje"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminál se pokouší otevřít nový port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Požadovaný port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Přijmout"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Zamítnout"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Obnovení"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Odstranit data zálohy"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Odstranit <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Neopravitelná chyba"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Z chybového stavu se nepodařilo dostat.\nMůžete terminál zkusit restartovat, nebo vyzkoušet některou z možností obnovení.\nPokud všechny pokusy selžou, nevratně smažte všechna data tím, že v možnostech pro vývojáře zapnete/vypnete terminál Linux."</string>
     <string name="error_code" msgid="3585291676855383649">"Kód chyby: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Nastavení"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminál běží"</string>
diff --git a/android/TerminalApp/res/values-da/strings.xml b/android/TerminalApp/res/values-da/strings.xml
index 3a86899..6ef382f 100644
--- a/android/TerminalApp/res/values-da/strings.xml
+++ b/android/TerminalApp/res/values-da/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portstyring"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Tillad/afvis aktive porte"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Aktive porte"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Gemte tilladte porte"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Tilføj"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Slet <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Tillad en ny port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Angiv et nyt portnummer"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Gem"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Annuller"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Angiv et nummer"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ugyldigt portnummer"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Porten findes allerede"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminalen anmoder om at åbne en ny port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port, der anmodes om: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Acceptér"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Afvis"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Gendannelse"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Fjern data for sikkerhedskopi"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Fjern <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Uoprettelig fejl"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Gendannelse efter fejl mislykkedes.\nDu kan prøve at genstarte terminalen eller prøve en af gendannelsesmulighederne.\nHvis alle forsøg mislykkes, skal du rydde alle data ved at slå Linux-terminalen til/fra i indstillingerne for udviklere."</string>
     <string name="error_code" msgid="3585291676855383649">"Fejlkode: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Indstillinger"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminalen kører"</string>
diff --git a/android/TerminalApp/res/values-de/strings.xml b/android/TerminalApp/res/values-de/strings.xml
index 5eda1b6..deb3db3 100644
--- a/android/TerminalApp/res/values-de/strings.xml
+++ b/android/TerminalApp/res/values-de/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portsteuerung"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Überwachungsports zulassen / ablehnen"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Überwachungsports"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Gespeicherte zulässige Ports"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Gespeicherte zulässige Ports"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Hinzufügen"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"„<xliff:g id="PORT_NUMBER">%d</xliff:g>“ löschen"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Neuen Port zulassen"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Neue Portnummer eingeben"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Speichern"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Abbrechen"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Bitte gib eine Zahl ein"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ungültige Portnummer"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port ist bereits vorhanden"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal fordert an, einen neuen Port zu öffnen"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Angeforderter Port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Akzeptieren"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Ablehnen"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Wieder­herstellung"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Sicherungsdaten entfernen"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"„<xliff:g id="PATH">/mnt/backup</xliff:g>“ entfernen"</string>
     <string name="error_title" msgid="405150657301906598">"Nicht behebbarer Fehler"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Fehler konnte nicht behoben werden.\nDu kannst versuchen, das Terminal neu zu starten, oder eine der Wiederherstellungsoptionen ausprobieren.\nWenn alle Versuche fehlschlagen, lösche sämtliche Daten, indem du das Linux-Terminal in den Entwickleroptionen aktivierst und wieder deaktivierst."</string>
     <string name="error_code" msgid="3585291676855383649">"Fehlercode: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Einstellungen"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal wird ausgeführt"</string>
diff --git a/android/TerminalApp/res/values-el/strings.xml b/android/TerminalApp/res/values-el/strings.xml
index ff1bc81..78cf235 100644
--- a/android/TerminalApp/res/values-el/strings.xml
+++ b/android/TerminalApp/res/values-el/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Έλεγχος θυρών"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Να επιτρέπονται/μην επιτρέπονται οι θύρες ακρόασης"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Θύρες ακρόασης"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Αποθηκευμένες επιτρεπόμενες θύρες"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Αποθηκευμένες επιτρεπόμενες θύρες"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Προσθήκη"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Διαγραφή θύρας <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Να επιτρέπεται νέα θύρα"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Καταχωρίστε νέο αριθμό θύρας"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Αποθήκευση"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Ακύρωση"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Εισαγάγετε έναν αριθμό"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Μη έγκυρος αριθμός θύρας"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Η θύρα υπάρχει ήδη"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Το τερματικό ζητά να ανοίξει μια νέα θύρα"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Ζητήθηκε θύρα: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Αποδοχή"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Απόρριψη"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Ανάκτηση"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Κατάργηση δεδομένων αντιγράφου ασφαλείας"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Κατάργηση <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Ανεπανόρθωτο σφάλμα"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Αποτυχία ανάκτησης από σφάλμα.\nΜπορείτε να δοκιμάσετε να επανεκκινήσετε το τερματικό ή να δοκιμάσετε μια από τις επιλογές ανάκτησης.\nΕάν όλες οι προσπάθειες αποτύχουν, κάντε εκκαθάριση όλων των δεδομένων ενεργοποιώντας/απενεργοποιώντας το τερματικό Linux από τις επιλογές για προγραμματιστές."</string>
     <string name="error_code" msgid="3585291676855383649">"Κωδικός σφάλματος: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Ρυθμίσεις"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Το τερματικό εκτελείται"</string>
diff --git a/android/TerminalApp/res/values-en-rAU/strings.xml b/android/TerminalApp/res/values-en-rAU/strings.xml
index 01df64e..805257f 100644
--- a/android/TerminalApp/res/values-en-rAU/strings.xml
+++ b/android/TerminalApp/res/values-en-rAU/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Port control"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Allow/deny listening ports"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Listening ports"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Saved allowed ports"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Add"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Delete <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Allow a new port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Enter a new port number"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Save"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancel"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Please enter a number"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Invalid port number"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port already exists"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal is requesting to open a new port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port requested: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accept"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Deny"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recovery"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Remove backup data"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Remove <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Unrecoverable error"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Failed to recover from an error.\nYou can try restarting terminal or try one of the recovery options.\nIf all attempts fail, wipe all data by turning on/off Linux terminal from developer options."</string>
     <string name="error_code" msgid="3585291676855383649">"Error code: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Settings"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal is running"</string>
diff --git a/android/TerminalApp/res/values-en-rCA/strings.xml b/android/TerminalApp/res/values-en-rCA/strings.xml
index c02591c..f208e38 100644
--- a/android/TerminalApp/res/values-en-rCA/strings.xml
+++ b/android/TerminalApp/res/values-en-rCA/strings.xml
@@ -48,6 +48,7 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Port control"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Allow/deny listening ports"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Listening ports"</string>
+    <string name="settings_port_forwarding_active_ports_content" msgid="1818090784030797758">"<xliff:g id="PORT_NUMBER">%1$d</xliff:g> (<xliff:g id="PROCESS_NAME">%2$s</xliff:g>)"</string>
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Saved allowed ports"</string>
     <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Add"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Delete <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
@@ -59,7 +60,7 @@
     <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Invalid port number"</string>
     <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port already exists"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal is requesting to open a new port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port requested: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <string name="settings_port_forwarding_notification_content" msgid="779450349212040908">"Port requested: <xliff:g id="PORT_NUMBER">%1$d</xliff:g> (<xliff:g id="PROCESS_NAME">%2$s</xliff:g>)"</string>
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accept"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Deny"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recovery"</string>
diff --git a/android/TerminalApp/res/values-en-rGB/strings.xml b/android/TerminalApp/res/values-en-rGB/strings.xml
index 01df64e..805257f 100644
--- a/android/TerminalApp/res/values-en-rGB/strings.xml
+++ b/android/TerminalApp/res/values-en-rGB/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Port control"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Allow/deny listening ports"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Listening ports"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Saved allowed ports"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Add"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Delete <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Allow a new port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Enter a new port number"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Save"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancel"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Please enter a number"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Invalid port number"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port already exists"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal is requesting to open a new port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port requested: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accept"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Deny"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recovery"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Remove backup data"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Remove <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Unrecoverable error"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Failed to recover from an error.\nYou can try restarting terminal or try one of the recovery options.\nIf all attempts fail, wipe all data by turning on/off Linux terminal from developer options."</string>
     <string name="error_code" msgid="3585291676855383649">"Error code: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Settings"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal is running"</string>
diff --git a/android/TerminalApp/res/values-en-rIN/strings.xml b/android/TerminalApp/res/values-en-rIN/strings.xml
index 01df64e..805257f 100644
--- a/android/TerminalApp/res/values-en-rIN/strings.xml
+++ b/android/TerminalApp/res/values-en-rIN/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Port control"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Allow/deny listening ports"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Listening ports"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Saved allowed ports"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Add"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Delete <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Allow a new port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Enter a new port number"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Save"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancel"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Please enter a number"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Invalid port number"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port already exists"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal is requesting to open a new port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port requested: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accept"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Deny"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recovery"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Remove backup data"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Remove <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Unrecoverable error"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Failed to recover from an error.\nYou can try restarting terminal or try one of the recovery options.\nIf all attempts fail, wipe all data by turning on/off Linux terminal from developer options."</string>
     <string name="error_code" msgid="3585291676855383649">"Error code: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Settings"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal is running"</string>
diff --git a/android/TerminalApp/res/values-es-rUS/strings.xml b/android/TerminalApp/res/values-es-rUS/strings.xml
index 50ad822..ba758ac 100644
--- a/android/TerminalApp/res/values-es-rUS/strings.xml
+++ b/android/TerminalApp/res/values-es-rUS/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Control del puerto"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Permitir o denegar los puertos de escucha"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Puertos de escucha"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Puertos permitidos guardados"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Puertos permitidos guardados"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Agregar"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Borrar <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Permite un puerto nuevo"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Ingresa un nuevo número de portabilidad"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Guardar"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancelar"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Ingresa un número"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"El número de puerto no es válido"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"El puerto ya existe"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"La terminal está solicitando abrir un puerto nuevo"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Puerto solicitado: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Aceptar"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Rechazar"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recuperación"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Quitar datos de copia de seguridad"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Quitar <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Error irrecuperable"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"No se pudo recuperar después del error.\nPuedes reiniciar la terminal o probar una de las opciones de recuperación.\nSi estos intentos fallan, borra todos los datos activando o desactivando la terminal de Linux desde las opciones para desarrolladores."</string>
     <string name="error_code" msgid="3585291676855383649">"Código de error: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Configuración"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Se está ejecutando la terminal"</string>
diff --git a/android/TerminalApp/res/values-es/strings.xml b/android/TerminalApp/res/values-es/strings.xml
index 3088438..5a2629c 100644
--- a/android/TerminalApp/res/values-es/strings.xml
+++ b/android/TerminalApp/res/values-es/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Control de puerto"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Permitir/Denegar puertos de escucha"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Puertos de escucha"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Puertos permitidos guardados"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Añadir"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Eliminar <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Permitir un nuevo puerto"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Introduce un nuevo número de puerto"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Guardar"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancelar"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Introduce un número"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Número de puerto no válido"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"El puerto ya existe"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"El terminal está solicitando abrir un nuevo puerto"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Puerto solicitado: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Aceptar"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Denegar"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recuperación"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Eliminar datos de copia de seguridad"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Eliminar <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Error irrecuperable"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"No se ha podido recuperar después del error.\nPuedes intentar reiniciar el terminal o probar una de las opciones de recuperación.\nSi todos los intentos fallan, borra todos los datos activando o desactivando el terminal de Linux en las opciones para desarrolladores."</string>
     <string name="error_code" msgid="3585291676855383649">"Código de error: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Ajustes"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"El terminal se está ejecutando"</string>
diff --git a/android/TerminalApp/res/values-et/strings.xml b/android/TerminalApp/res/values-et/strings.xml
index 731f02f..e64729c 100644
--- a/android/TerminalApp/res/values-et/strings.xml
+++ b/android/TerminalApp/res/values-et/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portide haldamine"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Kuulamisportide lubamine/keelamine"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Kuulamispordid"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Salvestatud lubatud pordid"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Salvestatud lubatud pordid"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Lisa"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Kustuta <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Luba uus port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Sisestage uus pordi number"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Salvesta"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Tühista"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Sisestage number"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Sobimatu pordi number"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port on juba olemas"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal taotleb uue pordi avamist"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Taotletud port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Nõustu"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Keela"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Taastamine"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Varundusandmete eemaldamine"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Tee <xliff:g id="PATH">/mnt/backup</xliff:g> eemaldamine"</string>
     <string name="error_title" msgid="405150657301906598">"Taastamatu viga"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Veast taastumine ebaõnnestus.\nVõite proovida terminali taaskäivitada või proovida ühte taastevalikutest.\nKui kõik katsed ebaõnnestuvad, muutke kõik andmed loetamatuks, lülitades Linuxi terminali arendaja valikute kaudu sisse/välja."</string>
     <string name="error_code" msgid="3585291676855383649">"Veakood: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Seaded"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal töötab"</string>
diff --git a/android/TerminalApp/res/values-eu/strings.xml b/android/TerminalApp/res/values-eu/strings.xml
index 3069154..fde1d1d 100644
--- a/android/TerminalApp/res/values-eu/strings.xml
+++ b/android/TerminalApp/res/values-eu/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Ataka kontrolatzeko aukerak"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Onartu/Baztertu ataka aktiboak"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Ataka aktiboak"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Gorde dira onartutako atakak"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Gehitu"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Ezabatu <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Onartu beste ataka bat"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Idatzi beste ataka-zenbaki bat"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Gorde"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Utzi"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Idatzi zenbaki bat"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ataka-zenbakiak ez du balio"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Ataka jada badago"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminalak beste ataka bat irekitzeko eskatu du"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Ataka hau eskatu da: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Onartu"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Ukatu"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Berreskuratzea"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Kendu babeskopien datuak"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Kendu <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Leheneratu ezin den errorea"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Ezin izan da leheneratu errorea.\nBerrabiarazi terminala edo probatu leheneratzeko aukeretako bat.\nSaiakera guztiek huts egiten badute, xahutu datu guztiak garatzaileentzako aukeren bidez Linux-en terminala aktibatuta/desaktibatuta."</string>
     <string name="error_code" msgid="3585291676855383649">"Errore-kodea: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Ezarpenak"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminala abian da"</string>
diff --git a/android/TerminalApp/res/values-fa/strings.xml b/android/TerminalApp/res/values-fa/strings.xml
index 80c90d1..b040c24 100644
--- a/android/TerminalApp/res/values-fa/strings.xml
+++ b/android/TerminalApp/res/values-fa/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"کنترل درگاه"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"مجاز/ رد کردن درگاه‌های گوش کردن"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"درگاه‌های گوش کردن"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"درگاه‌های مجاز ذخیره شدند"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"درگاه‌های مجاز ذخیره شدند"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"افزودن"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"حذف کردن <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"مجاز کردن درگاهی جدید"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"شماره درگاه جدیدی را وارد کنید"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"ذخیره"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"لغو"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"لطفاً شماره‌ای را وارد کنید"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"شماره درگاه نامعتبر است"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"درگاه ازقبل موجود است"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"پایانه می‌خواهد درگاه جدیدی باز کند"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"درگاه درخواست‌شده: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"پذیرفتن"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"رد کردن"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"بازیابی"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"حذف داده‌های پشتیبان"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"حذف <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"خطای غیرقابل‌بازیابی"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"بازیابی از خطا ممکن نبود.\nمی‌توانید پایانه را بازراه‌اندازی کنید یا یکی از گزینه‌های بازیابی را امتحان کنید.\nاگر همه تلاش‌ها ناموفق بود، همه داده‌ها را با روشن/خاموش کردن پایانه Linux از گزینه‌های توسعه‌دهندگان محو کنید."</string>
     <string name="error_code" msgid="3585291676855383649">"کد خطا: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"تنظیمات"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"پایانه درحال اجرا است"</string>
diff --git a/android/TerminalApp/res/values-fi/strings.xml b/android/TerminalApp/res/values-fi/strings.xml
index ea4b877..5dcfadf 100644
--- a/android/TerminalApp/res/values-fi/strings.xml
+++ b/android/TerminalApp/res/values-fi/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Porttien ohjaus"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Salli/kiellä kuunteluportit"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Kuunteluportit"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Tallennetut sallitut portit"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Lisää"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Poista <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Salli uusi portti"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Lisää uusi porttinumero"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Tallenna"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Peru"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Lisää numero"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Virheellinen portin numero"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Portti on jo olemassa"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Pääte yrittää avata uuden portin"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Pyydetty portti: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Hyväksy"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Hylkää"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Palautus"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Poista varmuuskopiodata"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Poista <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Peruuttamaton virhe"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Virheen korjaaminen epäonnistui.\nVoit yrittää käynnistää päätelaitteen uudelleen tai kokeilla jotakin korjausvaihtoehtoa.\nJos kaikki yritykset epäonnistuvat, pyyhi kaikki data laittamalla Linux-päätelaite päälle ja pois päältä kehittäjäasetuksista."</string>
     <string name="error_code" msgid="3585291676855383649">"Virhekoodi: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Asetukset"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Pääte on käynnissä"</string>
diff --git a/android/TerminalApp/res/values-fr-rCA/strings.xml b/android/TerminalApp/res/values-fr-rCA/strings.xml
index 19ffcc6..a320c62 100644
--- a/android/TerminalApp/res/values-fr-rCA/strings.xml
+++ b/android/TerminalApp/res/values-fr-rCA/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Contrôle du port"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Autoriser/Refuser les ports en mode Réception"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Ports en mode Réception"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Ports autorisés enregistrés"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Ports autorisés enregistrés"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Ajouter"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Supprimer le port <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Autoriser un nouveau port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Entrez un nouveau numéro de port"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Enregistrer"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Annuler"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Veuillez entrer un numéro"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Numéro de port incorrect"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Le port existe déjà"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Le terminal demande d\'ouvrir un nouveau port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port demandé : <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accepter"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Refuser"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Récupération"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Retirer les données de sauvegarde"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Retirez <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Erreur irrécupérable"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Échec de la récupération suite à une erreur.\nVous pouvez essayer de redémarrer le terminal ou essayer l\'une des options de récupération.\nSi toutes les tentatives échouent, effacez toutes les données en activant/désactivant le terminal Linux à partir des options pour les développeurs."</string>
     <string name="error_code" msgid="3585291676855383649">"Code d\'erreur : <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Paramètres"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Le terminal fonctionne"</string>
diff --git a/android/TerminalApp/res/values-fr/strings.xml b/android/TerminalApp/res/values-fr/strings.xml
index a258b9b..edb91b4 100644
--- a/android/TerminalApp/res/values-fr/strings.xml
+++ b/android/TerminalApp/res/values-fr/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Contrôle de port"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Autoriser/refuser les ports d\'écoute"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Ports d\'écoute"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Ports autorisés enregistrés"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Ajouter"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Supprimer <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Autoriser un nouveau port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Saisissez le numéro du nouveau port"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Enregistrer"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Annuler"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Veuillez saisir un numéro"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Numéro de port incorrect"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Le port existe déjà"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Le terminal demande l\'ouverture d\'un nouveau port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port demandé : <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accepter"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Refuser"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Récupération"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Supprimer les données de sauvegarde"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Supprimer <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Erreur irrécupérable"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Échec de la récupération après une erreur.\nVous pouvez essayer de redémarrer le terminal ou d\'utiliser l\'une des options de récupération.\nSi toutes les tentatives échouent, effacez toutes les données en activant/désactivant le terminal Linux à partir des options pour les développeurs."</string>
     <string name="error_code" msgid="3585291676855383649">"Code d\'erreur : <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Paramètres"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal en cours d\'exécution"</string>
diff --git a/android/TerminalApp/res/values-gl/strings.xml b/android/TerminalApp/res/values-gl/strings.xml
index dfd6564..c1d4f82 100644
--- a/android/TerminalApp/res/values-gl/strings.xml
+++ b/android/TerminalApp/res/values-gl/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Control de portos"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Permitir ou rexeitar portos de escoita"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Portos de escoita"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Gardáronse os portos permitidos"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Gardáronse os portos permitidos"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Engadir"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Eliminar <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Permitir outro porto"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Mete outro número de porto"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Gardar"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancelar"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Pon un número"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"O número de porto non é válido"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Xa existe o porto"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"O terminal está solicitando que se abra outro porto"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Porto solicitado: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Aceptar"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Denegar"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recuperación"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Quitar datos da copia de seguranza"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Quita <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Produciuse un erro que impide a recuperación"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Non se puido recuperar a información despois dun erro.\nPodes probar a reiniciar o terminal ou tentar usar unha das opcións de recuperación.\nSe non se soluciona o problema, activa e desactiva o terminal de Linux nas opcións de programación para borrar todos os datos."</string>
     <string name="error_code" msgid="3585291676855383649">"Código de erro: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Configuración"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"O terminal está en funcionamento"</string>
diff --git a/android/TerminalApp/res/values-gu/strings.xml b/android/TerminalApp/res/values-gu/strings.xml
index f759538..b1a452f 100644
--- a/android/TerminalApp/res/values-gu/strings.xml
+++ b/android/TerminalApp/res/values-gu/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"પોર્ટ નિયંત્રણ"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"સાંભળનાર પોર્ટને મંજૂરી આપો/નકારો"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"સાંભળનાર પોર્ટ"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"સાચવેલા મંજૂરીપ્રાપ્ત પોર્ટ"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"ઉમેરો"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> ડિલીટ કરો"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"નવા પોર્ટને મંજૂરી આપો"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"નવો પોર્ટ નંબર દાખલ કરો"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"સાચવો"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"રદ કરો"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"કૃપા કરીને કોઈ નંબર દાખલ કરો"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"પોર્ટ નંબર અમાન્ય છે"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"પોર્ટ પહેલેથી અસ્તિત્વમાં છે"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ટર્મિનલ નવું પોર્ટ ખોલવા માટે વિનંતી કરી રહ્યું છે"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"પોર્ટની વિનંતી કરવામાં આવી: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"સ્વીકારો"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"નકારો"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"રિકવરી"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"બૅકઅપ ડેટા કાઢી નાખો"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> કાઢી નાખો"</string>
     <string name="error_title" msgid="405150657301906598">"ભૂલને કારણે રિકવર કરવો અશક્ય"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"કોઈ ભૂલમાંથી રિકવર કરવામાં નિષ્ફળ રહ્યાં.\nતમે ટર્મિનલને ફરી શરૂ કરવાનો પ્રયાસ કરી શકો છો અથવા રિકવરીના વિકલ્પોમાંનો કોઈ એક વિકલ્પ અજમાવી શકો છો.\nજો બધા પ્રયાસો નિષ્ફળ જાય, તો ડેવલપરના વિકલ્પોમાંથી Linux ટર્મિનલને ચાલુ/બંધ કરીને બધો ડેટા વાઇપ કરો."</string>
     <string name="error_code" msgid="3585291676855383649">"ભૂલનો કોડ: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"સેટિંગ"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ટર્મિનલ ચાલી રહ્યું છે"</string>
diff --git a/android/TerminalApp/res/values-hi/strings.xml b/android/TerminalApp/res/values-hi/strings.xml
index 392f739..af2ddaa 100644
--- a/android/TerminalApp/res/values-hi/strings.xml
+++ b/android/TerminalApp/res/values-hi/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"पोर्ट कंट्रोल"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"लिसनिंग पोर्ट को अनुमति दें या अनुमति न दें"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"लिसनिंग पोर्ट"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"अनुमति पा चुके सभी पोर्ट को सेव किया गया"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"अनुमति पा चुके सभी पोर्ट को सेव किया गया"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"जोड़ें"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> को मिटाएं"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"नए पोर्ट को अनुमति दें"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"नया पोर्ट नंबर डालें"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"सेव करें"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"रद्द करें"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"कृपया कोई नंबर डालें"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"अमान्य पोर्ट संख्या"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"पोर्ट पहले से मौजूद है"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"टर्मिनल, एक नया पोर्ट खोलने का अनुरोध कर रहा है"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"पोर्ट खोलने का अनुरोध किया गया: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"स्वीकार करें"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"अस्वीकार करें"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"इमेज रिकवर करें"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"बैकअप डेटा हटाएं"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> को हटाएं"</string>
     <string name="error_title" msgid="405150657301906598">"ऐसी गड़बड़ी जिसकी वजह से डेटा वापस नहीं पाया जा सकता"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"किसी गड़बड़ी की वजह से डेटा वापस नहीं पाया जा सका.\nटर्मिनल को रीस्टार्ट करके देखें या डेटा वापस पाने के किसी एक विकल्प को आज़माएं.\nअगर सभी कोशिशें बेकार हो जाती हैं, तो \'डेवलपर के लिए सेटिंग और टूल\' से Linux टर्मिनल को चालू/बंद करके, सारा डेटा मिटा दें."</string>
     <string name="error_code" msgid="3585291676855383649">"गड़बड़ी का कोड: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"सेटिंग"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"टर्मिनल चालू है"</string>
diff --git a/android/TerminalApp/res/values-hr/strings.xml b/android/TerminalApp/res/values-hr/strings.xml
index 8dd01db..87d4798 100644
--- a/android/TerminalApp/res/values-hr/strings.xml
+++ b/android/TerminalApp/res/values-hr/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kontrola priključka"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Dopusti/odbij priključke za slušanje"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Priključci za slušanje"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Spremljeni dopušteni priključci"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Spremljeni dopušteni priključci"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Dodaj"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Izbriši priključak <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Dopusti novi priključak"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Unesite novi broj priključka"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Spremi"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Odustani"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Unesite broj"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Nevažeći broj priključka"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Priključak već postoji"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal zahtijeva da se otvori novi priključak"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Zatraženi priključak: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Prihvati"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Odbij"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Oporavak"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Ukloni podatke sigurnosne kopije"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Uklanjanje puta <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Nepopravljiva pogreška"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Oporavak od pogreške nije uspio.\nMožete pokušati ponovo pokrenuti terminal ili isprobajte jednu od opcija oporavka.\nAko nijedna metoda ne uspije, izbrišite sve podatke tako da uključite/isključite Linux terminal u opcijama za razvojne programere."</string>
     <string name="error_code" msgid="3585291676855383649">"Kôd pogreške: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Postavke"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal je pokrenut"</string>
diff --git a/android/TerminalApp/res/values-hu/strings.xml b/android/TerminalApp/res/values-hu/strings.xml
index 041d0fe..e1b17e6 100644
--- a/android/TerminalApp/res/values-hu/strings.xml
+++ b/android/TerminalApp/res/values-hu/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portvezérlés"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Figyelő portok engedélyezése/letiltása"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Figyelő portok"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Mentett engedélyezett portok"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Hozzáadás"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> törlése"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Új port engedélyezése"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Adja meg az új portszámot"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Mentés"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Mégse"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Adjon meg egy számot"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Érvénytelen a portszám"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"A port már létezik"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"A terminál új port megnyitását kéri"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Kért port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Elfogadás"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Elutasítás"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Helyreállítás"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Biztonsági másolat adatainak eltávolítása"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> eltávolítása"</string>
     <string name="error_title" msgid="405150657301906598">"Helyrehozhatatlan hiba"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Nem sikerült a hiba utáni helyreállítás.\nPróbálkozhat a terminál újraindításával, vagy kipróbálhatja valamelyik helyreállítási lehetőséget.\nHa minden próbálkozás sikertelen, a fejlesztői beállítások közötti Linux-terminál be-/kikapcsolásával visszaállíthatatlanul törölheti az összes adatot."</string>
     <string name="error_code" msgid="3585291676855383649">"Hibakód: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Beállítások"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"A terminál fut"</string>
diff --git a/android/TerminalApp/res/values-hy/strings.xml b/android/TerminalApp/res/values-hy/strings.xml
index cf4b897..06ae4cb 100644
--- a/android/TerminalApp/res/values-hy/strings.xml
+++ b/android/TerminalApp/res/values-hy/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Միացքների կառավարում"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Թույլատրել/մերժել ունկնդրման միացքները"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Ունկնդրման միացքներ"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Պահված թույլատրված միացքներ"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Պահված թույլատրված միացքներ"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Ավելացնել"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Ջնջել <xliff:g id="PORT_NUMBER">%d</xliff:g> համարի միացքը"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Թույլատրել նոր միացք"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Մուտքագրեք նոր միացքի համարը"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Պահել"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Չեղարկել"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Մուտքագրեք համարը"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Միացքի անվավեր համար"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Միացքն արդեն գոյություն ունի"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Տերմինալը խնդրում է նոր միացք բացել"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Հարցված միացքը՝ <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Ընդունել"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Մերժել"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Վերականգ­նում"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Հեռացնել պահուստավորված տվյալները"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Հեռացնել <xliff:g id="PATH">/mnt/backup</xliff:g>ը"</string>
     <string name="error_title" msgid="405150657301906598">"Հնարավոր չէ վերականգնել"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Չհաջողվեց վերացնել սխալը։\nՎերագործարկեք տերմինալը կամ փորձեք վերականգնման տարբերակներից մեկը։\nԵթե բոլոր փորձերը ձախողվեն, մաքրեք բոլոր տվյալները՝ միացնելով/անջատելով Լինուքս տերմինալը մշակողի ընտրանքներից։"</string>
     <string name="error_code" msgid="3585291676855383649">"Սխալի կոդը՝ <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Կարգավորումներ"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Տերմինալն աշխատում է"</string>
diff --git a/android/TerminalApp/res/values-in/strings.xml b/android/TerminalApp/res/values-in/strings.xml
index 77ec70c..1a844b1 100644
--- a/android/TerminalApp/res/values-in/strings.xml
+++ b/android/TerminalApp/res/values-in/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kontrol port"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Izinkan/tolak port yang sedang dalam proses"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Port yang sedang dalam proses"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Port yang diizinkan tersimpan"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Tambahkan"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Hapus <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Izinkan port baru"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Masukkan nomor port baru"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Simpan"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Batal"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Masukkan nomor"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Nomor port tidak valid"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port sudah ada"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal meminta untuk membuka port baru"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port yang diminta: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Terima"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Tolak"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Pemulihan"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Hapus data cadangan"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Hapus <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Error yang tidak dapat dipulihkan"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Gagal memulihkan dari error.\nAnda dapat mencoba memulai ulang terminal atau mencoba salah satu opsi pemulihan.\nJika semua upaya gagal, hapus semua data dengan mengaktifkan/menonaktifkan terminal Linux dari opsi developer."</string>
     <string name="error_code" msgid="3585291676855383649">"Kode error: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Setelan"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal sedang berjalan"</string>
diff --git a/android/TerminalApp/res/values-is/strings.xml b/android/TerminalApp/res/values-is/strings.xml
index 29aa82e..638da38 100644
--- a/android/TerminalApp/res/values-is/strings.xml
+++ b/android/TerminalApp/res/values-is/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Gáttarstýring"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Leyfa hlustunargáttir/hafna hlustunargáttum"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Hlustunargáttir"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Vistaðar leyfðar gáttir"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Bæta við"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Eyða <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Leyfa nýja gátt"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Slá inn nýtt gáttarnúmer"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Vista"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Hætta við"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Sláðu inn númer"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ógilt númer tengis"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Tengi er þegar til"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Útstöðin bað um að opna nýja gátt"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Beiðni um gátt: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Samþykkja"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Hafna"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Endurheimt"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Fjarlægja afrituð gögn"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Fjarlægja <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Óleiðréttanleg villa"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Ekki tókst að endurheimta eftir villu.\nÞú getur prófað að endurræsa útstöðina eða velja einn af endurheimtarkostunum.\nEf allar tilraunir mistakast skaltu hreinsa öll gögn með því að kveikja/slökkva á Linux-útstöð í forritunarvalkostum."</string>
     <string name="error_code" msgid="3585291676855383649">"Villukóði: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Stillingar"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Útstöð er í gangi"</string>
diff --git a/android/TerminalApp/res/values-it/strings.xml b/android/TerminalApp/res/values-it/strings.xml
index da12f9b..5660cc3 100644
--- a/android/TerminalApp/res/values-it/strings.xml
+++ b/android/TerminalApp/res/values-it/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Controllo porte"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Consenti/rifiuta porte di ascolto"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Porte di ascolto"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Porte consentite salvate"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Porte consentite salvate"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Aggiungi"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Elimina <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Consenti una nuova porta"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Inserisci un nuovo numero di porta"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Salva"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Annulla"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Inserisci un numero"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Numero di porta non valido"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"La porta esiste già"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Il terminale sta chiedendo di aprire una nuova porta"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Porta richiesta: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accetta"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Rifiuta"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Ripristino"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Rimuovi i dati di backup"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Rimuovi <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Errore irreversibile"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Impossibile recuperare da un errore.\nPuoi provare a riavviare il terminale o a utilizzare una delle opzioni di recupero.\nSe tutti i tentativi falliscono, cancella tutti i dati attivando/disattivando il terminale Linux dalle Opzioni sviluppatore."</string>
     <string name="error_code" msgid="3585291676855383649">"Codice di errore: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Impostazioni"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Il terminale è in esecuzione"</string>
diff --git a/android/TerminalApp/res/values-iw/strings.xml b/android/TerminalApp/res/values-iw/strings.xml
index 07441b9..3ea2227 100644
--- a/android/TerminalApp/res/values-iw/strings.xml
+++ b/android/TerminalApp/res/values-iw/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"בקרת יציאות"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"אישור או דחייה של יציאות להאזנה"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"יציאות להאזנה"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"יציאות מורשות שנשמרו"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"הוספה"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"מחיקה של <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"אישור ליציאה חדשה"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"צריך להזין מספר של יציאה חדשה"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"שמירה"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ביטול"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"צריך להזין מספר"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"מספר היציאה לא תקין"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"היציאה כבר קיימת"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"הטרמינל מבקש לפתוח יציאה חדשה"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"נשלחה בקשה ליציאה: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"אישור"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"דחייה"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"שחזור"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"הסרת נתוני הגיבוי"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"הסרה של <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"שגיאה שבעקבותיה אי אפשר לשחזר"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"השחזור נכשל בגלל שגיאה.\nאפשר להפעיל מחדש את הטרמינל או לנסות אחת מאפשרויות השחזור.\nאם כל הניסיונות נכשלו, אפשר לאפס את כל הנתונים על ידי הפעלה או השבתה של טרמינל Linux דרך האפשרויות למפתחים."</string>
     <string name="error_code" msgid="3585291676855383649">"קוד שגיאה: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"הגדרות"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"הטרמינל פועל"</string>
diff --git a/android/TerminalApp/res/values-ja/strings.xml b/android/TerminalApp/res/values-ja/strings.xml
index 636a19d..bcc3378 100644
--- a/android/TerminalApp/res/values-ja/strings.xml
+++ b/android/TerminalApp/res/values-ja/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"ポートの管理"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"リスニング ポートの許可 / 拒否"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"リスニング ポート"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"保存済みの許可ポート"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"保存済みの許可ポート"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"追加"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> を削除"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"新しいポートを許可する"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"新しいポート番号を入力してください"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"保存"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"キャンセル"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"数字を入力してください"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"ポート番号が無効です"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"ポートはすでに存在します"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ターミナルが新しいポートを開くリクエストをしました"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"移行リクエスト済み: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"許可する"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"許可しない"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"リカバリ"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"バックアップ データの削除"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> を削除"</string>
     <string name="error_title" msgid="405150657301906598">"修復不可能なエラー"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"エラーを修復できませんでした。\nターミナルを再起動するか、いずれかの復元オプションをお試しください。\nそれでも修復できない場合は、開発者向けオプションで Linux ターミナルをオン / オフにして、すべてのデータをワイプしてください。"</string>
     <string name="error_code" msgid="3585291676855383649">"エラーコード: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"設定"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ターミナルは実行中です"</string>
diff --git a/android/TerminalApp/res/values-ka/strings.xml b/android/TerminalApp/res/values-ka/strings.xml
index 2573f9d..51960f6 100644
--- a/android/TerminalApp/res/values-ka/strings.xml
+++ b/android/TerminalApp/res/values-ka/strings.xml
@@ -20,7 +20,7 @@
     <string name="terminal_display" msgid="4810127497644015237">"ტერმინალის წარმოჩენა"</string>
     <string name="terminal_input" msgid="4602512831433433551">"კურსორი"</string>
     <string name="empty_line" msgid="5012067143408427178">"ცარიელი სტრიქონი"</string>
-    <string name="double_tap_to_edit_text" msgid="2344363097580051316">"ორმაგად შეეხეთ ასაკრეფად"</string>
+    <string name="double_tap_to_edit_text" msgid="2344363097580051316">"ორმაგად შეეხეთ ტექსტის ასაკრეფად"</string>
     <string name="installer_title_text" msgid="500663060973466805">"Linux ტერმინალის ინსტალაცია"</string>
     <string name="installer_desc_text_format" msgid="5935117404303982823">"Linux ტერმინალის გაშვებისთვის საჭიროა ქსელიდან ჩამოტვირთოთ დაახლოებით <xliff:g id="EXPECTED_SIZE">%1$s</xliff:g> ზომის მონაცემები.\nგსურთ გაგრძელება?"</string>
     <string name="installer_wait_for_wifi_checkbox_text" msgid="5812378362605046639">"ჩამოტვირთვა მხოლოდ Wi-Fi-ს გამოყენებით"</string>
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"პორტის მართვა"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"მოსმენის პორტების დაშვება/აკრძალვა"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"მოსმენის პორტები"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"შენახული დაშვებული პორტები"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"შენახული დაშვებული პორტები"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"დამატება"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g>-ის წაშლა"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"ახალი პორტის დაშვება"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"შეიყვანეთ ახალი პორტის ნომერი"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"შენახვა"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"გაუქმება"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"გთხოვთ, შეიყვანოთ ნომერი"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"პორტის არასწორი ნომერი"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"პორტი უკვე არსებობს"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ტერმინალი ითხოვს ახალი პორტის გახსნას"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"მოთხოვნილი პორტი: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"დათანხმება"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"უარყოფა"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"აღდგენა"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"სარეზერვო ასლის მონაცემების ამოშლა"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g>-ის ამოშლა"</string>
     <string name="error_title" msgid="405150657301906598">"გამოუსწორებელი შეცდომა"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"შეცდომა ვერ გამოსწორდა.\nშეგიძლიათ ცადოთ ტერმინალის გადატვირთვა ან აღდგენის ვარიანტებიდან ერთ-ერთი.\nთუ ყველა მცდელობა წარუმატებელი იქნება, ამოშალეთ ნებისმიერი მონაცემი დეველოპერთა პარამეტრებიდან Linux ტერმინალის ჩართვა/გამორთვის გზით."</string>
     <string name="error_code" msgid="3585291676855383649">"შეცდომის კოდი: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"პარამეტრები"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ტერმინალი გაშვებულია"</string>
diff --git a/android/TerminalApp/res/values-kk/strings.xml b/android/TerminalApp/res/values-kk/strings.xml
index c541284..ea12254 100644
--- a/android/TerminalApp/res/values-kk/strings.xml
+++ b/android/TerminalApp/res/values-kk/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Портты басқару"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Тыңдау порттарына рұқсат беру/тыйым салу"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Тыңдау порттары"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Рұқсат берілген порттар сақталды"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Қосу"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> портты жою"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Жаңа портқа рұқсат беру"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Жаңа порт нөмірін енгізіңіз."</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Сақтау"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Бас тарту"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Нөмірді енгізіңіз."</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Порт нөмірі жарамсыз."</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Порт бұрыннан бар."</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Терминал жаңа порт ашуды сұрайды"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Қажетті порт: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Қабылдау"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Қабылдамау"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Қалпына келтіру"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Сақтық көшірме дерегін өшіру"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> жолын өшіру"</string>
     <string name="error_title" msgid="405150657301906598">"Қалпына келтіруге жол бермейтін қате"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Қатеден кейін қалпына келтіру мүмкін болмады.\nТерминалды өшіріп қосып көріңіз немесе қалпына келтіру опцияларының бірін пайдаланып көріңіз.\nБарлық әрекет сәтсіз аяқталса, әзірлеуші опцияларынан Linux терминалын қосу/өшіру арқылы барлық деректі тазартыңыз."</string>
     <string name="error_code" msgid="3585291676855383649">"Қате коды: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Параметрлер"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Терминал іске қосылып тұр"</string>
diff --git a/android/TerminalApp/res/values-km/strings.xml b/android/TerminalApp/res/values-km/strings.xml
index b2d06c0..d85ead9 100644
--- a/android/TerminalApp/res/values-km/strings.xml
+++ b/android/TerminalApp/res/values-km/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"ការគ្រប់គ្រងច្រក"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"អនុញ្ញាត/បដិសេធច្រកស្ដាប់"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"ច្រកស្ដាប់"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"បានរក្សាទុកច្រកដែលត្រូវបានអនុញ្ញាត"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"បញ្ចូល"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"លុប <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"អនុញ្ញាតច្រកថ្មី"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"បញ្ចូលលេខច្រកថ្មី"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"រក្សាទុក"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"បោះបង់"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"សូម​បញ្ចូល​លេខ​"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"លេខច្រកមិនត្រឹមត្រូវ"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"មានច្រករួចហើយ"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ទែមីណាល់កំពុងស្នើសុំបើកច្រកថ្មី"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"បានស្នើសុំច្រក៖ <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ទទួលយក"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"បដិសេធ"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"ស្ដារ"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ដកទិន្នន័យបម្រុងទុកចេញ"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"ដក <xliff:g id="PATH">/mnt/backup</xliff:g> ចេញ"</string>
     <string name="error_title" msgid="405150657301906598">"បញ្ហា​ដែលបណ្ដាលឱ្យមិនអាច​ស្ដារបាន"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"មិនអាចស្ដារឡើងវិញពីបញ្ហាបានទេ។\nអ្នកអាចសាកល្បងចាប់ផ្ដើមទែមីណាល់ឡើងវិញ ឬសាកល្បងប្រើជម្រើសមួយក្នុងចំណោមជម្រើសស្ដារ។\nប្រសិនបើការព្យាយាមទាំងអស់មិនបានសម្រេច សូមឈូសទិន្នន័យទាំងអស់ដោយបើក/បិទទែមីណាល់ Linux ពីជម្រើសសម្រាប់អ្នក​អភិវឌ្ឍន៍។"</string>
     <string name="error_code" msgid="3585291676855383649">"លេខ​កូដ​បញ្ហា៖ <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ការកំណត់"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ទែមីណាល់កំពុងដំណើរការ"</string>
diff --git a/android/TerminalApp/res/values-kn/strings.xml b/android/TerminalApp/res/values-kn/strings.xml
index d7b3d87..8010e73 100644
--- a/android/TerminalApp/res/values-kn/strings.xml
+++ b/android/TerminalApp/res/values-kn/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"ಪೋರ್ಟ್ ನಿಯಂತ್ರಣ"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"ಆಲಿಸುವ ಪೋರ್ಟ್‌ಗಳನ್ನು ಅನುಮತಿಸಿ/ನಿರಾಕರಿಸಿ"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"ಆಲಿಸುವ ಪೋರ್ಟ್‌ಗಳು"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ಅನುಮತಿಸಲಾದ ಪೋರ್ಟ್‌ಗಳನ್ನು ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ಅನುಮತಿಸಲಾದ ಪೋರ್ಟ್‌ಗಳನ್ನು ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"ಸೇರಿಸಿ"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> ಅನ್ನು ಅಳಿಸಿ"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"ಹೊಸ ಪೋರ್ಟ್ ಅನ್ನು ಅನುಮತಿಸಿ"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"ಹೊಸ ಪೋರ್ಟ್ ಸಂಖ್ಯೆವೊಂದನ್ನು ನಮೂದಿಸಿ"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"ಸೇವ್ ಮಾಡಿ"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ರದ್ದುಮಾಡಿ"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"ಅಮಾನ್ಯ ಪೋರ್ಟ್ ಸಂಖ್ಯೆ"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"ಪೋರ್ಟ್ ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿದೆ"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ಟರ್ಮಿನಲ್‌ ಹೊಸ ಪೋರ್ಟ್‌ ಅನ್ನು ತೆರೆಯಲು ವಿನಂತಿಸುತ್ತಿದೆ"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"ಪೋರ್ಟ್ ಅನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ಸಮ್ಮತಿಸಿ"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ನಿರಾಕರಿಸಿ"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"ರಿಕವರಿ"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ಬ್ಯಾಕಪ್‌ ಡೇಟಾವನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> ತೆಗೆದುಹಾಕಿ"</string>
     <string name="error_title" msgid="405150657301906598">"ಮರುಪಡೆಯಲಾಗದ ದೋಷ"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"ದೋಷದಿಂದ ಚೇತರಿಸಿಕೊಳ್ಳಲು ವಿಫಲವಾಗಿದೆ.\nನೀವು ಟರ್ಮಿನಲ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಲು ಪ್ರಯತ್ನಿಸಬಹುದು ಅಥವಾ ಮರುಪ್ರಾಪ್ತಿ ಆಯ್ಕೆಗಳಲ್ಲಿ ಒಂದನ್ನು ಪ್ರಯತ್ನಿಸಬಹುದು.\nಎಲ್ಲಾ ಪ್ರಯತ್ನಗಳು ವಿಫಲವಾದರೆ, ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳಿಂದ Linux ಟರ್ಮಿನಲ್ ಅನ್ನು ಆನ್/ಆಫ್ ಮಾಡುವ ಮೂಲಕ ಎಲ್ಲಾ ಡೇಟಾವನ್ನು ಅಳಿಸಿಹಾಕು."</string>
     <string name="error_code" msgid="3585291676855383649">"ದೋಷ ಕೋಡ್‌: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ಟರ್ಮಿನಲ್‌ ರನ್‌ ಆಗುತ್ತಿದೆ"</string>
diff --git a/android/TerminalApp/res/values-ko/strings.xml b/android/TerminalApp/res/values-ko/strings.xml
index e904fdd..173cade 100644
--- a/android/TerminalApp/res/values-ko/strings.xml
+++ b/android/TerminalApp/res/values-ko/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"포트 제어"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"리스닝 포트 허용/거부"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"리스닝 포트"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"저장된 허용 포트"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"추가"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> 삭제"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"새 포트 허용"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"새 포트 번호 입력"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"저장"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"취소"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"숫자를 입력하세요."</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"포트 번호가 잘못되었습니다."</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"포트가 이미 존재합니다."</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"터미널에서 새 포트를 열려고 합니다"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"요청된 포트: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"수락"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"거부"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"복구"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"백업 데이터 삭제"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> 삭제"</string>
     <string name="error_title" msgid="405150657301906598">"복구 불가 오류"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"오류에서 복구할 수 없습니다.\n터미널을 다시 시작하거나 복구 옵션을 시도해 보세요.\n모든 시도가 실패하면 개발자 옵션에서 Linux 터미널을 사용 설정/중지하여 모든 데이터를 삭제하세요."</string>
     <string name="error_code" msgid="3585291676855383649">"오류 코드: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"설정"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"터미널이 실행 중입니다"</string>
diff --git a/android/TerminalApp/res/values-ky/strings.xml b/android/TerminalApp/res/values-ky/strings.xml
index 7c7a9e0..21d0e2a 100644
--- a/android/TerminalApp/res/values-ky/strings.xml
+++ b/android/TerminalApp/res/values-ky/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Портту көзөмөлдөө"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Угуу портторуна уруксат берүү/тыюу салуу"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Угуу порттору"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Уруксат берилген порттор сакталды"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Кошуу"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Өчүрүү: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Жаңы портко уруксат берүү"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Жаңы порттун номерин киргизиңиз"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Сактоо"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Жокко чыгаруу"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Санды киргизиңиз"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Порттун номери жараксыз"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Порт бар"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Терминал жаңы оюкчаны ачууну суранып жатат"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Оюкча суралды: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Кабыл алуу"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Четке кагуу"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Калыбына келтирүү"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Маалыматтын камдык көчүрмөсүн өчүрүү"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Өчүрүү: <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Оңдолбос ката"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Катадан кийин калыбына келтирилген жок.\nТерминалды өчүрүп күйгүзүп же калыбына келтирүү жолдорунун бирин колдонуп көрүңүз.\nЭгер бардык аракеттер ишке ашпай калса, Linux терминалын иштеп чыгуучунун параметрлеринен күйгүзүп/өчүрүп, бардык маалыматты тазалаңыз."</string>
     <string name="error_code" msgid="3585291676855383649">"Ката коду: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Параметрлер"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Терминал иштеп жатат"</string>
diff --git a/android/TerminalApp/res/values-lo/strings.xml b/android/TerminalApp/res/values-lo/strings.xml
index c5a274a..109a304 100644
--- a/android/TerminalApp/res/values-lo/strings.xml
+++ b/android/TerminalApp/res/values-lo/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"ການຄວບຄຸມຜອດ"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"ອະນຸຍາດ/ປະຕິເສດຜອດການຟັງ"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"ຜອດການຟັງ"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ຜອດທີ່ອະນຸຍາດເຊິ່ງບັນທຶກໄວ້"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ຜອດທີ່ອະນຸຍາດເຊິ່ງບັນທຶກໄວ້"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"ເພີ່ມ"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"ລຶບ <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"ອະນຸຍາດຜອດໃໝ່"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"ໃສ່ໝາຍເລກຜອດໃໝ່"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"ບັນທຶກ"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ຍົກເລີກ"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"ກະລຸນາໃສ່ໝາຍເລກ"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"ໝາຍເລກຜອດບໍ່ຖືກຕ້ອງ"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"ມີຜອດຢູ່ກ່ອນແລ້ວ"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ເທີມິນອນກຳລັງສົ່ງຄຳຮ້ອງຂໍໃຫ້ເປີດຜອດໃໝ່"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"ຜອດທີ່ຮ້ອງຂໍ: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ຍອມຮັບ"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ປະຕິເສດ"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"ການກູ້ຄືນ"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ລຶບການສຳຮອງຂໍ້ມູນອອກ"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"ລຶບ <xliff:g id="PATH">/mnt/backup</xliff:g> ອອກ"</string>
     <string name="error_title" msgid="405150657301906598">"ຂໍ້ຜິດພາດທີ່ບໍ່ສາມາດກູ້ຄືນມາໄດ້"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"ກູ້ຄືນຈາກຂໍ້ຜິດພາດບໍ່ສຳເລັດ.\nທ່ານສາມາດລອງຣີສະຕາດເທີມິນອນ ຫຼື ລອງໃຊ້ໜຶ່ງໃນຕົວເລືອກການກູ້ຄືນໄດ້.\nຫາກພະຍາຍາມດຳເນີນການທຸກຢ່າງແລ້ວແຕ່ບໍ່ສຳເລັດ, ໃຫ້ລຶບລ້າງຂໍ້ມູນທັງໝົດໂດຍເປີດ/ປິດເທີມິນອນ Linux ຈາກຕົວເລືອກນັກພັດທະນາ."</string>
     <string name="error_code" msgid="3585291676855383649">"ລະຫັດຂໍ້ຜິດພາດ: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ການຕັ້ງຄ່າ"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ເທີມິນອນກຳລັງເຮັດວຽກຢູ່"</string>
diff --git a/android/TerminalApp/res/values-lt/strings.xml b/android/TerminalApp/res/values-lt/strings.xml
index 4760ba6..beefadd 100644
--- a/android/TerminalApp/res/values-lt/strings.xml
+++ b/android/TerminalApp/res/values-lt/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Prievado valdymas"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Leisti klausymo prievadus / neleisti jų"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Klausymo prievadai"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Išsaugoti leidžiami prievadai"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Išsaugoti leidžiami prievadai"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Pridėti"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Ištrinti <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Leisti naują prievadą"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Įveskite naują prievado numerį"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Išsaugoti"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Atšaukti"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Įveskite skaičių"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Netinkamas prievado numeris"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Prievadas jau yra"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminalas bando atidaryti naują prievadą"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Prievadas, kurio užklausa pateikta: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Sutikti"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Atmesti"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Atkūrimas"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Pašalinti atsarginės kopijos duomenis"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Pašalinti <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Nepataisoma klaida"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Nepavyko atkurti po klaidos.\nGalite bandyti iš naujo paleisti terminalą arba išbandyti vieną iš atkūrimo parinkčių.\nJei nepavyksta atkurti, panaikinkite visus duomenis įjungę ir (arba) išjungę „Linux“ terminalą iš kūrėjo parinkčių."</string>
     <string name="error_code" msgid="3585291676855383649">"Klaidos kodas: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Nustatymai"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminalas veikia"</string>
diff --git a/android/TerminalApp/res/values-lv/strings.xml b/android/TerminalApp/res/values-lv/strings.xml
index ce67c93..d45aeb3 100644
--- a/android/TerminalApp/res/values-lv/strings.xml
+++ b/android/TerminalApp/res/values-lv/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portu kontrole"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Atļaut/aizliegt klausīšanās portus"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Klausīšanās porti"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Saglabātie atļautie porti"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Pievienot"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Dzēst <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Atļaut jaunu portu"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Ievadiet jaunu porta numuru"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Saglabāt"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Atcelt"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Lūdzu, ievadiet numuru"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Nederīgs porta numurs"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Ports jau pastāv"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminālis pieprasa jauna porta atvēršanu"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Pieprasītais ports: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Piekrist"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Noraidīt"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Atkopšana"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Dublējuma datu noņemšana"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Datu noņemšana no ceļa <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Neatkopjama kļūda"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Neizdevās veikt atkopšanu pēc kļūdas.\nVarat restartēt termināli vai izmēģināt kādu no atkopšanas opcijām.\nJa neviens mēģinājums neizdodas, notīriet visus datus, izstrādātāju opcijās ieslēdzot/izslēdzot Linux termināli."</string>
     <string name="error_code" msgid="3585291676855383649">"Kļūdas kods: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Iestatījumi"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminālis darbojas"</string>
diff --git a/android/TerminalApp/res/values-mk/strings.xml b/android/TerminalApp/res/values-mk/strings.xml
index f79e4fb..99cc48c 100644
--- a/android/TerminalApp/res/values-mk/strings.xml
+++ b/android/TerminalApp/res/values-mk/strings.xml
@@ -46,25 +46,23 @@
     <string name="settings_disk_resize_resize_confirm_dialog_message" msgid="6906352501525496328">"Терминалот ќе се рестартира за да се промени големината на дискот"</string>
     <string name="settings_disk_resize_resize_confirm_dialog_confirm" msgid="7347432999245803583">"Потврди"</string>
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Контрола на портите"</string>
-    <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Дозволете/одбијте слушање на портите"</string>
-    <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Се слушаат порти"</string>
+    <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Дозволете/одбијте порти за примање барања"</string>
+    <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Порти за примање барања"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Дозволените порти се зачувани"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Додај"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Избриши <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Дозволете нова порта"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Внесете нов број на порта"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Зачувај"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Откажи"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Внесете број"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Неважечки број на порта"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Портата веќе постои"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Терминалот бара да отвори нова порта"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Побарана е порта: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Прифати"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Одбиј"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Враќање"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Отстранете ги податоците од бекапот"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Отстранете „<xliff:g id="PATH">/mnt/backup</xliff:g>“"</string>
     <string name="error_title" msgid="405150657301906598">"Непоправлива грешка"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Грешката не можеше да се поправи.\nМоже да се обидете да го рестартирате терминалот или да испробате некоја од опциите за враќање.\nАко сите обиди се неуспешни, избришете ги сите податоци трајно со вклучување/исклучување на Linux-терминалот од „Програмерски опции“."</string>
     <string name="error_code" msgid="3585291676855383649">"Код за грешка: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Поставки"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Терминалот е активен"</string>
diff --git a/android/TerminalApp/res/values-ml/strings.xml b/android/TerminalApp/res/values-ml/strings.xml
index f308244..c2bab30 100644
--- a/android/TerminalApp/res/values-ml/strings.xml
+++ b/android/TerminalApp/res/values-ml/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"പോർട്ട് നിയന്ത്രണം"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"ശ്രവണ പോർട്ടുകൾ അനുവദിക്കുക/നിരസിക്കുക"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"ശ്രവണ പോർട്ടുകൾ"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"സംരക്ഷിച്ച അനുവദനീയമായ പോർട്ടുകൾ"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"സംരക്ഷിച്ച അനുവദനീയമായ പോർട്ടുകൾ"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"ചേർക്കുക"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> ഇല്ലാതാക്കുക"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"പുതിയ പോർട്ട് അനുവദിക്കുക"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"പുതിയ പോർട്ട് നമ്പർ നൽകുക"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"സംരക്ഷിക്കുക"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"റദ്ദാക്കുക"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"ഒരു നമ്പർ നൽകുക"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"പോർട്ട് നമ്പർ അസാധുവാണ്"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"പോർട്ട് ഇതിനകം നിലവിലുണ്ട്"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ഒരു പുതിയ പോർട്ട് തുറക്കാൻ ടെർമിനൽ അഭ്യർത്ഥിക്കുന്നു"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"പോർട്ട് അഭ്യർത്ഥിച്ചു: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"അംഗീകരിക്കുക"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"നിരസിക്കുക"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"വീണ്ടെടുക്കുക"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ബാക്കപ്പ് ഡാറ്റ നീക്കം ചെയ്യുക"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> നീക്കം ചെയ്യുക"</string>
     <string name="error_title" msgid="405150657301906598">"വീണ്ടെടുക്കാനാകാത്ത വിധത്തിലാക്കിയ പിശക്"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"ഒരു പിശകിൽ നിന്ന് വീണ്ടെടുക്കാനായില്ല.\nനിങ്ങൾക്ക് ടെർമിനൽ റീസ്റ്റാർട്ട് ചെയ്യാൻ ശ്രമിക്കാം അല്ലെങ്കിൽ വീണ്ടെടുക്കൽ ഓപ്‌ഷനുകളിലൊന്ന് ശ്രമിച്ചുനോക്കാം.\nഎല്ലാ ശ്രമങ്ങളും പരാജയപ്പെടുകയാണെങ്കിൽ, ഡെവലപ്പർ ഓ‌പ്ഷനുകളിൽ നിന്ന് Linux ടെർമിനൽ ഓൺ/ഓഫ് ആക്കി എല്ലാ ഡാറ്റയും തുടച്ചുനീക്കുക."</string>
     <string name="error_code" msgid="3585291676855383649">"പിശക് കോഡ്: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ക്രമീകരണം"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ടെർമിനൽ റൺ ചെയ്യുന്നു"</string>
diff --git a/android/TerminalApp/res/values-mn/strings.xml b/android/TerminalApp/res/values-mn/strings.xml
index 674aa70..4849006 100644
--- a/android/TerminalApp/res/values-mn/strings.xml
+++ b/android/TerminalApp/res/values-mn/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Портын тохиргоо"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Сүлжээний хүсэлт хүлээж буй портуудыг зөвшөөрөх/татгалзах"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Сүлжээний хүсэлт хүлээж буй портууд"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Хадгалсан зөвшөөрөгдсөн портууд"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Хадгалсан зөвшөөрөгдсөн портууд"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Нэмэх"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g>-г устгах"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Шинэ портыг зөвшөөрөх"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Шинэ портын дугаар оруулах"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Хадгалах"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Цуцлах"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Дугаар оруулна уу"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Портын дугаар буруу байна"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Порт аль хэдийн байна"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Терминал шинэ порт нээхийг хүсэж байна"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Хүссэн порт: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Зөвшөөрөх"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Татгалзах"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Сэргээх"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Нөөц өгөгдлийг устгах"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g>-г устгах"</string>
     <string name="error_title" msgid="405150657301906598">"Сэргээх боломжгүй алдаа"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Алдааны улмаас сэргээж чадсангүй.\nТа терминалыг дахин эхлүүлэх эсвэл сэргээх сонголтуудын аль нэгийг туршиж үзэх боломжтой.\nХэрэв бүх оролдлого амжилтгүй болбол хөгжүүлэгчийн тохиргооноос Linux терминалыг асаах/унтраах замаар бүх өгөгдлийг арчина уу."</string>
     <string name="error_code" msgid="3585291676855383649">"Алдааны код: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Тохиргоо"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Терминал ажиллаж байна"</string>
diff --git a/android/TerminalApp/res/values-mr/strings.xml b/android/TerminalApp/res/values-mr/strings.xml
index e3f68f5..70a093e 100644
--- a/android/TerminalApp/res/values-mr/strings.xml
+++ b/android/TerminalApp/res/values-mr/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"पोर्ट नियंत्रण"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"ऐकण्याच्या पोर्टना अनुमती द्या/नाकारा"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"ऐकण्याचे पोर्ट"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"सेव्ह केलेले व अनुमती असलेले पोर्ट"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"जोडा"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> हटवा"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"नवीन पोर्टला अनुमती द्या"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"नवीन पोर्ट नंबर एंटर करा"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"सेव्ह करा"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"रद्द करा"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"कृपया नंबर एंटर करा"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"पोर्ट नंबर चुकीचा आहे"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"पोर्ट आधीपासून अस्तित्वात आहे"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"टर्मिनल नवीन पोर्ट उघडण्याची विनंती करत आहे"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"विनंती केलेला पोर्ट: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"स्वीकारा"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"नकार द्या"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"रिकव्हरी"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"बॅकअप डेटा काढून टाका"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> काढून टाका"</string>
     <string name="error_title" msgid="405150657301906598">"रिकव्‍हर न करता येणारी एरर"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"एररमधून रिकव्हर करता आले नाही.\nतुम्ही टर्मिनल रीस्टार्ट करण्याचा प्रयत्न करू शकता किंवा रिकव्हरी पर्यायांपैकी एखादा पर्याय वापरून पाहू शकता.\nसर्व प्रयत्न अयशस्वी झाल्यास, डेव्हलपर पर्यायांमधून Linux टर्मिनल सुरू/बंद करून सर्व डेटा पुसून टाका."</string>
     <string name="error_code" msgid="3585291676855383649">"एरर कोड: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"सेटिंग्ज"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"टर्मिनल रन होत आहे"</string>
diff --git a/android/TerminalApp/res/values-ms/strings.xml b/android/TerminalApp/res/values-ms/strings.xml
index 26d1c28..a14d938 100644
--- a/android/TerminalApp/res/values-ms/strings.xml
+++ b/android/TerminalApp/res/values-ms/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kawalan port"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Benarkan/tolak port mendengar"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Port mendengar"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Port yang dibenarkan disimpan"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Port yang dibenarkan disimpan"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Tambah"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Padamkan <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Benarkan port baharu"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Masukkan nombor port baharu"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Simpan"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Batal"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Sila masukkan nombor"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Nombor port tidak sah"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port sudah wujud"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal membuat permintaan untuk membuka port baharu"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port diminta: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Terima"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Tolak"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Pemulihan"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Alih keluar data sandaran"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Alih keluar <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Ralat yang tidak dapat dipulihkan"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Gagal dipulihkan daripada ralat.\nAnda boleh cuba memulakan semula terminal atau cuba satu daripada pilihan pemulihan.\nJika semua percubaan gagal, hapuskan semua data dengan menghidupkan/mematikan terminal Linux daripada pilihan pembangun."</string>
     <string name="error_code" msgid="3585291676855383649">"Kod ralat: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Tetapan"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal sedang dijalankan"</string>
diff --git a/android/TerminalApp/res/values-my/strings.xml b/android/TerminalApp/res/values-my/strings.xml
index f5d4ded..eda6e90 100644
--- a/android/TerminalApp/res/values-my/strings.xml
+++ b/android/TerminalApp/res/values-my/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"ပို့တ်ထိန်းချုပ်မှု"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"စောင့်နေသောပို့တ်များကို ခွင့်ပြုရန်/ငြင်းပယ်ရန်"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"စောင့်နေသောပို့တ်များ"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ခွင့်ပြုထားသောပို့တ်များ သိမ်းပြီးပြီ"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ခွင့်ပြုထားသောပို့တ်များ သိမ်းပြီးပြီ"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"ထည့်ရန်"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> ကို ဖျက်ရန်"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"ပို့တ်အသစ် ခွင့်ပြုခြင်း"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"ပို့တ်နံပါတ်အသစ် ထည့်ပါ"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"သိမ်းရန်"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"မလုပ်တော့"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"နံပါတ်တစ်ခု ရိုက်ထည့်ပါ"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"ပို့တ်နံပါတ် မမှန်ပါ"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"ပို့တ်ရှိပြီးသားဖြစ်သည်"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"တာမီနယ်က ပို့တ်အသစ်ဖွင့်ရန် တောင်းဆိုနေသည်"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"တောင်းဆိုထားသော ပို့တ်- <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"လက်ခံရန်"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ငြင်းပယ်ရန်"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"ပြန်လည်ရယူခြင်း"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"အရန်ဒေတာ ဖယ်ရှားခြင်း"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> ကို ဖယ်ရှားရန်"</string>
     <string name="error_title" msgid="405150657301906598">"ပြန်ပြင်၍မရသော အမှား"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"အမှားကို ပြန်ပြင်၍မရလိုက်ပါ။\nတာမီနယ်ကို ပြန်စနိုင်သည် (သို့) ပြန်ရယူရေး နည်းလမ်းများထဲမှ တစ်ခုကို စမ်းကြည့်နိုင်သည်။\nကြိုးပမ်းမှုအားလုံး အဆင်မပြေပါက ဆော့ဖ်ဝဲရေးသူအတွက် ရွေးစရာများမှနေ၍ Linux တာမီနယ်ကို ဖွင့်ခြင်း/ပိတ်ခြင်းဖြင့် ဒေတာအားလုံး ရှင်းလင်းပါ။"</string>
     <string name="error_code" msgid="3585291676855383649">"အမှားကုဒ်- <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ဆက်တင်များ"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"တာမီနယ်ကို ဖွင့်ထားသည်"</string>
diff --git a/android/TerminalApp/res/values-nb/strings.xml b/android/TerminalApp/res/values-nb/strings.xml
index 6d6d7ce..b2492db 100644
--- a/android/TerminalApp/res/values-nb/strings.xml
+++ b/android/TerminalApp/res/values-nb/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portkontroll"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Tillat/avslå lytteporter"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Lytteporter"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Lagrede tillatte porter"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Legg til"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Slett <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Tillat en ny port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Skriv inn et nytt portnummer"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Lagre"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Avbryt"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Legg inn et nummer"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ugyldig portnummer"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Porten finnes allerede"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminalen prøver å åpne en ny port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Forespurt port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Godta"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Avvis"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Gjenoppretting"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Fjern sikkerhetskopierte data"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Fjern <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Ugjenopprettelig feil"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Kunne ikke gjenopprette etter en feil.\nDu kan prøve å starte terminalen på nytt eller prøve et av gjenopprettingsalternativene.\nHvis alle forsøkene mislykkes, kan du slette alle dataene ved å slå på/av Linux-terminalen fra utvikleralternativene."</string>
     <string name="error_code" msgid="3585291676855383649">"Feilkode: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Innstillinger"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminalen kjører"</string>
diff --git a/android/TerminalApp/res/values-ne/strings.xml b/android/TerminalApp/res/values-ne/strings.xml
index 425273a..714dcf3 100644
--- a/android/TerminalApp/res/values-ne/strings.xml
+++ b/android/TerminalApp/res/values-ne/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"पोर्टसम्बन्धी कन्ट्रोल"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"लिसनिङ पोर्टहरू हाल्ने अनुमति दिनुहोस्/नदिनुहोस्"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"लिसनिङ पोर्टहरू"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"सेभ गरिएका अनुमति दिइएका पोर्टहरू"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"सेभ गरिएका अनुमति दिइएका पोर्टहरू"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"हाल्नुहोस्"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> मेटाउनुहोस्"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"नयाँ पोर्ट नम्बर हाल्ने अनुमति दिनुहोस्"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"नयाँ पोर्ट नम्बर हाल्नुहोस्"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"सेभ गर्नुहोस्"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"रद्द गर्नुहोस्"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"कृपया नम्बर हाल्नुहोस्"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"अवैध पोर्ट नम्बर"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"पोर्ट पहिल्यैदेखि छ"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"टर्मिनलले एउटा नयाँ पोर्ट खोल्न अनुरोध गरिरहेको छ"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"निम्न पोर्ट खोल्न अनुरोध गरिएको छ: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"स्वीकार गर्नुहोस्"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"अस्वीकार गर्नुहोस्"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"रिकभरी"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ब्याकअप डेटा हटाउनुहोस्"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> हटाउनुहोस्"</string>
     <string name="error_title" msgid="405150657301906598">"रिकभर गर्न नदिने त्रुटि"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"कुनै त्रुटिका कारण रिकभर गर्न सकिएन।\nतपाईं टर्मिनल रिस्टार्ट गरी हेर्न वा रिकभर गर्ने विकल्पहरूमध्ये कुनै एउटा विकल्प अपनाई हेर्न सक्नुहुन्छ।\nसबै विकल्प अपनाउँदा पनि त्रुटि समाधान भएन भने विकासकर्ता मोडबाट Linux टर्मिनल अन/अफ गरी सबै डेटा मेटाउनुहोस्।"</string>
     <string name="error_code" msgid="3585291676855383649">"त्रुटिको कोड: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"सेटिङ"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"टर्मिनल चलिरहेको छ"</string>
diff --git a/android/TerminalApp/res/values-nl/strings.xml b/android/TerminalApp/res/values-nl/strings.xml
index 79c75dc..6c1e42a 100644
--- a/android/TerminalApp/res/values-nl/strings.xml
+++ b/android/TerminalApp/res/values-nl/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Poortcontrole"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Luisterende poorten toestaan/weigeren"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Luisterende poorten"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Toegestane poorten opgeslagen"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Toegestane poorten opgeslagen"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Toevoegen"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> verwijderen"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Een nieuwe poort toestaan"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Voer een nieuw poortnummer in"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Opslaan"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Annuleren"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Voer een nummer in"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ongeldig poortnummer"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Poort bestaat al"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal verzoekt om een nieuwe poort te openen"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Poort aangevraagd: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Accepteren"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Weigeren"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Herstel"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Back-upgegevens verwijderen"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> verwijderen"</string>
     <string name="error_title" msgid="405150657301906598">"Onherstelbare fout"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Kan niet herstellen van een fout.\nJe kunt de terminal opnieuw opstarten of een van de herstelopties proberen.\nAls alle pogingen mislukken, wis je alle gegevens door de Linux-terminal aan/uit te zetten via de ontwikkelaarsopties."</string>
     <string name="error_code" msgid="3585291676855383649">"Foutcode: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Instellingen"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal wordt uitgevoerd"</string>
diff --git a/android/TerminalApp/res/values-or/strings.xml b/android/TerminalApp/res/values-or/strings.xml
index 5b91166..29855af 100644
--- a/android/TerminalApp/res/values-or/strings.xml
+++ b/android/TerminalApp/res/values-or/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"ପୋର୍ଟ ନିୟନ୍ତ୍ରଣ"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"ଶୁଣିବା ପୋର୍ଟଗୁଡ଼ିକୁ ଅନୁମତି ଦିଅନ୍ତୁ/ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"ଶୁଣିବା ପୋର୍ଟ"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ଅନୁମତି ଦିଆଯାଇଥିବା ପୋର୍ଟଗୁଡ଼ିକୁ ସେଭ କରାଯାଇଛି"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ଅନୁମତି ଦିଆଯାଇଥିବା ପୋର୍ଟଗୁଡ଼ିକୁ ସେଭ କରାଯାଇଛି"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"ଯୋଗ କରନ୍ତୁ"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g>କୁ ଡିଲିଟ କରନ୍ତୁ"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"ଏକ ନୂଆ ପୋର୍ଟକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"ଏକ ନୂଆ ପୋର୍ଟ ନମ୍ବର ଲେଖନ୍ତୁ"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"ସେଭ କରନ୍ତୁ"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ବାତିଲ କରନ୍ତୁ"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"ଦୟାକରି ଗୋଟିଏ ନମ୍ବର ଲେଖନ୍ତୁ"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"ଅବୈଧ ପୋର୍ଟ ନମ୍ବର"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"ପୋର୍ଟ ପୂର୍ବରୁ ଉପଲବ୍ଧ ଅଛି"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ଏକ ନୂଆ ପୋର୍ଟ ଖୋଲିବାକୁ ଟର୍ମିନାଲ ଅନୁରୋଧ କରୁଛି"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"ପୋର୍ଟ ପାଇଁ ଅନୁରୋଧ କରାଯାଇଛି: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ଗ୍ରହଣ କରନ୍ତୁ"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"ରିକଭରି"</string>
@@ -81,14 +80,13 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ବେକଅପ ଡାଟାକୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g>କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="error_title" msgid="405150657301906598">"ରିକଭର କରିହେଉନଥିବା ତ୍ରୁଟି"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"ଏକ ତ୍ରୁଟିରୁ ରିକଭର କରିବାରେ ବିଫଳ ହୋଇଛି।\nଆପଣ ଟର୍ମିନାଲ ରିଷ୍ଟାର୍ଟ କରିବାକୁ ଚେଷ୍ଟା କରିପାରିବେ କିମ୍ବା ରିକଭରି ବିକଳ୍ପଗୁଡ଼ିକ ମଧ୍ୟରୁ ଗୋଟିଏ ବିକଳ୍ପ ଚେଷ୍ଟା କରିପାରିବେ।\nଯଦି ସମସ୍ତ ପ୍ରଚେଷ୍ଟା ବିଫଳ ହୁଏ, ତେବେ ଡେଭେଲପର ବିକଳ୍ପରୁ Linux ଟର୍ମିନାଲକୁ ଚାଲୁ/ବନ୍ଦ କରି ସମସ୍ତ ଡାଟା ୱାଇପ କରନ୍ତୁ।"</string>
     <string name="error_code" msgid="3585291676855383649">"ତ୍ରୁଟି କୋଡ: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ସେଟିଂସ"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ଟର୍ମିନାଲ ଚାଲୁ ଅଛି"</string>
     <string name="service_notification_content" msgid="5772901142342308273">"ଟର୍ମିନାଲ ଖୋଲିବାକୁ କ୍ଲିକ କରନ୍ତୁ"</string>
     <string name="service_notification_quit_action" msgid="4888327875869277455">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="service_notification_close_title" msgid="1442526433361428844">"ଟର୍ମିନାଲ ବନ୍ଦ ହେବାକୁ ଯାଉଛି"</string>
-    <string name="service_notification_force_quit_action" msgid="3462226330416157775">"ବଳପୂର୍ବକ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="service_notification_force_quit_action" msgid="3462226330416157775">"ଫୋର୍ସ କ୍ଲୋଜ କରନ୍ତୁ"</string>
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g>କୁ ସକ୍ଷମ କରାଯାଇଛି"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-pa/strings.xml b/android/TerminalApp/res/values-pa/strings.xml
index de313f5..a4279ce 100644
--- a/android/TerminalApp/res/values-pa/strings.xml
+++ b/android/TerminalApp/res/values-pa/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"ਪੋਰਟ ਕੰਟਰੋਲ"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"ਲਿਸਨਿੰਗ ਪੋਰਟਾਂ ਨੂੰ ਆਗਿਆ ਦਿਓ/ਅਸਵੀਕਾਰ ਕਰੋ"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"ਲਿਸਨਿੰਗ ਪੋਰਟ"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"ਮਨਜ਼ੂਰਸ਼ੁਦਾ ਪੋਰਟ ਰੱਖਿਅਤ ਕੀਤੇ ਗਏ"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> ਨੂੰ ਮਿਟਾਓ"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"ਨਵੇਂ ਪੋਰਟ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"ਇੱਕ ਨਵਾਂ ਪੋਰਟ ਨੰਬਰ ਦਾਖਲ ਕਰੋ"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"ਰੱਖਿਅਤ ਕਰੋ"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ਰੱਦ ਕਰੋ"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"ਕਿਰਪਾ ਕਰਕੇ ਕੋਈ ਨੰਬਰ ਦਾਖਲ ਕਰੋ"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"ਅਵੈਧ ਪੋਰਟ ਨੰਬਰ"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"ਪੋਰਟ ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਮੌਜੂਦ ਹੈ"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ਟਰਮੀਨਲ ਇੱਕ ਨਵੇਂ ਪੋਰਟ ਨੂੰ ਖੋਲ੍ਹਣ ਦੀ ਬੇਨਤੀ ਕਰ ਰਿਹਾ ਹੈ"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"ਪੋਰਟ ਸੰਬੰਧੀ ਬੇਨਤੀ ਕੀਤੀ ਗਈ: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ਸਵੀਕਾਰ ਕਰੋ"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ਅਸਵੀਕਾਰ ਕਰੋ"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"ਰਿਕਵਰੀ"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"ਬੈਕਅੱਪ ਡਾਟਾ ਹਟਾਓ"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> ਨੂੰ ਹਟਾਓ"</string>
     <string name="error_title" msgid="405150657301906598">"ਮੁੜ-ਹਾਸਲ ਨਾ ਹੋਣਯੋਗ ਡਾਟੇ ਸੰਬੰਧੀ ਗੜਬੜ"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"ਗੜਬੜ ਨੂੰ ਠੀਕ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।\nਤੁਸੀਂ ਟਰਮੀਨਲ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦੇ ਹੋ ਜਾਂ ਰਿਕਵਰੀ ਦੇ ਵਿਕਲਪਾਂ ਵਿੱਚੋਂ ਕਿਸੇ ਇੱਕ ਨੂੰ ਅਜ਼ਮਾ ਕੇ ਦੇਖ ਸਕਦੇ ਹੋ।\nਜੇ ਸਾਰੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ ਅਸਫਲ ਹੋ ਜਾਂਦੀਆਂ ਹਨ, ਤਾਂ ਵਿਕਾਸਕਾਰ ਵਿਕਲਪਾਂ ਤੋਂ Linux ਟਰਮੀਨਲ ਨੂੰ ਚਾਲੂ/ਬੰਦ ਕਰ ਕੇ ਸਾਰਾ ਡਾਟਾ ਸਾਫ਼ ਕਰੋ।"</string>
     <string name="error_code" msgid="3585291676855383649">"ਗੜਬੜ ਕੋਡ: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ਟਰਮੀਨਲ ਚਾਲੂ ਹੈ"</string>
diff --git a/android/TerminalApp/res/values-pl/strings.xml b/android/TerminalApp/res/values-pl/strings.xml
index 6c48b2f..924c14c 100644
--- a/android/TerminalApp/res/values-pl/strings.xml
+++ b/android/TerminalApp/res/values-pl/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kontrola portów"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Zezwalaj/odrzucaj porty nasłuchujące"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Porty nasłuchujące"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Zapisane dozwolone porty"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Zapisane dozwolone porty"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Dodaj"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Usuń port <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Zezwól na nowy port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Wpisz nowy numer portu"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Zapisz"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Anuluj"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Podaj numer"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Nieprawidłowy numer portu"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port już istnieje"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal wysłał żądanie otwarcia nowego portu"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Żądany port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Zaakceptuj"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Odrzuć"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Odzyskiwanie"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Usuń dane kopii zapasowej"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Usuń: <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Nieodwracalny błąd"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Nie udało się przywrócić aplikacji po błędzie.\nMożesz spróbować ponownie uruchomić terminal lub skorzystać z jednej z opcji odzyskiwania.\nJeśli wszystkie próby zawiodą, wyczyść dane, włączając/wyłączając terminal Linuxa w opcjach dla programistów."</string>
     <string name="error_code" msgid="3585291676855383649">"Kod błędu: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Ustawienia"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal jest uruchomiony"</string>
diff --git a/android/TerminalApp/res/values-pt-rPT/strings.xml b/android/TerminalApp/res/values-pt-rPT/strings.xml
index 909f846..3a57ba6 100644
--- a/android/TerminalApp/res/values-pt-rPT/strings.xml
+++ b/android/TerminalApp/res/values-pt-rPT/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Controlo de portas"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Permitir/negar portas de audição"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Portas de audição"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Portas permitidas guardadas"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Portas permitidas guardadas"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Adicionar"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Eliminar <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Permitir uma nova porta"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Introduza um novo número de porta"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Guardar"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancelar"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Introduza um número"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Número de porta inválido"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"A porta já existe"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"O terminal está a pedir para abrir uma nova porta"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Porta pedida: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Aceitar"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Recusar"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recuperação"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Remova os dados da cópia de segurança"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Remova <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Erro irrecuperável"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Falha ao recuperar de um erro.\nPode tentar reiniciar o terminal ou experimentar uma das opções de recuperação.\nSe todas as tentativas falharem, limpe todos os dados ativando/desativando o terminal do Linux nas opções de programador."</string>
     <string name="error_code" msgid="3585291676855383649">"Código de erro: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Definições"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"O terminal está em execução"</string>
diff --git a/android/TerminalApp/res/values-pt/strings.xml b/android/TerminalApp/res/values-pt/strings.xml
index 0ac359c..9d6a16f 100644
--- a/android/TerminalApp/res/values-pt/strings.xml
+++ b/android/TerminalApp/res/values-pt/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Controle de portas"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Permitir/negar portas de detecção"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Portas de detecção"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"As portas permitidas foram salvas"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"As portas permitidas foram salvas"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Adicionar"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Excluir <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Permitir uma nova porta"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Digite o número de uma nova porta"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Salvar"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Cancelar"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Insira um número"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Número de porta inválido"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"A porta já existe"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"O terminal está pedindo para abrir uma nova porta"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Porta solicitada: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Aceitar"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Negar"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recuperação"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Remover dados de backup"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Remover <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Erro irrecuperável"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Falha ao recuperar o terminal de um erro.\nTente reiniciar o terminal ou usar uma das opções de recuperação.\nSe todas as tentativas falharem, exclua permanentemente os dados ativando/desativando o terminal Linux nas Opções do desenvolvedor."</string>
     <string name="error_code" msgid="3585291676855383649">"Código do erro: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Configurações"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"O terminal está em execução"</string>
diff --git a/android/TerminalApp/res/values-ro/strings.xml b/android/TerminalApp/res/values-ro/strings.xml
index 876467c..779277e 100644
--- a/android/TerminalApp/res/values-ro/strings.xml
+++ b/android/TerminalApp/res/values-ro/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Controlul porturilor"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Permite / refuză porturile active"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Porturi active"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Porturi permise salvate"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Adaugă"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Șterge <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Acceptă un port nou"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Introdu un număr de port nou"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Salvează"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Anulează"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Introdu un număr"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Număr de port nevalid"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Portul există deja"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminalul solicită să deschidă un nou port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Portul solicitat: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Acceptă"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Refuză"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Recuperare"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Elimină datele din backup"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Elimină <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Eroare ireversibilă"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Nu s-a putut recupera în urma unei erori.\nRepornește terminalul sau încearcă una dintre opțiunile de recuperare.\nDacă toate încercările eșuează, șterge toate datele activând sau dezactivând terminalul Linux din opțiunile pentru dezvoltatori."</string>
     <string name="error_code" msgid="3585291676855383649">"Cod de eroare: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Setări"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminalul rulează"</string>
diff --git a/android/TerminalApp/res/values-ru/strings.xml b/android/TerminalApp/res/values-ru/strings.xml
index 5087256..ffdcaf6 100644
--- a/android/TerminalApp/res/values-ru/strings.xml
+++ b/android/TerminalApp/res/values-ru/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Управление портами"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Открыть/закрыть доступ к портам прослушивания"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Порты прослушивания"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Сохраненные порты с открытым доступом"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Сохраненные порты с открытым доступом"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Добавить"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Удалить <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Открыть доступ к новому порту"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Введите номер нового порта"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Сохранить"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Отмена"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Введите номер."</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Неверный номер порта."</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Порт уже существует."</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Терминал просит открыть новый порт"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Запрашиваемый порт: <xliff:g id="PORT_NUMBER">%d</xliff:g>."</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Разрешить"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Не разрешать"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Восста­но­вле­ние"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Удалить данные резервного копирования"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Удалить <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Неустранимая ошибка"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Не удалось исправить ошибку.\nПопробуйте перезапустить терминал или воспользуйтесь одним из вариантов восстановления.\nЕсли ни один из способов не поможет, включите или отключите режим \"Для разработчиков\" на терминале Linux."</string>
     <string name="error_code" msgid="3585291676855383649">"Код ошибки: <xliff:g id="ERROR_CODE">%s</xliff:g>."</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Настройки"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Терминал запущен"</string>
diff --git a/android/TerminalApp/res/values-si/strings.xml b/android/TerminalApp/res/values-si/strings.xml
index ec10cff..cc67088 100644
--- a/android/TerminalApp/res/values-si/strings.xml
+++ b/android/TerminalApp/res/values-si/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"පෝටය පාලනය"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"සවන්දීමේ පෝටයන්ට ඉඩ දෙන්න/ප්‍රතික්ෂේප කරන්න"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"සවන්දීමේ පෝටයන්"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"සුරකින ලද ඉඩ දුන් පෝටයන්"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"එක් කරන්න"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> මකන්න"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"නව පෝටයකට ඉඩ දෙන්න"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"නව පෝට අංකයක් ඇතුළු කරන්න"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"සුරකින්න"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"අවලංගු කරන්න"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"අංකයක් ඇතුළු කරන්න"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"වලංගු නොවන පෝට අංකය"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"පෝටය දැනටමත් පවතී"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ටර්මිනලය නව පෝටයක් විවෘත කිරීමට ඉල්ලීම් කරයි"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"පෝටය ඉල්ලා ඇත: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"පිළිගන්න"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ප්‍රතික්ෂේප කරන්න"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"ප්‍රතිසාධනය"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"උපස්ථ දත්ත ඉවත් කරන්න"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> ඉවත් කරන්න"</string>
     <string name="error_title" msgid="405150657301906598">"ප්‍රතිසාධනය කළ නොහැකි දෝෂය"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"දෝෂයකින් ප්‍රතිසාධනය කිරීමට අසමත් විය.\nඔබට ටර්මිනලය නැවත ආරම්භ කිරීමට උත්සාහ කළ හැක, නැතහොත් ප්‍රතිසාධන විකල්ප වලින් එකක් උත්සාහ කරන්න.\nසියලු උත්සාහයන් අසමත් වුවහොත්, සංවර්ධක විකල්ප වලින් Linux පර්යන්තය ක්‍රියාත්මක/ක්‍රියාවිරහිත කිරීමෙන් සියලු දත්ත පිස දමන්න."</string>
     <string name="error_code" msgid="3585291676855383649">"දෝෂ කේතය: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"සැකසීම්"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"පර්යන්තය ධාවනය වේ"</string>
diff --git a/android/TerminalApp/res/values-sk/strings.xml b/android/TerminalApp/res/values-sk/strings.xml
index 55bc781..64abba6 100644
--- a/android/TerminalApp/res/values-sk/strings.xml
+++ b/android/TerminalApp/res/values-sk/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Ovládanie portov"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Povoliť alebo zakázať porty počúvania"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Porty počúvania"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Uložené povolené porty"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Pridať"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Odstrániť <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Povoľte nový port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Zadajte nové číslo portu"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Uložiť"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Zrušiť"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Zadajte číslo"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Neplatné číslo portu"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port už existuje"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminál požaduje otvoriť nový port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Požadovaný port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Prijať"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Zamietnuť"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Obnovenie"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Odstrániť údaje zálohy"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Odstránenie cesty <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Neopraviteľná chyba"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Nepodarilo sa obnoviť z chybového stavu.\nSkúste terminál reštartovať alebo vyskúšajte jednu z možností obnovenia.\nAk všetky pokusy zlyhajú, trvale vymažte všetky údaje tým, že v sekcii Pre vývojárov zapnete alebo vypnete terminál Linux."</string>
     <string name="error_code" msgid="3585291676855383649">"Kód chyby: <xliff:g id="ERROR_CODE">%s</xliff:g>."</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Nastavenia"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminál je spustený"</string>
diff --git a/android/TerminalApp/res/values-sl/strings.xml b/android/TerminalApp/res/values-sl/strings.xml
index c144391..8d8dc3a 100644
--- a/android/TerminalApp/res/values-sl/strings.xml
+++ b/android/TerminalApp/res/values-sl/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Nadzor vrat"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Dovoli/zavrni vrata za poslušanje"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Vrata za poslušanje"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Shranjena dovoljena vrata"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Shranjena dovoljena vrata"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Dodaj"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Izbriši <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Dovoli nova vrata"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Vnesite številko novih vrat"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Shrani"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Prekliči"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Vnesite številko"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Neveljavna številka vrat"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Vrata že obstajajo"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal zahteva odpiranje novih vrat"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Zahtevana vrata: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Sprejmi"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Zavrni"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Obnovitev"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Odstranitev varnostno kopiranih podatkov"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Odstrani <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Nepopravljiva napaka"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Obnovitev po napaki ni uspela.\nPoskusite znova zagnati terminal ali uporabiti eno od možnosti obnovitve.\nČe noben poskus ne uspe, izbrišite vse podatke tako, da v možnostih za razvijalce vklopite/izklopite terminal Linux."</string>
     <string name="error_code" msgid="3585291676855383649">"Koda napake: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Nastavitve"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal se izvaja"</string>
diff --git a/android/TerminalApp/res/values-sq/strings.xml b/android/TerminalApp/res/values-sq/strings.xml
index 456c20f..871671d 100644
--- a/android/TerminalApp/res/values-sq/strings.xml
+++ b/android/TerminalApp/res/values-sq/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kontrolli i portës"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Lejo/refuzo portat e dëgjimit"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Portat e dëgjimit"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Portat e lejuara të ruajtura"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Shto"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Fshi <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Lejo një portë të re"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Fut një numër të një porte të re"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Ruaj"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Anulo"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Fut një numër"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Numër i pavlefshëm i portës"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Porta ekziston tashmë"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminali po përpiqet të hapë një portë të re"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Porta e kërkuar: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Prano"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Refuzo"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Rikuperimi"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Hiq të dhënat e rezervimit"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Hiq <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Gabim i parikuperueshëm"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Rikuperimi nga një gabim dështoi.\nMund të provosh ta rinisësh terminalin ose provo një nga opsionet e rikuperimit.\nNëse dështojnë të gjitha përpjekjet, pastro të gjitha të dhënat duke aktivizuar/çaktivizuar terminalin e Linux nga opsionet e zhvilluesit."</string>
     <string name="error_code" msgid="3585291676855383649">"Kodi i gabimit: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Cilësimet"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminali po ekzekutohet"</string>
diff --git a/android/TerminalApp/res/values-sr/strings.xml b/android/TerminalApp/res/values-sr/strings.xml
index a2d9d51..76e790d 100644
--- a/android/TerminalApp/res/values-sr/strings.xml
+++ b/android/TerminalApp/res/values-sr/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Контрола порта"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Дозволите или забраните портове за слушање"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Портови за слушање"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Сачувани дозвољени портови"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Сачувани дозвољени портови"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Додај"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Избриши <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Дозволите нови порт"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Унесите нови број порта"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Сачувај"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Откажи"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Унесите број"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Неважећи број порта"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Порт већ постоји"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Терминал тражи да отвори нови порт"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Обавезан порт: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Прихвати"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Одбиј"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Опоравак"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Уклоните резервну копију"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Уклоните <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Непоправљива грешка"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Опоравак од грешке није успео.\nПокушајте да рестартујете терминал или испробајте једну од опција за враћање.\nАко ниједан покушај не успе, обришите све податке тако што ћете укључити или искључити Linux терминал у опцијама за програмере."</string>
     <string name="error_code" msgid="3585291676855383649">"Кôд грешке: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Подешавања"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Терминал је активан"</string>
diff --git a/android/TerminalApp/res/values-sv/strings.xml b/android/TerminalApp/res/values-sv/strings.xml
index ce63c99..33b00d8 100644
--- a/android/TerminalApp/res/values-sv/strings.xml
+++ b/android/TerminalApp/res/values-sv/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portkontroll"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Tillåt/neka lyssningsportar"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Lyssningsportar"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Sparade tillåtna portar"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Sparade tillåtna portar"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Lägg till"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Radera <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Tillåt en ny port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Ange ett nytt portnummer"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Spara"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Avbryt"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Ange ett nummer"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Ogiltigt portnummer"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Porten finns redan"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminalen begär att öppna en ny port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Port som begärs: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Godkänn"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Neka"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Återställning"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Ta bort säkerhetskopierad data"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Ta bort <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Allvarligt fel"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Det gick inte att återställa på grund av ett fel.\nDu kan försöka starta om terminalen eller prova ett av återställningsalternativen.\nOm alla försök misslyckas kan du radera all data genom att aktivera/inaktivera Linux-terminalen i utvecklaralternativen."</string>
     <string name="error_code" msgid="3585291676855383649">"Felkod: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Inställningar"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminalen körs"</string>
diff --git a/android/TerminalApp/res/values-sw/strings.xml b/android/TerminalApp/res/values-sw/strings.xml
index 1505659..2affb51 100644
--- a/android/TerminalApp/res/values-sw/strings.xml
+++ b/android/TerminalApp/res/values-sw/strings.xml
@@ -20,7 +20,7 @@
     <string name="terminal_display" msgid="4810127497644015237">"Skrini ya kituo"</string>
     <string name="terminal_input" msgid="4602512831433433551">"Kiteuzi"</string>
     <string name="empty_line" msgid="5012067143408427178">"Mstari usio na chochote"</string>
-    <string name="double_tap_to_edit_text" msgid="2344363097580051316">"Gusa mara mbili ili uandike data"</string>
+    <string name="double_tap_to_edit_text" msgid="2344363097580051316">"Gusa mara mbili ili uweke data kwa kuandika"</string>
     <string name="installer_title_text" msgid="500663060973466805">"Weka temino ya Linux"</string>
     <string name="installer_desc_text_format" msgid="5935117404303982823">"Unahitaji kupakua takribani <xliff:g id="EXPECTED_SIZE">%1$s</xliff:g> ya data kupitia mtandao ili uwashe temino ya Linux.\nUngependa kuendelea?"</string>
     <string name="installer_wait_for_wifi_checkbox_text" msgid="5812378362605046639">"Pakua ukitumia Wi-Fi pekee"</string>
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Udhibiti wa mlango"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Ruhusu au kataa milango ya usikilizaji"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Milango ya usikilizaji"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Umehifadhi milango inayoruhusiwa"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Umehifadhi milango inayoruhusiwa"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Weka"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Futa <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Ruhusu mlango mpya"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Weka namba ya mlango mpya"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Hifadhi"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Acha"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Tafadhali weka namba"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Namba ya mlango si sahihi"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Tayari mlango upo"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Temino inatuma ombi la kufungua mlango mpya"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Ombi la mlango: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Kubali"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Kataa"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Kurejesha"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Ondoa data ya nakala"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Ondoa <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Hitilafu inayozuia kurejesha"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Imeshindwa kurejesha data kutokana na hitilafu.\nUnaweza kujaribu kufunga kisha ufungue kifaa au ujaribu mojawapo ya chaguo za kurejesha.\nIwapo utashindwa, ondoa kabisa data yote kwa kuwasha au kuzima kifaa cha Linux katika chaguo za wasanidi programu."</string>
     <string name="error_code" msgid="3585291676855383649">"Msimbo wa hitilafu: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Mipangilio"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Temino inatumika"</string>
diff --git a/android/TerminalApp/res/values-ta/strings.xml b/android/TerminalApp/res/values-ta/strings.xml
index fbd4c97..a0e56d5 100644
--- a/android/TerminalApp/res/values-ta/strings.xml
+++ b/android/TerminalApp/res/values-ta/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"போர்ட் கட்டுப்பாடு"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"லிஸனிங் போர்ட்டுகளை அனுமதித்தல்/நிராகரித்தல்"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"லிஸனிங் போர்ட்டுகள்"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"சேமித்த அனுமதிக்கப்பட்ட போர்ட்டுகள்"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"சேமித்த அனுமதிக்கப்பட்ட போர்ட்டுகள்"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"சேர்க்கும்"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> ஐ நீக்கும்"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"புதிய போர்ட்டை அனுமதித்தல்"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"புதிய போர்ட் எண்ணை டைப் செய்யுங்கள்"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"சேமி"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ரத்துசெய்"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"எண்ணை டைப் செய்யவும்"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"தவறான போர்ட் எண்"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"போர்ட் ஏற்கெனவே உள்ளது"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"டெர்மினல் புதிய போர்ட்டைத் திறக்குமாறு கேட்கிறது"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"போர்ட் கேட்கப்பட்டுள்ளது: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ஏற்கிறேன்"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"நிராகரி"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"மீட்டெடுத்தல்"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"காப்புப் பிரதித் தரவை அகற்றுதல்"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> ஐ அகற்றுதல்"</string>
     <string name="error_title" msgid="405150657301906598">"சரிசெய்ய முடியாத பிழை"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"பிழையில் இருந்து மீட்டெடுக்க முடியவில்லை.\nநீங்கள் டெர்மினலை மீண்டும் தொடங்கியோ மீட்டெடுப்பு விருப்பங்களில் ஒன்றைப் பயன்படுத்தியோ பார்க்கலாம்.\nஅனைத்து முயற்சிகளும் தோல்வி அடைந்தால் டெவெலப்பர் விருப்பங்களில் Linux டெர்மினலை ஆன்/ஆஃப் செய்து அனைத்துத் தரவையும் அழிக்கவும்."</string>
     <string name="error_code" msgid="3585291676855383649">"பிழைக் குறியீடு: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"அமைப்புகள்"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"டெர்மினல் இயக்கத்தில் உள்ளது"</string>
diff --git a/android/TerminalApp/res/values-te/strings.xml b/android/TerminalApp/res/values-te/strings.xml
index b6d71df..7eab5da 100644
--- a/android/TerminalApp/res/values-te/strings.xml
+++ b/android/TerminalApp/res/values-te/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"పోర్ట్ కంట్రోల్"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"వినే పోర్ట్‌లకు అనుమతినివ్వండి/తిరస్కరించండి"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"వినే పోర్ట్‌లు"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"సేవ్ చేసిన, అనుమతి ఉన్న పోర్ట్‌లు"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"సేవ్ చేసిన, అనుమతి ఉన్న పోర్ట్‌లు"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"జోడించండి"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g>‌ను తొలగించండి"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"కొత్త పోర్ట్‌కు అనుమతినివ్వండి"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"కొత్త పోర్ట్ నంబర్‌ను ఎంటర్ చేయండి"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"సేవ్ చేయండి"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"రద్దు చేయండి"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"దయచేసి నంబర్‌ను ఎంటర్ చేయండి."</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"చెల్లుబాటు కాని పోర్ట్ నంబర్"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"పోర్ట్ ఇప్పటికే ఉంది"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"టెర్మినల్ ఒక కొత్త పోర్ట్‌ను తెరవడానికి రిక్వెస్ట్ చేస్తోంది"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"పోర్ట్ రిక్వెస్ట్ చేయబడింది: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ఆమోదించండి"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"తిరస్కరించండి"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"రికవరీ"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"బ్యాకప్ డేటాను తీసివేయండి"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g>‌ను తీసివేయండి"</string>
     <string name="error_title" msgid="405150657301906598">"రికవరీని అసాధ్యం చేసే ఎర్రర్"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"ఎర్రర్‌ను రికవర్ చేయడంలో విఫలమైంది.\nమీరు టెర్మినల్‌ను రీస్టార్ట్ చేసి ట్రై చేయవచ్చు లేదా రికవరీ ఆప్షన్‌లలో ఒకదాన్ని ట్రై చేయవచ్చు.\nఅన్ని ప్రయత్నాలు విఫలమైతే, డెవలపర్ ఆప్షన్‌ల నుండి Linux టెర్మినల్‌ను ఆన్/ఆఫ్ చేయడం ద్వారా మొత్తం డేటాను పూర్తిగా తొలగించండి."</string>
     <string name="error_code" msgid="3585291676855383649">"ఎర్రర్ కోడ్: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"సెట్టింగ్‌లు"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"టెర్మినల్ రన్ అవుతోంది"</string>
diff --git a/android/TerminalApp/res/values-th/strings.xml b/android/TerminalApp/res/values-th/strings.xml
index 58f638d..77e0e5f 100644
--- a/android/TerminalApp/res/values-th/strings.xml
+++ b/android/TerminalApp/res/values-th/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"การควบคุมพอร์ต"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"อนุญาต/ปฏิเสธพอร์ตที่กำลังรอการเชื่อมต่อ"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"พอร์ตที่กำลังรอการเชื่อมต่อ"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"บันทึกพอร์ตที่อนุญาตแล้ว"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"บันทึกพอร์ตที่อนุญาตแล้ว"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"เพิ่ม"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"ลบ <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"อนุญาตพอร์ตใหม่"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"ป้อนหมายเลขพอร์ตใหม่"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"บันทึก"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"ยกเลิก"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"โปรดป้อนหมายเลข"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"หมายเลขพอร์ตไม่ถูกต้อง"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"มีพอร์ตอยู่แล้ว"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"เทอร์มินัลกำลังส่งคำขอเปิดพอร์ตใหม่"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"พอร์ตที่ขอ: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"ยอมรับ"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"ปฏิเสธ"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"การกู้คืน"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"นําข้อมูลสํารองออก"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"นำ <xliff:g id="PATH">/mnt/backup</xliff:g> ออก"</string>
     <string name="error_title" msgid="405150657301906598">"ข้อผิดพลาดที่กู้คืนไม่ได้"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"กู้คืนจากข้อผิดพลาดไม่สำเร็จ\nคุณสามารถลองรีสตาร์ทเทอร์มินัลหรือลองใช้ตัวเลือกการกู้คืนได้\nหากพยายามดำเนินการทุกอย่างแล้วแต่ไม่สำเร็จ ให้ล้างข้อมูลทั้งหมดโดยเปิด/ปิดเทอร์มินัล Linux จากตัวเลือกสำหรับนักพัฒนาแอป"</string>
     <string name="error_code" msgid="3585291676855383649">"รหัสข้อผิดพลาด: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"การตั้งค่า"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"เทอร์มินัลกำลังทำงาน"</string>
diff --git a/android/TerminalApp/res/values-tl/strings.xml b/android/TerminalApp/res/values-tl/strings.xml
index 5e4a345..c11620e 100644
--- a/android/TerminalApp/res/values-tl/strings.xml
+++ b/android/TerminalApp/res/values-tl/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kontrol ng port"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Payagan/tanggihan ang mga port ng pakikinig"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Mga port sa pakikinig"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"I-save ang mga pinayagang port"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"I-save ang mga pinayagang port"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Magdagdag"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"I-delete ang <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Payagan ang bagong port"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Maglagay ng bagong port number"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"I-save"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Kanselahin"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Maglagay ng numero"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Invalid na numero ng port"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Mayroon na ng port na ito"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Nag-request ang terminal na magbukas ng bagong port"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Ni-request na port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Tanggapin"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Tanggihan"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Pag-recover"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Alisin ang backup data"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Alisin ang <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Hindi nare-recover na error"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Hindi naka-recover mula sa isang error.\nPuwede mong subukang i-restart ang terminal o subukan ang isa sa mga opsyon sa pag-recover.\nKung mabigo ang lahat ng pagtatangka, i-wipe ang lahat ng data sa pamamagitan ng pag-on/off sa terminal ng Linux mula sa mga opsyon ng developer."</string>
     <string name="error_code" msgid="3585291676855383649">"Code ng error: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Mga Setting"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Gumagana ang terminal"</string>
diff --git a/android/TerminalApp/res/values-tr/strings.xml b/android/TerminalApp/res/values-tr/strings.xml
index c00846d..3b2bf0b 100644
--- a/android/TerminalApp/res/values-tr/strings.xml
+++ b/android/TerminalApp/res/values-tr/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Bağlantı noktası kontrolü"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Dinelenen bağlantı noktalarına izin ver/izin verme"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Dinlenen bağlantı noktaları"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"İzin verilen kayıtlı bağlantı noktaları"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Ekle"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Sil: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Yeni bir bağlantı noktasına izin verme"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Yeni bir bağlantı noktası numarası girin"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Kaydet"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"İptal"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Lütfen bir telefon numarası girin"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Bağlantı noktası numarası geçersiz"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Bağlantı noktası zaten mevcut"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal yeni bir bağlantı noktası açmak istiyor"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"İstenilen bağlantı noktası: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Kabul et"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Reddet"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Kurtarma"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Yedek verileri kaldır"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> etiketini kaldır"</string>
     <string name="error_title" msgid="405150657301906598">"Düzeltilemeyen hata"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Bir hatayı düzeltme işlemi başarısız oldu.\nTerminali yeniden başlatmayı veya kurtarma seçeneklerinden birini uygulamayı deneyebilirsiniz.\nTüm denemeler başarısız olursa geliştirici seçeneklerinden Linux terminalini açıp kapatarak tüm verileri temizleyin."</string>
     <string name="error_code" msgid="3585291676855383649">"Hata kodu: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Ayarlar"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal çalışıyor"</string>
diff --git a/android/TerminalApp/res/values-uk/strings.xml b/android/TerminalApp/res/values-uk/strings.xml
index 3a22312..f3a4906 100644
--- a/android/TerminalApp/res/values-uk/strings.xml
+++ b/android/TerminalApp/res/values-uk/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Керування портами"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Дозволити/заборонити порти прослуховування"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Порти прослуховування"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Збережені дозволені порти"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Додати"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Видалити <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Дозволити новий порт"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Введіть новий номер порту"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Зберегти"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Скасувати"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Введіть номер"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Недійсний номер порту"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Порт уже існує"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Термінал просить відкрити новий порт"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Порт, указаний у запиті: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Прийняти"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Відхилити"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Відновлення"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Видалити резервну копію даних"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Видалити <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Помилка з неможливістю відновлення"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Не вдалося виконати відновлення після помилки.\nСпробуйте перезапустити термінал або скористатись одним зі способів відновлення.\nЯкщо це не допоможе, очистьте всі дані, увімкнувши або вимкнувши термінал Linux у параметрах розробника."</string>
     <string name="error_code" msgid="3585291676855383649">"Код помилки: <xliff:g id="ERROR_CODE">%s</xliff:g>."</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Налаштування"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Термінал запущено"</string>
diff --git a/android/TerminalApp/res/values-ur/strings.xml b/android/TerminalApp/res/values-ur/strings.xml
index ccb751c..f0e5351 100644
--- a/android/TerminalApp/res/values-ur/strings.xml
+++ b/android/TerminalApp/res/values-ur/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"پورٹ کنٹرول"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"لسننگ پورٹس کو اجازت دیں/مسترد کریں"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"لسننگ پورٹس"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"اجازت یافتہ پورٹس کو محفوظ کیا گیا"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"اجازت یافتہ پورٹس کو محفوظ کیا گیا"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"شامل کریں"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> حذف کریں"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"نئے پورٹ کی اجازت دی"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"نیا پورٹ نمبر درج کریں"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"محفوظ کریں"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"منسوخ کریں"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"براہ کرم ایک نمبر درج کریں"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"غلط پورٹ نمبر"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"پورٹ پہلے سے موجود ہے"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"ٹرمینل ایک نیا پورٹ کھولنے کی درخواست کر رہا ہے"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"پورٹ کی درخواست کی گئی: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"قبول کریں"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"مسترد کریں"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"بازیابی"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"بیک اپ ڈیٹا ہٹائیں"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"<xliff:g id="PATH">/mnt/backup</xliff:g> ہٹائیں"</string>
     <string name="error_title" msgid="405150657301906598">"نا قابل بازیابی کی خرابی"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"کسی خرابی سے بازیاب کرنے میں ناکام۔\nآپ ٹرمینل کو ری اسٹارٹ کرنے کی کوشش کر سکتے ہیں یا بازیابی کے اختیارات میں سے ایک اختیار آزما سکتے ہیں۔\nاگر تمام کوششیں ناکام ہوتی ہیں تو ڈویلپر کے اختیارات سے Linux ٹرمینل کو آن/آف کر کے تمام ڈیٹا صاف کریں۔"</string>
     <string name="error_code" msgid="3585291676855383649">"خرابی کا کوڈ: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"ترتیبات"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"ٹرمینل چل رہا ہے"</string>
diff --git a/android/TerminalApp/res/values-uz/strings.xml b/android/TerminalApp/res/values-uz/strings.xml
index 99b8546..5c65633 100644
--- a/android/TerminalApp/res/values-uz/strings.xml
+++ b/android/TerminalApp/res/values-uz/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Portlar boshqaruvi"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Uzatish portlariga ruxsat berish/ularni taqiqlash"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Uzatish postlari"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Ruxsat etilgan portlar saqlandi"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Ruxsat etilgan portlar saqlandi"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Qoʻshish"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"<xliff:g id="PORT_NUMBER">%d</xliff:g> hisobini oʻchirish"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Yangi portga ruxsat bering"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Yangi port raqamini kiriting"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Saqlash"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Bekor qilish"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Raqamni kiriting"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Port raqami yaroqsiz"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Port allaqachon mavjud"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Terminal yangi port ochishni talab qilmoqda"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Talab qilingan port: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Qabul qilish"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Rad etish"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Tiklash"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Zaxira maʼlumotlarini olib tashlash"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Olib tashlash: <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Qayta tiklanmaydigan xato"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Xatolikdan tiklanmadi.\nTerminalni qayta ishga tushirishingiz yoki tiklash variantlaridan birini bajarishingiz mumkin.\nAgar barcha urinishlar muvaffaqiyatsiz chiqsa, dasturchi parametrlaridan Linux terminalini yoqish/oʻchirish orqali barcha maʼlumotlarni oʻchirib tashlang."</string>
     <string name="error_code" msgid="3585291676855383649">"Xatolik kodi: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Sozlamalar"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal ishga tushgan"</string>
diff --git a/android/TerminalApp/res/values-vi/strings.xml b/android/TerminalApp/res/values-vi/strings.xml
index 948a2be..5013918 100644
--- a/android/TerminalApp/res/values-vi/strings.xml
+++ b/android/TerminalApp/res/values-vi/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Kiểm soát cổng"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Cho phép/từ chối cổng nghe"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Cổng nghe"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Đã lưu các cổng được cho phép"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Thêm"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Xoá <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Cho phép một cổng mới"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Nhập số cổng mới"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Lưu"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Huỷ"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Vui lòng nhập một số"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Số cổng không hợp lệ"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Cổng đã tồn tại"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Ứng dụng Terminal đang yêu cầu mở một cổng mới"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Cổng được yêu cầu: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Chấp nhận"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Từ chối"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Khôi phục"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Xoá dữ liệu sao lưu"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Xoá <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Lỗi không thể khôi phục"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Không khôi phục được dữ liệu sau khi xảy ra lỗi.\nBạn có thể thử khởi động lại ứng dụng Terminal hoặc thử một trong các tuỳ chọn khôi phục.\nNếu tất cả các cách đều không hiệu quả, hãy xoá sạch mọi dữ liệu bằng cách bật/tắt ứng dụng Terminal trên Linux trong phần tuỳ chọn cho nhà phát triển."</string>
     <string name="error_code" msgid="3585291676855383649">"Mã lỗi: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Cài đặt"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Terminal đang chạy"</string>
diff --git a/android/TerminalApp/res/values-zh-rCN/strings.xml b/android/TerminalApp/res/values-zh-rCN/strings.xml
index 3881e8e..4d424ed 100644
--- a/android/TerminalApp/res/values-zh-rCN/strings.xml
+++ b/android/TerminalApp/res/values-zh-rCN/strings.xml
@@ -48,22 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"端口控制"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"允许/拒绝使用监听端口"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"监听端口"</string>
-    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"已保存的获准端口"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
     <skip />
+    <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"已保存的获准端口"</string>
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"添加"</string>
     <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"删除“<xliff:g id="PORT_NUMBER">%d</xliff:g>”"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"允许使用新端口"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"输入新端口号"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"保存"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"取消"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"请输入数字"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"端口号无效"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"端口已存在"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"终端正在请求打开新端口"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"请求的端口:<xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"接受"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"拒绝"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"恢复"</string>
@@ -81,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"移除备份数据"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"移除 <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"不可恢复的错误"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"未能从错误中恢复。\n您可以尝试重启终端,或尝试某一恢复选项。\n如果所有尝试都失败,请通过在“开发者选项”中开启/关闭 Linux 终端来擦除所有数据。"</string>
     <string name="error_code" msgid="3585291676855383649">"错误代码:<xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"设置"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"终端正在运行"</string>
diff --git a/android/TerminalApp/res/values-zh-rHK/strings.xml b/android/TerminalApp/res/values-zh-rHK/strings.xml
index a64269b..317ec3a 100644
--- a/android/TerminalApp/res/values-zh-rHK/strings.xml
+++ b/android/TerminalApp/res/values-zh-rHK/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"連接埠控制項"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"允許/拒絕聆聽連接埠"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"聆聽連接埠"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"已儲存許可的連接埠"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"新增"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"刪除 <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"允許存取新的連接埠"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"輸入新的連接埠號碼"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"儲存"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"取消"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"請輸入數字"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"連接埠號碼無效"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"連接埠已存在"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"終端機正在要求開啟新的連接埠"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"已要求連接埠:<xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"接受"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"拒絕"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"復原"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"移除備份資料"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"移除 <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"無法復原的錯誤"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"無法從錯誤中復原。\n你可嘗試重新啟動終端機,或使用其中一個復原選項。\n如果各種嘗試皆失敗,前往開發人員選項開啟/關閉 Linux 終端機即可抹除所有資料。"</string>
     <string name="error_code" msgid="3585291676855383649">"錯誤代碼:<xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"設定"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"終端機執行中"</string>
diff --git a/android/TerminalApp/res/values-zh-rTW/strings.xml b/android/TerminalApp/res/values-zh-rTW/strings.xml
index a1d2fbc..d3576d5 100644
--- a/android/TerminalApp/res/values-zh-rTW/strings.xml
+++ b/android/TerminalApp/res/values-zh-rTW/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"通訊埠控制"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"允許/拒絕監聽通訊埠"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"監聽通訊埠"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"已儲存允許的通訊埠"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"新增"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"刪除 <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"允許的新通訊埠"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"輸入新的通訊埠號碼"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"儲存"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"取消"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"請輸入數字"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"通訊埠號碼無效"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"已有這個通訊埠"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"終端機正在要求開啟新的通訊埠"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"要求的通訊埠:<xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"接受"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"拒絕"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"復原"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"移除備份資料"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"移除 <xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"無法復原的錯誤"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"無法從錯誤中復原。\n你可以試著重新啟動終端機,也可以使用其中一個復原選項。\n如果都無法解決問題,請使用開發人員選項開啟/關閉 Linux 終端機,抹除所有資料。"</string>
     <string name="error_code" msgid="3585291676855383649">"錯誤代碼:<xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"設定"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"終端機運作中"</string>
diff --git a/android/TerminalApp/res/values-zu/strings.xml b/android/TerminalApp/res/values-zu/strings.xml
index 1a86444..8b81522 100644
--- a/android/TerminalApp/res/values-zu/strings.xml
+++ b/android/TerminalApp/res/values-zu/strings.xml
@@ -48,23 +48,21 @@
     <string name="settings_port_forwarding_title" msgid="4971368519093858377">"Ulawulo lwembobo"</string>
     <string name="settings_port_forwarding_sub_title" msgid="6547942778715654953">"Vumela/nqabela izimbobo zokulalela"</string>
     <string name="settings_port_forwarding_active_ports_title" msgid="1841436780635889858">"Izimbobo zokulalela"</string>
+    <!-- no translation found for settings_port_forwarding_active_ports_content (1818090784030797758) -->
+    <skip />
     <string name="settings_port_forwarding_other_enabled_ports_title" msgid="2644381842623436676">"Izimbobo ezivunyelwe ezilondoloziwe"</string>
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_add_button (4402301203801949152) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_other_enabled_port_close_button (8475029060852721339) -->
-    <skip />
+    <string name="settings_port_forwarding_other_enabled_port_add_button" msgid="4402301203801949152">"Engeza"</string>
+    <string name="settings_port_forwarding_other_enabled_port_close_button" msgid="8475029060852721339">"Sula i-<xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
     <string name="settings_port_forwarding_dialog_title" msgid="2734992099990516463">"Vumela imbobo entsha"</string>
     <string name="settings_port_forwarding_dialog_textview_hint" msgid="3514035855169269600">"Faka inombolo yembobo entsha"</string>
     <string name="settings_port_forwarding_dialog_save" msgid="1097831033824718393">"Londoloza"</string>
     <string name="settings_port_forwarding_dialog_cancel" msgid="1972597831318470889">"Khansela"</string>
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_input (7589299096002468249) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_invalid_port_range (6682935312557379651) -->
-    <skip />
-    <!-- no translation found for settings_port_forwarding_dialog_error_existing_port (768426750758769928) -->
-    <skip />
+    <string name="settings_port_forwarding_dialog_error_invalid_input" msgid="7589299096002468249">"Sicela ufake inombolo"</string>
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range" msgid="6682935312557379651">"Inombolo engasebenzi yembobo"</string>
+    <string name="settings_port_forwarding_dialog_error_existing_port" msgid="768426750758769928">"Imbobo isikhona"</string>
     <string name="settings_port_forwarding_notification_title" msgid="6950621555592547524">"Itheminali icela ukuvula imbobo entsha"</string>
-    <string name="settings_port_forwarding_notification_content" msgid="5072621159244211971">"Imbobo iceliwe: <xliff:g id="PORT_NUMBER">%d</xliff:g>"</string>
+    <!-- no translation found for settings_port_forwarding_notification_content (779450349212040908) -->
+    <skip />
     <string name="settings_port_forwarding_notification_accept" msgid="3571520986524038185">"Yamukela"</string>
     <string name="settings_port_forwarding_notification_deny" msgid="636848749634710403">"Yenqaba"</string>
     <string name="settings_recovery_title" msgid="6586840079226383285">"Ukuthola"</string>
@@ -82,8 +80,7 @@
     <string name="settings_recovery_remove_backup_title" msgid="1540850288876158899">"Susa idatha eyisipele"</string>
     <string name="settings_recovery_remove_backup_sub_title" msgid="7791375988320242059">"Susa i-<xliff:g id="PATH">/mnt/backup</xliff:g>"</string>
     <string name="error_title" msgid="405150657301906598">"Iphutha elingabuyiseki"</string>
-    <!-- no translation found for error_desc (4588252235686826721) -->
-    <skip />
+    <string name="error_desc" msgid="4588252235686826721">"Kuhlulekile ukutholakala ngemva kwephutha. \nUngazama ukuqala kabusha itheminali noma uzame okunye kokungakhethwa kukho kokutakula. \nUma yonke imizamo ihluleka, sula yonke idatha ngokuvula/ngokuvala itheminali yeLinux kokungakhethwa kukho konjiniyela."</string>
     <string name="error_code" msgid="3585291676855383649">"Ikhodi yephutha: <xliff:g id="ERROR_CODE">%s</xliff:g>"</string>
     <string name="service_notification_settings" msgid="1437365721184401135">"Amasethingi"</string>
     <string name="service_notification_title" msgid="2918088850910713393">"Itheminali iyasebenza"</string>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 40c354e..d3440d3 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -87,6 +87,8 @@
     <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>
+    <!-- Text content for active ports setting in port forwarding [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_active_ports_content"><xliff:g id="port_number" example="8000">%1$d</xliff:g> (<xliff:g id="process_name" example="undefined">%2$s</xliff:g>)</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 add button for other enabled ports. Used for talkback. [CHAR LIMIT=16] -->
@@ -112,7 +114,7 @@
     <!-- 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 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>
+    <string name="settings_port_forwarding_notification_content">Port requested: <xliff:g id="port_number" example="8000">%1$d</xliff:g> (<xliff:g id="process_name" example="undefined">%2$s</xliff:g>)</string>
     <!-- Notification action accept [CHAR LIMIT=16] -->
     <string name="settings_port_forwarding_notification_accept">Accept</string>
     <!-- Notification action deny [CHAR LIMIT=16] -->
diff --git a/android/virtmgr/Android.bp b/android/virtmgr/Android.bp
index ad63995..c2d67cf 100644
--- a/android/virtmgr/Android.bp
+++ b/android/virtmgr/Android.bp
@@ -38,7 +38,6 @@
         "libcfg_if",
         "libclap",
         "libcrosvm_control_static",
-        "libcstr",
         "libcommand_fds",
         "libdisk",
         "libglob",
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index c71b5c5..5ad7ee1 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -68,7 +68,6 @@
     self, wait_for_interface, Accessor, BinderFeatures, ConnectionInfo, ExceptionCode, Interface, ParcelFileDescriptor,
     SpIBinder, Status, StatusCode, Strong, IntoBinderResult,
 };
-use cstr::cstr;
 use glob::glob;
 use libc::{AF_VSOCK, sa_family_t, sockaddr_vm};
 use log::{debug, error, info, warn};
@@ -976,7 +975,7 @@
             "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())]
+        vec![(c"vendor_hashtree_descriptor_root_digest", vendor_hashtree_digest.as_slice())]
     } else {
         vec![]
     };
@@ -984,18 +983,18 @@
     let key_material;
     let mut untrusted_props = Vec::with_capacity(2);
     if cfg!(llpvm_changes) {
-        untrusted_props.push((cstr!("instance-id"), &instance_id[..]));
+        untrusted_props.push((c"instance-id", &instance_id[..]));
         let want_updatable = extract_want_updatable(config);
         if want_updatable && is_secretkeeper_supported() {
             // Let guest know that it can defer rollback protection to Secretkeeper by setting
             // an empty property in untrusted node in DT. This enables Updatable VMs.
-            untrusted_props.push((cstr!("defer-rollback-protection"), &[]));
+            untrusted_props.push((c"defer-rollback-protection", &[]));
             let sk: Strong<dyn ISecretkeeper> =
                 binder::wait_for_interface(SECRETKEEPER_IDENTIFIER)?;
             if sk.getInterfaceVersion()? >= 2 {
                 let PublicKey { keyMaterial } = sk.getSecretkeeperIdentity()?;
                 key_material = keyMaterial;
-                trusted_props.push((cstr!("secretkeeper_public_key"), key_material.as_slice()));
+                trusted_props.push((c"secretkeeper_public_key", key_material.as_slice()));
             }
         }
     }
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index a90c1ff..096d3b5 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -324,7 +324,7 @@
             let tap =
                 if let Some(tap_file) = &config.tap { Some(tap_file.try_clone()?) } else { None };
 
-            run_virtiofs(&config)?;
+            let vhost_fs_devices = run_virtiofs(&config)?;
 
             // If this fails and returns an error, `self` will be left in the `Failed` state.
             let child =
@@ -339,7 +339,13 @@
             let child_clone = child.clone();
             let instance_clone = instance.clone();
             let monitor_vm_exit_thread = Some(thread::spawn(move || {
-                instance_clone.monitor_vm_exit(child_clone, failure_pipe_read, vfio_devices, tap);
+                instance_clone.monitor_vm_exit(
+                    child_clone,
+                    failure_pipe_read,
+                    vfio_devices,
+                    tap,
+                    vhost_fs_devices,
+                );
             }));
 
             if detect_hangup {
@@ -486,6 +492,7 @@
         failure_pipe_read: File,
         vfio_devices: Vec<VfioDevice>,
         tap: Option<File>,
+        vhost_user_devices: Vec<SharedChild>,
     ) {
         let failure_reason_thread = std::thread::spawn(move || {
             // Read the pipe to see if any failure reason is written
@@ -513,6 +520,34 @@
             }
         }
 
+        // In crosvm, when vhost_user frontend is dead, vhost_user backend device will detect and
+        // exit. We can safely wait() for vhost user device after waiting crosvm main
+        // process.
+        for device in vhost_user_devices {
+            match device.wait() {
+                Ok(status) => {
+                    info!("Vhost user device({}) exited with status {}", device.id(), status);
+                    if !status.success() {
+                        if let Some(code) = status.code() {
+                            // vhost_user backend device exit with error code
+                            error!(
+                                "vhost user device({}) exited with error code: {}",
+                                device.id(),
+                                code
+                            );
+                        } else {
+                            // The spawned child process of vhost_user backend device is
+                            // killed by signal
+                            error!("vhost user device({}) killed by signal", device.id());
+                        }
+                    }
+                }
+                Err(e) => {
+                    error!("Error waiting for vhost user device({}) to die: {}", device.id(), e);
+                }
+            }
+        }
+
         let failure_reason = failure_reason_thread.join().expect("failure_reason_thread panic'd");
 
         let mut vm_state = self.vm_state.lock().unwrap();
@@ -915,7 +950,8 @@
     }
 }
 
-fn run_virtiofs(config: &CrosvmConfig) -> io::Result<()> {
+fn run_virtiofs(config: &CrosvmConfig) -> io::Result<Vec<SharedChild>> {
+    let mut devices: Vec<SharedChild> = Vec::new();
     for shared_path in &config.shared_paths {
         if shared_path.app_domain {
             continue;
@@ -947,9 +983,10 @@
 
         let result = SharedChild::spawn(&mut command)?;
         info!("Spawned virtiofs crosvm({})", result.id());
+        devices.push(result);
     }
 
-    Ok(())
+    Ok(devices)
 }
 
 /// Starts an instance of `crosvm` to manage a new VM.
diff --git a/android/virtmgr/src/dt_overlay.rs b/android/virtmgr/src/dt_overlay.rs
index 108ed61..fe2a419 100644
--- a/android/virtmgr/src/dt_overlay.rs
+++ b/android/virtmgr/src/dt_overlay.rs
@@ -15,14 +15,13 @@
 //! This module support creating AFV related overlays, that can then be appended to DT by VM.
 
 use anyhow::{anyhow, Result};
-use cstr::cstr;
 use fsfdt::FsFdt;
 use libfdt::Fdt;
 use std::ffi::CStr;
 use std::path::Path;
 
-pub(crate) const AVF_NODE_NAME: &CStr = cstr!("avf");
-pub(crate) const UNTRUSTED_NODE_NAME: &CStr = cstr!("untrusted");
+pub(crate) const AVF_NODE_NAME: &CStr = c"avf";
+pub(crate) const UNTRUSTED_NODE_NAME: &CStr = c"untrusted";
 pub(crate) const VM_DT_OVERLAY_PATH: &str = "vm_dt_overlay.dtbo";
 pub(crate) const VM_DT_OVERLAY_MAX_SIZE: usize = 2000;
 
@@ -63,13 +62,13 @@
         Fdt::create_empty_tree(buffer).map_err(|e| anyhow!("Failed to create empty Fdt: {e:?}"))?;
     let mut fragment = fdt
         .root_mut()
-        .add_subnode(cstr!("fragment@0"))
+        .add_subnode(c"fragment@0")
         .map_err(|e| anyhow!("Failed to add fragment node: {e:?}"))?;
     fragment
-        .setprop(cstr!("target-path"), b"/\0")
+        .setprop(c"target-path", b"/\0")
         .map_err(|e| anyhow!("Failed to set target-path property: {e:?}"))?;
     let overlay = fragment
-        .add_subnode(cstr!("__overlay__"))
+        .add_subnode(c"__overlay__")
         .map_err(|e| anyhow!("Failed to add __overlay__ node: {e:?}"))?;
     let avf =
         overlay.add_subnode(AVF_NODE_NAME).map_err(|e| anyhow!("Failed to add avf node: {e:?}"))?;
@@ -87,12 +86,12 @@
 
     // Read dt_path from host DT and overlay onto fdt.
     if let Some(path) = dt_path {
-        fdt.overlay_onto(cstr!("/fragment@0/__overlay__"), path)?;
+        fdt.overlay_onto(c"/fragment@0/__overlay__", path)?;
     }
 
     if !trusted_props.is_empty() {
         let mut avf = fdt
-            .node_mut(cstr!("/fragment@0/__overlay__/avf"))
+            .node_mut(c"/fragment@0/__overlay__/avf")
             .map_err(|e| anyhow!("Failed to search avf node: {e:?}"))?
             .ok_or(anyhow!("Failed to get avf node"))?;
         for (name, value) in trusted_props {
@@ -120,14 +119,14 @@
     #[test]
     fn untrusted_prop_test() {
         let mut buffer = vec![0_u8; VM_DT_OVERLAY_MAX_SIZE];
-        let prop_name = cstr!("XOXO");
+        let prop_name = c"XOXO";
         let prop_val_input = b"OXOX";
         let fdt =
             create_device_tree_overlay(&mut buffer, None, &[(prop_name, prop_val_input)], &[])
                 .unwrap();
 
         let prop_value_dt = fdt
-            .node(cstr!("/fragment@0/__overlay__/avf/untrusted"))
+            .node(c"/fragment@0/__overlay__/avf/untrusted")
             .unwrap()
             .expect("/avf/untrusted node doesn't exist")
             .getprop(prop_name)
@@ -139,14 +138,14 @@
     #[test]
     fn trusted_prop_test() {
         let mut buffer = vec![0_u8; VM_DT_OVERLAY_MAX_SIZE];
-        let prop_name = cstr!("XOXOXO");
+        let prop_name = c"XOXOXO";
         let prop_val_input = b"OXOXOX";
         let fdt =
             create_device_tree_overlay(&mut buffer, None, &[], &[(prop_name, prop_val_input)])
                 .unwrap();
 
         let prop_value_dt = fdt
-            .node(cstr!("/fragment@0/__overlay__/avf"))
+            .node(c"/fragment@0/__overlay__/avf")
             .unwrap()
             .expect("/avf node doesn't exist")
             .getprop(prop_name)
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 9bb1481..a426d22 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -57,18 +57,16 @@
 			;;
 	esac
 	if [[ "${*:$OPTIND:1}" ]]; then
-		built_image="${*:$OPTIND:1}"
+		output="${*:$OPTIND:1}"
 	fi
 }
 
 prepare_build_id() {
-	local filename=build_id
 	if [ -z "${KOKORO_BUILD_NUMBER}" ]; then
-		echo eng-$(hostname)-$(date --utc) > ${filename}
+		echo eng-$(hostname)-$(date --utc)
 	else
-		echo ${KOKORO_BUILD_NUMBER} > ${filename}
+		echo ${KOKORO_BUILD_NUMBER}
 	fi
-	echo ${filename}
 }
 
 install_prerequisites() {
@@ -158,6 +156,7 @@
 	source "$HOME"/.cargo/env
 	rustup target add "${arch}"-unknown-linux-gnu
 	cargo install cargo-license
+	cargo install cargo-deb
 }
 
 download_debian_cloud_image() {
@@ -170,23 +169,11 @@
 	wget -O - "${url}" | tar xz -C "${outdir}" --strip-components=1
 }
 
-build_rust_binary_and_copy() {
+build_rust_as_deb() {
 	pushd "$(dirname "$0")/../../guest/$1" > /dev/null
-	local release_flag=
-	local artifact_mode=debug
-	if [[ "$mode" == "release" ]]; then
-		release_flag="--release"
-		artifact_mode=release
-	fi
-	RUSTFLAGS="-C linker=${arch}-linux-gnu-gcc" cargo build \
+	cargo deb \
 		--target "${arch}-unknown-linux-gnu" \
-		--target-dir "${workdir}/$1" ${release_flag}
-	mkdir -p "${dst}/files/usr/local/bin/$1"
-	cp "${workdir}/$1/${arch}-unknown-linux-gnu/${artifact_mode}/$1" "${dst}/files/usr/local/bin/$1/AVF"
-	chmod 777 "${dst}/files/usr/local/bin/$1/AVF"
-
-	mkdir -p "${dst}/files/usr/share/doc/$1"
-	cargo license > "${dst}/files/usr/share/doc/$1/copyright"
+		--output "${debian_cloud_image}/localdebs"
 	popd > /dev/null
 }
 
@@ -220,10 +207,9 @@
 
 	cp -R "$(dirname "$0")/localdebs/" "${debian_cloud_image}/"
 	build_ttyd
-	build_rust_binary_and_copy forwarder_guest
-	build_rust_binary_and_copy forwarder_guest_launcher
-	build_rust_binary_and_copy ip_addr_reporter
-	build_rust_binary_and_copy shutdown_runner
+	build_rust_as_deb forwarder_guest
+	build_rust_as_deb forwarder_guest_launcher
+	build_rust_as_deb shutdown_runner
 }
 
 package_custom_kernel() {
@@ -303,17 +289,23 @@
 }
 
 run_fai() {
-	local out="${built_image}"
+	local out="${raw_disk_image}"
 	make -C "${debian_cloud_image}" "image_bookworm_nocloud_${debian_arch}"
 	mv "${debian_cloud_image}/image_bookworm_nocloud_${debian_arch}.raw" "${out}"
 }
 
-extract_partitions() {
-	root_partition_num=1
-	bios_partition_num=14
-	efi_partition_num=15
+generate_output_package() {
+	fdisk -l "${raw_disk_image}"
+	local vm_config="$(realpath $(dirname "$0"))/vm_config.json.${arch}"
+	local root_partition_num=1
+	local bios_partition_num=14
+	local efi_partition_num=15
 
-	loop=$(losetup -f --show --partscan $built_image)
+	pushd ${workdir} > /dev/null
+
+	echo ${build_id} > build_id
+
+	loop=$(losetup -f --show --partscan $raw_disk_image)
 	dd if="${loop}p$root_partition_num" of=root_part
 	if [[ "$arch" == "x86_64" ]]; then
 		dd if="${loop}p$bios_partition_num" of=bios_part
@@ -321,11 +313,38 @@
 	dd if="${loop}p$efi_partition_num" of=efi_part
 	losetup -d "${loop}"
 
-	sed -i "s/{root_part_guid}/$(sfdisk --part-uuid $built_image $root_partition_num)/g" vm_config.json
+	cp ${vm_config} vm_config.json
+	sed -i "s/{root_part_guid}/$(sfdisk --part-uuid $raw_disk_image $root_partition_num)/g" vm_config.json
 	if [[ "$arch" == "x86_64" ]]; then
-		sed -i "s/{bios_part_guid}/$(sfdisk --part-uuid $built_image $bios_partition_num)/g" vm_config.json
+		sed -i "s/{bios_part_guid}/$(sfdisk --part-uuid $raw_disk_image $bios_partition_num)/g" vm_config.json
 	fi
-	sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid $built_image $efi_partition_num)/g" vm_config.json
+	sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid $raw_disk_image $efi_partition_num)/g" vm_config.json
+
+	images=()
+	if [[ "$arch" == "aarch64" ]]; then
+		images+=(
+			root_part
+			efi_part
+		)
+	# TODO(b/365955006): remove these lines when uboot supports x86_64 EFI application
+	elif [[ "$arch" == "x86_64" ]]; then
+		rm -f vmlinuz initrd.img
+		virt-get-kernel -a "${raw_disk_image}"
+		mv vmlinuz* vmlinuz
+		mv initrd.img* initrd.img
+		images+=(
+			bios_part
+			root_part
+			efi_part
+			vmlinuz
+			initrd.img
+		)
+	fi
+
+	popd > /dev/null
+
+	# --sparse option isn't supported in apache-commons-compress
+	tar czv -f ${output} -C ${workdir} build_id "${images[@]}" vm_config.json
 }
 
 clean_up() {
@@ -335,8 +354,9 @@
 set -e
 trap clean_up EXIT
 
-built_image=image.raw
+output=images.tar.gz
 workdir=$(mktemp -d)
+raw_disk_image=${workdir}/image.raw
 build_id=$(prepare_build_id)
 debian_cloud_image=${workdir}/debian_cloud_image
 debian_version=bookworm
@@ -354,32 +374,4 @@
 copy_android_config
 package_custom_kernel
 run_fai
-fdisk -l "${built_image}"
-images=()
-
-cp "$(dirname "$0")/vm_config.json.${arch}" vm_config.json
-
-extract_partitions
-
-if [[ "$arch" == "aarch64" ]]; then
-	images+=(
-		root_part
-		efi_part
-	)
-# TODO(b/365955006): remove these lines when uboot supports x86_64 EFI application
-elif [[ "$arch" == "x86_64" ]]; then
-	rm -f vmlinuz initrd.img
-	virt-get-kernel -a "${built_image}"
-	mv vmlinuz* vmlinuz
-	mv initrd.img* initrd.img
-	images+=(
-		bios_part
-		root_part
-		efi_part
-		vmlinuz
-		initrd.img
-	)
-fi
-
-# --sparse option isn't supported in apache-commons-compress
-tar czv -f images.tar.gz ${build_id} "${images[@]}" vm_config.json
+generate_output_package
diff --git a/build/debian/fai_config/files/etc/avahi/services/ttyd.service/AVF b/build/debian/fai_config/files/etc/avahi/services/ttyd.service/AVF
new file mode 100644
index 0000000..64f9d0a
--- /dev/null
+++ b/build/debian/fai_config/files/etc/avahi/services/ttyd.service/AVF
@@ -0,0 +1,13 @@
+<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
+<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
+
+<service-group>
+
+  <name>ttyd</name>
+
+  <service protocol="ipv4">
+    <type>_http._tcp</type>
+    <port>7681</port>
+  </service>
+
+</service-group>
diff --git a/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF b/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
index f4c2a24..61da1ce 100644
--- a/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
@@ -4,7 +4,7 @@
 After=network.target
 After=virtiofs_internal.service
 [Service]
-ExecStart=/usr/bin/bash -c '/usr/local/bin/forwarder_guest_launcher --host 192.168.0.1 --grpc_port $(cat /mnt/internal/debian_service_port)'
+ExecStart=/usr/bin/bash -c '/usr/bin/forwarder_guest_launcher --grpc_port $(cat /mnt/internal/debian_service_port)'
 Type=simple
 Restart=on-failure
 RestartSec=1
diff --git a/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF b/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF
deleted file mode 100644
index b9f3193..0000000
--- a/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF
+++ /dev/null
@@ -1,14 +0,0 @@
-[Unit]
-Description=ip report service
-After=syslog.target
-After=network.target
-Requires=ttyd.service
-After=virtiofs_internal.service
-[Service]
-ExecStart=/usr/bin/bash -c '/usr/local/bin/ip_addr_reporter --grpc_port $(cat /mnt/internal/debian_service_port)'
-Type=simple
-Restart=on-failure
-User=root
-Group=root
-[Install]
-WantedBy=multi-user.target
diff --git a/build/debian/fai_config/files/etc/systemd/system/shutdown_runner.service/AVF b/build/debian/fai_config/files/etc/systemd/system/shutdown_runner.service/AVF
index bfb8afb..8656c72 100644
--- a/build/debian/fai_config/files/etc/systemd/system/shutdown_runner.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/shutdown_runner.service/AVF
@@ -3,7 +3,7 @@
 After=network.target
 After=virtiofs_internal.service
 [Service]
-ExecStart=/usr/bin/bash -c '/usr/local/bin/shutdown_runner --grpc_port $(cat /mnt/internal/debian_service_port)'
+ExecStart=/usr/bin/bash -c '/usr/bin/shutdown_runner --grpc_port $(cat /mnt/internal/debian_service_port)'
 Type=simple
 User=root
 Group=root
diff --git a/build/debian/fai_config/package_config/AVF b/build/debian/fai_config/package_config/AVF
index a91c354..98b558b 100644
--- a/build/debian/fai_config/package_config/AVF
+++ b/build/debian/fai_config/package_config/AVF
@@ -1,4 +1,10 @@
 PACKAGES install
 
+avahi-daemon
+avahi-utils
 bpfcc-tools
+libnss-mdns
 procps
+forwarder-guest
+forwarder-guest-launcher
+shutdown-runner
diff --git a/build/debian/fai_config/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index 119bec7..a86d4c9 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -1,12 +1,7 @@
 #!/bin/bash
 
-chmod +x $target/usr/local/bin/forwarder_guest
-chmod +x $target/usr/local/bin/forwarder_guest_launcher
-chmod +x $target/usr/local/bin/ip_addr_reporter
-chmod +x $target/usr/local/bin/shutdown_runner
 chmod +x $target/usr/local/bin/ttyd
 ln -s /etc/systemd/system/ttyd.service $target/etc/systemd/system/multi-user.target.wants/ttyd.service
-ln -s /etc/systemd/system/ip_addr_reporter.service $target/etc/systemd/system/multi-user.target.wants/ip_addr_reporter.service
 ln -s /etc/systemd/system/virtiofs.service $target/etc/systemd/system/multi-user.target.wants/virtiofs.service
 ln -s /etc/systemd/system/forwarder_guest_launcher.service $target/etc/systemd/system/multi-user.target.wants/forwarder_guest_launcher.service
 ln -s /etc/systemd/system/virtiofs_internal.service $target/etc/systemd/system/multi-user.target.wants/virtiofs_internal.service
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 0a7cca6..03657ed 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -9,7 +9,7 @@
 * aosp\_oriole (Pixel 6)
 * aosp\_raven (Pixel 6 Pro)
 * aosp\_felix (Pixel Fold)
-* aosp\_tangopro (Pixel Tablet)
+* aosp\_tangorpro (Pixel Tablet)
 * aosp\_cf\_x86\_64\_phone (Cuttlefish a.k.a. Cloud Android). Follow [this
   instruction](https://source.android.com/docs/setup/create/cuttlefish-use) to
   use.
diff --git a/guest/derive_microdroid_vendor_dice_node/Android.bp b/guest/derive_microdroid_vendor_dice_node/Android.bp
index 8b79aad..7a3ccbe 100644
--- a/guest/derive_microdroid_vendor_dice_node/Android.bp
+++ b/guest/derive_microdroid_vendor_dice_node/Android.bp
@@ -10,7 +10,6 @@
     rustlibs: [
         "libanyhow",
         "libclap",
-        "libcstr",
         "libdice_driver",
         "libdiced_open_dice",
         "libdm_rust",
diff --git a/guest/derive_microdroid_vendor_dice_node/src/main.rs b/guest/derive_microdroid_vendor_dice_node/src/main.rs
index 0f0631e..4ec91ad 100644
--- a/guest/derive_microdroid_vendor_dice_node/src/main.rs
+++ b/guest/derive_microdroid_vendor_dice_node/src/main.rs
@@ -16,7 +16,6 @@
 
 use anyhow::{bail, Context, Result};
 use clap::Parser;
-use cstr::cstr;
 use dice_driver::DiceDriver;
 use diced_open_dice::{
     hash, retry_bcc_format_config_descriptor, DiceConfigValues, OwnedDiceArtifacts, HIDDEN_SIZE,
@@ -50,7 +49,7 @@
 // See dice_for_avf_guest.cddl for CDDL of Configuration Descriptor of VM components.
 fn build_descriptor(vbmeta: &VbMetaImage) -> Result<Vec<u8>> {
     let values = DiceConfigValues {
-        component_name: Some(cstr!("Microdroid vendor")),
+        component_name: Some(c"Microdroid vendor"),
         security_version: Some(vbmeta.rollback_index()),
         ..Default::default()
     };
diff --git a/guest/forwarder_guest/.cargo/config.toml b/guest/forwarder_guest/.cargo/config.toml
new file mode 100644
index 0000000..a451cda
--- /dev/null
+++ b/guest/forwarder_guest/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.aarch64-unknown-linux-gnu]
+linker = "aarch64-linux-gnu-gcc"
+rustflags = ["-C", "target-feature=+crt-static"]
+
+[target.x86_64-unknown-linux-gnu]
+rustflags = ["-C", "target-feature=+crt-static"]
diff --git a/guest/forwarder_guest/Cargo.toml b/guest/forwarder_guest/Cargo.toml
index ce50e4c..bb9b826 100644
--- a/guest/forwarder_guest/Cargo.toml
+++ b/guest/forwarder_guest/Cargo.toml
@@ -10,3 +10,8 @@
 poll_token_derive = "0.1.0"
 remain = "0.2.14"
 vmm-sys-util = "0.12.1"
+
+[package.metadata.deb]
+maintainer = "ferrochrome-dev@google.com"
+copyright = "2024, The Android Open Source Project"
+depends = "$auto"
diff --git a/guest/forwarder_guest_launcher/.cargo/config.toml b/guest/forwarder_guest_launcher/.cargo/config.toml
new file mode 100644
index 0000000..a451cda
--- /dev/null
+++ b/guest/forwarder_guest_launcher/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.aarch64-unknown-linux-gnu]
+linker = "aarch64-linux-gnu-gcc"
+rustflags = ["-C", "target-feature=+crt-static"]
+
+[target.x86_64-unknown-linux-gnu]
+rustflags = ["-C", "target-feature=+crt-static"]
diff --git a/guest/forwarder_guest_launcher/Cargo.toml b/guest/forwarder_guest_launcher/Cargo.toml
index c875484..091d1cf 100644
--- a/guest/forwarder_guest_launcher/Cargo.toml
+++ b/guest/forwarder_guest_launcher/Cargo.toml
@@ -12,6 +12,7 @@
 futures = "0.3.31"
 listeners = "0.2.1"
 log = "0.4.22"
+netdev = "0.31.0"
 prost = "0.13.3"
 serde = { version = "1.0.215", features = ["derive"] }
 tokio = { version = "1.40.0", features = ["process", "rt-multi-thread"] }
@@ -20,3 +21,8 @@
 
 [build-dependencies]
 tonic-build = "0.12.3"
+
+[package.metadata.deb]
+maintainer = "ferrochrome-dev@google.com"
+copyright = "2024, The Android Open Source Project"
+depends = "$auto"
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index f6944d6..963a531 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -18,11 +18,11 @@
 use clap::Parser;
 use csv_async::AsyncReader;
 use debian_service::debian_service_client::DebianServiceClient;
-use debian_service::{QueueOpeningRequest, ReportVmActivePortsRequest};
+use debian_service::{ActivePort, QueueOpeningRequest, ReportVmActivePortsRequest};
 use futures::stream::StreamExt;
 use log::{debug, error};
 use serde::Deserialize;
-use std::collections::HashSet;
+use std::collections::HashMap;
 use std::process::Stdio;
 use tokio::io::BufReader;
 use tokio::process::Command;
@@ -46,16 +46,14 @@
     ip: i8,
     lport: i32,
     rport: i32,
+    #[serde(alias = "C-COMM")]
+    c_comm: String,
     newstate: String,
 }
 
 #[derive(Parser)]
 /// Flags for running command
 pub struct Args {
-    /// Host IP address
-    #[arg(long)]
-    #[arg(alias = "host")]
-    host_addr: String,
     /// grpc port number
     #[arg(long)]
     #[arg(alias = "grpc_port")]
@@ -92,12 +90,12 @@
 }
 
 async fn send_active_ports_report(
-    listening_ports: HashSet<i32>,
+    listening_ports: HashMap<i32, ActivePort>,
     client: &mut DebianServiceClient<Channel>,
 ) -> Result<(), Box<dyn std::error::Error>> {
     let res = client
         .report_vm_active_ports(Request::new(ReportVmActivePortsRequest {
-            ports: listening_ports.into_iter().collect(),
+            ports: listening_ports.values().cloned().collect(),
         }))
         .await?
         .into_inner();
@@ -130,12 +128,16 @@
     // TODO(b/340126051): Consider using NETLINK_SOCK_DIAG for the optimization.
     let listeners = listeners::get_all()?;
     // TODO(b/340126051): Support distinguished port forwarding for ipv6 as well.
-    let mut listening_ports: HashSet<_> = listeners
+    let mut listening_ports: HashMap<_, _> = listeners
         .iter()
-        .map(|x| x.socket)
-        .filter(|x| x.is_ipv4())
-        .map(|x| x.port().into())
-        .filter(|x| is_forwardable_port(*x))
+        .filter(|x| x.socket.is_ipv4())
+        .map(|x| {
+            (
+                x.socket.port().into(),
+                ActivePort { port: x.socket.port().into(), comm: x.process.name.to_string() },
+            )
+        })
+        .filter(|(x, _)| is_forwardable_port(*x))
         .collect();
     send_active_ports_report(listening_ports.clone(), &mut client).await?;
 
@@ -153,7 +155,7 @@
         }
         match row.newstate.as_str() {
             TCPSTATES_STATE_LISTEN => {
-                listening_ports.insert(row.lport);
+                listening_ports.insert(row.lport, ActivePort { port: row.lport, comm: row.c_comm });
             }
             TCPSTATES_STATE_CLOSE => {
                 listening_ports.remove(&row.lport);
@@ -171,7 +173,8 @@
     env_logger::init();
     debug!("Starting forwarder_guest_launcher");
     let args = Args::parse();
-    let addr = format!("https://{}:{}", args.host_addr, args.grpc_port);
+    let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
+    let addr = format!("https://{}:{}", gateway_ip_addr.to_string(), args.grpc_port);
     let channel = Endpoint::from_shared(addr)?.connect().await?;
     let client = DebianServiceClient::new(channel);
 
diff --git a/guest/ip_addr_reporter/.gitignore b/guest/ip_addr_reporter/.gitignore
deleted file mode 100644
index ea8c4bf..0000000
--- a/guest/ip_addr_reporter/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/target
diff --git a/guest/ip_addr_reporter/Cargo.toml b/guest/ip_addr_reporter/Cargo.toml
deleted file mode 100644
index 7592e3f..0000000
--- a/guest/ip_addr_reporter/Cargo.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[package]
-name = "ip_addr_reporter"
-version = "0.1.0"
-edition = "2021"
-license = "Apache-2.0"
-
-[dependencies]
-clap = { version = "4.5.20", features = ["derive"] }
-netdev = "0.31.0"
-prost = "0.13.3"
-tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
-tonic = "0.12.3"
-
-[build-dependencies]
-tonic-build = "0.12.3"
diff --git a/guest/ip_addr_reporter/build.rs b/guest/ip_addr_reporter/build.rs
deleted file mode 100644
index e3939d4..0000000
--- a/guest/ip_addr_reporter/build.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-fn main() -> Result<(), Box<dyn std::error::Error>> {
-    let proto_file = "../../libs/debian_service/proto/DebianService.proto";
-
-    tonic_build::compile_protos(proto_file).unwrap();
-
-    Ok(())
-}
diff --git a/guest/ip_addr_reporter/src/main.rs b/guest/ip_addr_reporter/src/main.rs
deleted file mode 100644
index 62a7aef..0000000
--- a/guest/ip_addr_reporter/src/main.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use api::debian_service_client::DebianServiceClient;
-use api::IpAddr;
-
-use clap::Parser;
-pub mod api {
-    tonic::include_proto!("com.android.virtualization.terminal.proto");
-}
-
-#[derive(Parser)]
-/// Flags for running command
-pub struct Args {
-    /// grpc port number
-    #[arg(long)]
-    #[arg(alias = "grpc_port")]
-    grpc_port: String,
-}
-
-#[tokio::main]
-async fn main() -> Result<(), String> {
-    let args = Args::parse();
-    let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
-    let ip_addr = netdev::get_default_interface()?.ipv4[0].addr();
-
-    let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), args.grpc_port);
-
-    println!("local ip addr: {}", ip_addr.to_string());
-    println!("coonect to grpc server {}", server_addr);
-
-    let mut client = DebianServiceClient::connect(server_addr).await.map_err(|e| e.to_string())?;
-
-    let request = tonic::Request::new(IpAddr { addr: ip_addr.to_string() });
-
-    let response = client.report_vm_ip_addr(request).await.map_err(|e| e.to_string())?;
-    println!("response from server: {:?}", response);
-    Ok(())
-}
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index 51f7802..da056d6 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -17,7 +17,6 @@
         "libciborium_nostd",
         "libciborium_io_nostd",
         "libcoset_nostd",
-        "libcstr",
         "libdiced_open_dice_nostd",
         "libhypervisor_backends",
         "liblibfdt_nostd",
@@ -52,9 +51,6 @@
         unit_test: true,
     },
     prefer_rlib: true,
-    rustlibs: [
-        "libcstr",
-    ],
 }
 
 rust_test {
@@ -384,6 +380,8 @@
         "-E",
         "-P",
         "-xassembler-with-cpp", // allow C preprocessor directives
+        // Suppress an error about the unused -c that precedes -S.
+        "-Wno-unused-command-line-argument",
     ],
     visibility: ["//visibility:private"],
 }
diff --git a/guest/pvmfw/avb/tests/utils.rs b/guest/pvmfw/avb/tests/utils.rs
index 79552b5..e8590ac 100644
--- a/guest/pvmfw/avb/tests/utils.rs
+++ b/guest/pvmfw/avb/tests/utils.rs
@@ -124,8 +124,7 @@
     let footer = extract_avb_footer(&kernel)?;
     let kernel_digest =
         hash(&[&hash(&[b"bootloader"]), &kernel[..usize::try_from(footer.original_image_size)?]]);
-    let capabilities =
-        if cfg!(llpvm_changes) { vec![Capability::SecretkeeperProtection] } else { vec![] };
+    let capabilities = vec![Capability::SecretkeeperProtection];
     let initrd_digest = Some(hash(&[&hash(&[initrd_salt]), initrd]));
     let expected_boot_data = VerifiedBootData {
         debug_level: expected_debug_level,
@@ -133,7 +132,7 @@
         initrd_digest,
         public_key: &public_key,
         capabilities,
-        rollback_index: if cfg!(llpvm_changes) { 1 } else { 0 },
+        rollback_index: 1,
         page_size,
     };
     assert_eq!(expected_boot_data, verified_boot_data);
diff --git a/guest/pvmfw/src/bootargs.rs b/guest/pvmfw/src/bootargs.rs
index aacd8e0..0a5697f 100644
--- a/guest/pvmfw/src/bootargs.rs
+++ b/guest/pvmfw/src/bootargs.rs
@@ -108,7 +108,6 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use cstr::cstr;
 
     fn check(raw: &CStr, expected: Result<&[(&str, Option<&str>)], ()>) {
         let actual = BootArgsIterator::new(raw);
@@ -136,35 +135,35 @@
 
     #[test]
     fn empty() {
-        check(cstr!(""), Ok(&[]));
-        check(cstr!("    "), Ok(&[]));
-        check(cstr!("  \n  "), Ok(&[]));
+        check(c"", Ok(&[]));
+        check(c"    ", Ok(&[]));
+        check(c"  \n  ", Ok(&[]));
     }
 
     #[test]
     fn single() {
-        check(cstr!("foo"), Ok(&[("foo", None)]));
-        check(cstr!("   foo"), Ok(&[("foo", None)]));
-        check(cstr!("foo   "), Ok(&[("foo", None)]));
-        check(cstr!("   foo   "), Ok(&[("foo", None)]));
+        check(c"foo", Ok(&[("foo", None)]));
+        check(c"   foo", Ok(&[("foo", None)]));
+        check(c"foo   ", Ok(&[("foo", None)]));
+        check(c"   foo   ", Ok(&[("foo", None)]));
     }
 
     #[test]
     fn single_with_value() {
-        check(cstr!("foo=bar"), Ok(&[("foo", Some("=bar"))]));
-        check(cstr!("   foo=bar"), Ok(&[("foo", Some("=bar"))]));
-        check(cstr!("foo=bar   "), Ok(&[("foo", Some("=bar"))]));
-        check(cstr!("   foo=bar   "), Ok(&[("foo", Some("=bar"))]));
+        check(c"foo=bar", Ok(&[("foo", Some("=bar"))]));
+        check(c"   foo=bar", Ok(&[("foo", Some("=bar"))]));
+        check(c"foo=bar   ", Ok(&[("foo", Some("=bar"))]));
+        check(c"   foo=bar   ", Ok(&[("foo", Some("=bar"))]));
 
-        check(cstr!("foo="), Ok(&[("foo", Some("="))]));
-        check(cstr!("   foo="), Ok(&[("foo", Some("="))]));
-        check(cstr!("foo=   "), Ok(&[("foo", Some("="))]));
-        check(cstr!("   foo=   "), Ok(&[("foo", Some("="))]));
+        check(c"foo=", Ok(&[("foo", Some("="))]));
+        check(c"   foo=", Ok(&[("foo", Some("="))]));
+        check(c"foo=   ", Ok(&[("foo", Some("="))]));
+        check(c"   foo=   ", Ok(&[("foo", Some("="))]));
     }
 
     #[test]
     fn single_with_quote() {
-        check(cstr!("foo=hello\" \"world"), Ok(&[("foo", Some("=hello\" \"world"))]));
+        check(c"foo=hello\" \"world", Ok(&[("foo", Some("=hello\" \"world"))]));
     }
 
     #[test]
@@ -175,32 +174,32 @@
     #[test]
     fn multiple() {
         check(
-            cstr!(" a=b   c=d   e=  f g  "),
+            c" a=b   c=d   e=  f g  ",
             Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
         );
         check(
-            cstr!("   a=b  \n c=d      e=  f g"),
+            c"   a=b  \n c=d      e=  f g",
             Ok(&[("a", Some("=b")), ("c", Some("=d")), ("e", Some("=")), ("f", None), ("g", None)]),
         );
     }
 
     #[test]
     fn incomplete_quote() {
-        check(
-            cstr!("foo=incomplete\" quote bar=y"),
-            Ok(&[("foo", Some("=incomplete\" quote bar=y"))]),
-        );
+        check(c"foo=incomplete\" quote bar=y", Ok(&[("foo", Some("=incomplete\" quote bar=y"))]));
     }
 
     #[test]
     fn complex() {
-        check(cstr!("  a  a1=  b=c d=e,f,g x=\"value with quote\" y=val\"ue with \"multiple\" quo\"te  "), Ok(&[
-            ("a", None),
-            ("a1", Some("=")),
-            ("b", Some("=c")),
-            ("d", Some("=e,f,g")),
-            ("x", Some("=\"value with quote\"")),
-            ("y", Some("=val\"ue with \"multiple\" quo\"te")),
-        ]));
+        check(
+            c"  a  a1=  b=c d=e,f,g x=\"value with quote\" y=val\"ue with \"multiple\" quo\"te  ",
+            Ok(&[
+                ("a", None),
+                ("a1", Some("=")),
+                ("b", Some("=c")),
+                ("d", Some("=e,f,g")),
+                ("x", Some("=\"value with quote\"")),
+                ("y", Some("=val\"ue with \"multiple\" quo\"te")),
+            ]),
+        );
     }
 }
diff --git a/guest/pvmfw/src/device_assignment.rs b/guest/pvmfw/src/device_assignment.rs
index f37f443..bb2e6ce 100644
--- a/guest/pvmfw/src/device_assignment.rs
+++ b/guest/pvmfw/src/device_assignment.rs
@@ -37,18 +37,6 @@
 use zerocopy::byteorder::big_endian::U32;
 use zerocopy::FromBytes as _;
 
-// TODO(b/308694211): Use cstr! from vmbase instead.
-macro_rules! cstr {
-    ($str:literal) => {{
-        const S: &str = concat!($str, "\0");
-        const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
-            Ok(v) => v,
-            Err(_) => panic!("string contains interior NUL"),
-        };
-        C
-    }};
-}
-
 // TODO(b/277993056): Keep constants derived from platform.dts in one place.
 const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
 
@@ -332,7 +320,7 @@
     //       };
     //    };
     //
-    // Then locate_overlay_target_path(cstr!("/fragment@rng/__overlay__/rng")) is Ok("/rng")
+    // Then locate_overlay_target_path(c"/fragment@rng/__overlay__/rng") is Ok("/rng")
     //
     // Contrary to fdt_overlay_target_offset(), this API enforces overlay target property
     // 'target-path = "/"', so the overlay doesn't modify and/or append platform DT's existing
@@ -343,10 +331,9 @@
         dtbo_node: &FdtNode,
     ) -> Result<CString> {
         let fragment_node = dtbo_node.supernode_at_depth(1)?;
-        let target_path = fragment_node
-            .getprop_str(cstr!("target-path"))?
-            .ok_or(DeviceAssignmentError::InvalidDtbo)?;
-        if target_path != cstr!("/") {
+        let target_path =
+            fragment_node.getprop_str(c"target-path")?.ok_or(DeviceAssignmentError::InvalidDtbo)?;
+        if target_path != c"/" {
             return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
         }
 
@@ -415,7 +402,7 @@
 
     /// Parses Physical devices in VM DTBO
     fn parse_physical_devices(&self) -> Result<BTreeMap<Phandle, PhysicalDeviceInfo>> {
-        let Some(physical_node) = self.as_ref().node(cstr!("/host"))? else {
+        let Some(physical_node) = self.as_ref().node(c"/host")? else {
             return Ok(BTreeMap::new());
         };
 
@@ -459,7 +446,7 @@
         let vm_dtbo = self.as_ref();
 
         let mut phandle_map = BTreeMap::new();
-        let Some(local_fixups) = vm_dtbo.node(cstr!("/__local_fixups__"))? else {
+        let Some(local_fixups) = vm_dtbo.node(c"/__local_fixups__")? else {
             return Ok(phandle_map);
         };
 
@@ -615,15 +602,14 @@
 
 impl PvIommu {
     fn parse(node: &FdtNode) -> Result<Self> {
-        let iommu_cells = node
-            .getprop_u32(cstr!("#iommu-cells"))?
-            .ok_or(DeviceAssignmentError::InvalidPvIommu)?;
+        let iommu_cells =
+            node.getprop_u32(c"#iommu-cells")?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
         // Ensures #iommu-cells = <1>. It means that `<iommus>` entry contains pair of
         // (pvIOMMU ID, vSID)
         if iommu_cells != 1 {
             return Err(DeviceAssignmentError::InvalidPvIommu);
         }
-        let id = node.getprop_u32(cstr!("id"))?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
+        let id = node.getprop_u32(c"id")?.ok_or(DeviceAssignmentError::InvalidPvIommu)?;
         Ok(Self { id })
     }
 }
@@ -687,10 +673,10 @@
 
 impl PhysIommu {
     fn parse(node: &FdtNode) -> Result<Option<Self>> {
-        let Some(token) = node.getprop_u64(cstr!("android,pvmfw,token"))? else {
+        let Some(token) = node.getprop_u64(c"android,pvmfw,token")? else {
             return Ok(None);
         };
-        let Some(iommu_cells) = node.getprop_u32(cstr!("#iommu-cells"))? else {
+        let Some(iommu_cells) = node.getprop_u32(c"#iommu-cells")? else {
             return Err(DeviceAssignmentError::InvalidPhysIommu);
         };
         // Currently only supports #iommu-cells = <1>.
@@ -715,7 +701,7 @@
         phys_iommus: &BTreeMap<Phandle, PhysIommu>,
     ) -> Result<Vec<(PhysIommu, Sid)>> {
         let mut iommus = vec![];
-        let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
+        let Some(mut cells) = node.getprop_cells(c"iommus")? else {
             return Ok(iommus);
         };
         while let Some(cell) = cells.next() {
@@ -735,7 +721,7 @@
     }
 
     fn parse(node: &FdtNode, phys_iommus: &BTreeMap<Phandle, PhysIommu>) -> Result<Option<Self>> {
-        let Some(phandle) = node.getprop_u32(cstr!("android,pvmfw,target"))? else {
+        let Some(phandle) = node.getprop_u32(c"android,pvmfw,target")? else {
             return Ok(None);
         };
         let target = Phandle::try_from(phandle)?;
@@ -812,7 +798,7 @@
         // Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
         // We can't know how many interrupts would exist.
         let interrupts_cells = node
-            .getprop_cells(cstr!("interrupts"))?
+            .getprop_cells(c"interrupts")?
             .ok_or(DeviceAssignmentError::InvalidInterrupts)?
             .count();
         if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
@@ -820,7 +806,7 @@
         }
 
         // Once validated, keep the raw bytes so patch can be done with setprop()
-        Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
+        Ok(node.getprop(c"interrupts").unwrap().unwrap().into())
     }
 
     // TODO(b/277993056): Also validate /__local_fixups__ to ensure that <iommus> has phandle.
@@ -829,7 +815,7 @@
         pviommus: &BTreeMap<Phandle, PvIommu>,
     ) -> Result<Vec<(PvIommu, Vsid)>> {
         let mut iommus = vec![];
-        let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? else {
+        let Some(mut cells) = node.getprop_cells(c"iommus")? else {
             return Ok(iommus);
         };
         while let Some(cell) = cells.next() {
@@ -917,15 +903,15 @@
 
     fn patch(&self, fdt: &mut Fdt, pviommu_phandles: &BTreeMap<PvIommu, Phandle>) -> Result<()> {
         let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
-        dst.setprop(cstr!("reg"), &to_be_bytes(&self.reg))?;
-        dst.setprop(cstr!("interrupts"), &self.interrupts)?;
+        dst.setprop(c"reg", &to_be_bytes(&self.reg))?;
+        dst.setprop(c"interrupts", &self.interrupts)?;
         let mut iommus = Vec::with_capacity(8 * self.iommus.len());
         for (pviommu, vsid) in &self.iommus {
             let phandle = pviommu_phandles.get(pviommu).unwrap();
             iommus.extend_from_slice(&u32::from(*phandle).to_be_bytes());
             iommus.extend_from_slice(&vsid.0.to_be_bytes());
         }
-        dst.setprop(cstr!("iommus"), &iommus)?;
+        dst.setprop(c"iommus", &iommus)?;
 
         Ok(())
     }
@@ -939,7 +925,7 @@
 }
 
 impl DeviceAssignmentInfo {
-    const PVIOMMU_COMPATIBLE: &'static CStr = cstr!("pkvm,pviommu");
+    const PVIOMMU_COMPATIBLE: &'static CStr = c"pkvm,pviommu";
 
     /// Parses pvIOMMUs in fdt
     // Note: This will validate pvIOMMU ids' uniqueness, even when unassigned.
@@ -1046,8 +1032,8 @@
         Self::validate_pviommu_topology(&assigned_devices)?;
 
         let mut vm_dtbo_mask = vm_dtbo.build_mask(assigned_device_paths)?;
-        vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__local_fixups__"))?);
-        vm_dtbo_mask.mask_all(&DtPathTokens::new(cstr!("/__symbols__"))?);
+        vm_dtbo_mask.mask_all(&DtPathTokens::new(c"/__local_fixups__")?);
+        vm_dtbo_mask.mask_all(&DtPathTokens::new(c"/__symbols__")?);
 
         // Note: Any node without __overlay__ will be ignored by fdt_apply_overlay,
         // so doesn't need to be filtered.
@@ -1060,7 +1046,7 @@
         let vm_dtbo = vm_dtbo.as_mut();
 
         // Filter unused references in /__local_fixups__
-        if let Some(local_fixups) = vm_dtbo.node_mut(cstr!("/__local_fixups__"))? {
+        if let Some(local_fixups) = vm_dtbo.node_mut(c"/__local_fixups__")? {
             filter_with_mask(local_fixups, &self.vm_dtbo_mask)?;
         }
 
@@ -1078,7 +1064,7 @@
         for pviommu in &self.pviommus {
             let mut node = compatible.ok_or(DeviceAssignmentError::TooManyPvIommu)?;
             let phandle = node.as_node().get_phandle()?.ok_or(DeviceAssignmentError::Internal)?;
-            node.setprop_inplace(cstr!("id"), &pviommu.id.to_be_bytes())?;
+            node.setprop_inplace(c"id", &pviommu.id.to_be_bytes())?;
             if pviommu_phandles.insert(*pviommu, phandle).is_some() {
                 return Err(DeviceAssignmentError::Internal);
             }
@@ -1108,10 +1094,10 @@
 
 /// Cleans device trees not to contain any pre-populated nodes/props for device assignment.
 pub fn clean(fdt: &mut Fdt) -> Result<()> {
-    let mut compatible = fdt.root_mut().next_compatible(cstr!("pkvm,pviommu"))?;
+    let mut compatible = fdt.root_mut().next_compatible(c"pkvm,pviommu")?;
     // Filters pre-populated
     while let Some(filtered_pviommu) = compatible {
-        compatible = filtered_pviommu.delete_and_next_compatible(cstr!("pkvm,pviommu"))?;
+        compatible = filtered_pviommu.delete_and_next_compatible(c"pkvm,pviommu")?;
     }
 
     // Removes any dangling references in __symbols__ (e.g. removed pvIOMMUs)
@@ -1239,24 +1225,23 @@
                 return Err(FdtError::NotFound.into());
             };
 
-            let reg = node.getprop(cstr!("reg"))?.ok_or(DeviceAssignmentError::MalformedReg)?;
-            let interrupts = node
-                .getprop(cstr!("interrupts"))?
-                .ok_or(DeviceAssignmentError::InvalidInterrupts)?;
+            let reg = node.getprop(c"reg")?.ok_or(DeviceAssignmentError::MalformedReg)?;
+            let interrupts =
+                node.getprop(c"interrupts")?.ok_or(DeviceAssignmentError::InvalidInterrupts)?;
             let mut iommus = vec![];
-            if let Some(mut cells) = node.getprop_cells(cstr!("iommus"))? {
+            if let Some(mut cells) = node.getprop_cells(c"iommus")? {
                 while let Some(pviommu_id) = cells.next() {
                     // pvIOMMU id
                     let phandle = Phandle::try_from(pviommu_id)?;
                     let pviommu = fdt
                         .node_with_phandle(phandle)?
                         .ok_or(DeviceAssignmentError::MalformedIommus)?;
-                    let compatible = pviommu.getprop_str(cstr!("compatible"));
-                    if compatible != Ok(Some(cstr!("pkvm,pviommu"))) {
+                    let compatible = pviommu.getprop_str(c"compatible");
+                    if compatible != Ok(Some(c"pkvm,pviommu")) {
                         return Err(DeviceAssignmentError::MalformedIommus);
                     }
                     let id = pviommu
-                        .getprop_u32(cstr!("id"))?
+                        .getprop_u32(c"id")?
                         .ok_or(DeviceAssignmentError::MalformedIommus)?;
                     iommus.push(id);
 
@@ -1273,8 +1258,8 @@
 
     fn collect_pviommus(fdt: &Fdt) -> Result<Vec<u32>> {
         let mut pviommus = BTreeSet::new();
-        for pviommu in fdt.compatible_nodes(cstr!("pkvm,pviommu"))? {
-            if let Ok(Some(id)) = pviommu.getprop_u32(cstr!("id")) {
+        for pviommu in fdt.compatible_nodes(c"pkvm,pviommu")? {
+            if let Ok(Some(id)) = pviommu.getprop_u32(c"id") {
                 pviommus.insert(id);
             }
         }
@@ -1395,24 +1380,24 @@
 
         let symbols = vm_dtbo.symbols().unwrap().unwrap();
 
-        let rng = vm_dtbo.node(cstr!("/fragment@0/__overlay__/rng")).unwrap();
+        let rng = vm_dtbo.node(c"/fragment@0/__overlay__/rng").unwrap();
         assert_ne!(rng, None);
-        let rng_symbol = symbols.getprop_str(cstr!("rng")).unwrap();
-        assert_eq!(Some(cstr!("/fragment@0/__overlay__/rng")), rng_symbol);
+        let rng_symbol = symbols.getprop_str(c"rng").unwrap();
+        assert_eq!(Some(c"/fragment@0/__overlay__/rng"), rng_symbol);
 
-        let light = vm_dtbo.node(cstr!("/fragment@0/__overlay__/light")).unwrap();
+        let light = vm_dtbo.node(c"/fragment@0/__overlay__/light").unwrap();
         assert_eq!(light, None);
-        let light_symbol = symbols.getprop_str(cstr!("light")).unwrap();
+        let light_symbol = symbols.getprop_str(c"light").unwrap();
         assert_eq!(None, light_symbol);
 
-        let led = vm_dtbo.node(cstr!("/fragment@0/__overlay__/led")).unwrap();
+        let led = vm_dtbo.node(c"/fragment@0/__overlay__/led").unwrap();
         assert_eq!(led, None);
-        let led_symbol = symbols.getprop_str(cstr!("led")).unwrap();
+        let led_symbol = symbols.getprop_str(c"led").unwrap();
         assert_eq!(None, led_symbol);
 
-        let backlight = vm_dtbo.node(cstr!("/fragment@0/__overlay__/bus0/backlight")).unwrap();
+        let backlight = vm_dtbo.node(c"/fragment@0/__overlay__/bus0/backlight").unwrap();
         assert_eq!(backlight, None);
-        let backlight_symbol = symbols.getprop_str(cstr!("backlight")).unwrap();
+        let backlight_symbol = symbols.getprop_str(c"backlight").unwrap();
         assert_eq!(None, backlight_symbol);
     }
 
@@ -1440,19 +1425,19 @@
         }
         device_info.patch(platform_dt).unwrap();
 
-        let rng_node = platform_dt.node(cstr!("/bus0/backlight")).unwrap().unwrap();
-        let phandle = rng_node.getprop_u32(cstr!("phandle")).unwrap();
+        let rng_node = platform_dt.node(c"/bus0/backlight").unwrap().unwrap();
+        let phandle = rng_node.getprop_u32(c"phandle").unwrap();
         assert_ne!(None, phandle);
 
         // Note: Intentionally not using AssignedDeviceNode for matching all props.
         type FdtResult<T> = libfdt::Result<T>;
         let expected: Vec<(FdtResult<&CStr>, FdtResult<Vec<u8>>)> = vec![
-            (Ok(cstr!("android,backlight,ignore-gctrl-reset")), Ok(Vec::new())),
-            (Ok(cstr!("compatible")), Ok(Vec::from(*b"android,backlight\0"))),
-            (Ok(cstr!("interrupts")), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
-            (Ok(cstr!("iommus")), Ok(Vec::new())),
-            (Ok(cstr!("phandle")), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
-            (Ok(cstr!("reg")), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
+            (Ok(c"android,backlight,ignore-gctrl-reset"), Ok(Vec::new())),
+            (Ok(c"compatible"), Ok(Vec::from(*b"android,backlight\0"))),
+            (Ok(c"interrupts"), Ok(into_fdt_prop(vec![0x0, 0xF, 0x4]))),
+            (Ok(c"iommus"), Ok(Vec::new())),
+            (Ok(c"phandle"), Ok(into_fdt_prop(vec![phandle.unwrap()]))),
+            (Ok(c"reg"), Ok(into_fdt_prop(vec![0x0, 0x9, 0x0, 0xFF]))),
         ];
 
         let mut properties: Vec<_> = rng_node
@@ -1493,7 +1478,7 @@
         }
         device_info.patch(platform_dt).unwrap();
 
-        let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu")).unwrap();
+        let compatible = platform_dt.root().next_compatible(c"pkvm,pviommu").unwrap();
         assert_eq!(None, compatible);
 
         if let Some(symbols) = platform_dt.symbols().unwrap() {
@@ -1794,12 +1779,12 @@
         let mut platform_dt_data = pvmfw_fdt_template::RAW.to_vec();
         let platform_dt = Fdt::from_mut_slice(&mut platform_dt_data).unwrap();
 
-        let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
+        let compatible = platform_dt.root().next_compatible(c"pkvm,pviommu");
         assert_ne!(None, compatible.unwrap());
 
         clean(platform_dt).unwrap();
 
-        let compatible = platform_dt.root().next_compatible(cstr!("pkvm,pviommu"));
+        let compatible = platform_dt.root().next_compatible(c"pkvm,pviommu");
         assert_eq!(Ok(None), compatible);
     }
 
diff --git a/guest/pvmfw/src/fdt.rs b/guest/pvmfw/src/fdt.rs
index 4370675..29212f9 100644
--- a/guest/pvmfw/src/fdt.rs
+++ b/guest/pvmfw/src/fdt.rs
@@ -28,7 +28,6 @@
 use core::fmt;
 use core::mem::size_of;
 use core::ops::Range;
-use cstr::cstr;
 use hypervisor_backends::get_device_assigner;
 use hypervisor_backends::get_mem_sharer;
 use libfdt::AddressRange;
@@ -83,10 +82,10 @@
 /// Extract from /config the address range containing the pre-loaded kernel. Absence of /config is
 /// not an error.
 pub fn read_kernel_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
-    let addr = cstr!("kernel-address");
-    let size = cstr!("kernel-size");
+    let addr = c"kernel-address";
+    let size = c"kernel-size";
 
-    if let Some(config) = fdt.node(cstr!("/config"))? {
+    if let Some(config) = fdt.node(c"/config")? {
         if let (Some(addr), Some(size)) = (config.getprop_u32(addr)?, config.getprop_u32(size)?) {
             let addr = addr as usize;
             let size = size as usize;
@@ -101,8 +100,8 @@
 /// Extract from /chosen the address range containing the pre-loaded ramdisk. Absence is not an
 /// error as there can be initrd-less VM.
 pub fn read_initrd_range_from(fdt: &Fdt) -> libfdt::Result<Option<Range<usize>>> {
-    let start = cstr!("linux,initrd-start");
-    let end = cstr!("linux,initrd-end");
+    let start = c"linux,initrd-start";
+    let end = c"linux,initrd-end";
 
     if let Some(chosen) = fdt.chosen()? {
         if let (Some(start), Some(end)) = (chosen.getprop_u32(start)?, chosen.getprop_u32(end)?) {
@@ -118,14 +117,14 @@
     let end = u32::try_from(initrd_range.end).unwrap();
 
     let mut node = fdt.chosen_mut()?.ok_or(FdtError::NotFound)?;
-    node.setprop(cstr!("linux,initrd-start"), &start.to_be_bytes())?;
-    node.setprop(cstr!("linux,initrd-end"), &end.to_be_bytes())?;
+    node.setprop(c"linux,initrd-start", &start.to_be_bytes())?;
+    node.setprop(c"linux,initrd-end", &end.to_be_bytes())?;
     Ok(())
 }
 
 fn read_bootargs_from(fdt: &Fdt) -> libfdt::Result<Option<CString>> {
     if let Some(chosen) = fdt.chosen()? {
-        if let Some(bootargs) = chosen.getprop_str(cstr!("bootargs"))? {
+        if let Some(bootargs) = chosen.getprop_str(c"bootargs")? {
             // We need to copy the string to heap because the original fdt will be invalidated
             // by the templated DT
             let copy = CString::new(bootargs.to_bytes()).map_err(|_| FdtError::BadValue)?;
@@ -140,7 +139,7 @@
     // This function is called before the verification is done. So, we just copy the bootargs to
     // the new FDT unmodified. This will be filtered again in the modify_for_next_stage function
     // if the VM is not debuggable.
-    node.setprop(cstr!("bootargs"), bootargs.to_bytes_with_nul())
+    node.setprop(c"bootargs", bootargs.to_bytes_with_nul())
 }
 
 /// Reads and validates the memory range in the DT.
@@ -186,9 +185,9 @@
 fn patch_memory_range(fdt: &mut Fdt, memory_range: &Range<usize>) -> libfdt::Result<()> {
     let addr = u64::try_from(MEM_START).unwrap();
     let size = u64::try_from(memory_range.len()).unwrap();
-    fdt.node_mut(cstr!("/memory"))?
+    fdt.node_mut(c"/memory")?
         .ok_or(FdtError::NotFound)?
-        .setprop_inplace(cstr!("reg"), [addr.to_be(), size.to_be()].as_bytes())
+        .setprop_inplace(c"reg", [addr.to_be(), size.to_be()].as_bytes())
 }
 
 #[derive(Debug, Default)]
@@ -207,7 +206,7 @@
     let mut table = ArrayVec::new();
     let mut opp_nodes = opp_node.subnodes()?;
     for subnode in opp_nodes.by_ref().take(table.capacity()) {
-        let prop = subnode.getprop_u64(cstr!("opp-hz"))?.ok_or(FdtError::NotFound)?;
+        let prop = subnode.getprop_u64(c"opp-hz")?.ok_or(FdtError::NotFound)?;
         table.push(prop);
     }
 
@@ -239,7 +238,7 @@
 }
 
 fn read_cpu_map_from(fdt: &Fdt) -> libfdt::Result<Option<BTreeMap<Phandle, (usize, usize)>>> {
-    let Some(cpu_map) = fdt.node(cstr!("/cpus/cpu-map"))? else {
+    let Some(cpu_map) = fdt.node(c"/cpus/cpu-map")? else {
         return Ok(None);
     };
 
@@ -254,7 +253,7 @@
             let Some(core) = cluster.subnode(&name)? else {
                 break;
             };
-            let cpu = core.getprop_u32(cstr!("cpu"))?.ok_or(FdtError::NotFound)?;
+            let cpu = core.getprop_u32(c"cpu")?.ok_or(FdtError::NotFound)?;
             let prev = topology.insert(cpu.try_into()?, (n, m));
             if prev.is_some() {
                 return Err(FdtError::BadValue);
@@ -273,10 +272,10 @@
     let cpu_map = read_cpu_map_from(fdt)?;
     let mut topology: CpuTopology = Default::default();
 
-    let mut cpu_nodes = fdt.compatible_nodes(cstr!("arm,armv8"))?;
+    let mut cpu_nodes = fdt.compatible_nodes(c"arm,armv8")?;
     for (idx, cpu) in cpu_nodes.by_ref().take(cpus.capacity()).enumerate() {
-        let cpu_capacity = cpu.getprop_u32(cstr!("capacity-dmips-mhz"))?;
-        let opp_phandle = cpu.getprop_u32(cstr!("operating-points-v2"))?;
+        let cpu_capacity = cpu.getprop_u32(c"capacity-dmips-mhz")?;
+        let opp_phandle = cpu.getprop_u32(c"operating-points-v2")?;
         let opptable_info = if let Some(phandle) = opp_phandle {
             let phandle = phandle.try_into()?;
             let node = fdt.node_with_phandle(phandle)?.ok_or(FdtError::NotFound)?;
@@ -313,7 +312,7 @@
 }
 
 fn read_vcpufreq_info(fdt: &Fdt) -> libfdt::Result<Option<VcpufreqInfo>> {
-    let mut nodes = fdt.compatible_nodes(cstr!("virtual,android-v-only-cpufreq"))?;
+    let mut nodes = fdt.compatible_nodes(c"virtual,android-v-only-cpufreq")?;
     let Some(node) = nodes.next() else {
         return Ok(None);
     };
@@ -351,7 +350,7 @@
     node: FdtNodeMut,
     opptable: Option<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>>,
 ) -> libfdt::Result<()> {
-    let oppcompat = cstr!("operating-points-v2");
+    let oppcompat = c"operating-points-v2";
     let next = node.next_compatible(oppcompat)?.ok_or(FdtError::NoSpace)?;
 
     let Some(opptable) = opptable else {
@@ -362,7 +361,7 @@
 
     for entry in opptable {
         let mut subnode = next_subnode.ok_or(FdtError::NoSpace)?;
-        subnode.setprop_inplace(cstr!("opp-hz"), &entry.to_be_bytes())?;
+        subnode.setprop_inplace(c"opp-hz", &entry.to_be_bytes())?;
         next_subnode = subnode.next_subnode()?;
     }
 
@@ -391,14 +390,14 @@
     cpus: &[CpuInfo],
     topology: &Option<CpuTopology>,
 ) -> libfdt::Result<()> {
-    const COMPAT: &CStr = cstr!("arm,armv8");
+    const COMPAT: &CStr = c"arm,armv8";
     let mut cpu_phandles = Vec::new();
     for (idx, cpu) in cpus.iter().enumerate() {
         let mut cur = get_nth_compatible(fdt, idx, COMPAT)?.ok_or(FdtError::NoSpace)?;
         let phandle = cur.as_node().get_phandle()?.unwrap();
         cpu_phandles.push(phandle);
         if let Some(cpu_capacity) = cpu.cpu_capacity {
-            cur.setprop_inplace(cstr!("capacity-dmips-mhz"), &cpu_capacity.to_be_bytes())?;
+            cur.setprop_inplace(c"capacity-dmips-mhz", &cpu_capacity.to_be_bytes())?;
         }
         patch_opptable(cur, cpu.opptable_info)?;
     }
@@ -418,7 +417,7 @@
                     iter = if let Some(core_idx) = core {
                         let phandle = *cpu_phandles.get(core_idx).unwrap();
                         let value = u32::from(phandle).to_be_bytes();
-                        core_node.setprop_inplace(cstr!("cpu"), &value)?;
+                        core_node.setprop_inplace(c"cpu", &value)?;
                         core_node.next_subnode()?
                     } else {
                         core_node.delete_and_next_subnode()?
@@ -430,7 +429,7 @@
             }
         }
     } else {
-        fdt.node_mut(cstr!("/cpus/cpu-map"))?.unwrap().nop()?;
+        fdt.node_mut(c"/cpus/cpu-map")?.unwrap().nop()?;
     }
 
     Ok(())
@@ -440,7 +439,7 @@
 /// the guest that don't require being validated by pvmfw.
 fn parse_untrusted_props(fdt: &Fdt) -> libfdt::Result<BTreeMap<CString, Vec<u8>>> {
     let mut props = BTreeMap::new();
-    if let Some(node) = fdt.node(cstr!("/avf/untrusted"))? {
+    if let Some(node) = fdt.node(c"/avf/untrusted")? {
         for property in node.properties()? {
             let name = property.name()?;
             let value = property.value()?;
@@ -457,7 +456,7 @@
 /// Read candidate properties' names from DT which could be overlaid
 fn parse_vm_ref_dt(fdt: &Fdt) -> libfdt::Result<BTreeMap<CString, Vec<u8>>> {
     let mut property_map = BTreeMap::new();
-    if let Some(avf_node) = fdt.node(cstr!("/avf"))? {
+    if let Some(avf_node) = fdt.node(c"/avf")? {
         for property in avf_node.properties()? {
             let name = property.name()?;
             let value = property.value()?;
@@ -471,8 +470,7 @@
 }
 
 fn validate_untrusted_props(props: &BTreeMap<CString, Vec<u8>>) -> Result<(), FdtValidationError> {
-    const FORBIDDEN_PROPS: &[&CStr] =
-        &[cstr!("compatible"), cstr!("linux,phandle"), cstr!("phandle")];
+    const FORBIDDEN_PROPS: &[&CStr] = &[c"compatible", c"linux,phandle", c"phandle"];
 
     for name in FORBIDDEN_PROPS {
         if props.contains_key(*name) {
@@ -491,9 +489,9 @@
     props_info: &BTreeMap<CString, Vec<u8>>,
 ) -> libfdt::Result<()> {
     let root_vm_dt = vm_dt.root_mut();
-    let mut avf_vm_dt = root_vm_dt.add_subnode(cstr!("avf"))?;
+    let mut avf_vm_dt = root_vm_dt.add_subnode(c"avf")?;
     // TODO(b/318431677): Validate nodes beyond /avf.
-    let avf_node = vm_ref_dt.node(cstr!("/avf"))?.ok_or(FdtError::NotFound)?;
+    let avf_node = vm_ref_dt.node(c"/avf")?.ok_or(FdtError::NotFound)?;
     for (name, value) in props_info.iter() {
         if let Some(ref_value) = avf_node.getprop(name)? {
             if value != ref_value {
@@ -551,14 +549,13 @@
 
 /// Read pci host controller ranges, irq maps, and irq map masks from DT
 fn read_pci_info_from(fdt: &Fdt) -> libfdt::Result<PciInfo> {
-    let node =
-        fdt.compatible_nodes(cstr!("pci-host-cam-generic"))?.next().ok_or(FdtError::NotFound)?;
+    let node = fdt.compatible_nodes(c"pci-host-cam-generic")?.next().ok_or(FdtError::NotFound)?;
 
     let mut ranges = node.ranges::<(u32, u64), u64, u64>()?.ok_or(FdtError::NotFound)?;
     let range0 = ranges.next().ok_or(FdtError::NotFound)?;
     let range1 = ranges.next().ok_or(FdtError::NotFound)?;
 
-    let irq_masks = node.getprop_cells(cstr!("interrupt-map-mask"))?.ok_or(FdtError::NotFound)?;
+    let irq_masks = node.getprop_cells(c"interrupt-map-mask")?.ok_or(FdtError::NotFound)?;
     let mut chunks = CellChunkIterator::<{ PciInfo::IRQ_MASK_CELLS }>::new(irq_masks);
     let irq_masks = (&mut chunks).take(PciInfo::MAX_IRQS).collect();
 
@@ -567,7 +564,7 @@
         return Err(FdtError::NoSpace);
     }
 
-    let irq_maps = node.getprop_cells(cstr!("interrupt-map"))?.ok_or(FdtError::NotFound)?;
+    let irq_maps = node.getprop_cells(c"interrupt-map")?.ok_or(FdtError::NotFound)?;
     let mut chunks = CellChunkIterator::<{ PciInfo::IRQ_MAP_CELLS }>::new(irq_maps);
     let irq_maps = (&mut chunks).take(PciInfo::MAX_IRQS).collect();
 
@@ -721,16 +718,16 @@
 
 fn patch_pci_info(fdt: &mut Fdt, pci_info: &PciInfo) -> libfdt::Result<()> {
     let mut node =
-        fdt.root_mut().next_compatible(cstr!("pci-host-cam-generic"))?.ok_or(FdtError::NotFound)?;
+        fdt.root_mut().next_compatible(c"pci-host-cam-generic")?.ok_or(FdtError::NotFound)?;
 
     let irq_masks_size = pci_info.irq_masks.len() * size_of::<PciIrqMask>();
-    node.trimprop(cstr!("interrupt-map-mask"), irq_masks_size)?;
+    node.trimprop(c"interrupt-map-mask", irq_masks_size)?;
 
     let irq_maps_size = pci_info.irq_maps.len() * size_of::<PciIrqMap>();
-    node.trimprop(cstr!("interrupt-map"), irq_maps_size)?;
+    node.trimprop(c"interrupt-map", irq_maps_size)?;
 
     node.setprop_inplace(
-        cstr!("ranges"),
+        c"ranges",
         [pci_info.ranges[0].to_cells(), pci_info.ranges[1].to_cells()].as_flattened(),
     )
 }
@@ -747,7 +744,7 @@
 fn read_serial_info_from(fdt: &Fdt) -> libfdt::Result<SerialInfo> {
     let mut addrs = ArrayVec::new();
 
-    let mut serial_nodes = fdt.compatible_nodes(cstr!("ns16550a"))?;
+    let mut serial_nodes = fdt.compatible_nodes(c"ns16550a")?;
     for node in serial_nodes.by_ref().take(addrs.capacity()) {
         let reg = node.first_reg()?;
         addrs.push(reg.addr);
@@ -793,7 +790,7 @@
 }
 
 fn read_wdt_info_from(fdt: &Fdt) -> libfdt::Result<WdtInfo> {
-    let mut node_iter = fdt.compatible_nodes(cstr!("qemu,vcpu-stall-detector"))?;
+    let mut node_iter = fdt.compatible_nodes(c"qemu,vcpu-stall-detector")?;
     let node = node_iter.next().ok_or(FdtError::NotFound)?;
     let mut ranges = node.reg()?.ok_or(FdtError::NotFound)?;
 
@@ -803,7 +800,7 @@
         warn!("Discarding extra vmwdt <reg> entries.");
     }
 
-    let interrupts = node.getprop_cells(cstr!("interrupts"))?.ok_or(FdtError::NotFound)?;
+    let interrupts = node.getprop_cells(c"interrupts")?.ok_or(FdtError::NotFound)?;
     let mut chunks = CellChunkIterator::<{ WdtInfo::IRQ_CELLS }>::new(interrupts);
     let irq = chunks.next().ok_or(FdtError::NotFound)?;
 
@@ -831,15 +828,15 @@
 
     let mut node = fdt
         .root_mut()
-        .next_compatible(cstr!("qemu,vcpu-stall-detector"))?
+        .next_compatible(c"qemu,vcpu-stall-detector")?
         .ok_or(libfdt::FdtError::NotFound)?;
-    node.setprop_inplace(cstr!("interrupts"), interrupts.as_bytes())?;
+    node.setprop_inplace(c"interrupts", interrupts.as_bytes())?;
     Ok(())
 }
 
 /// Patch the DT by deleting the ns16550a compatible nodes whose address are unknown
 fn patch_serial_info(fdt: &mut Fdt, serial_info: &SerialInfo) -> libfdt::Result<()> {
-    let name = cstr!("ns16550a");
+    let name = c"ns16550a";
     let mut next = fdt.root_mut().next_compatible(name);
     while let Some(current) = next? {
         let reg =
@@ -889,27 +886,27 @@
 
 fn patch_swiotlb_info(fdt: &mut Fdt, swiotlb_info: &SwiotlbInfo) -> libfdt::Result<()> {
     let mut node =
-        fdt.root_mut().next_compatible(cstr!("restricted-dma-pool"))?.ok_or(FdtError::NotFound)?;
+        fdt.root_mut().next_compatible(c"restricted-dma-pool")?.ok_or(FdtError::NotFound)?;
 
     if let Some(range) = swiotlb_info.fixed_range() {
         node.setprop_addrrange_inplace(
-            cstr!("reg"),
+            c"reg",
             range.start.try_into().unwrap(),
             range.len().try_into().unwrap(),
         )?;
-        node.nop_property(cstr!("size"))?;
-        node.nop_property(cstr!("alignment"))?;
+        node.nop_property(c"size")?;
+        node.nop_property(c"alignment")?;
     } else {
-        node.nop_property(cstr!("reg"))?;
-        node.setprop_inplace(cstr!("size"), &swiotlb_info.size.to_be_bytes())?;
-        node.setprop_inplace(cstr!("alignment"), &swiotlb_info.align.unwrap().to_be_bytes())?;
+        node.nop_property(c"reg")?;
+        node.setprop_inplace(c"size", &swiotlb_info.size.to_be_bytes())?;
+        node.setprop_inplace(c"alignment", &swiotlb_info.align.unwrap().to_be_bytes())?;
     }
 
     Ok(())
 }
 
 fn patch_gic(fdt: &mut Fdt, num_cpus: usize) -> libfdt::Result<()> {
-    let node = fdt.compatible_nodes(cstr!("arm,gic-v3"))?.next().ok_or(FdtError::NotFound)?;
+    let node = fdt.compatible_nodes(c"arm,gic-v3")?.next().ok_or(FdtError::NotFound)?;
     let mut ranges = node.reg()?.ok_or(FdtError::NotFound)?;
     let range0 = ranges.next().ok_or(FdtError::NotFound)?;
     let mut range1 = ranges.next().ok_or(FdtError::NotFound)?;
@@ -927,16 +924,15 @@
     let (addr1, size1) = range1.to_cells();
     let value = [addr0, size0.unwrap(), addr1, size1.unwrap()];
 
-    let mut node =
-        fdt.root_mut().next_compatible(cstr!("arm,gic-v3"))?.ok_or(FdtError::NotFound)?;
-    node.setprop_inplace(cstr!("reg"), value.as_flattened())
+    let mut node = fdt.root_mut().next_compatible(c"arm,gic-v3")?.ok_or(FdtError::NotFound)?;
+    node.setprop_inplace(c"reg", value.as_flattened())
 }
 
 fn patch_timer(fdt: &mut Fdt, num_cpus: usize) -> libfdt::Result<()> {
     const NUM_INTERRUPTS: usize = 4;
     const CELLS_PER_INTERRUPT: usize = 3;
-    let node = fdt.compatible_nodes(cstr!("arm,armv8-timer"))?.next().ok_or(FdtError::NotFound)?;
-    let interrupts = node.getprop_cells(cstr!("interrupts"))?.ok_or(FdtError::NotFound)?;
+    let node = fdt.compatible_nodes(c"arm,armv8-timer")?.next().ok_or(FdtError::NotFound)?;
+    let interrupts = node.getprop_cells(c"interrupts")?.ok_or(FdtError::NotFound)?;
     let mut value: ArrayVec<[u32; NUM_INTERRUPTS * CELLS_PER_INTERRUPT]> =
         interrupts.take(NUM_INTERRUPTS * CELLS_PER_INTERRUPT).collect();
 
@@ -953,20 +949,19 @@
 
     let value = value.into_inner();
 
-    let mut node =
-        fdt.root_mut().next_compatible(cstr!("arm,armv8-timer"))?.ok_or(FdtError::NotFound)?;
-    node.setprop_inplace(cstr!("interrupts"), value.as_bytes())
+    let mut node = fdt.root_mut().next_compatible(c"arm,armv8-timer")?.ok_or(FdtError::NotFound)?;
+    node.setprop_inplace(c"interrupts", value.as_bytes())
 }
 
 fn patch_untrusted_props(fdt: &mut Fdt, props: &BTreeMap<CString, Vec<u8>>) -> libfdt::Result<()> {
-    let avf_node = if let Some(node) = fdt.node_mut(cstr!("/avf"))? {
+    let avf_node = if let Some(node) = fdt.node_mut(c"/avf")? {
         node
     } else {
-        fdt.root_mut().add_subnode(cstr!("avf"))?
+        fdt.root_mut().add_subnode(c"avf")?
     };
 
     // The node shouldn't already be present; if it is, return the error.
-    let mut node = avf_node.add_subnode(cstr!("untrusted"))?;
+    let mut node = avf_node.add_subnode(c"untrusted")?;
 
     for (name, value) in props {
         node.setprop(name, value)?;
@@ -982,9 +977,9 @@
 }
 
 fn patch_vcpufreq(fdt: &mut Fdt, vcpufreq_info: &Option<VcpufreqInfo>) -> libfdt::Result<()> {
-    let mut node = fdt.node_mut(cstr!("/cpufreq"))?.unwrap();
+    let mut node = fdt.node_mut(c"/cpufreq")?.unwrap();
     if let Some(info) = vcpufreq_info {
-        node.setprop_addrrange_inplace(cstr!("reg"), info.addr, info.size)
+        node.setprop_addrrange_inplace(c"reg", info.addr, info.size)
     } else {
         node.nop()
     }
@@ -1304,9 +1299,9 @@
     patch_dice_node(fdt, bcc.as_ptr() as usize, bcc.len())?;
 
     if let Some(mut chosen) = fdt.chosen_mut()? {
-        empty_or_delete_prop(&mut chosen, cstr!("avf,strict-boot"), strict_boot)?;
-        empty_or_delete_prop(&mut chosen, cstr!("avf,new-instance"), new_instance)?;
-        chosen.setprop_inplace(cstr!("kaslr-seed"), &kaslr_seed.to_be_bytes())?;
+        empty_or_delete_prop(&mut chosen, c"avf,strict-boot", strict_boot)?;
+        empty_or_delete_prop(&mut chosen, c"avf,new-instance", new_instance)?;
+        chosen.setprop_inplace(c"kaslr-seed", &kaslr_seed.to_be_bytes())?;
     };
     if !debuggable {
         if let Some(bootargs) = read_bootargs_from(fdt)? {
@@ -1323,13 +1318,13 @@
 fn patch_dice_node(fdt: &mut Fdt, addr: usize, size: usize) -> libfdt::Result<()> {
     // We reject DTs with missing reserved-memory node as validation should have checked that the
     // "swiotlb" subnode (compatible = "restricted-dma-pool") was present.
-    let node = fdt.node_mut(cstr!("/reserved-memory"))?.ok_or(libfdt::FdtError::NotFound)?;
+    let node = fdt.node_mut(c"/reserved-memory")?.ok_or(libfdt::FdtError::NotFound)?;
 
-    let mut node = node.next_compatible(cstr!("google,open-dice"))?.ok_or(FdtError::NotFound)?;
+    let mut node = node.next_compatible(c"google,open-dice")?.ok_or(FdtError::NotFound)?;
 
     let addr: u64 = addr.try_into().unwrap();
     let size: u64 = size.try_into().unwrap();
-    node.setprop_inplace(cstr!("reg"), [addr.to_be_bytes(), size.to_be_bytes()].as_flattened())
+    node.setprop_inplace(c"reg", [addr.to_be_bytes(), size.to_be_bytes()].as_flattened())
 }
 
 fn empty_or_delete_prop(
@@ -1376,7 +1371,7 @@
 }
 
 fn has_common_debug_policy(fdt: &Fdt, debug_feature_name: &CStr) -> libfdt::Result<bool> {
-    if let Some(node) = fdt.node(cstr!("/avf/guest/common"))? {
+    if let Some(node) = fdt.node(c"/avf/guest/common")? {
         if let Some(value) = node.getprop_u32(debug_feature_name)? {
             return Ok(value == 1);
         }
@@ -1385,8 +1380,8 @@
 }
 
 fn filter_out_dangerous_bootargs(fdt: &mut Fdt, bootargs: &CStr) -> libfdt::Result<()> {
-    let has_crashkernel = has_common_debug_policy(fdt, cstr!("ramdump"))?;
-    let has_console = has_common_debug_policy(fdt, cstr!("log"))?;
+    let has_crashkernel = has_common_debug_policy(fdt, c"ramdump")?;
+    let has_console = has_common_debug_policy(fdt, c"log")?;
 
     let accepted: &[(&str, Box<dyn Fn(Option<&str>) -> bool>)] = &[
         ("panic", Box::new(|v| if let Some(v) = v { v == "=-1" } else { false })),
@@ -1417,5 +1412,5 @@
     new_bootargs.push(b'\0');
 
     let mut node = fdt.chosen_mut()?.ok_or(FdtError::NotFound)?;
-    node.setprop(cstr!("bootargs"), new_bootargs.as_slice())
+    node.setprop(c"bootargs", new_bootargs.as_slice())
 }
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index a28a039..0a3dca6 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 cstr::cstr;
 use diced_open_dice::{bcc_handover_parse, DiceArtifacts, DiceContext, Hidden, VM_KEY_ALGORITHM};
 use libfdt::{Fdt, FdtNode};
 use log::{debug, error, info, trace, warn};
@@ -130,7 +129,7 @@
         RebootReason::InternalError
     })?;
 
-    let instance_hash = if cfg!(llpvm_changes) { Some(salt_from_instance_id(fdt)?) } else { None };
+    let instance_hash = Some(salt_from_instance_id(fdt)?);
     let (new_instance, salt, defer_rollback_protection) = perform_rollback_protection(
         fdt,
         &verified_boot_data,
@@ -220,7 +219,7 @@
 
 fn instance_id(fdt: &Fdt) -> Result<&[u8], RebootReason> {
     let node = avf_untrusted_node(fdt)?;
-    let id = node.getprop(cstr!("instance-id")).map_err(|e| {
+    let id = node.getprop(c"instance-id").map_err(|e| {
         error!("Failed to get instance-id in DT: {e}");
         RebootReason::InvalidFdt
     })?;
@@ -231,7 +230,7 @@
 }
 
 fn avf_untrusted_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
-    let node = fdt.node(cstr!("/avf/untrusted")).map_err(|e| {
+    let node = fdt.node(c"/avf/untrusted").map_err(|e| {
         error!("Failed to get /avf/untrusted node: {e}");
         RebootReason::InvalidFdt
     })?;
diff --git a/guest/pvmfw/src/rollback.rs b/guest/pvmfw/src/rollback.rs
index bc16332..f7723d7 100644
--- a/guest/pvmfw/src/rollback.rs
+++ b/guest/pvmfw/src/rollback.rs
@@ -19,7 +19,6 @@
 use crate::instance::EntryBody;
 use crate::instance::Error as InstanceError;
 use crate::instance::{get_recorded_entry, record_instance_entry};
-use cstr::cstr;
 use diced_open_dice::Hidden;
 use libfdt::{Fdt, FdtNode};
 use log::{error, info};
@@ -31,7 +30,7 @@
 /// Performs RBP based on the input payload, current DICE chain, and host-controlled platform.
 ///
 /// On success, returns a tuple containing:
-/// - `new_instance`: true if a new entry was created using the legacy instance.img solution;
+/// - `new_instance`: true if the legacy instance.img solution was used and a new entry created;
 /// - `salt`: the salt representing the instance, to be used during DICE derivation;
 /// - `defer_rollback_protection`: if RBP is being deferred.
 pub fn perform_rollback_protection(
@@ -42,67 +41,87 @@
     cdi_seal: &[u8],
     instance_hash: Option<Hidden>,
 ) -> Result<(bool, Hidden, bool), RebootReason> {
-    let defer_rollback_protection = should_defer_rollback_protection(fdt)?
-        && verified_boot_data.has_capability(Capability::SecretkeeperProtection);
-    let (new_instance, salt) = if defer_rollback_protection {
-        info!("Guest OS is capable of Secretkeeper protection, deferring rollback protection");
-        // rollback_index of the image is used as security_version and is expected to be > 0 to
-        // discourage implicit allocation.
-        if verified_boot_data.rollback_index == 0 {
-            error!("Expected positive rollback_index, found 0");
-            return Err(RebootReason::InvalidPayload);
-        };
-        (false, instance_hash.unwrap())
+    if should_defer_rollback_protection(fdt)?
+        && verified_boot_data.has_capability(Capability::SecretkeeperProtection)
+    {
+        perform_deferred_rollback_protection(verified_boot_data)?;
+        Ok((false, instance_hash.unwrap(), true))
     } else if verified_boot_data.has_capability(Capability::RemoteAttest) {
-        info!("Service VM capable of remote attestation detected, performing version checks");
-        if service_vm_version::VERSION != verified_boot_data.rollback_index {
-            // For RKP VM, we only boot if the version in the AVB footer of its kernel matches
-            // the one embedded in pvmfw at build time.
-            // This prevents the pvmfw from booting a roll backed RKP VM.
-            error!(
-                "Service VM version mismatch: expected {}, found {}",
-                service_vm_version::VERSION,
-                verified_boot_data.rollback_index
-            );
-            return Err(RebootReason::InvalidPayload);
-        }
-        (false, instance_hash.unwrap())
+        perform_fixed_index_rollback_protection(verified_boot_data)?;
+        Ok((false, instance_hash.unwrap(), false))
     } else if verified_boot_data.has_capability(Capability::TrustySecurityVm) {
-        // The rollback protection of Trusty VMs are handled by AuthMgr, so we don't need to
-        // handle it here.
-        info!("Trusty Security VM detected");
-        (false, instance_hash.unwrap())
+        skip_rollback_protection()?;
+        Ok((false, instance_hash.unwrap(), false))
     } else {
-        info!("Fallback to instance.img based rollback checks");
-        let (recorded_entry, mut instance_img, header_index) =
-            get_recorded_entry(pci_root, cdi_seal).map_err(|e| {
-                error!("Failed to get entry from instance.img: {e}");
-                RebootReason::InternalError
-            })?;
-        let (new_instance, salt) = if let Some(entry) = recorded_entry {
-            check_dice_measurements_match_entry(dice_inputs, &entry)?;
-            let salt = instance_hash.unwrap_or(entry.salt);
-            (false, salt)
-        } else {
-            // New instance!
-            let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
-                error!("Failed to generated instance.img salt: {e}");
-                RebootReason::InternalError
-            })?;
+        perform_legacy_rollback_protection(dice_inputs, pci_root, cdi_seal, instance_hash)
+    }
+}
 
-            let entry = EntryBody::new(dice_inputs, &salt);
-            record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(
-                |e| {
-                    error!("Failed to get recorded entry in instance.img: {e}");
-                    RebootReason::InternalError
-                },
-            )?;
-            (true, salt)
-        };
-        (new_instance, salt)
+fn perform_deferred_rollback_protection(
+    verified_boot_data: &VerifiedBootData,
+) -> Result<(), RebootReason> {
+    info!("Deferring rollback protection");
+    // rollback_index of the image is used as security_version and is expected to be > 0 to
+    // discourage implicit allocation.
+    if verified_boot_data.rollback_index == 0 {
+        error!("Expected positive rollback_index, found 0");
+        Err(RebootReason::InvalidPayload)
+    } else {
+        Ok(())
+    }
+}
+
+fn perform_fixed_index_rollback_protection(
+    verified_boot_data: &VerifiedBootData,
+) -> Result<(), RebootReason> {
+    info!("Performing fixed-index rollback protection");
+    let fixed_index = service_vm_version::VERSION;
+    let index = verified_boot_data.rollback_index;
+    if index != fixed_index {
+        error!("Rollback index mismatch: expected {fixed_index}, found {index}");
+        Err(RebootReason::InvalidPayload)
+    } else {
+        Ok(())
+    }
+}
+
+fn skip_rollback_protection() -> Result<(), RebootReason> {
+    info!("Skipping rollback protection");
+    Ok(())
+}
+
+/// Performs RBP using instance.img where updates require clearing old entries, causing new CDIs.
+fn perform_legacy_rollback_protection(
+    dice_inputs: &PartialInputs,
+    pci_root: &mut PciRoot,
+    cdi_seal: &[u8],
+    instance_hash: Option<Hidden>,
+) -> Result<(bool, Hidden, bool), RebootReason> {
+    info!("Fallback to instance.img based rollback checks");
+    let (recorded_entry, mut instance_img, header_index) = get_recorded_entry(pci_root, cdi_seal)
+        .map_err(|e| {
+        error!("Failed to get entry from instance.img: {e}");
+        RebootReason::InternalError
+    })?;
+    let (new_instance, salt) = if let Some(entry) = recorded_entry {
+        check_dice_measurements_match_entry(dice_inputs, &entry)?;
+        let salt = instance_hash.unwrap_or(entry.salt);
+        (false, salt)
+    } else {
+        // New instance!
+        let salt = instance_hash.map_or_else(rand::random_array, Ok).map_err(|e| {
+            error!("Failed to generated instance.img salt: {e}");
+            RebootReason::InternalError
+        })?;
+
+        let entry = EntryBody::new(dice_inputs, &salt);
+        record_instance_entry(&entry, cdi_seal, &mut instance_img, header_index).map_err(|e| {
+            error!("Failed to get recorded entry in instance.img: {e}");
+            RebootReason::InternalError
+        })?;
+        (true, salt)
     };
-
-    Ok((new_instance, salt, defer_rollback_protection))
+    Ok((new_instance, salt, false))
 }
 
 fn check_dice_measurements_match_entry(
@@ -138,7 +157,7 @@
 fn should_defer_rollback_protection(fdt: &Fdt) -> Result<bool, RebootReason> {
     let node = avf_untrusted_node(fdt)?;
     let defer_rbp = node
-        .getprop(cstr!("defer-rollback-protection"))
+        .getprop(c"defer-rollback-protection")
         .map_err(|e| {
             error!("Failed to get defer-rollback-protection property in DT: {e}");
             RebootReason::InvalidFdt
@@ -148,7 +167,7 @@
 }
 
 fn avf_untrusted_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
-    let node = fdt.node(cstr!("/avf/untrusted")).map_err(|e| {
+    let node = fdt.node(c"/avf/untrusted").map_err(|e| {
         error!("Failed to get /avf/untrusted node: {e}");
         RebootReason::InvalidFdt
     })?;
diff --git a/guest/rialto/Android.bp b/guest/rialto/Android.bp
index a525168..35ede7a 100644
--- a/guest/rialto/Android.bp
+++ b/guest/rialto/Android.bp
@@ -12,7 +12,6 @@
         "libbssl_avf_nostd",
         "libciborium_io_nostd",
         "libciborium_nostd",
-        "libcstr",
         "libdiced_open_dice_nostd",
         "libhypervisor_backends",
         "liblibfdt_nostd",
diff --git a/guest/rialto/src/fdt.rs b/guest/rialto/src/fdt.rs
index e97a262..06879d0 100644
--- a/guest/rialto/src/fdt.rs
+++ b/guest/rialto/src/fdt.rs
@@ -15,24 +15,23 @@
 //! High-level FDT functions.
 
 use core::ops::Range;
-use cstr::cstr;
 use libfdt::{Fdt, FdtError};
 
 /// Reads the DICE data range from the given `fdt`.
 pub fn read_dice_range_from(fdt: &Fdt) -> libfdt::Result<Range<usize>> {
-    let node = fdt.node(cstr!("/reserved-memory"))?.ok_or(FdtError::NotFound)?;
-    let node = node.next_compatible(cstr!("google,open-dice"))?.ok_or(FdtError::NotFound)?;
+    let node = fdt.node(c"/reserved-memory")?.ok_or(FdtError::NotFound)?;
+    let node = node.next_compatible(c"google,open-dice")?.ok_or(FdtError::NotFound)?;
     node.first_reg()?.try_into()
 }
 
 pub(crate) fn read_vendor_hashtree_root_digest(fdt: &Fdt) -> libfdt::Result<Option<&[u8]>> {
-    let node = fdt.node(cstr!("/avf"))?.ok_or(FdtError::NotFound)?;
-    node.getprop(cstr!("vendor_hashtree_descriptor_root_digest"))
+    let node = fdt.node(c"/avf")?.ok_or(FdtError::NotFound)?;
+    node.getprop(c"vendor_hashtree_descriptor_root_digest")
 }
 
 pub(crate) fn read_is_strict_boot(fdt: &Fdt) -> libfdt::Result<bool> {
     match fdt.chosen()? {
-        Some(node) => Ok(node.getprop(cstr!("avf,strict-boot"))?.is_some()),
+        Some(node) => Ok(node.getprop(c"avf,strict-boot")?.is_some()),
         None => Ok(false),
     }
 }
diff --git a/guest/shutdown_runner/.cargo/config.toml b/guest/shutdown_runner/.cargo/config.toml
new file mode 100644
index 0000000..a451cda
--- /dev/null
+++ b/guest/shutdown_runner/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.aarch64-unknown-linux-gnu]
+linker = "aarch64-linux-gnu-gcc"
+rustflags = ["-C", "target-feature=+crt-static"]
+
+[target.x86_64-unknown-linux-gnu]
+rustflags = ["-C", "target-feature=+crt-static"]
diff --git a/guest/shutdown_runner/Cargo.toml b/guest/shutdown_runner/Cargo.toml
index b74e7ee..564daf6 100644
--- a/guest/shutdown_runner/Cargo.toml
+++ b/guest/shutdown_runner/Cargo.toml
@@ -15,3 +15,8 @@
 
 [build-dependencies]
 tonic-build = "0.12.3"
+
+[package.metadata.deb]
+maintainer = "ferrochrome-dev@google.com"
+copyright = "2024, The Android Open Source Project"
+depends = "$auto"
diff --git a/guest/vmbase_example/Android.bp b/guest/vmbase_example/Android.bp
index 09bd77c..ab21191 100644
--- a/guest/vmbase_example/Android.bp
+++ b/guest/vmbase_example/Android.bp
@@ -9,7 +9,6 @@
     srcs: ["src/main.rs"],
     rustlibs: [
         "libaarch64_paging",
-        "libcstr",
         "libdiced_open_dice_nostd",
         "liblibfdt_nostd",
         "liblog_rust_nostd",
diff --git a/guest/vmbase_example/src/main.rs b/guest/vmbase_example/src/main.rs
index 4c5e880..f5b41bd 100644
--- a/guest/vmbase_example/src/main.rs
+++ b/guest/vmbase_example/src/main.rs
@@ -27,7 +27,6 @@
 use crate::pci::check_pci;
 use alloc::{vec, vec::Vec};
 use core::ptr::addr_of_mut;
-use cstr::cstr;
 use libfdt::Fdt;
 use log::{debug, error, info, trace, warn, LevelFilter};
 use vmbase::{
@@ -147,7 +146,7 @@
         info!("memory @ {reg:#x?}");
     }
 
-    let compatible = cstr!("ns16550a");
+    let compatible = c"ns16550a";
 
     for c in reader.compatible_nodes(compatible).unwrap() {
         let reg = c.reg().unwrap().unwrap().next().unwrap();
@@ -159,17 +158,17 @@
     writer.unpack().unwrap();
     info!("FDT successfully unpacked.");
 
-    let path = cstr!("/memory");
+    let path = c"/memory";
     let node = writer.node_mut(path).unwrap().unwrap();
-    let name = cstr!("child");
+    let name = c"child";
     let mut child = node.add_subnode(name).unwrap();
     info!("Created subnode '{}/{}'.", path.to_str().unwrap(), name.to_str().unwrap());
 
-    let name = cstr!("str-property");
+    let name = c"str-property";
     child.appendprop(name, b"property-value\0").unwrap();
     info!("Appended property '{}'.", name.to_str().unwrap());
 
-    let name = cstr!("pair-property");
+    let name = c"pair-property";
     let addr = 0x0123_4567u64;
     let size = 0x89ab_cdefu64;
     child.appendprop_addrrange(name, addr, size).unwrap();
diff --git a/libs/cstr/rules.mk b/libs/cstr/rules.mk
new file mode 100644
index 0000000..2309c30
--- /dev/null
+++ b/libs/cstr/rules.mk
@@ -0,0 +1,28 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+SRC_DIR := packages/modules/Virtualization/libs/cstr
+
+MODULE_SRCS := $(SRC_DIR)/src/lib.rs
+
+MODULE_CRATE_NAME := cstr
+
+MODULE_RUST_EDITION := 2021
+
+include make/library.mk
diff --git a/libs/debian_service/proto/DebianService.proto b/libs/debian_service/proto/DebianService.proto
index 739f0ac..43955fa 100644
--- a/libs/debian_service/proto/DebianService.proto
+++ b/libs/debian_service/proto/DebianService.proto
@@ -23,7 +23,6 @@
 
 service DebianService {
   rpc ReportVmActivePorts (ReportVmActivePortsRequest) returns (ReportVmActivePortsResponse) {}
-  rpc ReportVmIpAddr (IpAddr) returns (ReportVmIpAddrResponse) {}
   rpc OpenForwardingRequestQueue (QueueOpeningRequest) returns (stream ForwardingRequestItem) {}
   rpc OpenShutdownRequestQueue (ShutdownQueueOpeningRequest) returns (stream ShutdownRequestItem) {}
 }
@@ -32,16 +31,13 @@
   int32 cid = 1;
 }
 
-message IpAddr {
-  string addr = 1;
-}
-
-message ReportVmIpAddrResponse {
-  bool success = 1;
+message ActivePort {
+  int32 port = 1;
+  string comm = 2;
 }
 
 message ReportVmActivePortsRequest {
-  repeated int32 ports = 1;
+  repeated ActivePort ports = 1;
 }
 
 message ReportVmActivePortsResponse {
@@ -55,4 +51,4 @@
 
 message ShutdownQueueOpeningRequest {}
 
-message ShutdownRequestItem {}
\ No newline at end of file
+message ShutdownRequestItem {}
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
index 079f4ae..b583e21 100644
--- a/libs/libavf/Android.bp
+++ b/libs/libavf/Android.bp
@@ -10,6 +10,7 @@
     source_stem: "bindings",
     bindgen_flags: ["--default-enum-style rust"],
     apex_available: ["com.android.virt"],
+    visibility: ["//packages/modules/Virtualization/tests/vts"],
 }
 
 rust_defaults {
diff --git a/libs/libavf/include/android/virtualization.h b/libs/libavf/include/android/virtualization.h
index 6b54bf7..ef57325 100644
--- a/libs/libavf/include/android/virtualization.h
+++ b/libs/libavf/include/android/virtualization.h
@@ -70,7 +70,13 @@
                                      const char* _Nonnull name) __INTRODUCED_IN(36);
 
 /**
- * Set an instance ID of a virtual machine.
+ * Set an instance ID of a virtual machine. Every virtual machine is identified by a unique
+ * `instanceId` which the virtual machine uses as its persistent identity while performing stateful
+ * operations that are expected to outlast single boot of the VM. For example, some virtual machines
+ * use it as a `Id` for storing secrets in Secretkeeper, which are retrieved on next boot of th VM.
+ *
+ * The `instanceId` is expected to be re-used for the VM instance with an associated state (secret,
+ * encrypted storage) - i.e., rebooting the VM must not change the instanceId.
  *
  * \param config a virtual machine config object.
  * \param instanceId a pointer to a 64-byte buffer for the instance ID.
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 09f288d..829b30f 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -35,7 +35,6 @@
     ],
     edition: "2021",
     rustlibs: [
-        "libcstr",
         "liblibfdt_bindgen",
         "libstatic_assertions",
         "libzerocopy_nostd",
@@ -79,7 +78,6 @@
     ],
     prefer_rlib: true,
     rustlibs: [
-        "libcstr",
         "liblibfdt",
     ],
 }
diff --git a/libs/libfdt/bindgen/rules.mk b/libs/libfdt/bindgen/rules.mk
new file mode 100644
index 0000000..130a317
--- /dev/null
+++ b/libs/libfdt/bindgen/rules.mk
@@ -0,0 +1,38 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/src/lib.rs
+
+MODULE_CRATE_NAME := libfdt_bindgen
+
+MODULE_DEPS += \
+	external/dtc/libfdt \
+
+MODULE_BINDGEN_ALLOW_FUNCTIONS := \
+	fdt_.* \
+
+MODULE_BINDGEN_ALLOW_VARS := \
+	FDT_.* \
+
+MODULE_BINDGEN_ALLOW_TYPES := \
+	fdt_.* \
+
+MODULE_BINDGEN_SRC_HEADER := $(LOCAL_DIR)/fdt.h
+
+include make/library.mk
diff --git a/libs/libfdt/bindgen/src/lib.rs b/libs/libfdt/bindgen/src/lib.rs
new file mode 100644
index 0000000..015132b
--- /dev/null
+++ b/libs/libfdt/bindgen/src/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+//! # Interface library for libfdt.
+
+#![no_std]
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(env!("BINDGEN_INC_FILE"));
diff --git a/libs/libfdt/rules.mk b/libs/libfdt/rules.mk
new file mode 100644
index 0000000..2b4e470
--- /dev/null
+++ b/libs/libfdt/rules.mk
@@ -0,0 +1,37 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+SRC_DIR := packages/modules/Virtualization/libs/libfdt
+
+MODULE_SRCS := $(SRC_DIR)/src/lib.rs
+
+MODULE_CRATE_NAME := libfdt
+
+MODULE_RUST_EDITION := 2021
+
+MODULE_LIBRARY_DEPS += \
+	external/dtc/libfdt \
+	packages/modules/Virtualization/libs/cstr \
+	packages/modules/Virtualization/libs/libfdt/bindgen \
+	$(call FIND_CRATE,zerocopy) \
+	$(call FIND_CRATE,static_assertions) \
+
+MODULE_RUST_USE_CLIPPY := true
+
+include make/library.mk
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index c969749..0dcd31a 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -31,7 +31,6 @@
 
 use core::ffi::{c_void, CStr};
 use core::ops::Range;
-use cstr::cstr;
 use libfdt::get_slice_at_ptr;
 use zerocopy::IntoBytes as _;
 
@@ -167,12 +166,12 @@
 
     /// Returns the standard (deprecated) device_type <string> property.
     pub fn device_type(&self) -> Result<Option<&CStr>> {
-        self.getprop_str(cstr!("device_type"))
+        self.getprop_str(c"device_type")
     }
 
     /// Returns the standard reg <prop-encoded-array> property.
     pub fn reg(&self) -> Result<Option<RegIterator<'a>>> {
-        if let Some(cells) = self.getprop_cells(cstr!("reg"))? {
+        if let Some(cells) = self.getprop_cells(c"reg")? {
             let parent = self.parent()?;
 
             let addr_cells = parent.address_cells()?;
@@ -186,7 +185,7 @@
 
     /// Returns the standard ranges property.
     pub fn ranges<A, P, S>(&self) -> Result<Option<RangesIterator<'a, A, P, S>>> {
-        if let Some(cells) = self.getprop_cells(cstr!("ranges"))? {
+        if let Some(cells) = self.getprop_cells(c"ranges")? {
             let parent = self.parent()?;
             let addr_cells = self.address_cells()?;
             let parent_addr_cells = parent.address_cells()?;
@@ -320,9 +319,9 @@
     /// Returns the phandle
     pub fn get_phandle(&self) -> Result<Option<Phandle>> {
         // This rewrites the fdt_get_phandle() because it doesn't return error code.
-        if let Some(prop) = self.getprop_u32(cstr!("phandle"))? {
+        if let Some(prop) = self.getprop_u32(c"phandle")? {
             Ok(Some(prop.try_into()?))
-        } else if let Some(prop) = self.getprop_u32(cstr!("linux,phandle"))? {
+        } else if let Some(prop) = self.getprop_u32(c"linux,phandle")? {
             Ok(Some(prop.try_into()?))
         } else {
             Ok(None)
@@ -693,8 +692,8 @@
     ///
     /// NOTE: This does not support individual "/memory@XXXX" banks.
     pub fn memory(&self) -> Result<MemRegIterator> {
-        let node = self.root().subnode(cstr!("memory"))?.ok_or(FdtError::NotFound)?;
-        if node.device_type()? != Some(cstr!("memory")) {
+        let node = self.root().subnode(c"memory")?.ok_or(FdtError::NotFound)?;
+        if node.device_type()? != Some(c"memory") {
             return Err(FdtError::BadValue);
         }
         node.reg()?.ok_or(FdtError::BadValue).map(MemRegIterator::new)
@@ -707,12 +706,12 @@
 
     /// Returns the standard /chosen node.
     pub fn chosen(&self) -> Result<Option<FdtNode>> {
-        self.root().subnode(cstr!("chosen"))
+        self.root().subnode(c"chosen")
     }
 
     /// Returns the standard /chosen node as mutable.
     pub fn chosen_mut(&mut self) -> Result<Option<FdtNodeMut>> {
-        self.node_mut(cstr!("/chosen"))
+        self.node_mut(c"/chosen")
     }
 
     /// Returns the root node of the tree.
@@ -722,12 +721,12 @@
 
     /// Returns the standard /__symbols__ node.
     pub fn symbols(&self) -> Result<Option<FdtNode>> {
-        self.root().subnode(cstr!("__symbols__"))
+        self.root().subnode(c"__symbols__")
     }
 
     /// Returns the standard /__symbols__ node as mutable
     pub fn symbols_mut(&mut self) -> Result<Option<FdtNodeMut>> {
-        self.node_mut(cstr!("/__symbols__"))
+        self.node_mut(c"/__symbols__")
     }
 
     /// Returns a tree node by its full path.
diff --git a/libs/libfdt/tests/api_test.rs b/libs/libfdt/tests/api_test.rs
index f521a00..e027164 100644
--- a/libs/libfdt/tests/api_test.rs
+++ b/libs/libfdt/tests/api_test.rs
@@ -17,7 +17,6 @@
 //! Integration tests of the library libfdt.
 
 use core::ffi::CStr;
-use cstr::cstr;
 use libfdt::{Fdt, FdtError, FdtNodeMut, Phandle};
 use std::collections::HashSet;
 use std::ffi::CString;
@@ -82,14 +81,14 @@
     let fdt = Fdt::from_slice(&data).unwrap();
 
     let root = fdt.root();
-    assert_eq!(root.name(), Ok(cstr!("")));
+    assert_eq!(root.name(), Ok(c""));
 
     let chosen = fdt.chosen().unwrap().unwrap();
-    assert_eq!(chosen.name(), Ok(cstr!("chosen")));
+    assert_eq!(chosen.name(), Ok(c"chosen"));
 
-    let nested_node_path = cstr!("/cpus/PowerPC,970@0");
+    let nested_node_path = c"/cpus/PowerPC,970@0";
     let nested_node = fdt.node(nested_node_path).unwrap().unwrap();
-    assert_eq!(nested_node.name(), Ok(cstr!("PowerPC,970@0")));
+    assert_eq!(nested_node.name(), Ok(c"PowerPC,970@0"));
 }
 
 #[test]
@@ -97,7 +96,7 @@
     let data = fs::read(TEST_TREE_WITH_NO_MEMORY_NODE_PATH).unwrap();
     let fdt = Fdt::from_slice(&data).unwrap();
     let root = fdt.root();
-    let expected = [Ok(cstr!("cpus")), Ok(cstr!("randomnode")), Ok(cstr!("chosen"))];
+    let expected = [Ok(c"cpus"), Ok(c"randomnode"), Ok(c"chosen")];
 
     let root_subnodes = root.subnodes().unwrap();
     let subnode_names: Vec<_> = root_subnodes.map(|node| node.name()).collect();
@@ -112,11 +111,11 @@
     let one_be = 0x1_u32.to_be_bytes();
     type Result<T> = core::result::Result<T, FdtError>;
     let expected: Vec<(Result<&CStr>, Result<&[u8]>)> = vec![
-        (Ok(cstr!("model")), Ok(b"MyBoardName\0".as_ref())),
-        (Ok(cstr!("compatible")), Ok(b"MyBoardName\0MyBoardFamilyName\0".as_ref())),
-        (Ok(cstr!("#address-cells")), Ok(&one_be)),
-        (Ok(cstr!("#size-cells")), Ok(&one_be)),
-        (Ok(cstr!("empty_prop")), Ok(&[])),
+        (Ok(c"model"), Ok(b"MyBoardName\0".as_ref())),
+        (Ok(c"compatible"), Ok(b"MyBoardName\0MyBoardFamilyName\0".as_ref())),
+        (Ok(c"#address-cells"), Ok(&one_be)),
+        (Ok(c"#size-cells"), Ok(&one_be)),
+        (Ok(c"empty_prop"), Ok(&[])),
     ];
 
     let properties = root.properties().unwrap();
@@ -129,8 +128,8 @@
 fn node_supernode_at_depth() {
     let data = fs::read(TEST_TREE_WITH_NO_MEMORY_NODE_PATH).unwrap();
     let fdt = Fdt::from_slice(&data).unwrap();
-    let node = fdt.node(cstr!("/cpus/PowerPC,970@1")).unwrap().unwrap();
-    let expected = vec![Ok(cstr!("")), Ok(cstr!("cpus")), Ok(cstr!("PowerPC,970@1"))];
+    let node = fdt.node(c"/cpus/PowerPC,970@1").unwrap().unwrap();
+    let expected = vec![Ok(c""), Ok(c"cpus"), Ok(c"PowerPC,970@1")];
 
     let mut supernode_names = vec![];
     let mut depth = 0;
@@ -187,12 +186,12 @@
     // Test linux,phandle
     let phandle = Phandle::new(0xFF).unwrap();
     let node = fdt.node_with_phandle(phandle).unwrap().unwrap();
-    assert_eq!(node.name(), Ok(cstr!("node_zz")));
+    assert_eq!(node.name(), Ok(c"node_zz"));
 
     // Test phandle
     let phandle = Phandle::new(0x22).unwrap();
     let node = fdt.node_with_phandle(phandle).unwrap().unwrap();
-    assert_eq!(node.name(), Ok(cstr!("node_abc")));
+    assert_eq!(node.name(), Ok(c"node_abc"));
 }
 
 #[test]
@@ -203,12 +202,12 @@
     // Test linux,phandle
     let phandle = Phandle::new(0xFF).unwrap();
     let node: FdtNodeMut = fdt.node_mut_with_phandle(phandle).unwrap().unwrap();
-    assert_eq!(node.as_node().name(), Ok(cstr!("node_zz")));
+    assert_eq!(node.as_node().name(), Ok(c"node_zz"));
 
     // Test phandle
     let phandle = Phandle::new(0x22).unwrap();
     let node: FdtNodeMut = fdt.node_mut_with_phandle(phandle).unwrap().unwrap();
-    assert_eq!(node.as_node().name(), Ok(cstr!("node_abc")));
+    assert_eq!(node.as_node().name(), Ok(c"node_abc"));
 }
 
 #[test]
@@ -217,15 +216,15 @@
     let fdt = Fdt::from_slice(&data).unwrap();
 
     // Test linux,phandle
-    let node = fdt.node(cstr!("/node_z/node_zz")).unwrap().unwrap();
+    let node = fdt.node(c"/node_z/node_zz").unwrap().unwrap();
     assert_eq!(node.get_phandle(), Ok(Phandle::new(0xFF)));
 
     // Test phandle
-    let node = fdt.node(cstr!("/node_a/node_ab/node_abc")).unwrap().unwrap();
+    let node = fdt.node(c"/node_a/node_ab/node_abc").unwrap().unwrap();
     assert_eq!(node.get_phandle(), Ok(Phandle::new(0x22)));
 
     // Test no phandle
-    let node = fdt.node(cstr!("/node_b")).unwrap().unwrap();
+    let node = fdt.node(c"/node_b").unwrap().unwrap();
     assert_eq!(node.get_phandle(), Ok(None));
 }
 
@@ -234,7 +233,7 @@
     let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
     let phandle = Phandle::new(0xFF).unwrap();
-    let path = cstr!("/node_z/node_zz");
+    let path = c"/node_z/node_zz";
 
     fdt.node_with_phandle(phandle).unwrap().unwrap();
     let node = fdt.node_mut(path).unwrap().unwrap();
@@ -259,8 +258,8 @@
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
     fdt.unpack().unwrap();
 
-    let node_path = cstr!("/node_z/node_zz");
-    let subnode_name = cstr!("123456789");
+    let node_path = c"/node_z/node_zz";
+    let subnode_name = c"123456789";
 
     for len in 0..subnode_name.to_bytes().len() {
         let name = &subnode_name.to_bytes()[0..len];
@@ -289,7 +288,7 @@
     let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
     let fdt = Fdt::from_slice(&data).unwrap();
 
-    let name = cstr!("node_a");
+    let name = c"node_a";
     let root = fdt.root();
     let node = root.subnode(name).unwrap();
     assert_ne!(None, node);
@@ -309,7 +308,7 @@
     assert_ne!(None, node);
     let node = node.unwrap();
 
-    assert_eq!(Ok(cstr!("node_a")), node.name());
+    assert_eq!(Ok(c"node_a"), node.name());
 }
 
 #[test]
@@ -317,7 +316,7 @@
     let data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
     let fdt = Fdt::from_slice(&data).unwrap();
 
-    let name = cstr!("node_a");
+    let name = c"node_a";
     let node = {
         let root = fdt.root();
         root.subnode(name).unwrap().unwrap()
@@ -332,7 +331,7 @@
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
 
     let symbols = fdt.symbols().unwrap().unwrap();
-    assert_eq!(symbols.name(), Ok(cstr!("__symbols__")));
+    assert_eq!(symbols.name(), Ok(c"__symbols__"));
 
     // Validates type.
     let _symbols: FdtNodeMut = fdt.symbols_mut().unwrap().unwrap();
@@ -343,14 +342,14 @@
     let mut data = fs::read(TEST_TREE_WITH_ONE_MEMORY_RANGE_PATH).unwrap();
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
 
-    let mut memory = fdt.node_mut(cstr!("/memory")).unwrap().unwrap();
+    let mut memory = fdt.node_mut(c"/memory").unwrap().unwrap();
     {
         let memory = memory.as_node();
-        assert_eq!(memory.name(), Ok(cstr!("memory")));
+        assert_eq!(memory.name(), Ok(c"memory"));
     }
 
     // Just check whether borrow checker doesn't complain this.
-    memory.setprop_inplace(cstr!("device_type"), b"MEMORY\0").unwrap();
+    memory.setprop_inplace(c"device_type", b"MEMORY\0").unwrap();
 }
 
 #[test]
@@ -358,18 +357,13 @@
     let mut data = fs::read(TEST_TREE_PHANDLE_PATH).unwrap();
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
 
-    let node_z = fdt.node(cstr!("/node_z")).unwrap().unwrap();
+    let node_z = fdt.node(c"/node_z").unwrap().unwrap();
     let descendants: Vec<_> =
         node_z.descendants().map(|(node, depth)| (node.name().unwrap(), depth)).collect();
 
     assert_eq!(
         descendants,
-        vec![
-            (cstr!("node_za"), 1),
-            (cstr!("node_zb"), 1),
-            (cstr!("node_zz"), 1),
-            (cstr!("node_zzz"), 2)
-        ]
+        vec![(c"node_za", 1), (c"node_zb", 1), (c"node_zz", 1), (c"node_zzz", 2)]
     );
 }
 
@@ -382,7 +376,7 @@
     let mut subnode_iter = root.first_subnode().unwrap();
 
     while let Some(subnode) = subnode_iter {
-        if subnode.as_node().name() == Ok(cstr!("node_z")) {
+        if subnode.as_node().name() == Ok(c"node_z") {
             subnode_iter = subnode.delete_and_next_subnode().unwrap();
         } else {
             subnode_iter = subnode.next_subnode().unwrap();
@@ -390,12 +384,7 @@
     }
 
     let root = fdt.root();
-    let expected_names = vec![
-        Ok(cstr!("node_a")),
-        Ok(cstr!("node_b")),
-        Ok(cstr!("node_c")),
-        Ok(cstr!("__symbols__")),
-    ];
+    let expected_names = vec![Ok(c"node_a"), Ok(c"node_b"), Ok(c"node_c"), Ok(c"__symbols__")];
     let subnode_names: Vec<_> = root.subnodes().unwrap().map(|node| node.name()).collect();
 
     assert_eq!(expected_names, subnode_names);
@@ -407,19 +396,19 @@
     let fdt = Fdt::from_mut_slice(&mut data).unwrap();
 
     let expected_nodes = vec![
-        (Ok(cstr!("node_b")), 1),
-        (Ok(cstr!("node_c")), 1),
-        (Ok(cstr!("node_z")), 1),
-        (Ok(cstr!("node_za")), 2),
-        (Ok(cstr!("node_zb")), 2),
-        (Ok(cstr!("__symbols__")), 1),
+        (Ok(c"node_b"), 1),
+        (Ok(c"node_c"), 1),
+        (Ok(c"node_z"), 1),
+        (Ok(c"node_za"), 2),
+        (Ok(c"node_zb"), 2),
+        (Ok(c"__symbols__"), 1),
     ];
 
     let mut expected_nodes_iter = expected_nodes.iter();
     let mut iter = fdt.root_mut().next_node(0).unwrap();
     while let Some((node, depth)) = iter {
         let node_name = node.as_node().name();
-        if node_name == Ok(cstr!("node_a")) || node_name == Ok(cstr!("node_zz")) {
+        if node_name == Ok(c"node_a") || node_name == Ok(c"node_zz") {
             iter = node.delete_and_next_node(depth).unwrap();
         } else {
             // Note: Checking name here is easier than collecting names and assert_eq!(),
@@ -464,7 +453,7 @@
         root.name()
         // Make root to be dropped
     };
-    assert_eq!(Ok(cstr!("")), name);
+    assert_eq!(Ok(c""), name);
 }
 
 #[test]
@@ -473,7 +462,7 @@
     let fdt = Fdt::create_empty_tree(&mut data).unwrap();
 
     let root = fdt.root_mut();
-    let names = [cstr!("a"), cstr!("b")];
+    let names = [c"a", c"b"];
     root.add_subnodes(&names).unwrap();
 
     let expected: HashSet<_> = names.into_iter().collect();
@@ -492,14 +481,14 @@
     let name = {
         let node_a = {
             let root = fdt.root();
-            root.subnode(cstr!("node_a")).unwrap()
+            root.subnode(c"node_a").unwrap()
             // Make root to be dropped
         };
         assert_ne!(None, node_a);
         node_a.unwrap().name()
         // Make node_a to be dropped
     };
-    assert_eq!(Ok(cstr!("node_a")), name);
+    assert_eq!(Ok(c"node_a"), name);
 }
 
 #[test]
@@ -521,7 +510,7 @@
         first_subnode.name()
         // Make first_subnode to be dropped
     };
-    assert_eq!(Ok(cstr!("node_a")), first_subnode_name);
+    assert_eq!(Ok(c"node_a"), first_subnode_name);
 }
 
 #[test]
@@ -543,5 +532,5 @@
         first_descendant.name()
         // Make first_descendant to be dropped
     };
-    assert_eq!(Ok(cstr!("node_a")), first_descendant_name);
+    assert_eq!(Ok(c"node_a"), first_descendant_name);
 }
diff --git a/libs/libservice_vm_fake_chain/Android.bp b/libs/libservice_vm_fake_chain/Android.bp
index 56fb22a..65eddf8 100644
--- a/libs/libservice_vm_fake_chain/Android.bp
+++ b/libs/libservice_vm_fake_chain/Android.bp
@@ -26,9 +26,6 @@
         "//packages/modules/Virtualization/guest/rialto:__subpackages__",
     ],
     prefer_rlib: true,
-    rustlibs: [
-        "libcstr",
-    ],
 }
 
 rust_library {
diff --git a/libs/libservice_vm_fake_chain/src/client_vm.rs b/libs/libservice_vm_fake_chain/src/client_vm.rs
index dc499e0..fa72739 100644
--- a/libs/libservice_vm_fake_chain/src/client_vm.rs
+++ b/libs/libservice_vm_fake_chain/src/client_vm.rs
@@ -22,7 +22,6 @@
 use ciborium::{cbor, value::Value};
 use core::result;
 use coset::CborSerializable;
-use cstr::cstr;
 use diced_open_dice::{
     hash, retry_bcc_format_config_descriptor, retry_bcc_main_flow, Config, DiceArtifacts,
     DiceConfigValues, DiceError, DiceMode, InputValues, OwnedDiceArtifacts, Result, HASH_SIZE,
@@ -96,7 +95,7 @@
 
     // Adds an entry describing the Microdroid kernel.
     let config_values = DiceConfigValues {
-        component_name: Some(cstr!("vm_entry")),
+        component_name: Some(c"vm_entry"),
         component_version: Some(12),
         resettable: true,
         security_version: Some(13),
diff --git a/libs/libservice_vm_fake_chain/src/service_vm.rs b/libs/libservice_vm_fake_chain/src/service_vm.rs
index 04297e4..5064ff8 100644
--- a/libs/libservice_vm_fake_chain/src/service_vm.rs
+++ b/libs/libservice_vm_fake_chain/src/service_vm.rs
@@ -24,7 +24,6 @@
     iana::{self, EnumI64},
     Algorithm, AsCborValue, CborSerializable, CoseKey, KeyOperation, KeyType, Label,
 };
-use cstr::cstr;
 use diced_open_dice::{
     derive_cdi_private_key_seed, keypair_from_seed, retry_bcc_format_config_descriptor,
     retry_bcc_main_flow, retry_dice_main_flow, CdiValues, Config, DiceConfigValues, DiceError,
@@ -113,7 +112,7 @@
 
     // Gets the pvmfw certificate to as the root certificate of DICE chain.
     let config_values = DiceConfigValues {
-        component_name: Some(cstr!("Protected VM firmware")),
+        component_name: Some(c"Protected VM firmware"),
         component_version: Some(1),
         resettable: true,
         rkp_vm_marker: true,
@@ -156,7 +155,7 @@
 pub fn fake_service_vm_dice_artifacts() -> Result<OwnedDiceArtifacts> {
     let (cdi_values, dice_chain) = fake_dice_artifacts_up_to_pvmfw()?;
     let config_values = DiceConfigValues {
-        component_name: Some(cstr!("vm_entry")),
+        component_name: Some(c"vm_entry"),
         component_version: Some(12),
         resettable: true,
         rkp_vm_marker: true,
diff --git a/libs/libvmbase/Android.bp b/libs/libvmbase/Android.bp
index 3088633..de347c7 100644
--- a/libs/libvmbase/Android.bp
+++ b/libs/libvmbase/Android.bp
@@ -80,7 +80,6 @@
         "libaarch64_paging",
         "libbuddy_system_allocator",
         "libcfg_if",
-        "libcstr",
         "libhypervisor_backends",
         "liblibfdt_nostd",
         "liblog_rust_nostd",
diff --git a/libs/libvmbase/src/bionic.rs b/libs/libvmbase/src/bionic.rs
index 3c0cd6f..37b6e45 100644
--- a/libs/libvmbase/src/bionic.rs
+++ b/libs/libvmbase/src/bionic.rs
@@ -24,7 +24,6 @@
 use core::slice;
 use core::str;
 
-use cstr::cstr;
 use log::error;
 use log::info;
 
@@ -230,138 +229,138 @@
 fn cstr_error(n: c_int) -> &'static CStr {
     // Messages taken from errno(1).
     match n {
-        0 => cstr!("Success"),
-        1 => cstr!("Operation not permitted"),
-        2 => cstr!("No such file or directory"),
-        3 => cstr!("No such process"),
-        4 => cstr!("Interrupted system call"),
-        5 => cstr!("Input/output error"),
-        6 => cstr!("No such device or address"),
-        7 => cstr!("Argument list too long"),
-        8 => cstr!("Exec format error"),
-        9 => cstr!("Bad file descriptor"),
-        10 => cstr!("No child processes"),
-        11 => cstr!("Resource temporarily unavailable"),
-        12 => cstr!("Cannot allocate memory"),
-        13 => cstr!("Permission denied"),
-        14 => cstr!("Bad address"),
-        15 => cstr!("Block device required"),
-        16 => cstr!("Device or resource busy"),
-        17 => cstr!("File exists"),
-        18 => cstr!("Invalid cross-device link"),
-        19 => cstr!("No such device"),
-        20 => cstr!("Not a directory"),
-        21 => cstr!("Is a directory"),
-        22 => cstr!("Invalid argument"),
-        23 => cstr!("Too many open files in system"),
-        24 => cstr!("Too many open files"),
-        25 => cstr!("Inappropriate ioctl for device"),
-        26 => cstr!("Text file busy"),
-        27 => cstr!("File too large"),
-        28 => cstr!("No space left on device"),
-        29 => cstr!("Illegal seek"),
-        30 => cstr!("Read-only file system"),
-        31 => cstr!("Too many links"),
-        32 => cstr!("Broken pipe"),
-        33 => cstr!("Numerical argument out of domain"),
-        34 => cstr!("Numerical result out of range"),
-        35 => cstr!("Resource deadlock avoided"),
-        36 => cstr!("File name too long"),
-        37 => cstr!("No locks available"),
-        38 => cstr!("Function not implemented"),
-        39 => cstr!("Directory not empty"),
-        40 => cstr!("Too many levels of symbolic links"),
-        42 => cstr!("No message of desired type"),
-        43 => cstr!("Identifier removed"),
-        44 => cstr!("Channel number out of range"),
-        45 => cstr!("Level 2 not synchronized"),
-        46 => cstr!("Level 3 halted"),
-        47 => cstr!("Level 3 reset"),
-        48 => cstr!("Link number out of range"),
-        49 => cstr!("Protocol driver not attached"),
-        50 => cstr!("No CSI structure available"),
-        51 => cstr!("Level 2 halted"),
-        52 => cstr!("Invalid exchange"),
-        53 => cstr!("Invalid request descriptor"),
-        54 => cstr!("Exchange full"),
-        55 => cstr!("No anode"),
-        56 => cstr!("Invalid request code"),
-        57 => cstr!("Invalid slot"),
-        59 => cstr!("Bad font file format"),
-        60 => cstr!("Device not a stream"),
-        61 => cstr!("No data available"),
-        62 => cstr!("Timer expired"),
-        63 => cstr!("Out of streams resources"),
-        64 => cstr!("Machine is not on the network"),
-        65 => cstr!("Package not installed"),
-        66 => cstr!("Object is remote"),
-        67 => cstr!("Link has been severed"),
-        68 => cstr!("Advertise error"),
-        69 => cstr!("Srmount error"),
-        70 => cstr!("Communication error on send"),
-        71 => cstr!("Protocol error"),
-        72 => cstr!("Multihop attempted"),
-        73 => cstr!("RFS specific error"),
-        74 => cstr!("Bad message"),
-        75 => cstr!("Value too large for defined data type"),
-        76 => cstr!("Name not unique on network"),
-        77 => cstr!("File descriptor in bad state"),
-        78 => cstr!("Remote address changed"),
-        79 => cstr!("Can not access a needed shared library"),
-        80 => cstr!("Accessing a corrupted shared library"),
-        81 => cstr!(".lib section in a.out corrupted"),
-        82 => cstr!("Attempting to link in too many shared libraries"),
-        83 => cstr!("Cannot exec a shared library directly"),
-        84 => cstr!("Invalid or incomplete multibyte or wide character"),
-        85 => cstr!("Interrupted system call should be restarted"),
-        86 => cstr!("Streams pipe error"),
-        87 => cstr!("Too many users"),
-        88 => cstr!("Socket operation on non-socket"),
-        89 => cstr!("Destination address required"),
-        90 => cstr!("Message too long"),
-        91 => cstr!("Protocol wrong type for socket"),
-        92 => cstr!("Protocol not available"),
-        93 => cstr!("Protocol not supported"),
-        94 => cstr!("Socket type not supported"),
-        95 => cstr!("Operation not supported"),
-        96 => cstr!("Protocol family not supported"),
-        97 => cstr!("Address family not supported by protocol"),
-        98 => cstr!("Address already in use"),
-        99 => cstr!("Cannot assign requested address"),
-        100 => cstr!("Network is down"),
-        101 => cstr!("Network is unreachable"),
-        102 => cstr!("Network dropped connection on reset"),
-        103 => cstr!("Software caused connection abort"),
-        104 => cstr!("Connection reset by peer"),
-        105 => cstr!("No buffer space available"),
-        106 => cstr!("Transport endpoint is already connected"),
-        107 => cstr!("Transport endpoint is not connected"),
-        108 => cstr!("Cannot send after transport endpoint shutdown"),
-        109 => cstr!("Too many references: cannot splice"),
-        110 => cstr!("Connection timed out"),
-        111 => cstr!("Connection refused"),
-        112 => cstr!("Host is down"),
-        113 => cstr!("No route to host"),
-        114 => cstr!("Operation already in progress"),
-        115 => cstr!("Operation now in progress"),
-        116 => cstr!("Stale file handle"),
-        117 => cstr!("Structure needs cleaning"),
-        118 => cstr!("Not a XENIX named type file"),
-        119 => cstr!("No XENIX semaphores available"),
-        120 => cstr!("Is a named type file"),
-        121 => cstr!("Remote I/O error"),
-        122 => cstr!("Disk quota exceeded"),
-        123 => cstr!("No medium found"),
-        124 => cstr!("Wrong medium type"),
-        125 => cstr!("Operation canceled"),
-        126 => cstr!("Required key not available"),
-        127 => cstr!("Key has expired"),
-        128 => cstr!("Key has been revoked"),
-        129 => cstr!("Key was rejected by service"),
-        130 => cstr!("Owner died"),
-        131 => cstr!("State not recoverable"),
-        132 => cstr!("Operation not possible due to RF-kill"),
-        133 => cstr!("Memory page has hardware error"),
-        _ => cstr!("Unknown errno value"),
+        0 => c"Success",
+        1 => c"Operation not permitted",
+        2 => c"No such file or directory",
+        3 => c"No such process",
+        4 => c"Interrupted system call",
+        5 => c"Input/output error",
+        6 => c"No such device or address",
+        7 => c"Argument list too long",
+        8 => c"Exec format error",
+        9 => c"Bad file descriptor",
+        10 => c"No child processes",
+        11 => c"Resource temporarily unavailable",
+        12 => c"Cannot allocate memory",
+        13 => c"Permission denied",
+        14 => c"Bad address",
+        15 => c"Block device required",
+        16 => c"Device or resource busy",
+        17 => c"File exists",
+        18 => c"Invalid cross-device link",
+        19 => c"No such device",
+        20 => c"Not a directory",
+        21 => c"Is a directory",
+        22 => c"Invalid argument",
+        23 => c"Too many open files in system",
+        24 => c"Too many open files",
+        25 => c"Inappropriate ioctl for device",
+        26 => c"Text file busy",
+        27 => c"File too large",
+        28 => c"No space left on device",
+        29 => c"Illegal seek",
+        30 => c"Read-only file system",
+        31 => c"Too many links",
+        32 => c"Broken pipe",
+        33 => c"Numerical argument out of domain",
+        34 => c"Numerical result out of range",
+        35 => c"Resource deadlock avoided",
+        36 => c"File name too long",
+        37 => c"No locks available",
+        38 => c"Function not implemented",
+        39 => c"Directory not empty",
+        40 => c"Too many levels of symbolic links",
+        42 => c"No message of desired type",
+        43 => c"Identifier removed",
+        44 => c"Channel number out of range",
+        45 => c"Level 2 not synchronized",
+        46 => c"Level 3 halted",
+        47 => c"Level 3 reset",
+        48 => c"Link number out of range",
+        49 => c"Protocol driver not attached",
+        50 => c"No CSI structure available",
+        51 => c"Level 2 halted",
+        52 => c"Invalid exchange",
+        53 => c"Invalid request descriptor",
+        54 => c"Exchange full",
+        55 => c"No anode",
+        56 => c"Invalid request code",
+        57 => c"Invalid slot",
+        59 => c"Bad font file format",
+        60 => c"Device not a stream",
+        61 => c"No data available",
+        62 => c"Timer expired",
+        63 => c"Out of streams resources",
+        64 => c"Machine is not on the network",
+        65 => c"Package not installed",
+        66 => c"Object is remote",
+        67 => c"Link has been severed",
+        68 => c"Advertise error",
+        69 => c"Srmount error",
+        70 => c"Communication error on send",
+        71 => c"Protocol error",
+        72 => c"Multihop attempted",
+        73 => c"RFS specific error",
+        74 => c"Bad message",
+        75 => c"Value too large for defined data type",
+        76 => c"Name not unique on network",
+        77 => c"File descriptor in bad state",
+        78 => c"Remote address changed",
+        79 => c"Can not access a needed shared library",
+        80 => c"Accessing a corrupted shared library",
+        81 => c".lib section in a.out corrupted",
+        82 => c"Attempting to link in too many shared libraries",
+        83 => c"Cannot exec a shared library directly",
+        84 => c"Invalid or incomplete multibyte or wide character",
+        85 => c"Interrupted system call should be restarted",
+        86 => c"Streams pipe error",
+        87 => c"Too many users",
+        88 => c"Socket operation on non-socket",
+        89 => c"Destination address required",
+        90 => c"Message too long",
+        91 => c"Protocol wrong type for socket",
+        92 => c"Protocol not available",
+        93 => c"Protocol not supported",
+        94 => c"Socket type not supported",
+        95 => c"Operation not supported",
+        96 => c"Protocol family not supported",
+        97 => c"Address family not supported by protocol",
+        98 => c"Address already in use",
+        99 => c"Cannot assign requested address",
+        100 => c"Network is down",
+        101 => c"Network is unreachable",
+        102 => c"Network dropped connection on reset",
+        103 => c"Software caused connection abort",
+        104 => c"Connection reset by peer",
+        105 => c"No buffer space available",
+        106 => c"Transport endpoint is already connected",
+        107 => c"Transport endpoint is not connected",
+        108 => c"Cannot send after transport endpoint shutdown",
+        109 => c"Too many references: cannot splice",
+        110 => c"Connection timed out",
+        111 => c"Connection refused",
+        112 => c"Host is down",
+        113 => c"No route to host",
+        114 => c"Operation already in progress",
+        115 => c"Operation now in progress",
+        116 => c"Stale file handle",
+        117 => c"Structure needs cleaning",
+        118 => c"Not a XENIX named type file",
+        119 => c"No XENIX semaphores available",
+        120 => c"Is a named type file",
+        121 => c"Remote I/O error",
+        122 => c"Disk quota exceeded",
+        123 => c"No medium found",
+        124 => c"Wrong medium type",
+        125 => c"Operation canceled",
+        126 => c"Required key not available",
+        127 => c"Key has expired",
+        128 => c"Key has been revoked",
+        129 => c"Key was rejected by service",
+        130 => c"Owner died",
+        131 => c"State not recoverable",
+        132 => c"Operation not possible due to RF-kill",
+        133 => c"Memory page has hardware error",
+        _ => c"Unknown errno value",
     }
 }
diff --git a/libs/libvmbase/src/fdt.rs b/libs/libvmbase/src/fdt.rs
index aaf354e..2113787 100644
--- a/libs/libvmbase/src/fdt.rs
+++ b/libs/libvmbase/src/fdt.rs
@@ -17,7 +17,6 @@
 pub mod pci;
 
 use core::ops::Range;
-use cstr::cstr;
 use libfdt::{self, Fdt, FdtError};
 
 /// Represents information about a SWIOTLB buffer.
@@ -34,7 +33,7 @@
 impl SwiotlbInfo {
     /// Creates a `SwiotlbInfo` struct from the given device tree.
     pub fn new_from_fdt(fdt: &Fdt) -> libfdt::Result<Option<SwiotlbInfo>> {
-        let Some(node) = fdt.compatible_nodes(cstr!("restricted-dma-pool"))?.next() else {
+        let Some(node) = fdt.compatible_nodes(c"restricted-dma-pool")?.next() else {
             return Ok(None);
         };
         let (addr, size, align) = if let Some(mut reg) = node.reg()? {
@@ -42,8 +41,8 @@
             let size = reg.size.ok_or(FdtError::BadValue)?;
             (Some(reg.addr.try_into().unwrap()), size.try_into().unwrap(), None)
         } else {
-            let size = node.getprop_u64(cstr!("size"))?.ok_or(FdtError::NotFound)?;
-            let align = node.getprop_u64(cstr!("alignment"))?.ok_or(FdtError::NotFound)?;
+            let size = node.getprop_u64(c"size")?.ok_or(FdtError::NotFound)?;
+            let align = node.getprop_u64(c"alignment")?.ok_or(FdtError::NotFound)?;
             (None, size.try_into().unwrap(), Some(align.try_into().unwrap()))
         };
         Ok(Some(Self { addr, size, align }))
diff --git a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
index afa286c..febf343 100644
--- a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
+++ b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
@@ -163,11 +163,8 @@
 
         @Override
         public void enableVmTethering() {
-            LinkAddress local = new LinkAddress("192.168.0.1/24");
-            LinkAddress client = new LinkAddress("192.168.0.2/24");
             final TetheringRequest tr =
                     new TetheringRequest.Builder(TetheringManager.TETHERING_VIRTUAL)
-                            .setStaticIpv4Addresses(local, client)
                             .setConnectivityScope(TetheringManager.CONNECTIVITY_SCOPE_GLOBAL)
                             .build();
 
diff --git a/tests/Terminal/Android.bp b/tests/Terminal/Android.bp
index 029fbea..a4e1f6b 100644
--- a/tests/Terminal/Android.bp
+++ b/tests/Terminal/Android.bp
@@ -4,7 +4,7 @@
 
 android_test {
     name: "TerminalAppTests",
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.kt"],
     libs: [
         "android.test.runner.stubs.system",
         "android.test.base.stubs.system",
diff --git a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
deleted file mode 100644
index b0afb54..0000000
--- a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
+++ /dev/null
@@ -1,101 +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 static org.junit.Assert.assertTrue;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemProperties;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.microdroid.test.common.DeviceProperties;
-import com.android.microdroid.test.common.MetricsProcessor;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-@RunWith(AndroidJUnit4.class)
-public class TerminalAppTest {
-    private Instrumentation mInstr;
-    private Context mTargetContext;
-    private DeviceProperties mProperties;
-    private final MetricsProcessor mMetricsProc = new MetricsProcessor("avf_perf/terminal/");
-
-    @Before
-    public void setup() {
-        mInstr = InstrumentationRegistry.getInstrumentation();
-        mTargetContext = mInstr.getTargetContext();
-        mProperties = DeviceProperties.create(SystemProperties::get);
-        installVmImage();
-    }
-
-    private void installVmImage() {
-        final long INSTALL_TIMEOUT_MILLIS = 300_000; // 5 min
-
-        Intent intent = new Intent(mTargetContext, InstallerActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        if (mInstr.startActivitySync(intent) instanceof InstallerActivity activity) {
-            assertTrue(
-                    "Failed to install VM image",
-                    activity.waitForInstallCompleted(INSTALL_TIMEOUT_MILLIS));
-        }
-    }
-
-    @Test
-    public void boot() throws Exception {
-        final boolean isNestedVirt = mProperties.isCuttlefish() || mProperties.isGoldfish();
-        final long BOOT_TIMEOUT_MILLIS = isNestedVirt ? 180_000 : 30_000; // 30 sec (or 3 min)
-
-        Intent intent = new Intent(mTargetContext, MainActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        long start = System.currentTimeMillis();
-        if (mInstr.startActivitySync(intent) instanceof MainActivity activity) {
-            assertTrue("Failed to boot in 30s", activity.waitForBootCompleted(BOOT_TIMEOUT_MILLIS));
-        }
-        long delay = System.currentTimeMillis() - start;
-
-        // TODO: measure multiple times?
-        List<Long> measurements = new ArrayList<>();
-        measurements.add(delay);
-        Map<String, Double> stats = mMetricsProc.computeStats(measurements, "boot", "ms");
-        Bundle bundle = new Bundle();
-        for (Map.Entry<String, Double> entry : stats.entrySet()) {
-            bundle.putDouble(entry.getKey(), entry.getValue());
-        }
-        mInstr.sendStatus(0, bundle);
-    }
-
-    @After
-    public void tearDown() throws IOException {
-        PortsStateManager.getInstance(mTargetContext).clearEnabledPorts();
-        InstalledImage.getDefault(mTargetContext).uninstallFully();
-    }
-}
diff --git a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.kt b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.kt
new file mode 100644
index 0000000..88bdfab
--- /dev/null
+++ b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.app.Instrumentation
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.SystemProperties
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.microdroid.test.common.DeviceProperties
+import com.android.microdroid.test.common.MetricsProcessor
+import java.io.IOException
+import java.lang.Exception
+import java.util.ArrayList
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TerminalAppTest {
+    private lateinit var instr: Instrumentation
+    private lateinit var targetContext: Context
+    private lateinit var properties: DeviceProperties
+    private val metricsProc = MetricsProcessor("avf_perf/terminal/")
+
+    @Before
+    fun setup() {
+        instr = InstrumentationRegistry.getInstrumentation()
+        targetContext = instr.targetContext
+        properties =
+            DeviceProperties.create(DeviceProperties.PropertyGetter { SystemProperties.get(it) })
+        installVmImage()
+    }
+
+    private fun installVmImage() {
+        val INSTALL_TIMEOUT_MILLIS: Long = 300000 // 5 min
+
+        val intent = Intent(targetContext, InstallerActivity::class.java)
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        val activity = instr.startActivitySync(intent)
+        if (activity is InstallerActivity) {
+            Assert.assertTrue(
+                "Failed to install VM image",
+                activity.waitForInstallCompleted(INSTALL_TIMEOUT_MILLIS),
+            )
+        }
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun boot() {
+        val isNestedVirt = properties.isCuttlefish() || properties.isGoldfish()
+        val BOOT_TIMEOUT_MILLIS =
+            (if (isNestedVirt) 180000 else 30000).toLong() // 30 sec (or 3 min)
+
+        val intent = Intent(targetContext, MainActivity::class.java)
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+        val start = System.currentTimeMillis()
+        val activity = instr.startActivitySync(intent)
+        if (activity is MainActivity) {
+            Assert.assertTrue(
+                "Failed to boot in 30s",
+                activity.waitForBootCompleted(BOOT_TIMEOUT_MILLIS),
+            )
+        }
+        val delay = System.currentTimeMillis() - start
+
+        // TODO: measure multiple times?
+        val measurements: MutableList<Long?> = ArrayList<Long?>()
+        measurements.add(delay)
+        val stats = metricsProc.computeStats(measurements, "boot", "ms")
+        val bundle = Bundle()
+        for (entry in stats.entries) {
+            bundle.putDouble(entry.key, entry.value)
+        }
+        instr.sendStatus(0, bundle)
+    }
+
+    @After
+    @Throws(IOException::class)
+    fun tearDown() {
+        PortsStateManager.getInstance(targetContext).clearEnabledPorts()
+        InstalledImage.getDefault(targetContext).uninstallFully()
+    }
+}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 48e369c..6d7c25e 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -2,14 +2,9 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-java_test_host {
-    name: "MicrodroidHostTestCases",
+java_defaults {
+    name: "MicrodroidHostTestCases.default",
     srcs: ["java/**/*.java"],
-    test_suites: [
-        "cts",
-        "general-tests",
-        "pts",
-    ],
     libs: [
         "androidx.annotation_annotation",
         "tradefed",
@@ -21,27 +16,6 @@
         "microdroid_payload_metadata",
     ],
     per_testcase_directory: true,
-    device_common_data: [
-        ":MicrodroidTestApp",
-        ":MicrodroidTestAppUpdated",
-        ":microdroid_general_sepolicy.conf",
-        ":test.com.android.virt.pem",
-        ":test2.com.android.virt.pem",
-        "java/**/goldens/dt_dump_*",
-    ],
-    data_native_bins: [
-        "sepolicy-analyze",
-        // For re-sign test
-        "avbtool",
-        "img2simg",
-        "initrd_bootconfig",
-        "lpmake",
-        "lpunpack",
-        "lz4",
-        "sign_virt_apex",
-        "simg2img",
-        "dtc",
-    ],
     // java_test_host doesn't have data_native_libs but jni_libs can be used to put
     // native modules under ./lib directory.
     // This works because host tools have rpath (../lib and ./lib).
@@ -58,3 +32,105 @@
         "libz",
     ],
 }
+
+DEVICE_DATA = [
+    ":MicrodroidTestApp",
+    ":MicrodroidTestAppUpdated",
+    ":microdroid_general_sepolicy.conf",
+    ":test.com.android.virt.pem",
+    ":test2.com.android.virt.pem",
+    "java/**/goldens/dt_dump_*",
+]
+
+BINS = [
+    "sepolicy-analyze",
+    // For re-sign test
+    "avbtool",
+    "img2simg",
+    "initrd_bootconfig",
+    "lpmake",
+    "lpunpack",
+    "lz4",
+    "sign_virt_apex",
+    "simg2img",
+    "dtc",
+]
+
+java_test_host {
+    name: "MicrodroidHostTestCases",
+    defaults: ["MicrodroidHostTestCases.default"],
+    test_config_template: "AndroidTestTemplate.xml",
+    auto_gen_config: true,
+    test_suites: [
+        "general-tests",
+        "pts",
+    ],
+    device_common_data: DEVICE_DATA,
+    data_native_bins: BINS,
+}
+
+java_test_host {
+    name: "MicrodroidHostTestCases.CTS",
+    defaults: ["MicrodroidHostTestCases.default"],
+    test_config_template: "AndroidTestTemplate.xml",
+    test_suites: ["cts"],
+    auto_gen_config: true,
+    test_options: {
+        tradefed_options: [
+            {
+                name: "include-annotation",
+                value: "com.android.compatibility.common.util.CddTest",
+            },
+            {
+                name: "test-suite-tag",
+                value: "cts",
+            },
+        ],
+    },
+    device_common_data: DEVICE_DATA,
+    data_native_bins: BINS,
+}
+
+java_test_host {
+    name: "MicrodroidHostTestCases.VTS",
+    defaults: ["MicrodroidHostTestCases.default"],
+    test_config_template: "AndroidTestTemplate.xml",
+    test_suites: ["vts"],
+    auto_gen_config: true,
+    test_options: {
+        tradefed_options: [
+            {
+                name: "include-annotation",
+                value: "com.android.compatibility.common.util.VsrTest",
+            },
+            {
+                name: "test-suite-tag",
+                value: "vts",
+            },
+        ],
+    },
+    device_common_data: DEVICE_DATA,
+    data_native_bins: BINS,
+}
+
+java_test_host {
+    name: "MicrodroidHostTestCases.GTS",
+    defaults: ["MicrodroidHostTestCases.default"],
+    test_config_template: "AndroidTestTemplate.xml",
+    test_suites: ["gts"],
+    auto_gen_config: true,
+    test_options: {
+        tradefed_options: [
+            {
+                name: "include-annotation",
+                value: "com.android.compatibility.common.util.GmsTest",
+            },
+            {
+                name: "test-suite-tag",
+                value: "gts",
+            },
+        ],
+    },
+    device_common_data: DEVICE_DATA,
+    data_native_bins: BINS,
+}
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTestTemplate.xml
similarity index 93%
rename from tests/hostside/AndroidTest.xml
rename to tests/hostside/AndroidTestTemplate.xml
index f77def3..ac066bc 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTestTemplate.xml
@@ -14,7 +14,6 @@
      limitations under the License.
 -->
 <configuration description="Host driven tests for Microdroid">
-    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
@@ -26,10 +25,12 @@
     </target_preparer>
 
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="MicrodroidHostTestCases.jar" />
+        <option name="jar" value="{MODULE}.jar" />
     </test>
 
     <!-- Controller that will skip the module if a native bridge situation is detected -->
     <!-- For example: module wants to run arm and device is x86 -->
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
+
+    {EXTRA_CONFIGS}
 </configuration>
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 01ac6a1..4f9806a 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -36,6 +36,7 @@
 import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.GmsTest;
 import com.android.compatibility.common.util.PropertyUtil;
 import com.android.compatibility.common.util.VsrTest;
 import com.android.microdroid.test.common.ProcessUtil;
@@ -415,7 +416,8 @@
 
     @Test
     @CddTest
-    @VsrTest(requirements = {"VSR-7.1-001.008"})
+    @GmsTest(requirements = {"GMS-3-7.1-002", "GMS-VSR-7.1-001.006"})
+    @VsrTest(requirements = {"VSR-7.1-001.007"})
     public void UpgradedPackageIsAcceptedWithSecretkeeper() throws Exception {
         // Preconditions
         assumeVmTypeSupported("microdroid", true); // Non-protected VMs may not support upgrades
@@ -433,7 +435,8 @@
 
     @Test
     @CddTest
-    @VsrTest(requirements = {"VSR-7.1-001.008"})
+    @GmsTest(requirements = {"GMS-3-7.1-002", "GMS-VSR-7.1-001.006"})
+    @VsrTest(requirements = {"VSR-7.1-001.007"})
     public void DowngradedPackageIsRejectedProtectedVm() throws Exception {
         // Preconditions: Rollback protection is provided only for protected VM.
         assumeVmTypeSupported("microdroid", true);
@@ -482,7 +485,7 @@
     @Test
     @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
-    @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
+    @GmsTest(requirements = {"GMS-3-7.1-010"})
     public void protectedVmRunsPvmfw(String os) throws Exception {
         // Arrange
         assumeKernelSupported(os);
@@ -515,7 +518,7 @@
     @Test
     @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
-    @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-5", "9.17/C-2-6"})
+    @GmsTest(requirements = {"GMS-3-7.1-003", "GMS-3-7.1-010"})
     public void protectedVmWithImageSignedWithDifferentKeyFailsToVerifyPayload(String os)
             throws Exception {
         assumeKernelSupported(os);
@@ -544,7 +547,7 @@
     @Test
     @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
-    @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
+    @GmsTest(requirements = {"GMS-3-7.1-003", "GMS-3-7.1-010"})
     public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey(String os)
             throws Exception {
         // Preconditions
@@ -572,7 +575,7 @@
     @Test
     @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
-    @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-5", "9.17/C-2-6"})
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void testBootFailsWhenVbMetaDigestDoesNotMatchBootconfig(String os) throws Exception {
         // protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() is the protected case.
         assumeKernelSupported(os);
@@ -1010,7 +1013,8 @@
     @Test
     @Parameters(method = "params")
     @TestCaseName("{method}_protectedVm_{0}_os_{1}")
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
+    @CddTest
+    @GmsTest(requirements = {"GMS-3-7.1-001.002"})
     public void testMicrodroidBoots(boolean protectedVm, String os) throws Exception {
         // Preconditions
         assumeKernelSupported(os);
@@ -1097,6 +1101,7 @@
     }
 
     @Test
+    @CddTest
     public void testPathToBinaryIsRejected() throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
 
@@ -1145,6 +1150,7 @@
     }
 
     @Test
+    @CddTest
     public void testRunEmptyPayload() throws Exception {
         CommandRunner android = new CommandRunner(getDevice());
 
@@ -1198,7 +1204,6 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
     public void testAllVbmetaUseSHA256() throws Exception {
         File virtApexDir = FileUtil.createTempDir("virt_apex");
         // Pull the virt apex's etc/ directory (which contains images)
@@ -1309,6 +1314,7 @@
     @Test
     @Parameters(method = "params")
     @TestCaseName("{method}_protectedVm_{0}_os_{1}")
+    @CddTest
     public void testDeviceAssignment(boolean protectedVm, String os) throws Exception {
         // Preconditions
         assumeKernelSupported(os);
@@ -1364,6 +1370,8 @@
     }
 
     @Test
+    @CddTest
+    @GmsTest(requirements = {"GMS-3-7.1-001.002"})
     public void testOsVersions() throws Exception {
         for (String os : getSupportedOSList()) {
             assertWithMessage("Unknown OS \"%s\"", os).that(SUPPORTED_OSES.values()).contains(os);
@@ -1414,6 +1422,7 @@
     @Test
     @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
+    @CddTest
     public void microdroidDeviceTreeCompat(String os) throws Exception {
         assumeArm64Supported();
         final String configPath = "assets/vm_config.json";
@@ -1442,6 +1451,7 @@
     @Test
     @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
+    @CddTest
     public void microdroidProtectedDeviceTreeCompat(String os) throws Exception {
         assumeArm64Supported();
         final String configPath = "assets/vm_config.json";
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index cb374a5..d0e045b 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -73,7 +73,8 @@
     defaults: ["MicrodroidVersionsTestAppDefaults"],
     manifest: "AndroidManifestV5.xml",
     test_suites: ["general-tests"],
-    test_config: "AndroidTest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
+    auto_gen_config: true,
     data: DATA,
 }
 
@@ -81,8 +82,21 @@
     name: "MicrodroidTestApp.CTS",
     defaults: ["MicrodroidVersionsTestAppDefaults"],
     manifest: "AndroidManifestV5.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     test_suites: ["cts"],
-    test_config: ":MicrodroidTestApp.CTS.config",
+    auto_gen_config: true,
+    test_options: {
+        tradefed_options: [
+            {
+                name: "include-annotation",
+                value: "com.android.compatibility.common.util.CddTest",
+            },
+            {
+                name: "test-suite-tag",
+                value: "cts",
+            },
+        ],
+    },
     data: DATA,
 }
 
@@ -90,31 +104,44 @@
     name: "MicrodroidTestApp.VTS",
     defaults: ["MicrodroidVersionsTestAppDefaults"],
     manifest: "AndroidManifestV5.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     test_suites: ["vts"],
-    test_config: ":MicrodroidTestApp.VTS.config",
+    auto_gen_config: true,
+    test_options: {
+        tradefed_options: [
+            {
+                name: "include-annotation",
+                value: "com.android.compatibility.common.util.VsrTest",
+            },
+            {
+                name: "test-suite-tag",
+                value: "vts",
+            },
+        ],
+    },
     data: DATA,
 }
 
-genrule {
-    name: "MicrodroidTestApp.CTS.config",
-    srcs: ["AndroidTest.xml"],
-    out: ["out.xml"],
-    cmd: "sed " +
-        "-e 's/<!-- PLACEHOLDER_FOR_ANNOTATION -->/" +
-        "<option name=\"include-annotation\" value=\"com.android.compatibility.common.util.CddTest\" \\/>/' " +
-        "-e 's/MicrodroidTestApp.apk/MicrodroidTestApp.CTS.apk/' " +
-        "$(in) > $(out)",
-}
-
-genrule {
-    name: "MicrodroidTestApp.VTS.config",
-    srcs: ["AndroidTest.xml"],
-    out: ["out.xml"],
-    cmd: "sed " +
-        "-e 's/<!-- PLACEHOLDER_FOR_ANNOTATION -->/" +
-        "<option name=\"include-annotation\" value=\"com.android.compatibility.common.util.VsrTest\" \\/>/' " +
-        "-e 's/MicrodroidTestApp.apk/MicrodroidTestApp.VTS.apk/' " +
-        "$(in) > $(out)",
+android_test {
+    name: "MicrodroidTestApp.GTS",
+    defaults: ["MicrodroidVersionsTestAppDefaults"],
+    manifest: "AndroidManifestV5.xml",
+    test_config_template: "AndroidTestTemplate.xml",
+    test_suites: ["gts"],
+    auto_gen_config: true,
+    test_options: {
+        tradefed_options: [
+            {
+                name: "include-annotation",
+                value: "com.android.compatibility.common.util.GmsTest",
+            },
+            {
+                name: "test-suite-tag",
+                value: "gts",
+            },
+        ],
+    },
+    data: DATA,
 }
 
 android_test_helper_app {
@@ -224,7 +251,6 @@
         "libandroid_logger",
         "libanyhow",
         "libavflog",
-        "libcstr",
         "liblog_rust",
         "libvm_payload_rs",
     ],
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTestTemplate.xml
similarity index 92%
rename from tests/testapk/AndroidTest.xml
rename to tests/testapk/AndroidTestTemplate.xml
index 221c25c..613ce28 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTestTemplate.xml
@@ -14,14 +14,12 @@
      limitations under the License.
 -->
 <configuration description="Runs Microdroid device-side tests.">
-    <option name="test-suite-tag" value="cts" />
-    <option name="test-suite-tag" value="vts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="test-file-name" value="MicrodroidTestApp.apk" />
+        <option name="test-file-name" value="{MODULE}.apk" />
         <option name="test-file-name" value="MicrodroidVmShareApp.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
@@ -44,5 +42,5 @@
     <!-- For example: module wants to run arm and device is x86 -->
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
 
-    <!-- PLACEHOLDER_FOR_ANNOTATION -->
+    {EXTRA_CONFIGS}
 </configuration>
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
index 08bc310..df5525f 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidCapabilitiesTest.java
@@ -40,7 +40,7 @@
 @RunWith(JUnit4.class)
 public class MicrodroidCapabilitiesTest extends MicrodroidDeviceTestBase {
     @Test
-    @CddTest(requirements = "9.17/C-1-6")
+    @CddTest(requirements = "9.17/C-1-1")
     public void supportForProtectedOrNonProtectedVms() {
         assumeSupportedDevice();
 
@@ -61,7 +61,7 @@
     }
 
     @Test
-    @VsrTest(requirements = "VSR-7.1-001.005")
+    @VsrTest(requirements = "VSR-7.1-001.004")
     public void avfIsRequired() {
         assumeVsrCompliant();
         assume().withMessage("Requirement doesn't apply due to vendor API level")
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 9c66765..6260458 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -66,6 +66,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.GmsTest;
 import com.android.compatibility.common.util.VsrTest;
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 import com.android.microdroid.test.vmshare.IVmShareTestService;
@@ -216,13 +217,13 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     public void createAndConnectToVm() throws Exception {
         createAndConnectToVmHelper(CPU_TOPOLOGY_ONE_CPU, /* shouldUseHugepages= */ false);
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     public void createAndConnectToVm_HostCpuTopology() throws Exception {
         createAndConnectToVmHelper(CPU_TOPOLOGY_MATCH_HOST, /* shouldUseHugepages= */ false);
     }
@@ -248,8 +249,9 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     @VsrTest(requirements = {"VSR-7.1-001.006"})
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.005"})
     public void vmAttestationWhenRemoteAttestationIsNotSupported() throws Exception {
         // pVM remote attestation is only supported on protected VMs.
         assumeProtectedVM();
@@ -276,8 +278,9 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     @VsrTest(requirements = {"VSR-7.1-001.006"})
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.005"})
     public void vmAttestationWithVendorPartitionWhenSupported() throws Exception {
         // pVM remote attestation is only supported on protected VMs.
         assumeProtectedVM();
@@ -294,8 +297,9 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     @VsrTest(requirements = {"VSR-7.1-001.006"})
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.005"})
     public void vmAttestationWhenRemoteAttestationIsSupported() throws Exception {
         // pVM remote attestation is only supported on protected VMs.
         assumeProtectedVM();
@@ -340,7 +344,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     public void createAndRunNoDebugVm() throws Exception {
         assumeSupportedDevice();
 
@@ -361,7 +365,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void autoCloseVm() throws Exception {
         assumeSupportedDevice();
 
@@ -391,7 +395,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void autoCloseVmDescriptor() throws Exception {
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
@@ -420,7 +424,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmDescriptorClosedOnImport() throws Exception {
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
@@ -443,7 +447,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmLifecycleChecks() throws Exception {
         assumeSupportedDevice();
 
@@ -491,7 +495,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void connectVsock() throws Exception {
         assumeSupportedDevice();
 
@@ -529,7 +533,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void binderCallbacksWork() throws Exception {
         assumeSupportedDevice();
 
@@ -581,7 +585,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmConfigGetAndSetTests() {
         // Minimal has as little as specified as possible; everything that can be is defaulted.
         VirtualMachineConfig.Builder minimalBuilder =
@@ -650,7 +654,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmConfigBuilderValidationTests() {
         VirtualMachineConfig.Builder builder =
                 new VirtualMachineConfig.Builder(getContext()).setProtectedVm(mProtectedVm);
@@ -700,7 +704,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void compatibleConfigTests() {
         VirtualMachineConfig baseline = newBaselineBuilder().build();
 
@@ -787,7 +791,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmUnitTests() throws Exception {
         VirtualMachineConfig.Builder builder = newVmConfigBuilderWithPayloadBinary("binary.so");
         VirtualMachineConfig config = builder.build();
@@ -808,7 +812,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void testAvfRequiresUpdatableApex() throws Exception {
         assertWithMessage("Devices that support AVF must also support updatable APEX")
                 .that(SystemProperties.getBoolean("ro.apex.updatable", false))
@@ -816,7 +820,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmmGetAndCreate() throws Exception {
         assumeSupportedDevice();
 
@@ -863,7 +867,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmFilesStoredInDeDirWhenCreatedFromDEContext() throws Exception {
         final Context ctx = getContext().createDeviceProtectedStorageContext();
         final int userId = ctx.getUserId();
@@ -881,7 +885,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void vmFilesStoredInCeDirWhenCreatedFromCEContext() throws Exception {
         final Context ctx = getContext().createCredentialProtectedStorageContext();
         final int userId = ctx.getUserId();
@@ -898,7 +902,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void differentManagersForDifferentContexts() throws Exception {
         final Context ceCtx = getContext().createCredentialProtectedStorageContext();
         final Context deCtx = getContext().createDeviceProtectedStorageContext();
@@ -907,12 +911,7 @@
     }
 
     @Test
-    @CddTest(
-            requirements = {
-                "9.17/C-1-1",
-                "9.17/C-1-2",
-                "9.17/C-1-4",
-            })
+    @CddTest
     public void createVmWithConfigRequiresPermission() throws Exception {
         assumeSupportedDevice();
         revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
@@ -934,10 +933,7 @@
     }
 
     @Test
-    @CddTest(
-            requirements = {
-                "9.17/C-1-1",
-            })
+    @CddTest
     public void deleteVm() throws Exception {
         assumeSupportedDevice();
 
@@ -961,10 +957,7 @@
     }
 
     @Test
-    @CddTest(
-            requirements = {
-                "9.17/C-1-1",
-            })
+    @CddTest
     public void deleteVmFiles() throws Exception {
         assumeSupportedDevice();
 
@@ -994,10 +987,7 @@
     }
 
     @Test
-    @CddTest(
-            requirements = {
-                "9.17/C-1-1",
-            })
+    @CddTest
     public void validApkPathIsAccepted() throws Exception {
         assumeSupportedDevice();
 
@@ -1022,7 +1012,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void invalidVmNameIsRejected() {
         VirtualMachineManager vmm = getVirtualMachineManager();
         assertThrows(IllegalArgumentException.class, () -> vmm.get("../foo"));
@@ -1030,7 +1020,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     public void extraApk() throws Exception {
         assumeSupportedDevice();
 
@@ -1055,7 +1045,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     public void extraApkInVmConfig() throws Exception {
         assumeSupportedDevice();
         assumeFeatureEnabled(VirtualMachineManager.FEATURE_MULTI_TENANT);
@@ -1114,7 +1104,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    @CddTest
     public void changingNonDebuggableVmDebuggableInvalidatesVmIdentity() throws Exception {
         // Debuggability changes initrd which is verified by pvmfw.
         // Therefore, skip this on non-protected VM.
@@ -1168,7 +1158,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    @CddTest
     public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
         // Debuggability changes initrd which is verified by pvmfw.
         // Therefore, skip this on non-protected VM.
@@ -1249,7 +1239,8 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7", "9.17/C-3-4"})
+    @CddTest
+    @GmsTest(requirements = {"GMS-3-7.1-011"})
     public void instancesOfSameVmHaveDifferentCdis() throws Exception {
         assumeSupportedDevice();
         // TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
@@ -1276,7 +1267,8 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7", "9.17/C-3-4"})
+    @CddTest
+    @GmsTest(requirements = {"GMS-3-7.1-011"})
     public void sameInstanceKeepsSameCdis() throws Exception {
         assumeSupportedDevice();
         assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
@@ -1297,8 +1289,9 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    @CddTest
     @VsrTest(requirements = {"VSR-7.1-001.005"})
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.004"})
     public void bccIsSuperficiallyWellFormed() throws Exception {
         assumeSupportedDevice();
 
@@ -1344,6 +1337,7 @@
 
     @Test
     @VsrTest(requirements = {"VSR-7.1-001.005"})
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.004"})
     public void protectedVmHasValidDiceChain() throws Exception {
         // This test validates two things regarding the pVM DICE chain:
         // 1. The DICE chain is well-formed that all the entries conform to the DICE spec.
@@ -1377,7 +1371,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2"})
+    @CddTest
     public void accessToCdisIsRestricted() throws Exception {
         assumeSupportedDevice();
 
@@ -1454,7 +1448,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenMicrodroidDataIsCompromised() throws Exception {
         // If Updatable VM is supported => No instance.img required
         assumeNoUpdatableVmSupport();
@@ -1462,7 +1456,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenPvmFwDataIsCompromised() throws Exception {
         // If Updatable VM is supported => No instance.img required
         assumeNoUpdatableVmSupport();
@@ -1475,6 +1469,7 @@
     }
 
     @Test
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenConfigIsInvalid() throws Exception {
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         VirtualMachineConfig config =
@@ -1489,6 +1484,7 @@
     }
 
     @Test
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenBinaryNameIsInvalid() throws Exception {
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("DoesNotExist.so")
@@ -1502,6 +1498,7 @@
     }
 
     @Test
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenApkPathIsInvalid() {
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
@@ -1515,6 +1512,7 @@
     }
 
     @Test
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenExtraApkPackageIsInvalid() {
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
@@ -1558,6 +1556,7 @@
     }
 
     @Test
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenBinaryIsMissingEntryFunction() throws Exception {
         VirtualMachineConfig normalConfig =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidEmptyNativeLib.so")
@@ -1570,6 +1569,7 @@
     }
 
     @Test
+    @GmsTest(requirements = {"GMS-3-7.1-006"})
     public void bootFailsWhenBinaryTriesToLinkAgainstPrivateLibs() throws Exception {
         VirtualMachineConfig normalConfig =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidPrivateLinkingNativeLib.so")
@@ -1582,6 +1582,7 @@
     }
 
     @Test
+    @CddTest
     public void sameInstancesShareTheSameVmObject() throws Exception {
         VirtualMachineConfig config =
                 newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so").build();
@@ -1598,6 +1599,7 @@
     }
 
     @Test
+    @CddTest
     public void importedVmAndOriginalVmHaveTheSameCdi() throws Exception {
         assumeSupportedDevice();
         // Arrange
@@ -1693,7 +1695,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void encryptedStorageAvailable() throws Exception {
         assumeSupportedDevice();
 
@@ -1716,7 +1718,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void encryptedStorageIsInaccessibleToDifferentVm() throws Exception {
         assumeSupportedDevice();
         // TODO(b/325094712): VMs on CF with same payload have the same secret. This is because
@@ -1781,7 +1783,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     public void microdroidLauncherHasEmptyCapabilities() throws Exception {
         assumeSupportedDevice();
 
@@ -1805,7 +1807,8 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
+    @GmsTest(requirements = {"GMS-3-7.1-005"})
     public void payloadIsNotRoot() throws Exception {
         assumeSupportedDevice();
         assumeFeatureEnabled(VirtualMachineManager.FEATURE_MULTI_TENANT);
@@ -1828,7 +1831,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1"})
+    @CddTest
     public void encryptedStorageIsPersistent() throws Exception {
         assumeSupportedDevice();
 
@@ -1864,7 +1867,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    @CddTest
     public void canReadFileFromAssets_debugFull() throws Exception {
         assumeSupportedDevice();
 
@@ -1888,6 +1891,7 @@
     }
 
     @Test
+    @CddTest
     public void outputShouldBeExplicitlyCaptured() throws Exception {
         assumeSupportedDevice();
 
@@ -1910,6 +1914,7 @@
     }
 
     @Test
+    @CddTest
     public void inputShouldBeExplicitlyAllowed() throws Exception {
         assumeSupportedDevice();
 
@@ -1961,6 +1966,7 @@
     }
 
     @Test
+    @CddTest
     public void outputIsRedirectedToLogcatIfNotCaptured() throws Exception {
         assumeSupportedDevice();
 
@@ -1981,6 +1987,7 @@
     }
 
     @Test
+    @CddTest
     public void outputIsNotRedirectedToLogcatIfNotDebuggable() throws Exception {
         assumeSupportedDevice();
 
@@ -1991,6 +1998,7 @@
     }
 
     @Test
+    @CddTest
     public void testConsoleInputSupported() throws Exception {
         assumeSupportedDevice();
         assumeFalse("Not supported on GKI kernels", mOs.startsWith("microdroid_gki-"));
@@ -2020,6 +2028,7 @@
     }
 
     @Test
+    @CddTest
     public void testStartVmWithPayloadOfAnotherApp() throws Exception {
         assumeSupportedDevice();
 
@@ -2049,6 +2058,7 @@
     }
 
     @Test
+    @CddTest
     public void testVmDescriptorParcelUnparcel_noTrustedStorage() throws Exception {
         assumeSupportedDevice();
 
@@ -2082,6 +2092,7 @@
     }
 
     @Test
+    @CddTest
     public void testVmDescriptorParcelUnparcel_withTrustedStorage() throws Exception {
         assumeSupportedDevice();
 
@@ -2135,6 +2146,7 @@
     }
 
     @Test
+    @CddTest
     public void testShareVmWithAnotherApp() throws Exception {
         assumeSupportedDevice();
 
@@ -2180,6 +2192,7 @@
     }
 
     @Test
+    @CddTest
     public void testShareVmWithAnotherApp_encryptedStorage() throws Exception {
         assumeSupportedDevice();
 
@@ -2249,7 +2262,8 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-5"})
+    @CddTest
+    @GmsTest(requirements = {"GMS-3-7.1-005"})
     public void testFileUnderBinHasExecutePermission() throws Exception {
         assumeSupportedDevice();
 
@@ -2292,7 +2306,7 @@
     private static final int MS_NOATIME = 1024;
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-5"})
+    @GmsTest(requirements = {"GMS-3-7.1-004", "GMS-3-7.1-005"})
     public void dataIsMountedWithNoExec() throws Exception {
         assumeSupportedDevice();
 
@@ -2317,7 +2331,7 @@
     }
 
     @Test
-    @CddTest(requirements = {"9.17/C-1-5"})
+    @GmsTest(requirements = {"GMS-3-7.1-004", "GMS-3-7.1-005"})
     public void encryptedStoreIsMountedWithNoExec() throws Exception {
         assumeSupportedDevice();
 
@@ -2343,7 +2357,6 @@
     }
 
     @Test
-    @VsrTest(requirements = {"VSR-7.1-001.003"})
     public void kernelVersionRequirement() throws Exception {
         assumeVsrCompliant();
         int firstApiLevel = SystemProperties.getInt("ro.product.first_api_level", 0);
@@ -2445,6 +2458,9 @@
     }
 
     @Test
+    @CddTest
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.007"})
+    @VsrTest(requirements = {"VSR-7.1-001.008"})
     public void configuringVendorDiskImageRequiresCustomPermission() throws Exception {
         File vendorDiskImage =
                 new File("/data/local/tmp/cts/microdroid/test_microdroid_vendor_image.img");
@@ -2462,6 +2478,9 @@
     }
 
     @Test
+    @CddTest
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.007"})
+    @VsrTest(requirements = {"VSR-7.1-001.008"})
     public void bootsWithVendorPartition() throws Exception {
         File vendorDiskImage = new File("/vendor/etc/avf/microdroid/microdroid_vendor.img");
         assumeTrue("Microdroid vendor image doesn't exist, skip", vendorDiskImage.exists());
@@ -2481,6 +2500,9 @@
     }
 
     @Test
+    @CddTest
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.007"})
+    @VsrTest(requirements = {"VSR-7.1-001.008"})
     public void bootsWithCustomVendorPartitionForNonPvm() throws Exception {
         assumeNonProtectedVM();
         File vendorDiskImage =
@@ -2502,6 +2524,9 @@
     }
 
     @Test
+    @CddTest
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.007"})
+    @VsrTest(requirements = {"VSR-7.1-001.008"})
     public void bootFailsWithCustomVendorPartitionForPvm() throws Exception {
         assumeProtectedVM();
         File vendorDiskImage =
@@ -2514,6 +2539,9 @@
     }
 
     @Test
+    @CddTest
+    @GmsTest(requirements = {"GMS-VSR-7.1-001.007"})
+    @VsrTest(requirements = {"VSR-7.1-001.008"})
     public void creationFailsWithUnsignedVendorPartition() throws Exception {
         File vendorDiskImage =
                 new File(
@@ -2526,6 +2554,7 @@
     }
 
     @Test
+    @GmsTest(requirements = {"GMS-3-7.1-004", "GMS-3-7.1-005"})
     public void systemPartitionMountFlags() throws Exception {
         assumeSupportedDevice();
 
diff --git a/tests/testapk/src/native/testbinary.rs b/tests/testapk/src/native/testbinary.rs
index 2502113..a84b955 100644
--- a/tests/testapk/src/native/testbinary.rs
+++ b/tests/testapk/src/native/testbinary.rs
@@ -24,7 +24,6 @@
     },
     binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong},
 };
-use cstr::cstr;
 use log::{error, info};
 use std::process::exit;
 use std::string::String;
@@ -130,7 +129,7 @@
 }
 
 fn unimplemented<T>() -> BinderResult<T> {
-    let message = cstr!("Got a call to an unimplemented ITestService method in testbinary.rs");
+    let message = c"Got a call to an unimplemented ITestService method in testbinary.rs";
     error!("{message:?}");
     Err(Status::new_exception(ExceptionCode::UNSUPPORTED_OPERATION, Some(message)))
 }
diff --git a/tests/vts/Android.bp b/tests/vts/Android.bp
new file mode 100644
index 0000000..c8e2523
--- /dev/null
+++ b/tests/vts/Android.bp
@@ -0,0 +1,36 @@
+prebuilt_etc {
+    name: "vts_libavf_test_kernel",
+    filename: "rialto.bin",
+    src: ":empty_file",
+    target: {
+        android_arm64: {
+            src: ":rialto_signed",
+        },
+    },
+    installable: false,
+    visibility: ["//visibility:private"],
+}
+
+rust_test {
+    name: "vts_libavf_test",
+    crate_name: "vts_libavf_test",
+    srcs: ["src/vts_libavf_test.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libavf_bindgen",
+        "libciborium",
+        "liblog_rust",
+        "libhypervisor_props",
+        "libscopeguard",
+        "libservice_vm_comm",
+        "libvsock",
+    ],
+    shared_libs: ["libavf"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+    data: [":vts_libavf_test_kernel"],
+    test_config: "AndroidTest.xml",
+    compile_multilib: "first",
+}
diff --git a/tests/vts/AndroidTest.xml b/tests/vts/AndroidTest.xml
new file mode 100644
index 0000000..75c8d31
--- /dev/null
+++ b/tests/vts/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs vts_libavf_test.">
+    <option name="test-suite-tag" value="vts" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="vts_libavf_test->/data/local/tmp/vts_libavf_test" />
+        <option name="push" value="rialto.bin->/data/local/tmp/rialto.bin" />
+    </target_preparer>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ArchModuleController">
+        <option name="arch" value="arm64" />
+    </object>
+
+    <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+        <option name="test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="vts_libavf_test" />
+        <!-- rialto uses a fixed port number for the host, can't run two tests at the same time -->
+        <option name="native-test-flag" value="--test-threads=1" />
+    </test>
+</configuration>
diff --git a/tests/vts/src/vts_libavf_test.rs b/tests/vts/src/vts_libavf_test.rs
new file mode 100644
index 0000000..e30c175
--- /dev/null
+++ b/tests/vts/src/vts_libavf_test.rs
@@ -0,0 +1,196 @@
+// 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.
+
+//! Tests running a VM with LLNDK
+
+use anyhow::{bail, ensure, Context, Result};
+use log::info;
+use std::ffi::CStr;
+use std::fs::File;
+use std::io::{self, BufWriter, Write};
+use std::os::fd::IntoRawFd;
+use std::time::{Duration, Instant};
+use vsock::{VsockListener, VsockStream, VMADDR_CID_HOST};
+
+use avf_bindgen::*;
+use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
+
+const VM_MEMORY_MB: i32 = 16;
+const WRITE_BUFFER_CAPACITY: usize = 512;
+
+const LISTEN_TIMEOUT: Duration = Duration::from_secs(10);
+const READ_TIMEOUT: Duration = Duration::from_secs(10);
+const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
+const STOP_TIMEOUT: timespec = timespec { tv_sec: 10, tv_nsec: 0 };
+
+/// Processes the request in the service VM.
+fn process_request(vsock_stream: &mut VsockStream, request: Request) -> Result<Response> {
+    write_request(vsock_stream, &ServiceVmRequest::Process(request))?;
+    read_response(vsock_stream)
+}
+
+/// Sends the request to the service VM.
+fn write_request(vsock_stream: &mut VsockStream, request: &ServiceVmRequest) -> Result<()> {
+    let mut buffer = BufWriter::with_capacity(WRITE_BUFFER_CAPACITY, vsock_stream);
+    ciborium::into_writer(request, &mut buffer)?;
+    buffer.flush().context("Failed to flush the buffer")?;
+    Ok(())
+}
+
+/// Reads the response from the service VM.
+fn read_response(vsock_stream: &mut VsockStream) -> Result<Response> {
+    let response: Response = ciborium::from_reader(vsock_stream)
+        .context("Failed to read the response from the service VM")?;
+    Ok(response)
+}
+
+fn listen_from_guest(port: u32) -> Result<VsockStream> {
+    let vsock_listener =
+        VsockListener::bind_with_cid_port(VMADDR_CID_HOST, port).context("Failed to bind vsock")?;
+    vsock_listener.set_nonblocking(true).context("Failed to set nonblocking")?;
+    let start_time = Instant::now();
+    loop {
+        if start_time.elapsed() >= LISTEN_TIMEOUT {
+            bail!("Timeout while listening");
+        }
+        match vsock_listener.accept() {
+            Ok((vsock_stream, _peer_addr)) => return Ok(vsock_stream),
+            Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
+                std::thread::sleep(Duration::from_millis(100));
+            }
+            Err(e) => bail!("Failed to listen: {e:?}"),
+        }
+    }
+}
+
+fn run_rialto(protected_vm: bool) -> Result<()> {
+    let kernel_file =
+        File::open("/data/local/tmp/rialto.bin").context("Failed to open kernel file")?;
+    let kernel_fd = kernel_file.into_raw_fd();
+
+    // SAFETY: AVirtualMachineRawConfig_create() isn't unsafe but rust_bindgen forces it to be seen
+    // as unsafe
+    let config = unsafe { AVirtualMachineRawConfig_create() };
+
+    info!("raw config created");
+
+    // SAFETY: config is the only reference to a valid object
+    unsafe {
+        AVirtualMachineRawConfig_setName(
+            config,
+            CStr::from_bytes_with_nul(b"vts_libavf_test_rialto\0").unwrap().as_ptr(),
+        );
+        AVirtualMachineRawConfig_setKernel(config, kernel_fd);
+        AVirtualMachineRawConfig_setProtectedVm(config, protected_vm);
+        AVirtualMachineRawConfig_setMemoryMiB(config, VM_MEMORY_MB);
+    }
+
+    let mut vm = std::ptr::null_mut();
+    let mut service = std::ptr::null_mut();
+
+    ensure!(
+        // SAFETY: &mut service is a valid pointer to *AVirtualizationService
+        unsafe { AVirtualizationService_create(&mut service, false) } == 0,
+        "AVirtualizationService_create failed"
+    );
+
+    scopeguard::defer! {
+        // SAFETY: service is a valid pointer to AVirtualizationService
+        unsafe { AVirtualizationService_destroy(service); }
+    }
+
+    ensure!(
+        // SAFETY: &mut vm is a valid pointer to *AVirtualMachine
+        unsafe {
+            AVirtualMachine_createRaw(
+                service, config, -1, // console_in
+                -1, // console_out
+                -1, // log
+                &mut vm,
+            )
+        } == 0,
+        "AVirtualMachine_createRaw failed"
+    );
+
+    scopeguard::defer! {
+        // SAFETY: vm is a valid pointer to AVirtualMachine
+        unsafe { AVirtualMachine_destroy(vm); }
+    }
+
+    info!("vm created");
+
+    let vm_type = if protected_vm { VmType::ProtectedVm } else { VmType::NonProtectedVm };
+
+    let listener_thread = std::thread::spawn(move || listen_from_guest(vm_type.port()));
+
+    // SAFETY: vm is the only reference to a valid object
+    unsafe {
+        AVirtualMachine_start(vm);
+    }
+
+    info!("VM started");
+
+    let mut vsock_stream = listener_thread.join().unwrap()?;
+    vsock_stream.set_read_timeout(Some(READ_TIMEOUT))?;
+    vsock_stream.set_write_timeout(Some(WRITE_TIMEOUT))?;
+
+    info!("client connected");
+
+    let request_data = vec![1, 2, 3, 4, 5];
+    let expected_data = vec![5, 4, 3, 2, 1];
+    let response = process_request(&mut vsock_stream, Request::Reverse(request_data))
+        .context("Failed to process request")?;
+    let Response::Reverse(reversed_data) = response else {
+        bail!("Expected Response::Reverse but was {response:?}");
+    };
+    ensure!(reversed_data == expected_data, "Expected {expected_data:?} but was {reversed_data:?}");
+
+    info!("request processed");
+
+    write_request(&mut vsock_stream, &ServiceVmRequest::Shutdown)
+        .context("Failed to send shutdown")?;
+
+    info!("shutdown sent");
+
+    let mut stop_reason = AVirtualMachineStopReason::AVIRTUAL_MACHINE_UNRECOGNISED;
+    ensure!(
+        // SAFETY: vm is the only reference to a valid object
+        unsafe { AVirtualMachine_waitForStop(vm, &STOP_TIMEOUT, &mut stop_reason) },
+        "AVirtualMachine_waitForStop failed"
+    );
+
+    info!("stopped");
+
+    Ok(())
+}
+
+#[test]
+fn test_run_rialto_protected() -> Result<()> {
+    if hypervisor_props::is_protected_vm_supported()? {
+        run_rialto(true /* protected_vm */)
+    } else {
+        info!("pVMs are not supported on device. skipping test");
+        Ok(())
+    }
+}
+
+#[test]
+fn test_run_rialto_non_protected() -> Result<()> {
+    if hypervisor_props::is_vm_supported()? {
+        run_rialto(false /* protected_vm */)
+    } else {
+        info!("non-pVMs are not supported on device. skipping test");
+        Ok(())
+    }
+}