Implement notification for port forwarding
Bug: 340126051
Bug: 376826982
Test: Run any server listening tcp port in the VM
Change-Id: I5fd4443330a497c7c56da6fd49b47d8c18a46587
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
index 0b65cf6..1b2ce8c 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
@@ -38,14 +38,11 @@
final class DebianServiceImpl extends DebianServiceGrpc.DebianServiceImplBase {
public static final String TAG = "DebianService";
- private static final String PREFERENCE_FILE_KEY =
- "com.android.virtualization.terminal.PREFERENCE_FILE_KEY";
- private static final String PREFERENCE_FORWARDING_PORTS = "PREFERENCE_FORWARDING_PORTS";
- private static final String PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX =
- "PREFERENCE_FORWARDING_PORT_IS_ENABLED_";
private final Context mContext;
private final SharedPreferences mSharedPref;
+ private final String mPreferenceForwardingPorts;
+ private final String mPreferenceForwardingPortIsEnabled;
private SharedPreferences.OnSharedPreferenceChangeListener mPortForwardingListener;
private final DebianServiceCallback mCallback;
@@ -57,7 +54,12 @@
super();
mCallback = callback;
mContext = context;
- mSharedPref = mContext.getSharedPreferences(PREFERENCE_FILE_KEY, Context.MODE_PRIVATE);
+ mSharedPref =
+ mContext.getSharedPreferences(
+ mContext.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+ mPreferenceForwardingPorts = mContext.getString(R.string.preference_forwarding_ports);
+ mPreferenceForwardingPortIsEnabled =
+ mContext.getString(R.string.preference_forwarding_port_is_enabled);
}
@Override
@@ -66,19 +68,21 @@
StreamObserver<ReportVmActivePortsResponse> responseObserver) {
Log.d(DebianServiceImpl.TAG, "reportVmActivePorts: " + request.toString());
+ Set<String> prevPorts =
+ mSharedPref.getStringSet(mPreferenceForwardingPorts, Collections.emptySet());
SharedPreferences.Editor editor = mSharedPref.edit();
Set<String> ports = new HashSet<>();
for (int port : request.getPortsList()) {
ports.add(Integer.toString(port));
if (!mSharedPref.contains(
- PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX + Integer.toString(port))) {
+ mPreferenceForwardingPortIsEnabled + Integer.toString(port))) {
editor.putBoolean(
- PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX + Integer.toString(port),
- false);
+ mPreferenceForwardingPortIsEnabled + Integer.toString(port), false);
}
}
- editor.putStringSet(PREFERENCE_FORWARDING_PORTS, ports);
+ editor.putStringSet(mPreferenceForwardingPorts, ports);
editor.apply();
+ mCallback.onActivePortsChanged(prevPorts, ports);
ReportVmActivePortsResponse reply =
ReportVmActivePortsResponse.newBuilder().setSuccess(true).build();
@@ -105,8 +109,8 @@
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
- if (key.startsWith(PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX)
- || key.equals(PREFERENCE_FORWARDING_PORTS)) {
+ if (key.startsWith(mPreferenceForwardingPortIsEnabled)
+ || key.equals(mPreferenceForwardingPorts)) {
updateListeningPorts();
}
}
@@ -152,13 +156,12 @@
private void updateListeningPorts() {
updateListeningPorts(
mSharedPref
- .getStringSet(PREFERENCE_FORWARDING_PORTS, Collections.emptySet())
+ .getStringSet(mPreferenceForwardingPorts, Collections.emptySet())
.stream()
.filter(
port ->
mSharedPref.getBoolean(
- PREFERENCE_FORWARDING_PORT_IS_ENABLED_PREFIX + port,
- false))
+ mPreferenceForwardingPortIsEnabled + port, false))
.map(Integer::valueOf)
.mapToInt(Integer::intValue)
.toArray());
@@ -166,5 +169,7 @@
protected interface DebianServiceCallback {
void onIpAddressAvailable(String ipAddr);
+
+ void onActivePortsChanged(Set<String> oldPorts, Set<String> newPorts);
}
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
index a1509ad..32df273 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -62,48 +62,5 @@
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,
- 8080
- )
- )
- .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/java/com/android/virtualization/terminal/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index 25afcb7..1c00c8d 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -17,8 +17,15 @@
package com.android.virtualization.terminal;
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.IBinder;
import android.os.ResultReceiver;
@@ -44,13 +51,15 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Path;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
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";
+ static final String TAG = "VmLauncherService";
private static final int RESULT_START = 0;
private static final int RESULT_STOP = 1;
@@ -63,6 +72,7 @@
private ResultReceiver mResultReceiver;
private Server mServer;
private DebianServiceImpl mDebianService;
+ private PortForwardingRequestReceiver mPortForwardingReceiver;
@Override
public IBinder onBind(Intent intent) {
@@ -130,6 +140,10 @@
mResultReceiver.send(RESULT_START, null);
+ IntentFilter intentFilter =
+ new IntentFilter(PortForwardingRequestReceiver.ACTION_PORT_FORWARDING);
+ mPortForwardingReceiver = new PortForwardingRequestReceiver();
+ registerReceiver(mPortForwardingReceiver, intentFilter, RECEIVER_NOT_EXPORTED);
startDebianServer();
return START_NOT_STICKY;
@@ -137,7 +151,8 @@
@Override
public void onDestroy() {
- super.onDestroy();
+ unregisterReceiver(mPortForwardingReceiver);
+ getSystemService(NotificationManager.class).cancelAll();
stopDebianServer();
if (mVirtualMachine != null) {
if (mVirtualMachine.getStatus() == VirtualMachine.STATUS_RUNNING) {
@@ -152,6 +167,7 @@
mExecutorService = null;
mVirtualMachine = null;
}
+ super.onDestroy();
}
private void startDebianServer() {
@@ -224,4 +240,102 @@
b.putString(VmLauncherService.KEY_VM_IP_ADDR, ipAddr);
mResultReceiver.send(VmLauncherService.RESULT_IPADDR, b);
}
+
+ @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("%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 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(VmLauncherService.TAG, port);
+ }
+ }
}
diff --git a/android/forwarder_host/src/forwarder_host.rs b/android/forwarder_host/src/forwarder_host.rs
index 2138957..ba427f5 100644
--- a/android/forwarder_host/src/forwarder_host.rs
+++ b/android/forwarder_host/src/forwarder_host.rs
@@ -384,6 +384,10 @@
cid: jint,
callback: JObject,
) {
+ // Clear shutdown event FD before running forwarder host.
+ SHUTDOWN_EVT.write(1).expect("Failed to write shutdown event FD");
+ SHUTDOWN_EVT.read().expect("Failed to consume shutdown event FD");
+
match run_forwarder_host(cid, env, callback) {
Ok(_) => {
info!("forwarder_host is terminated");