Add mock notifications for terminal app

This commit adds two mock notifications for the terminal app:
1. VmLauncherService foreground notification
  As a foreground service, VmLauncherService should display a
  notification to indicate the service is running.
  Terminal app should pass a Notification to the
  VmLauncherService for the foreground notification
2. Port forwarding notification
  When the VM opens a port, terminal app should send a
  notification for user to allow or deny the request

Bug: 372170751
Test: Install on komodo
Change-Id: Iac0ae70354f984246abcb6af1d650a548d59387d
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index 105e454..28b5436 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -9,6 +9,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
 
     <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
 
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 20b4c96..d71a17c 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -15,8 +15,15 @@
  */
 package com.android.virtualization.terminal;
 
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.net.http.SslError;
 import android.os.Build;
 import android.os.Bundle;
@@ -72,6 +79,7 @@
     private PrivateKey mPrivateKey;
     private WebView mWebView;
     private AccessibilityManager mAccessibilityManager;
+    private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -88,6 +96,14 @@
                     .show();
         }
 
+        checkAndRequestPostNotificationsPermission();
+
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        NotificationChannel notificationChannel =
+                new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
+        assert notificationManager != null;
+        notificationManager.createNotificationChannel(notificationChannel);
+
         setContentView(R.layout.activity_headless);
 
         MaterialToolbar toolbar = (MaterialToolbar) findViewById(R.id.toolbar);
@@ -311,6 +327,15 @@
         return;
     }
 
+    private void checkAndRequestPostNotificationsPermission() {
+        if (getApplicationContext().checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(
+                    new String[]{Manifest.permission.POST_NOTIFICATIONS},
+                    POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE);
+        }
+    }
+
     @Override
     protected void onDestroy() {
         getSystemService(AccessibilityManager.class).removeTouchExplorationStateChangeListener(this);
@@ -393,7 +418,29 @@
         if (!InstallUtils.isImageInstalled(this)) {
             return;
         }
+        // TODO: implement intent for setting, close and tap to the notification
+        // Currently mock a PendingIntent for notification.
+        Intent intent = new Intent();
+        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        Icon icon = Icon.createWithResource(getResources(), R.drawable.ic_launcher_foreground);
+        Notification notification = new Notification.Builder(this, TAG)
+                .setChannelId(TAG)
+                .setSmallIcon(R.drawable.ic_launcher_foreground)
+                .setContentTitle(getResources().getString(R.string.service_notification_title))
+                .setContentText(getResources().getString(R.string.service_notification_content))
+                .setContentIntent(pendingIntent)
+                .setOngoing(true)
+                .addAction(new Notification.Action.Builder(icon,
+                        getResources().getString(R.string.service_notification_settings),
+                        pendingIntent).build())
+                .addAction(new Notification.Action.Builder(icon,
+                        getResources().getString(R.string.service_notification_quit_action),
+                        pendingIntent).build())
+                .build();
+
         android.os.Trace.beginAsyncSection("executeTerminal", 0);
-        VmLauncherServices.startVmLauncherService(this, this);
+        VmLauncherServices.startVmLauncherService(this, this, notification);
     }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
index 6c36cc8..7119225 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -15,12 +15,21 @@
  */
 package com.android.virtualization.terminal
 
+import android.Manifest
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 
 class SettingsPortForwardingActivity : AppCompatActivity() {
+    val TAG: String = "VmTerminalApp"
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.settings_port_forwarding)
@@ -37,5 +46,43 @@
         val recyclerView: RecyclerView = findViewById(R.id.settings_port_forwarding_recycler_view)
         recyclerView.layoutManager = LinearLayoutManager(this)
         recyclerView.adapter = settingsPortForwardingAdapter
