Notify VS when users or apps are removed

Register for the relevant broadcasts, and when we receive them pass
them on to VirtualizationService.

Bug: 294177871
Test: Manual; add/remove packages, users, see log messages
Change-Id: I154f620a04f8a1afa7c34baeb4ecf10a19374dd0
diff --git a/java/service/Android.bp b/java/service/Android.bp
index fdfb203..8bac7be 100644
--- a/java/service/Android.bp
+++ b/java/service/Android.bp
@@ -29,6 +29,9 @@
         "framework",
         "services.core",
     ],
+    static_libs: [
+        "android.system.virtualizationmaintenance-java",
+    ],
     sdk_version: "core_platform",
     apex_available: ["com.android.virt"],
     installable: true,
diff --git a/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java b/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java
index 2905acd..3f973b4 100644
--- a/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java
+++ b/java/service/src/com/android/system/virtualmachine/VirtualizationSystemService.java
@@ -16,16 +16,121 @@
 
 package com.android.system.virtualmachine;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.system.virtualizationmaintenance.IVirtualizationMaintenance;
+import android.util.Log;
+
+import com.android.internal.os.BackgroundThread;
 import com.android.server.SystemService;
 
-/** TODO */
+/**
+ * This class exists to notify virtualization service of relevant things happening in the Android
+ * framework.
+ *
+ * <p>It currently is responsible for Secretkeeper-related maintenance - ensuring that we are not
+ * storing secrets for apps or users that no longer exist.
+ */
 public class VirtualizationSystemService extends SystemService {
+    private static final String TAG = VirtualizationSystemService.class.getName();
+    private static final String SERVICE_NAME = "android.system.virtualizationmaintenance";
+    private Handler mHandler;
 
     public VirtualizationSystemService(Context context) {
         super(context);
     }
 
     @Override
-    public void onStart() {}
+    public void onStart() {
+        // Nothing needed here - we don't expose any binder service. The binder service we use is
+        // exposed as a lazy service by the virtualizationservice native binary.
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase != PHASE_BOOT_COMPLETED) return;
+
+        mHandler = BackgroundThread.getHandler();
+        new Receiver().registerForBroadcasts();
+    }
+
+    private void notifyAppRemoved(int uid) {
+        try {
+            IVirtualizationMaintenance maintenance = connectToMaintenanceService();
+            maintenance.appRemoved(UserHandle.getUserId(uid), UserHandle.getAppId(uid));
+        } catch (Exception e) {
+            Log.e(TAG, "notifyAppRemoved failed", e);
+        }
+    }
+
+    private void notifyUserRemoved(int userId) {
+        try {
+            IVirtualizationMaintenance maintenance = connectToMaintenanceService();
+            maintenance.userRemoved(userId);
+        } catch (Exception e) {
+            Log.e(TAG, "notifyUserRemoved failed", e);
+        }
+    }
+
+    private static IVirtualizationMaintenance connectToMaintenanceService() {
+        IBinder binder = ServiceManager.waitForService(SERVICE_NAME);
+        IVirtualizationMaintenance maintenance =
+                IVirtualizationMaintenance.Stub.asInterface(binder);
+        if (maintenance == null) {
+            throw new IllegalStateException("Failed to connect to " + SERVICE_NAME);
+        }
+        return maintenance;
+    }
+
+    private class Receiver extends BroadcastReceiver {
+        public void registerForBroadcasts() {
+            Context allUsers = getContext().createContextAsUser(UserHandle.ALL, 0 /* flags */);
+
+            allUsers.registerReceiver(this, new IntentFilter(Intent.ACTION_USER_REMOVED));
+
+            IntentFilter packageFilter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+            packageFilter.addDataScheme("package");
+            allUsers.registerReceiver(this, packageFilter);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_USER_REMOVED:
+                    onUserRemoved(intent);
+                    break;
+                case Intent.ACTION_PACKAGE_REMOVED:
+                    onPackageRemoved(intent);
+                    break;
+                default:
+                    Log.e(TAG, "received unexpected intent: " + intent.getAction());
+                    break;
+            }
+        }
+
+        private void onUserRemoved(Intent intent) {
+            int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId != UserHandle.USER_NULL) {
+                mHandler.post(() -> notifyUserRemoved(userId));
+            }
+        }
+
+        private void onPackageRemoved(Intent intent) {
+            if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
+                    || !intent.getBooleanExtra(Intent.EXTRA_DATA_REMOVED, false)) {
+                // Package is being updated rather than uninstalled.
+                return;
+            }
+            int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+            if (uid != -1) {
+                mHandler.post(() -> notifyAppRemoved(uid));
+            }
+        }
+    }
 }