Move port forward notification handling routines to PortNotifier

Bug: N/A
Test: N/A
Change-Id: Ib22a0e421c02d4b839c58929a368de2992c04087
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java b/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java
new file mode 100644
index 0000000..e353b1d
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/PortNotifier.java
@@ -0,0 +1,160 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.drawable.Icon;
+import android.util.Log;
+
+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 final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private final BroadcastReceiver mReceiver;
+
+    public PortNotifier(Context context) {
+        mContext = context;
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
+        mReceiver = new PortForwardingRequestReceiver();
+
+        IntentFilter intentFilter =
+                new IntentFilter(PortForwardingRequestReceiver.ACTION_PORT_FORWARDING);
+        mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
+    }
+
+    public void onActivePortsChanged(Set<String> oldPorts, Set<String> newPorts) {
+        Set<String> union = new HashSet<>(oldPorts);
+        union.addAll(newPorts);
+        for (String portStr : union) {
+            try {
+                if (!oldPorts.contains(portStr)) {
+                    showPortForwardingNotification(Integer.parseInt(portStr));
+                } else if (!newPorts.contains(portStr)) {
+                    discardPortForwardingNotification(Integer.parseInt(portStr));
+                }
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "Failed to parse port: " + portStr);
+                throw e;
+            }
+        }
+    }
+
+    public void stop() {
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    private String getString(int resId) {
+        return mContext.getString(resId);
+    }
+
+    private PendingIntent getPortForwardingPendingIntent(int port, boolean enabled) {
+        Intent intent = new Intent(PortForwardingRequestReceiver.ACTION_PORT_FORWARDING);
+        intent.setPackage(mContext.getPackageName());
+        intent.setIdentifier(String.format(Locale.ROOT, "%d_%b", port, enabled));
+        intent.putExtra(PortForwardingRequestReceiver.KEY_PORT, port);
+        intent.putExtra(PortForwardingRequestReceiver.KEY_ENABLED, enabled);
+        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    private void showPortForwardingNotification(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);
+
+        Notification notification =
+                new Notification.Builder(mContext, mContext.getPackageName())
+                        .setSmallIcon(R.drawable.ic_launcher_foreground)
+                        .setContentTitle(title)
+                        .setContentText(content)
+                        .setContentIntent(tapPendingIntent)
+                        .addAction(
+                                new Notification.Action.Builder(
+                                                icon,
+                                                acceptText,
+                                                getPortForwardingPendingIntent(
+                                                        port, true /* enabled */))
+                                        .build())
+                        .addAction(
+                                new Notification.Action.Builder(
+                                                icon,
+                                                denyText,
+                                                getPortForwardingPendingIntent(
+                                                        port, false /* enabled */))
+                                        .build())
+                        .build();
+        mNotificationManager.notify(TAG, port, notification);
+    }
+
+    private void discardPortForwardingNotification(int port) {
+        mNotificationManager.cancel(TAG, port);
+    }
+
+    private final class PortForwardingRequestReceiver extends BroadcastReceiver {
+        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";
+
+        @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);
+
+            SharedPreferences sharedPref =
+                    context.getSharedPreferences(
+                            context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = sharedPref.edit();
+            editor.putBoolean(
+                    context.getString(R.string.preference_forwarding_port_is_enabled)
+                            + Integer.toString(port),
+                    enabled);
+            editor.apply();
+
+            mNotificationManager.cancel(TAG, port);
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index cd1f65b..652af8e 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -20,14 +20,9 @@
 
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.app.Service;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -56,8 +51,6 @@
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.file.Path;
-import java.util.HashSet;
-import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -82,7 +75,7 @@
     private ResultReceiver mResultReceiver;
     private Server mServer;
     private DebianServiceImpl mDebianService;
-    private PortForwardingRequestReceiver mPortForwardingReceiver;
+    private PortNotifier mPortNotifier;
 
     private static Intent getMyIntent(Context context) {
         return new Intent(context.getApplicationContext(), VmLauncherService.class);
@@ -202,10 +195,7 @@
 
         mResultReceiver.send(RESULT_START, null);
 
-        IntentFilter intentFilter =
-                new IntentFilter(PortForwardingRequestReceiver.ACTION_PORT_FORWARDING);
-        mPortForwardingReceiver = new PortForwardingRequestReceiver();
-        registerReceiver(mPortForwardingReceiver, intentFilter, RECEIVER_NOT_EXPORTED);
+        mPortNotifier = new PortNotifier(this);
         startDebianServer();
 
         return START_NOT_STICKY;
@@ -273,6 +263,11 @@
         mResultReceiver.send(VmLauncherService.RESULT_IPADDR, b);
     }
 
+    @Override
+    public void onActivePortsChanged(Set<String> oldPorts, Set<String> newPorts) {
+        mPortNotifier.onActivePortsChanged(oldPorts, newPorts);
+    }
+
     public static void stop(Context context) {
         Intent i = getMyIntent(context);
         context.stopService(i);
@@ -280,7 +275,7 @@
 
     @Override
     public void onDestroy() {
-        unregisterReceiver(mPortForwardingReceiver);
+        mPortNotifier.stop();
         getSystemService(NotificationManager.class).cancelAll();
         stopDebianServer();
         if (mVirtualMachine != null) {
@@ -307,102 +302,4 @@
             mServer.shutdown();
         }
     }
-
-    @Override
-    public void onActivePortsChanged(Set<String> oldPorts, Set<String> newPorts) {
-        Set<String> union = new HashSet<>(oldPorts);
-        union.addAll(newPorts);
-        for (String portStr : union) {
-            try {
-                if (!oldPorts.contains(portStr)) {
-                    showPortForwardingNotification(Integer.parseInt(portStr));
-                } else if (!newPorts.contains(portStr)) {
-                    discardPortForwardingNotification(Integer.parseInt(portStr));
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Failed to parse port: " + portStr);
-                throw e;
-            }
-        }
-    }
-
-    private PendingIntent getPortForwardingPendingIntent(int port, boolean enabled) {
-        Intent intent = new Intent(PortForwardingRequestReceiver.ACTION_PORT_FORWARDING);
-        intent.setPackage(getPackageName());
-        intent.setIdentifier(String.format(Locale.ROOT, "%d_%b", port, enabled));
-        intent.putExtra(PortForwardingRequestReceiver.KEY_PORT, port);
-        intent.putExtra(PortForwardingRequestReceiver.KEY_ENABLED, enabled);
-        return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
-    }
-
-    private void showPortForwardingNotification(int port) {
-        Intent tapIntent = new Intent(this, SettingsPortForwardingActivity.class);
-        tapIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        PendingIntent tapPendingIntent =
-                PendingIntent.getActivity(this, 0, tapIntent, PendingIntent.FLAG_IMMUTABLE);
-
-        String title = getString(R.string.settings_port_forwarding_notification_title);
-        String content = 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(this, R.drawable.ic_launcher_foreground);
-
-        Notification notification =
-                new Notification.Builder(this, this.getPackageName())
-                        .setSmallIcon(R.drawable.ic_launcher_foreground)
-                        .setContentTitle(title)
-                        .setContentText(content)
-                        .setContentIntent(tapPendingIntent)
-                        .addAction(
-                                new Notification.Action.Builder(
-                                                icon,
-                                                acceptText,
-                                                getPortForwardingPendingIntent(
-                                                        port, true /* enabled */))
-                                        .build())
-                        .addAction(
-                                new Notification.Action.Builder(
-                                                icon,
-                                                denyText,
-                                                getPortForwardingPendingIntent(
-                                                        port, false /* enabled */))
-                                        .build())
-                        .build();
-        getSystemService(NotificationManager.class).notify(TAG, port, notification);
-    }
-
-    private void discardPortForwardingNotification(int port) {
-        getSystemService(NotificationManager.class).cancel(TAG, port);
-    }
-
-    private static final class PortForwardingRequestReceiver extends BroadcastReceiver {
-        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";
-
-        @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);
-
-            SharedPreferences sharedPref =
-                    context.getSharedPreferences(
-                            context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
-            SharedPreferences.Editor editor = sharedPref.edit();
-            editor.putBoolean(
-                    context.getString(R.string.preference_forwarding_port_is_enabled)
-                            + Integer.toString(port),
-                    enabled);
-            editor.apply();
-
-            context.getSystemService(NotificationManager.class).cancel(TAG, port);
-        }
-    }
 }