Create dialog for adding a new inactive port

Bug: 382998392
Test: Run VmTerminalApp
Change-Id: I597b92cd732ec92564418786d2d0f61710c652e5
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
index 83a8d05..27d6ce7 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -15,10 +15,20 @@
  */
 package com.android.virtualization.terminal
 
+import android.content.DialogInterface
 import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.widget.EditText
+import android.widget.ImageButton
+import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.app.AppCompatActivity
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+private const val PORT_RANGE_MIN: Int = 1024
+private const val PORT_RANGE_MAX: Int = 65535
 
 class SettingsPortForwardingActivity : AppCompatActivity() {
     private lateinit var mPortsStateManager: PortsStateManager
@@ -45,6 +55,88 @@
         inactiveRecyclerView.adapter = mInactivePortsAdapter
 
         mPortsStateListener = Listener()
+
+        val addButton = findViewById<ImageButton>(R.id.settings_port_forwarding_inactive_add_button)
+        addButton.setOnClickListener {
+            val dialog =
+                MaterialAlertDialogBuilder(this)
+                    .setTitle(R.string.settings_port_forwarding_dialog_title)
+                    .setView(R.layout.settings_port_forwarding_inactive_add_dialog)
+                    .setPositiveButton(R.string.settings_port_forwarding_dialog_save) {
+                        dialogInterface,
+                        _ ->
+                        val alertDialog = dialogInterface as AlertDialog
+                        val editText =
+                            alertDialog.findViewById<EditText>(
+                                R.id.settings_port_forwarding_inactive_add_dialog_text
+                            )!!
+                        val port = editText.text.toString().toInt()
+                        mPortsStateManager.updateEnabledPort(port, true)
+                    }
+                    .setNegativeButton(R.string.settings_port_forwarding_dialog_cancel, null)
+                    .create()
+            dialog.show()
+
+            val positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
+            positiveButton.setEnabled(false)
+            val editText =
+                dialog.findViewById<EditText>(
+                    R.id.settings_port_forwarding_inactive_add_dialog_text
+                )!!
+            editText.addTextChangedListener(
+                object : TextWatcher {
+                    override fun beforeTextChanged(
+                        s: CharSequence?,
+                        start: Int,
+                        count: Int,
+                        after: Int,
+                    ) {}
+
+                    override fun afterTextChanged(s: Editable?) {}
+
+                    override fun onTextChanged(
+                        s: CharSequence?,
+                        start: Int,
+                        before: Int,
+                        count: Int,
+                    ) {
+                        val port =
+                            try {
+                                s.toString().toInt()
+                            } catch (e: NumberFormatException) {
+                                editText.setError(
+                                    getString(
+                                        R.string.settings_port_forwarding_dialog_error_invalid_input
+                                    )
+                                )
+                                positiveButton.setEnabled(false)
+                                return@onTextChanged
+                            }
+                        if (port > PORT_RANGE_MAX || port < PORT_RANGE_MIN) {
+                            editText.setError(
+                                getString(
+                                    R.string
+                                        .settings_port_forwarding_dialog_error_invalid_port_range
+                                )
+                            )
+                            positiveButton.setEnabled(false)
+                        } else if (
+                            mPortsStateManager.getActivePorts().contains(port) ||
+                                mPortsStateManager.getEnabledPorts().contains(port)
+                        ) {
+                            editText.setError(
+                                getString(
+                                    R.string.settings_port_forwarding_dialog_error_existing_port
+                                )
+                            )
+                            positiveButton.setEnabled(false)
+                        } else {
+                            positiveButton.setEnabled(true)
+                        }
+                    }
+                }
+            )
+        }
     }
 
     private fun refreshAdapters() {
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
index ee0bee5..d572129 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingInactiveAdapter.kt
@@ -38,7 +38,7 @@
 
     class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
         val closeButton: ImageButton =
-            view.findViewById(R.id.settings_port_forwarding_active_item_close_button)
+            view.findViewById(R.id.settings_port_forwarding_inactive_item_close_button)
         val port: TextView = view.findViewById(R.id.settings_port_forwarding_inactive_item_port)
     }
 