+
+        // TODO: implement intent for accept, deny and tap to the notification
+        // Currently show a mock notification of a port opening
+        val terminalIntent = Intent()
+        val pendingIntent = PendingIntent.getActivity(
+            this, 0, terminalIntent,
+            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+        )
+        val notification =
+            Notification.Builder(this, TAG)
+                .setChannelId(TAG)
+                .setSmallIcon(R.drawable.ic_launcher_foreground)
+                .setContentTitle(resources.getString(R.string.settings_port_forwarding_notification_title))
+                .setContentText(resources.getString(R.string.settings_port_forwarding_notification_content, settingsPortForwardingItems[0].port))
+                .addAction(
+                    Notification.Action.Builder(
+                        Icon.createWithResource(resources, R.drawable.ic_launcher_foreground),
+                        resources.getString(R.string.settings_port_forwarding_notification_accept),
+                        pendingIntent
+                    ).build()
+                )
+                .addAction(
+                    Notification.Action.Builder(
+                        Icon.createWithResource(resources, R.drawable.ic_launcher_foreground),
+                        resources.getString(R.string.settings_port_forwarding_notification_deny),
+                        pendingIntent
+                    ).build()
+                )
+                .build()
+
+        with(NotificationManager.from(this)) {
+            if (ActivityCompat.checkSelfPermission(
+                    this@SettingsPortForwardingActivity, Manifest.permission.POST_NOTIFICATIONS
+                ) == PackageManager.PERMISSION_GRANTED
+            ) {
+                notify(0, notification)
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 1cbaee8..0cdb939 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -49,6 +49,14 @@
     <string name="settings_port_forwarding_title">Port Forwarding</string>
     <!-- Settings menu subtitle for 'port forwarding' [CHAR LIMIT=none] -->
     <string name="settings_port_forwarding_sub_title">Configure port forwarding</string>
+    <!-- Notification title for new port forwarding [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_notification_title">Terminal is trying to open a new port</string>
+    <!-- Notification content for new port forwarding [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_notification_content">Port requested to be open: <xliff:g id="port_number" example="8080">%d</xliff:g></string>
+    <!-- Notification action accept [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_notification_accept">Accept</string>
+    <!-- Notification action deny [CHAR LIMIT=none] -->
+    <string name="settings_port_forwarding_notification_deny">Deny</string>
 
     <!-- Settings menu title for recoverying image [CHAR LIMIT=none] -->
     <string name="settings_recovery_title">Recovery</string>
@@ -60,4 +68,13 @@
     <string name="settings_recovery_reset_sub_title">Remove all</string>
     <!-- Toast message for reset is completed [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_message">VM reset</string>
+
+    <!-- Notification action button for settings [CHAR LIMIT=none] -->
+    <string name="service_notification_settings">Settings</string>
+    <!-- Notification title for foreground service notification [CHAR LIMIT=none] -->
+    <string name="service_notification_title">Terminal is running</string>
+    <!-- Notification content for foreground service notification [CHAR LIMIT=none] -->
+    <string name="service_notification_content">Click to open the terminal.</string>
+    <!-- Notification action button for closing the virtual machine [CHAR LIMIT=none] -->
+    <string name="service_notification_quit_action">Close</string>
 </resources>
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
index a59cc3d..849cc24 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
@@ -17,8 +17,6 @@
 package com.android.virtualization.vmlauncher;
 
 import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Bundle;
@@ -39,6 +37,7 @@
 import java.util.concurrent.Executors;
 
 public class VmLauncherService extends Service implements DebianServiceImpl.DebianServiceCallback {
+    public static final String EXTRA_NOTIFICATION = "EXTRA_NOTIFICATION";
     private static final String TAG = "VmLauncherService";
 
     private static final int RESULT_START = 0;
@@ -57,18 +56,8 @@
         return null;
     }
 
-    private void startForeground() {
-        NotificationManager notificationManager = getSystemService(NotificationManager.class);
-        NotificationChannel notificationChannel =
-                new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
-        notificationManager.createNotificationChannel(notificationChannel);
-        startForeground(
-                this.hashCode(),
-                new Notification.Builder(this, TAG)
-                        .setChannelId(TAG)
-                        .setSmallIcon(android.R.drawable.ic_dialog_info)
-                        .setContentText("A VM " + mVirtualMachine.getName() + " is running")
-                        .build());
+    private void startForeground(Notification notification) {
+        startForeground(this.hashCode(), notification);
     }
 
     @Override
@@ -110,7 +99,10 @@
         Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
         Logger.setup(mVirtualMachine, logPath, mExecutorService);
 
-        startForeground();
+        Notification notification = intent.getParcelableExtra(EXTRA_NOTIFICATION,
+                Notification.class);
+
+        startForeground(notification);
 
         mResultReceiver.send(RESULT_START, null);
 
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
index 565b793..2fa0b32 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
@@ -16,6 +16,7 @@
 
 package com.android.virtualization.vmlauncher;
 
+import android.app.Notification;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -64,7 +65,8 @@
         context.stopService(i);
     }
 
-    public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback) {
+    public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback,
+            Notification notification) {
         Intent i = buildVmLauncherServiceIntent(context);
         if (i == null) {
             return;
@@ -93,6 +95,7 @@
                     }
                 };
         i.putExtra(Intent.EXTRA_RESULT_RECEIVER, getResultReceiverForIntent(resultReceiver));
+        i.putExtra(VmLauncherService.EXTRA_NOTIFICATION, notification);
         context.startForegroundService(i);
     }