Migration of BridgingManager and BridgingConfig from WSL to AndroidX

Bug: 176894971
Test: Robolectric test
Relnote: "Move BridgingManager and BridgingConfig classes from Wear Support Library to Androidx"
Change-Id: I3a17e9cf1db86b731b96f0ee0cd45b238aa3fcea
diff --git a/wear/wear-phone-interactions/api/current.txt b/wear/wear-phone-interactions/api/current.txt
index f71e090..e73768b 100644
--- a/wear/wear-phone-interactions/api/current.txt
+++ b/wear/wear-phone-interactions/api/current.txt
@@ -16,3 +16,40 @@
 
 }
 
+package androidx.wear.phone.interactions.notifications {
+
+  public final class BridgingConfig {
+    method public java.util.Set<java.lang.String>? getExcludedTags();
+    method public boolean isBridgingEnabled();
+    property public final java.util.Set<java.lang.String>? excludedTags;
+    property public final boolean isBridgingEnabled;
+  }
+
+  public static final class BridgingConfig.Builder {
+    ctor public BridgingConfig.Builder(android.content.Context context, boolean isBridgingEnabled);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig.Builder addExcludedTag(String tag);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig.Builder addExcludedTags(java.util.Collection<java.lang.String> tags);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig build();
+  }
+
+  public interface BridgingConfigurationHandler {
+    method public void applyBridgingConfiguration(androidx.wear.phone.interactions.notifications.BridgingConfig bridgingConfig);
+  }
+
+  public final class BridgingManager {
+    method public static androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
+    method public void setConfig(androidx.wear.phone.interactions.notifications.BridgingConfig bridgingConfig);
+    field public static final androidx.wear.phone.interactions.notifications.BridgingManager.Companion Companion;
+  }
+
+  public static final class BridgingManager.Companion {
+    method public androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
+  }
+
+  public final class BridgingManagerServiceBinder {
+    ctor public BridgingManagerServiceBinder(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
+    method public android.os.IBinder? getBinder();
+  }
+
+}
+
diff --git a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
index f71e090..e73768b 100644
--- a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
+++ b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
@@ -16,3 +16,40 @@
 
 }
 
+package androidx.wear.phone.interactions.notifications {
+
+  public final class BridgingConfig {
+    method public java.util.Set<java.lang.String>? getExcludedTags();
+    method public boolean isBridgingEnabled();
+    property public final java.util.Set<java.lang.String>? excludedTags;
+    property public final boolean isBridgingEnabled;
+  }
+
+  public static final class BridgingConfig.Builder {
+    ctor public BridgingConfig.Builder(android.content.Context context, boolean isBridgingEnabled);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig.Builder addExcludedTag(String tag);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig.Builder addExcludedTags(java.util.Collection<java.lang.String> tags);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig build();
+  }
+
+  public interface BridgingConfigurationHandler {
+    method public void applyBridgingConfiguration(androidx.wear.phone.interactions.notifications.BridgingConfig bridgingConfig);
+  }
+
+  public final class BridgingManager {
+    method public static androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
+    method public void setConfig(androidx.wear.phone.interactions.notifications.BridgingConfig bridgingConfig);
+    field public static final androidx.wear.phone.interactions.notifications.BridgingManager.Companion Companion;
+  }
+
+  public static final class BridgingManager.Companion {
+    method public androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
+  }
+
+  public final class BridgingManagerServiceBinder {
+    ctor public BridgingManagerServiceBinder(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
+    method public android.os.IBinder? getBinder();
+  }
+
+}
+
diff --git a/wear/wear-phone-interactions/api/restricted_current.txt b/wear/wear-phone-interactions/api/restricted_current.txt
index f71e090..e73768b 100644
--- a/wear/wear-phone-interactions/api/restricted_current.txt
+++ b/wear/wear-phone-interactions/api/restricted_current.txt
@@ -16,3 +16,40 @@
 
 }
 