diff --git a/android/TerminalApp/res/drawable/ic_add.xml b/android/TerminalApp/res/drawable/ic_add.xml
new file mode 100644
index 0000000..ebe9284
--- /dev/null
+++ b/android/TerminalApp/res/drawable/ic_add.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--  Copyright 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M440,520L200,520L200,440L440,440L440,200L520,200L520,440L760,440L760,520L520,520L520,760L440,760L440,520Z"/>
+</vector>
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding.xml b/android/TerminalApp/res/layout/settings_port_forwarding.xml
index 77b9bf7..880ac44 100644
--- a/android/TerminalApp/res/layout/settings_port_forwarding.xml
+++ b/android/TerminalApp/res/layout/settings_port_forwarding.xml
@@ -14,7 +14,9 @@
      limitations under the License.
  -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
@@ -46,13 +48,35 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 
-    <TextView
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:text="@string/settings_port_forwarding_other_enabled_ports_title"
-        android:textSize="24sp"
-        android:hyphenationFrequency="full"
-        android:layout_marginBottom="24dp"/>
+        android:layout_marginBottom="24dp">
+
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:text="@string/settings_port_forwarding_other_enabled_ports_title"
+            android:textSize="24sp"
+            android:hyphenationFrequency="full"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent" />
+
+        <ImageButton
+            android:id="@+id/settings_port_forwarding_inactive_add_button"
+            android:src="@drawable/ic_add"
+            android:background="@android:color/transparent"
+            android:contentDescription="@string/settings_port_forwarding_other_enabled_port_add_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="16dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/settings_port_forwarding_inactive_recycler_view"
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding_inactive_add_dialog.xml b/android/TerminalApp/res/layout/settings_port_forwarding_inactive_add_dialog.xml
new file mode 100644
index 0000000..84fb611
--- /dev/null
+++ b/android/TerminalApp/res/layout/settings_port_forwarding_inactive_add_dialog.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--  Copyright 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="16dp">
+
+    <EditText
+        android:id="@+id/settings_port_forwarding_inactive_add_dialog_text"
+        android:hint="@string/settings_port_forwarding_dialog_textview_hint"
+        android:importantForAutofill="no"
+        android:inputType="number"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding_inactive_item.xml b/android/TerminalApp/res/layout/settings_port_forwarding_inactive_item.xml
index 127b152..3e0d53b 100644
--- a/android/TerminalApp/res/layout/settings_port_forwarding_inactive_item.xml
+++ b/android/TerminalApp/res/layout/settings_port_forwarding_inactive_item.xml
@@ -34,7 +34,7 @@
         app:layout_constraintStart_toStartOf="parent"/>
 
     <ImageButton
-        android:id="@+id/settings_port_forwarding_active_item_close_button"
+        android:id="@+id/settings_port_forwarding_inactive_item_close_button"
         android:src="@drawable/ic_close"
         android:background="@android:color/transparent"
         android:contentDescription="@null"
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 493496c..e39c7c6 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -89,7 +89,9 @@
     <string name="settings_port_forwarding_active_ports_title">Listening ports</string>
     <!-- Title for other enabled ports setting in port forwarding [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_other_enabled_ports_title">Saved allowed ports</string>
-    <!-- Description of close button for other enabled ports. Used for talkback. [CHAR LIMIT=none] -->
+    <!-- Description of add button for other enabled ports. Used for talkback. [CHAR LIMIT=16] -->
+    <string name="settings_port_forwarding_other_enabled_port_add_button">Add</string>
+    <!-- Description of close button for other enabled ports. Used for talkback. [CHAR LIMIT=16] -->
     <string name="settings_port_forwarding_other_enabled_port_close_button">Delete <xliff:g id="port_number" example="8000">%d</xliff:g></string>
 
     <!-- Dialog title for enabling a new port [CHAR LIMIT=none] -->
@@ -100,6 +102,12 @@
     <string name="settings_port_forwarding_dialog_save">Save</string>
     <!-- Dialog cancel action for enabling a new port [CHAR LIMIT=16] -->
     <string name="settings_port_forwarding_dialog_cancel">Cancel</string>
+    <!-- Dialog error message for non number format input [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_dialog_error_invalid_input">Please enter a number</string>
+    <!-- Dialog error message for invalid port number [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_dialog_error_invalid_port_range">Invalid port number</string>
+    <!-- Dialog error message for existing port [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_dialog_error_existing_port">Port already exists</string>
 
     <!-- Notification title for a new active port [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_notification_title">Terminal is requesting to open a new port</string>