diff options
6 files changed, 245 insertions, 18 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 7124096e31b8..bc0a1ac847df 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.BroadcastReceiver; 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 @@ public class BluetoothEventManager { 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 @@ public class BluetoothEventManager { 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 @@ public class BluetoothEventManager { } } + @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 @@ public class BluetoothEventManager { } } - 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 7fe6205ed343..53c6075ccff4 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 @@ package com.android.settingslib.bluetooth; 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 @@ public class LocalBluetoothManager { /** 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 @@ public class LocalBluetoothManager { 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 @@ public class LocalBluetoothManager { * 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); + } + + /** + * 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) { - mContext = context; + 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 000000000000..d0ab46a2f30d --- /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 14bfb2798183..6648eddb3cbc 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 @@ public class BluetoothEventManagerTest { 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 6f4c29286c53..235699381bb1 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 @@ public class LocalBluetoothProfileManagerTest { 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 2c821b25f0a5..4afb79d061ad 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.HandlerThread; 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 @@ public class Dependency extends SystemUI { 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)); |