Unbind from service on app killed and dedup association requests

1. Listen to calling package binder death stopping the scanning on that.
2. Don't restart scanning when a request with the same values was made.

Bug: 30932767
Test: 1.:
  - Using a test app start scanning and kill the app.
  - In debug mode ensure that DeviceDiscoveryService#stopScan gets triggered
2.:
  - Start scanning and rotate the device while device chooser is visible
  - Ensure no visible loss of state is happening
Change-Id: If126a2c963da90172a956fbd88e6b3dcd7ac8b01
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9ed7c93..2165fe5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -307,7 +307,7 @@
 
             // In case the runtime switched since last boot (such as when
             // the old runtime was removed in an OTA), set the system
-            // property so that it is in sync. We can't do this in
+            // property so that it is in sync. We can | xq oqi't do this in
             // libnativehelper's JniInvocation::Init code where we already
             // had to fallback to a different runtime because it is
             // running as root and we need to be the system user to set
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index 9356dac..7790698 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.Manifest;
+import android.annotation.CheckResult;
 import android.annotation.Nullable;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -36,8 +37,11 @@
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
+import android.os.IInterface;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -68,11 +72,13 @@
 import java.util.function.Function;
 
 //TODO move to own package!
-//TODO un/linkToDeath & onBinderDied - unbind
 //TODO onStop schedule unbind in 5 seconds
-//TODO Prune association on app uninstall
+//TODO make sure APIs are only callable from currently focused app
+//TODO schedule stopScan on activity destroy(except if configuration change)
+//TODO on associate called again after configuration change -> replace old callback with new
+//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
 /** @hide */
-public class CompanionDeviceManagerService extends SystemService {
+public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
 
     private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
             CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
@@ -90,6 +96,8 @@
     private final CompanionDeviceManagerImpl mImpl;
     private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
     private IDeviceIdleController mIdleController;
+    private IFindDeviceCallback mFindDeviceCallback;
+    private ServiceConnection mServiceConnection;
 
     public CompanionDeviceManagerService(Context context) {
         super(context);
@@ -125,7 +133,51 @@
         publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
     }
 
+    @Override
+    public void binderDied() {
+        Handler.getMain().post(this::handleBinderDied);
+    }
+
+    private void handleBinderDied() {
+        mServiceConnection = unbind(mServiceConnection);
+        mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
+    }
+
+    /**
+     * Usage: {@code a = unlinkToDeath(a, deathRecipient, flags); }
+     */
+    @Nullable
+    @CheckResult
+    private static <T extends IInterface> T unlinkToDeath(T iinterface,
+            IBinder.DeathRecipient deathRecipient, int flags) {
+        if (iinterface != null) {
+            iinterface.asBinder().unlinkToDeath(deathRecipient, flags);
+        }
+        return null;
+    }
+
+    @Nullable
+    @CheckResult
+    private ServiceConnection unbind(@Nullable ServiceConnection conn) {
+        if (conn != null) {
+            getContext().unbindService(conn);
+        }
+        return null;
+    }
+
     class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+
+        @Override
+        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            try {
+                return super.onTransact(code, data, reply, flags);
+            } catch (Throwable e) {
+                Slog.e(LOG_TAG, "Error during IPC", e);
+                throw ExceptionUtils.propagate(e, RemoteException.class);
+            }
+        }
+
         @Override
         public void associate(
                 AssociationRequest request,
@@ -135,14 +187,14 @@
                 Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
                         + ", callingPackage = " + callingPackage + ")");
             }
-            checkNotNull(request);
-            checkNotNull(callback);
+            checkNotNull(request, "Request cannot be null");
+            checkNotNull(callback, "Callback cannot be null");
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 //TODO bindServiceAsUser
                 getContext().bindService(
                         new Intent().setComponent(SERVICE_TO_BIND_TO),
-                        getServiceConnection(request, callback, callingPackage),
+                        createServiceConnection(request, callback, callingPackage),
                         Context.BIND_AUTO_CREATE);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
@@ -168,11 +220,11 @@
         return UserHandle.getUserId(Binder.getCallingUid());
     }
 
-    private ServiceConnection getServiceConnection(
+    private ServiceConnection createServiceConnection(
             final AssociationRequest request,
             final IFindDeviceCallback findDeviceCallback,
             final String callingPackage) {
-        return new ServiceConnection() {
+        mServiceConnection = new ServiceConnection() {
             @Override
             public void onServiceConnected(ComponentName name, IBinder service) {
                 if (DEBUG) {
@@ -180,6 +232,14 @@
                             "onServiceConnected(name = " + name + ", service = "
                                     + service + ")");
                 }
+                mFindDeviceCallback = findDeviceCallback;
+                try {
+                    mFindDeviceCallback.asBinder().linkToDeath(
+                            CompanionDeviceManagerService.this, 0);
+                } catch (RemoteException e) {
+                    handleBinderDied();
+                    return;
+                }
                 try {
                     ICompanionDeviceDiscoveryService.Stub
                             .asInterface(service)
@@ -198,6 +258,7 @@
                 if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
             }
         };
+        return mServiceConnection;
     }
 
     private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {