Convert Port forwarding code to kotlin
There would be following-up CLs to refactor it
Bug: 383243644
Test: check port forwarding behavior
Change-Id: Iff96aa1ff31a9ba6e8ad5f042d0b62a45a65517a
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..ed6e02e
--- /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 java.util.HashSet
+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>) {
+ val union: MutableSet<Int> = HashSet<Int>(oldActivePorts)
+ union.addAll(newActivePorts)
+ for (port in union) {
+ if (!oldActivePorts.contains(port)) {
+ showNotificationFor(port)
+ } else if (!newActivePorts.contains(port)) {
+ discardNotificationFor(port)
+ }
+ }
+ }
+ }
+ 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)
+ 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)
+ .setContentIntent(tapPendingIntent)
+ .addAction(acceptAction)
+ .addAction(denyAction)
+ .build()
+ notificationManager.notify(MainActivity.TAG, port, notification)
+ }
+
+ private fun discardNotificationFor(port: Int) {
+ notificationManager.cancel(MainActivity.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..736176a
--- /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 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 var activePorts: MutableSet<Int> = hashSetOf()
+
+ @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(): MutableSet<Int> {
+ synchronized(lock) {
+ return HashSet<Int>(activePorts)
+ }
+ }
+
+ fun getEnabledPorts(): MutableSet<Int> {
+ synchronized(lock) {
+ return HashSet<Int>(enabledPorts)
+ }
+ }
+
+ fun updateActivePorts(ports: MutableSet<Int>) {
+ var oldPorts: MutableSet<Int>
+ synchronized(lock) {
+ oldPorts = activePorts
+ activePorts = ports
+ }
+ notifyPortsStateUpdated(oldPorts, ports)
+ }
+
+ fun updateEnabledPort(port: Int, enabled: Boolean) {
+ var activePorts: MutableSet<Int>
+ 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)
+ }
+ activePorts = this@PortsStateManager.activePorts
+ }
+ notifyPortsStateUpdated(activePorts, activePorts)
+ }
+
+ fun clearEnabledPorts() {
+ var activePorts: MutableSet<Int>
+ synchronized(lock) {
+ val editor = sharedPref.edit()
+ editor.clear()
+ editor.apply()
+ enabledPorts.clear()
+ activePorts = this@PortsStateManager.activePorts
+ }
+ notifyPortsStateUpdated(activePorts, activePorts)
+ }
+
+ fun registerListener(listener: Listener) {
+ synchronized(lock) { listeners.add(listener) }
+ }
+
+ fun unregisterListener(listener: Listener) {
+ synchronized(lock) { listeners.remove(listener) }
+ }
+
+ private fun notifyPortsStateUpdated(
+ oldActivePorts: MutableSet<Int>,
+ newActivePorts: MutableSet<Int>,
+ ) {
+ var listeners: MutableSet<Listener>
+ synchronized(lock) { listeners = HashSet<Listener>(this@PortsStateManager.listeners) }
+ for (listener in listeners) {
+ listener.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
+
+ @JvmStatic
+ @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!!
+ }
+ }
+}