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

Commit f95d6a17 authored by Jovana Knezevic's avatar Jovana Knezevic Committed by Android (Google) Code Review
Browse files

Merge "Makes LocalBluetoothManager multi-user aware."

parents ffe32260 206d6e44
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);
@@ -200,7 +236,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
@@ -78,7 +78,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);
        when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
Loading