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);
}