Makes LocalBluetoothManager multi-user aware.

It enables LocalBluetoothManager to receive broadcasts for a specific
UserHandle, not just the one that created it, by exposing an additional create
method.

To pass in UserHandle different from the one returned in context.getUser(),
one must have INTERACT_ACROSS_USERS_FULL permission.

Should be used by singletons with adequate permissions to be able to monitor
bluetooth state across all users.

For monitoring the state across all users, pass in UserHandle.ALL.

Change-Id: Id89d73b05bfebc2f9e8673c5610b3ff8f70dba0c
Fixes: 117517726
Test: working on them
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 7124096..bc0a1ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -26,9 +26,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.settingslib.R;
 
 import java.util.ArrayList;
@@ -54,21 +58,32 @@
     private final BroadcastReceiver mProfileBroadcastReceiver = new BluetoothBroadcastReceiver();
     private final Collection<BluetoothCallback> mCallbacks = new ArrayList<>();
     private final android.os.Handler mReceiverHandler;
+    private final UserHandle mUserHandle;
     private final Context mContext;
 
     interface Handler {
         void onReceive(Context context, Intent intent, BluetoothDevice device);
     }
 
+    /**
+     * Creates BluetoothEventManager with the ability to pass in {@link UserHandle} that tells it to
+     * listen for bluetooth events for that particular userHandle.
+     *
+     * <p> If passing in userHandle that's different from the user running the process,
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission is required. If
+     * userHandle passed in is {@code null}, we register event receiver for the
+     * {@code context.getUser()} handle.
+     */
     BluetoothEventManager(LocalBluetoothAdapter adapter,
             CachedBluetoothDeviceManager deviceManager, Context context,
-            android.os.Handler handler) {
+            android.os.Handler handler, @Nullable UserHandle userHandle) {
         mLocalAdapter = adapter;
         mDeviceManager = deviceManager;
         mAdapterIntentFilter = new IntentFilter();
         mProfileIntentFilter = new IntentFilter();
         mHandlerMap = new HashMap<>();
         mContext = context;
+        mUserHandle = userHandle;
         mReceiverHandler = handler;
 
         // Bluetooth on/off broadcasts
@@ -104,7 +119,7 @@
         addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
                 new AudioModeChangedHandler());
 
-        mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
+        registerAdapterIntentReceiver();
     }
 
     /** Register to start receiving callbacks for Bluetooth events. */
@@ -121,10 +136,31 @@
         }
     }
 
+    @VisibleForTesting
     void registerProfileIntentReceiver() {
-        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
+        registerIntentReceiver(mProfileBroadcastReceiver, mProfileIntentFilter);
     }
 
+    @VisibleForTesting
+    void registerAdapterIntentReceiver() {
+        registerIntentReceiver(mBroadcastReceiver, mAdapterIntentFilter);
+    }
+
+    /**
+     * Registers the provided receiver to receive the broadcasts that correspond to the
+     * passed intent filter, in the context of the provided handler.
+     */
+    private void registerIntentReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        if (mUserHandle == null) {
+            // If userHandle has not been provided, simply call registerReceiver.
+            mContext.registerReceiver(receiver, filter, null, mReceiverHandler);
+        } else {
+            // userHandle was explicitly specified, so need to call multi-user aware API.
+            mContext.registerReceiverAsUser(receiver, mUserHandle, filter, null, mReceiverHandler);
+        }
+    }
+
+    @VisibleForTesting
     void addProfileHandler(String action, Handler handler) {
         mHandlerMap.put(action, handler);
         mProfileIntentFilter.addAction(action);
@@ -201,7 +237,8 @@
         }
     }
 