+package androidx.wear.phone.interactions.notifications {
+
+  public final class BridgingConfig {
+    method public java.util.Set<java.lang.String>? getExcludedTags();
+    method public boolean isBridgingEnabled();
+    property public final java.util.Set<java.lang.String>? excludedTags;
+    property public final boolean isBridgingEnabled;
+  }
+
+  public static final class BridgingConfig.Builder {
+    ctor public BridgingConfig.Builder(android.content.Context context, boolean isBridgingEnabled);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig.Builder addExcludedTag(String tag);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig.Builder addExcludedTags(java.util.Collection<java.lang.String> tags);
+    method public androidx.wear.phone.interactions.notifications.BridgingConfig build();
+  }
+
+  public interface BridgingConfigurationHandler {
+    method public void applyBridgingConfiguration(androidx.wear.phone.interactions.notifications.BridgingConfig bridgingConfig);
+  }
+
+  public final class BridgingManager {
+    method public static androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
+    method public void setConfig(androidx.wear.phone.interactions.notifications.BridgingConfig bridgingConfig);
+    field public static final androidx.wear.phone.interactions.notifications.BridgingManager.Companion Companion;
+  }
+
+  public static final class BridgingManager.Companion {
+    method public androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
+  }
+
+  public final class BridgingManagerServiceBinder {
+    ctor public BridgingManagerServiceBinder(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
+    method public android.os.IBinder? getBinder();
+  }
+
+}
+
diff --git a/wear/wear-phone-interactions/build.gradle b/wear/wear-phone-interactions/build.gradle
index d78c282..b6e4126 100644
--- a/wear/wear-phone-interactions/build.gradle
+++ b/wear/wear-phone-interactions/build.gradle
@@ -28,6 +28,7 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
+    api("androidx.core:core:1.5.0-beta01")
     api(KOTLIN_STDLIB)
 
     testImplementation(ANDROIDX_TEST_CORE)
@@ -41,11 +42,15 @@
 
 android {
     defaultConfig {
-        minSdkVersion 23
+        minSdkVersion 25
     }
 
     // Use Robolectric 4.+
     testOptions.unitTests.includeAndroidResources = true
+
+    buildFeatures {
+        aidl = true
+    }
 }
 
 androidx {
diff --git a/wear/wear-phone-interactions/src/main/AndroidManifest.xml b/wear/wear-phone-interactions/src/main/AndroidManifest.xml
index 65f2d51..f111ff0 100644
--- a/wear/wear-phone-interactions/src/main/AndroidManifest.xml
+++ b/wear/wear-phone-interactions/src/main/AndroidManifest.xml
@@ -17,4 +17,4 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="androidx.wear.phone.interactions">
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
new file mode 100644
index 0000000..d887837
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/aidl/android/support/wearable/notifications/IBridgingManagerService.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.wearable.notifications;
+
+/**
+ * Interface of a service that allows setting the notification bridging configuration
+ *
+ * @hide
+ */
+interface IBridgingManagerService {
+    // IMPORTANT NOTE: All methods must be given an explicit transaction id that must never change
+    // in the future to remain binary backwards compatible.
+    // Next Id: 2
+
+    /**
+     * API version number. This should be incremented every time a new method is added.
+     */
+    const int API_VERSION = 1;
+
+    /**
+     * Sets the bridging configuration
+     *
+     * @since API version 1
+     */
+    void setBridgingConfig(in Bundle bridgingConfig) = 0;
+
+    /**
+     * Returns the version number for this API which the client can use to determine which methods
+     * are available.
+     *
+     * @since API version 1.
+     */
+    int getApiVersion() = 1;
+}
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingConfig.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingConfig.kt
new file mode 100644
index 0000000..de2eb7f
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingConfig.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.notifications
+
+import android.content.Context
+import android.os.Bundle
+import java.util.Objects
+import kotlin.collections.ArrayList
+import kotlin.collections.Collection
+import kotlin.collections.HashSet
+import kotlin.collections.MutableSet
+
+/**
+ * Bridging configuration to be specified at runtime, to set tags for notifications that are exempt
+ * from the bridging mode. specifically, create a [BridgingConfig] object, add excluded tags, then
+ * set it with [BridgingManager.setConfig]
+ *
+ * Specifying a bridging configuration at runtime overrides a bridging-related setting in the
+ * Android manifest file.
+ *
+ */
+public class BridgingConfig internal constructor(
+    /** Name of the package of the current context */
+    internal val packageName: String?,
+    /** Whether notification bridging is enabled in the configuration. */
+    public val isBridgingEnabled: Boolean,
+    /**
+     * The set of excluded tags in the configuration. The bridging mode for these tags is the
+     * opposite of the default mode (returned by [.isBridgingEnabled]).
+     */
+    public val excludedTags: MutableSet<String>?
+) {
+    internal companion object {
+        private const val TAG = "BridgingConfig"
+
+        private const val EXTRA_ORIGINAL_PACKAGE =
+            "android.support.wearable.notifications.extra.originalPackage"
+        private const val EXTRA_BRIDGING_ENABLED =
+            "android.support.wearable.notifications.extra.bridgingEnabled"
+        private const val EXTRA_EXCLUDED_TAGS =
+            "android.support.wearable.notifications.extra.excludedTags"
+
+        @JvmStatic
+        internal fun fromBundle(bundle: Bundle): BridgingConfig =
+            BridgingConfig(
+                bundle.getString(EXTRA_ORIGINAL_PACKAGE),
+                bundle.getBoolean(EXTRA_BRIDGING_ENABLED),
+                bundle.getStringArrayList(EXTRA_EXCLUDED_TAGS)?.toSet() as MutableSet<String>?
+            )
+    }
+
+    internal fun toBundle(context: Context): Bundle =
+        Bundle().apply {
+            putString(EXTRA_ORIGINAL_PACKAGE, context.getPackageName())
+            putBoolean(EXTRA_BRIDGING_ENABLED, isBridgingEnabled)
+            putStringArrayList(EXTRA_EXCLUDED_TAGS, excludedTags?.toList() as ArrayList<String>)
+        }
+
+    override fun equals(other: Any?): Boolean {
+        if (other is BridgingConfig) {
+            return other.isBridgingEnabled == isBridgingEnabled and
+                (other.excludedTags == excludedTags) and
+                (other.packageName == packageName)
+        }
+
+        return false
+    }
+
+    override fun hashCode(): Int = Objects.hash(packageName, isBridgingEnabled, excludedTags)
+
+    override fun toString(): String {
+        return "BridgingConfig{packageName='$packageName'" +
+            ", isBridgingEnabled='$isBridgingEnabled'" +
+            ", excludedTags=$excludedTags}"
+    }
+
+    /** Builder for BridgingConfig. */
+    public class Builder(context: Context, private val isBridgingEnabled: Boolean) {
+        private val packageName: String
+        private val excludedTags: MutableSet<String> = HashSet<String>()
+
+        // Initializes new instance of a Builder. By default the set of excluded tags is empty.
+        init {
+            packageName = context.packageName
+        }
+
+        /**
+         * Adds a tag for which the bridging mode is the opposite as the default mode.
+         *
+         * Examples:
+         *
+         * ```
+         * new BridgingConfig.Builder(context, false)  // bridging disabled by default
+         *   .addExcludedTag("foo")
+         *   .addExcludedTag("bar")
+         *   .build());
+         * ```
+         *
+         * ```
+         * new BridgingConfig.Builder(context, true)  // bridging enabled by default
+         *   .addExcludedTag("foo")
+         *   .addExcludedTag("bar")
+         *   .build());
+         * ```
+         *
+         * @param tag The tag to exclude from the default bridging mode.
+         * @return The Builder instance.
+         */
+        public fun addExcludedTag(tag: String): Builder {
+            excludedTags.add(tag)
+            return this
+        }
+
+        /**
+         * Sets a collection of tags for which the bridging mode is the opposite as the default mode.
+         *
+         * Examples:
+         *
+         * ```
+         * new BridgingConfig.Builder(context, false)  // bridging disabled by default
+         *   .addExcludedTags(Arrays.asList("foo", "bar", "baz"))
+         *   .build());
+         *```
+         *
+         * ```
+         * new BridgingConfig.Builder(context, true)  // bridging enabled by default
+         *   .addExcludedTags(Arrays.asList("foo", "bar", "baz"))
+         *   .build());
+         * }
+         * ```
+         *
+         * @param tags The collection of tags to exclude from the default bridging mode.
+         * @return The Builder instance.
+         */
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        // no getter needed for the builder, getter is provided in BridingConfig
+        public fun addExcludedTags(tags: Collection<String>): Builder {
+            excludedTags.addAll(tags)
+            return this
+        }
+
+        /** Builds a BridgingConfig object. */
+        public fun build(): BridgingConfig =
+            BridgingConfig(packageName, isBridgingEnabled, excludedTags)
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManager.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManager.kt
new file mode 100644
index 0000000..7f8b241
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManager.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.notifications
+
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.support.wearable.notifications.IBridgingManagerService
+
+/**
+ * APIs to enable/disable notification bridging at runtime.
+ *
+ *
+ * Using a BridgingManager object, you can set a bridging mode, and optionally set tags for
+ * notifications that are exempt from the bridging mode. Specifically, create a [BridgingConfig]
+ * object and set is as shown in the example usages below:
+ *
+ *
+ *  * Disable bridging at runtime:
+ * ```
+ * BridgingManager.fromContext(context).setConfig(
+ *     new BridgingConfig.Builder(context, false).build()
+ * );
+ * ```
+ *
+ *  * Disable bridging at runtime except for the tags "foo" and "bar":
+ * ```
+ * BridgingManager.fromContext(context).setConfig(
+ *     new BridgingConfig.Builder(context, false)
+ *         .addExcludedTag("foo")
+ *         .addExcludedTag("bar")
+ *         .build()
+ * );
+ * ```
+ *
+ *  * Disable bridging at runtime except for the tags "foo" and "bar" and "baz":
+ * ```
+ * BridgingManager.fromContext(context).setConfig(
+ *     new BridgingConfig.Builder(context, false)
+ *         .addExcludedTags(Arrays.asList("foo", "bar", "baz"))
+ *         .build()
+ *  );
+ * ```
+ *
+ *  * Adding a bridge tag to a notification posted on a phone:
+ * ```
+ * NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
+ * // ... set other fields ...
+ *     .extend(
+ *         new NotificationCompat.WearableExtender()
+ *             .setBridgeTag("foo")
+ *      );
+ * Notification notification = notificationBuilder.build();
+ * ```
+ *
+ * See also:
+ * * [BridgingConfig.Builder.addExcludedTag]
+ * * [BridgingConfig.Builder.addExcludedTags]
+ * * [androidx.core.app.NotificationCompat.WearableExtender.setBridgeTag]
+ */
+public class BridgingManager private constructor(private val context: Context) {
+    /**
+     * Sets the BridgingConfig object.
+     *
+     * @param bridgingConfig The BridgingConfig object.
+     *
+     * @throws RuntimeException if the service binding is failed.
+     */
+    @SuppressLint("SyntheticAccessor")
+    public fun setConfig(bridgingConfig: BridgingConfig) {
+        require(isWearableDevice(context)) { "API only supported on wearable devices" }
+        val connection = BridgingConfigServiceConnection(context, bridgingConfig)
+        val intent = Intent(ACTION_BIND_BRIDGING_MANAGER)
+        intent.setPackage(BRIDGING_CONFIG_SERVICE_PACKAGE)
+        if (!context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
+            context.unbindService(connection)
+            throw RuntimeException("BridgingManager: Failed to bind service")
+        }
+    }
+
+    /** Class responsible for sending the bridge mode to ClockworkHome.  */
+    private class BridgingConfigServiceConnection internal constructor(
+        private val context: Context,
+        bridgingConfig: BridgingConfig
+    ) : ServiceConnection {
+        private val bundle: Bundle = bridgingConfig.toBundle(context)
+
+        override fun onServiceConnected(className: ComponentName, binder: IBinder) {
+            val service: IBridgingManagerService = IBridgingManagerService.Stub.asInterface(binder)
+            try {
+                service.setBridgingConfig(bundle)
+            } catch (e: RemoteException) {
+                throw e.cause!!
+            }
+            context.unbindService(this)
+        }
+
+        override fun onServiceDisconnected(arg0: ComponentName) {
+            context.unbindService(this)
+        }
+    }
+
+    public companion object {
+        private const val ACTION_BIND_BRIDGING_MANAGER =
+            "android.support.wearable.notifications.action.BIND_BRIDGING_MANAGER"
+
+        private const val BRIDGING_CONFIG_SERVICE_PACKAGE = "com.google.android.wearable.app"
+
+        /**
+         * Create a BridgingManager instance with the passed in Context.
+         * @param context Context object.
+         */
+        @JvmStatic
+        public fun fromContext(context: Context): BridgingManager = BridgingManager(context)
+
+        private fun isWearableDevice(context: Context): Boolean =
+            context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerServiceBinder.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerServiceBinder.kt
new file mode 100644
index 0000000..8961404
--- /dev/null
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerServiceBinder.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.notifications
+
+import android.app.Service
+import android.content.Context
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.support.wearable.notifications.IBridgingManagerService
+
+/**
+ * Handler for applying the notification bridging configuration when received by the service.
+ */
+public interface BridgingConfigurationHandler {
+    /**
+     * Apply the notification bridging configurations.
+     * @param bridgingConfig The received bridging configuration
+     */
+    public fun applyBridgingConfiguration(bridgingConfig: BridgingConfig)
+}
+
+/**
+ * Class for providing the binder that clients used to communicate with the service regarding
+ * notification bridging configurations.
+ */
+public class BridgingManagerServiceBinder(
+    private val context: Context,
+    private val bridgingConfigurationHandler: BridgingConfigurationHandler
+) {
+    /**
+     * Call this method in [Service.onBind] to provide the interface that clients
+     * use to communicate with the service by returning an IBinder.
+     */
+    public fun getBinder(): IBinder? =
+        BridgingManagerServiceImpl(context, bridgingConfigurationHandler)
+}
+
+internal class BridgingManagerServiceImpl(
+    private val context: Context,
+    private val bridgingConfigurationHandler: BridgingConfigurationHandler
+) : IBridgingManagerService.Stub() {
+
+    override fun getApiVersion(): Int = IBridgingManagerService.API_VERSION
+
+    override fun setBridgingConfig(bridgingConfigBundle: Bundle) {
+        val bridgingConfig = BridgingConfig.fromBundle(bridgingConfigBundle)
+        val packageName = bridgingConfig.packageName
+        val senderAppPackage: String? =
+            context.packageManager.getNameForUid(Binder.getCallingUid())
+        require(senderAppPackage == packageName) {
+            "Package invalid: $senderAppPackage not equals $packageName"
+        }
+        bridgingConfigurationHandler
+            .applyBridgingConfiguration(bridgingConfig)
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/notifications/BridgingManagerTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/notifications/BridgingManagerTest.kt
new file mode 100644
index 0000000..62ebed5
--- /dev/null
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/notifications/BridgingManagerTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.phone.interactions.notifications
+
+import android.content.Context
+import android.content.pm.PackageManager
+import androidx.wear.phone.interactions.WearPhoneInteractionsTestRunner
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.internal.DoNotInstrument
+import java.util.Arrays
+import java.util.HashSet
+
+/** Unit tests for [BridgingManager].  */
+@RunWith(WearPhoneInteractionsTestRunner::class)
+@DoNotInstrument // Needed because it is defined in the "android" package.
+public class BridgingManagerTest {
+
+    private val mContext: Context = mock(Context::class.java)
+    private val packageManager: PackageManager = mock(PackageManager::class.java)
+
+    init {
+        `when`(mContext.packageName).thenReturn(PACKAGE_NAME)
+        `when`(mContext.packageManager).thenReturn(packageManager)
+        `when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true)
+    }
+
+    @Test
+    public fun disableBridging() {
+        val builder = BridgingConfig.Builder(
+            mContext, false
+        )
+
+        Assert.assertEquals(BridgingConfig(PACKAGE_NAME, false, HashSet()), builder.build())
+    }
+
+    @Test
+    public fun enableBridging() {
+        val builder = BridgingConfig.Builder(
+            mContext, true
+        )
+
+        Assert.assertEquals(BridgingConfig(PACKAGE_NAME, true, HashSet()), builder.build())
+    }
+
+    @Test
+    public fun bridgingEnableByDefault() {
+        val builder = BridgingConfig.Builder(
+            mContext, true
+        )
+
+        Assert.assertTrue(BridgingConfig(PACKAGE_NAME, true, HashSet()).equals(builder.build()))
+    }
+
+    @Test
+    public fun addTagsWithoutSettingBridging() {
+        val builder = BridgingConfig.Builder(
+            mContext, true
+        )
+        builder.addExcludedTag("foo")
+
+        Assert.assertEquals(
+            BridgingConfig(PACKAGE_NAME, true, HashSet(listOf("foo"))),
+            builder.build()
+        )
+    }
+
+    @Test
+    public fun disableBridgingWithTagsInSeparateCalls() {
+        val builder = BridgingConfig.Builder(
+            mContext, false
+        )
+            .addExcludedTag("foo")
+            .addExcludedTag("bar")
+            .addExcludedTag("foo")
+
+        Assert.assertEquals(
+            BridgingConfig(PACKAGE_NAME, false, HashSet(Arrays.asList("foo", "bar"))),
+            builder.build()
+        )
+    }
+
+    @Test
+    public fun disableBridgingWithTagsInOneCall() {
+        val builder = BridgingConfig.Builder(
+            mContext, false
+        )
+            .addExcludedTags(Arrays.asList("foo", "bar", "foo"))
+
+        Assert.assertEquals(
+            BridgingConfig(PACKAGE_NAME, false, HashSet(Arrays.asList("foo", "bar"))),
+            builder.build()
+        )
+    }
+
+    @Test
+    public fun disableBridgingWithTagsInMixOfCalls() {
+        val builder = BridgingConfig.Builder(
+            mContext, false
+        )
+            .addExcludedTag("123")
+            .addExcludedTags(Arrays.asList("foo", "bar", "foo"))
+            .addExcludedTags(Arrays.asList("foo", "bar", "abc"))
+            .addExcludedTag("aaa")
+            .addExcludedTag("foo")
+
+        Assert.assertEquals(
+            BridgingConfig(
+                PACKAGE_NAME, false, HashSet(Arrays.asList("foo", "bar", "123", "aaa", "abc"))
+            ),
+            builder.build()
+        )
+    }
+
+    private companion object {
+        private const val PACKAGE_NAME = "foo_package"
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-phone-interactions/src/test/resources/robolectric.properties b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
index 185e1de..c0066c1 100644
--- a/wear/wear-phone-interactions/src/test/resources/robolectric.properties
+++ b/wear/wear-phone-interactions/src/test/resources/robolectric.properties
@@ -14,7 +14,7 @@
 # limitations under the License.
 #
 
-# Robolectric currently doesn't support API 30, so we have to explicitly specify 28 as the target
-# sdk for now.
-# TODO(b/177072877) Remove when no longer necessary.
-sdk=28
\ No newline at end of file
+# Robolectric currently doesn't support API 30, so we have to explicitly the target sdk levels for
+# now.
+# TODO(b/177072877): Remove when no longer necessary.
+sdk=25,26,27,28