Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 206d6e44 authored by jovanak's avatar jovanak
Browse files

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
parent 2a9131f7
Loading
Loading
Loading
Loading
+41 −4
Original line number Diff line number Diff line
@@ -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);
    }
+38 −11
Original line number Diff line number Diff line
@@ -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();
+132 −0
Original line number Diff line number Diff line
/*
 * 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
+30 −1
Original line number Diff line number Diff line
@@ -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));
    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -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,
Loading