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/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 124749a..f719749 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -93,6 +93,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Build;
+import android.os.Debug;
 import android.os.DropBoxManager;
 import android.os.HardwarePropertiesManager;
 import android.os.IBatteryPropertiesRegistrar;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index bb844a3..919f4ba 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A request for the user to select a companion device to associate with.
@@ -69,6 +70,20 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        AssociationRequest that = (AssociationRequest) o;
+        return mSingleDevice == that.mSingleDevice &&
+                Objects.equals(mDeviceFilters, that.mDeviceFilters);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSingleDevice, mDeviceFilters);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeByte((byte) (mSingleDevice ? 1 : 0));
         dest.writeParcelableList(mDeviceFilters, flags);
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 1d8df7f..84e1536 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -35,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -123,6 +124,22 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BluetoothDeviceFilter that = (BluetoothDeviceFilter) o;
+        return Objects.equals(mNamePattern, that.mNamePattern) &&
+                Objects.equals(mAddress, that.mAddress) &&
+                Objects.equals(mServiceUuids, that.mServiceUuids) &&
+                Objects.equals(mServiceUuidMasks, that.mServiceUuidMasks);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern, mAddress, mServiceUuids, mServiceUuidMasks);
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java
index e057fbc..0444775 100644
--- a/core/java/android/companion/BluetoothLEDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.java
@@ -36,6 +36,8 @@
 import com.android.internal.util.ObjectUtils;
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -160,9 +162,39 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BluetoothLEDeviceFilter that = (BluetoothLEDeviceFilter) o;
+        return mRenameBytesFrom == that.mRenameBytesFrom &&
+                mRenameBytesTo == that.mRenameBytesTo &&
+                mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
+                Objects.equals(mNamePattern, that.mNamePattern) &&
+                Objects.equals(mScanFilter, that.mScanFilter) &&
+                Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
+                Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
+                Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
+                Objects.equals(mRenameSuffix, that.mRenameSuffix);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
+                mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesTo,
+                mRenameBytesReverseOrder);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(patternToString(getNamePattern()));
         dest.writeParcelable(mScanFilter, flags);
+        dest.writeByteArray(mRawDataFilter);
+        dest.writeByteArray(mRawDataFilterMask);
+        dest.writeString(mRenamePrefix);
+        dest.writeString(mRenameSuffix);
+        dest.writeInt(mRenameBytesFrom);
+        dest.writeInt(mRenameBytesTo);
+        dest.writeBoolean(mRenameBytesReverseOrder);
     }
 
     @Override
@@ -174,13 +206,23 @@
             = new Creator<BluetoothLEDeviceFilter>() {
         @Override
         public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
-            return new BluetoothLEDeviceFilter.Builder()
+            Builder builder = new Builder()
                     .setNamePattern(patternFromString(in.readString()))
-                    .setScanFilter(in.readParcelable(null))
-                    .setRawDataFilter(in.readBlob(), in.readBlob())
-                    .setRename(in.readString(), in.readString(),
-                            in.readInt(), in.readInt(), in.readBoolean())
-                    .build();
+                    .setScanFilter(in.readParcelable(null));
+            byte[] rawDataFilter = in.createByteArray();
+            byte[] rawDataFilterMask = in.createByteArray();
+            if (rawDataFilter != null) {
+                builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
+            }
+            String renamePrefix = in.readString();
+            String suffix = in.readString();
+            int bytesFrom = in.readInt();
+            int bytesTo = in.readInt();
+            boolean bytesReverseOrder = in.readBoolean();
+            if (renamePrefix != null) {
+                builder.setRename(renamePrefix, suffix, bytesFrom, bytesTo, bytesReverseOrder);
+            }
+            return builder.build();
         }
 
         @Override
@@ -240,12 +282,14 @@
          */
         @NonNull
         public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