-    private void addHandler(String action, Handler handler) {
+    @VisibleForTesting
+    void addHandler(String action, Handler handler) {
         mHandlerMap.put(action, handler);
         mAdapterIntentFilter.addAction(action);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 7fe6205..53c6075 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -18,10 +18,14 @@
 
 import android.content.Context;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresPermission;
+
 /**
  * LocalBluetoothManager provides a simplified interface on top of a subset of
  * the Bluetooth API. Note that {@link #getInstance} will return null
@@ -49,6 +53,7 @@
     /** The broadcast receiver event manager. */
     private final BluetoothEventManager mEventManager;
 
+    @Nullable
     public static synchronized LocalBluetoothManager getInstance(Context context,
             BluetoothManagerCallback onInitCallback) {
         if (sInstance == null) {
@@ -57,10 +62,11 @@
                 return null;
             }
             // This will be around as long as this process is
-            Context appContext = context.getApplicationContext();
-            sInstance = new LocalBluetoothManager(adapter, appContext, null);
+            sInstance = new LocalBluetoothManager(adapter, context, /* handler= */ null,
+                    /* userHandle= */ null);
             if (onInitCallback != null) {
-                onInitCallback.onBluetoothManagerInitialized(appContext, sInstance);
+                onInitCallback.onBluetoothManagerInitialized(context.getApplicationContext(),
+                        sInstance);
             }
         }
 
@@ -71,22 +77,43 @@
      * Returns a new instance of {@link LocalBluetoothManager} or null if Bluetooth is not
      * supported for this hardware. This instance should be globally cached by the caller.
      */
+    @Nullable
     public static LocalBluetoothManager create(Context context, Handler handler) {
         LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
         if (adapter == null) {
             return null;
         }
-        return new LocalBluetoothManager(adapter, context.getApplicationContext(), handler);
+        return new LocalBluetoothManager(adapter, context, handler, /* userHandle= */ null);
     }
 
-    private LocalBluetoothManager(
-            LocalBluetoothAdapter adapter, Context context, Handler handler) {
-        mContext = context;
+    /**
+     * Returns a new instance of {@link LocalBluetoothManager} or null if Bluetooth is not
+     * supported for this hardware. This instance should be globally cached by the caller.
+     *
+     * <p> Allows to specify a {@link UserHandle} for which to receive bluetooth events.
+     *
+     * <p> Requires {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+     */
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    public static LocalBluetoothManager create(Context context, Handler handler,
+            UserHandle userHandle) {
+        LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
+        if (adapter == null) {
+            return null;
+        }
+        return new LocalBluetoothManager(adapter, context, handler,
+                userHandle);
+    }
+
+    private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context, Handler handler,
+            UserHandle userHandle) {
+        mContext = context.getApplicationContext();
         mLocalAdapter = adapter;
-        mCachedDeviceManager = new CachedBluetoothDeviceManager(context, this);
-        mEventManager = new BluetoothEventManager(mLocalAdapter,
-                mCachedDeviceManager, context, handler);
-        mProfileManager = new LocalBluetoothProfileManager(context,
+        mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, this);
+        mEventManager = new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mContext,
+                handler, userHandle);
+        mProfileManager = new LocalBluetoothProfileManager(mContext,
                 mLocalAdapter, mCachedDeviceManager, mEventManager);
 
         mProfileManager.updateLocalProfiles();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java
new file mode 100644
index 0000000..d0ab46a
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 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 com.android.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Test that verifies that BluetoothEventManager can receive broadcasts for non-current
+ * users for all bluetooth events.
+ *
+ * <p>Creation and deletion of users takes a long time, so marking this as a LargeTest.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothEventManagerIntegTest {
+    private static final int LATCH_TIMEOUT = 4;
+
+    private Context mContext;
+    private UserManager mUserManager;
+    private BluetoothEventManager mBluetoothEventManager;
+
+    private UserInfo mOtherUser;
+    private final Intent mTestIntent = new Intent("Test intent");
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUserManager = UserManager.get(mContext);
+
+        mBluetoothEventManager = new BluetoothEventManager(
+                mock(LocalBluetoothAdapter.class), mock(CachedBluetoothDeviceManager.class),
+                mContext, /* handler= */ null, UserHandle.ALL);
+
+        // Create and start another user in the background.
+        mOtherUser = mUserManager.createUser("TestUser", /* flags= */ 0);
+        try {
+            ActivityManager.getService().startUserInBackground(mOtherUser.id);
+        } catch (RemoteException e) {
+            fail("Count't create an additional user.");
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (mOtherUser != null) {
+            mUserManager.removeUser(mOtherUser.id);
+        }
+    }
+
+    /**
+     * Verify that MultiUserAwareBluetoothEventManager's adapter receiver handles events coming from
+     * users other than current user.
+     */
+    @Test
+    public void registerAdapterReceiver_ifIntentFromAnotherUser_broadcastIsReceived()
+            throws Exception {
+        // Create a latch to listen for the intent.
+        final CountDownLatch broadcastLatch = new CountDownLatch(1);
+
+        // Register adapter receiver.
+        mBluetoothEventManager.addHandler(mTestIntent.getAction(),
+                (context, intent, device) -> broadcastLatch.countDown());
+        mBluetoothEventManager.registerAdapterIntentReceiver();
+
+        // Send broadcast from another user.
+        mContext.sendBroadcastAsUser(mTestIntent, mOtherUser.getUserHandle());
+
+        // Wait to receive it.
+        assertTrue(broadcastLatch.await(LATCH_TIMEOUT, SECONDS));
+    }
+
+    /**
+     * Verify that MultiUserAwareBluetoothEventManager's profile receiver handles events coming from
+     * users other than current user.
+     */
+    @Test
+    public void registerProfileReceiver_ifIntentFromAnotherUser_broadcastIsReceived()
+            throws Exception {
+        // Create a latch to listen for the intent.
+        final CountDownLatch broadcastLatch = new CountDownLatch(1);
+
+        // Register profile receiver.
+        mBluetoothEventManager.addProfileHandler(mTestIntent.getAction(),
+                (context, intent, device) -> broadcastLatch.countDown());
+        mBluetoothEventManager.registerProfileIntentReceiver();
+
+        // Send broadcast from another user.
+        mContext.sendBroadcastAsUser(mTestIntent, mOtherUser.getUserHandle());
+
+        // Wait to receive it.
+        assertTrue(broadcastLatch.await(LATCH_TIMEOUT, SECONDS));
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 14bfb27..6648edd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -15,12 +15,19 @@
  */
 package com.android.settingslib.bluetooth;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
 import com.android.settingslib.SettingsLibRobolectricTestRunner;
@@ -54,7 +61,29 @@
         mContext = RuntimeEnvironment.application;
 
         mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter,
-                mCachedDeviceManager, mContext, null);
+                mCachedDeviceManager, mContext, /* handler= */ null, /* userHandle= */ null);
+    }
+
+    @Test
+    public void ifUserHandleIsNull_registerReceiverIsCalled() {
+        Context mockContext = mock(Context.class);
+        BluetoothEventManager eventManager =
+                new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mockContext,
+                        /* handler= */ null, /* userHandle= */ null);
+
+        verify(mockContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
+                eq(null), eq(null));
+    }
+
+    @Test
+    public void ifUserHandleSpecified_registerReceiverAsUserIsCalled() {
+        Context mockContext = mock(Context.class);
+        BluetoothEventManager eventManager =
+                new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mockContext,
+                        /* handler= */ null, UserHandle.ALL);
+
+        verify(mockContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq(UserHandle.ALL),
+                any(IntentFilter.class), eq(null), eq(null));
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
index 6f4c292..2356993 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
@@ -76,7 +76,7 @@
         mContext = spy(RuntimeEnvironment.application);
         mLocalBluetoothAdapter = LocalBluetoothAdapter.getInstance();
         mEventManager = spy(new BluetoothEventManager(mLocalBluetoothAdapter, mDeviceManager,
-                mContext, null));
+                mContext, /* handler= */ null, /* userHandle= */ null));
         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
         when(mDeviceManager.findDevice(mDevice)).thenReturn(mCachedBluetoothDevice);
         mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter,
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 2c821b2..4afb79d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -22,6 +22,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.view.IWindowManager;
@@ -295,7 +296,8 @@
                 new PluginDependencyProvider(get(PluginManager.class)));
 
         mProviders.put(LocalBluetoothManager.class, () ->
-                LocalBluetoothManager.create(mContext, getDependency(BG_HANDLER)));
+                LocalBluetoothManager.create(mContext, getDependency(BG_HANDLER),
+                        UserHandle.ALL));
 
         mProviders.put(VolumeDialogController.class, () ->
                 new VolumeDialogControllerImpl(mContext));