-                @NonNull byte[] rawDataFilterMask) {
+                @Nullable byte[] rawDataFilterMask) {
             checkNotUsed();
-            checkArgument(rawDataFilter.length == rawDataFilterMask.length,
+            Preconditions.checkNotNull(rawDataFilter);
+            checkArgument(rawDataFilterMask == null ||
+                    rawDataFilter.length == rawDataFilterMask.length,
                     "Mask and filter should be the same length");
-            mRawDataFilter = Preconditions.checkNotNull(rawDataFilter);
-            mRawDataFilterMask = Preconditions.checkNotNull(rawDataFilterMask);
+            mRawDataFilter = rawDataFilter;
+            mRawDataFilterMask = rawDataFilterMask;
             return this;
         }
 
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index ecdc0ce..0d8e365 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -17,6 +17,8 @@
 package android.companion;
 
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -24,7 +26,6 @@
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -43,7 +44,7 @@
  */
 public final class CompanionDeviceManager {
 
-    private static final boolean DEBUG = false; //TODO
+    private static final boolean DEBUG = false;
     private static final String LOG_TAG = "CompanionDeviceManager";
 
     /**
@@ -129,10 +130,9 @@
         if (!checkFeaturePresent()) {
             return;
         }
-
-        final Handler finalHandler = handler != null
-                ? handler
-                : new Handler(Looper.getMainLooper());
+        checkNotNull(request, "Request cannot be null");
+        checkNotNull(callback, "Callback cannot be null");
+        final Handler finalHandler = Handler.mainIfNull(handler);
         try {
             mService.associate(
                     request,
diff --git a/core/java/android/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java
index 1ab9ce1..b6e704c 100644
--- a/core/java/android/companion/WifiDeviceFilter.java
+++ b/core/java/android/companion/WifiDeviceFilter.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.provider.OneTimeUseBuilder;
 
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -75,6 +76,19 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WifiDeviceFilter that = (WifiDeviceFilter) o;
+        return Objects.equals(mNamePattern, that.mNamePattern);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNamePattern);
+    }
+
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(patternToString(getNamePattern()));
     }
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 3c7c962..8678d95 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Log;
 import android.util.Printer;
 
@@ -69,6 +71,7 @@
      */
     private static final boolean FIND_POTENTIAL_LEAKS = false;
     private static final String TAG = "Handler";
+    private static Handler MAIN_THREAD_HANDLER = null;
 
     /**
      * Callback interface you can use when instantiating a Handler to avoid
@@ -231,6 +234,21 @@
         mAsynchronous = async;
     }
 
+    /** @hide */
+    @NonNull
+    public static Handler getMain() {
+        if (MAIN_THREAD_HANDLER == null) {
+            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
+        }
+        return MAIN_THREAD_HANDLER;
+    }
+
+    /** @hide */
+    @NonNull
+    public static Handler mainIfNull(@Nullable Handler handler) {
+        return handler == null ? getMain() : handler;
+    }
+
     /** {@hide} */
     public String getTraceName(Message message) {
         final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index 87231e1..44019c32 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -17,6 +17,7 @@
 package android.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.ParcelableException;
 
 import com.android.internal.util.Preconditions;
@@ -55,10 +56,26 @@
         return getCompleteMessage(null, t);
     }
 
+    public static <E extends Throwable> void propagateIfInstanceOf(
+            @Nullable Throwable t, Class<E> c) throws E {
+        if (t != null && c.isInstance(t)) {
+            throw c.cast(t);
+        }
+    }
+
+    /**
+     * @param <E> a checked exception that is ok to throw without wrapping
+     */
+    public static <E extends Exception> RuntimeException propagate(@NonNull Throwable t, Class<E> c)
+            throws E {
+        propagateIfInstanceOf(t, c);
+        return propagate(t);
+    }
+
     public static RuntimeException propagate(@NonNull Throwable t) {
         Preconditions.checkNotNull(t);
-        if (t instanceof Error) throw (Error)t;
-        if (t instanceof RuntimeException) throw (RuntimeException)t;
+        propagateIfInstanceOf(t, Error.class);
+        propagateIfInstanceOf(t, RuntimeException.class);
         throw new RuntimeException(t);
     }
 }