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

Commit eec5790f authored by android-build-team Robot's avatar android-build-team Robot
Browse files

Snap for 4745538 from 3b1e61d6 to pi-release

Change-Id: I63d415c2d6d8931d7ce73a6811586080d892a0c4
parents fb67388e 3b1e61d6
Loading
Loading
Loading
Loading
+68 −88
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ import java.util.Objects;

/**
 * The active device manager is responsible for keeping track of the
 * connected A2DP/HFP/AVRCP devices and select which device is
 * connected A2DP/HFP/AVRCP/HearingAid devices and select which device is
 * active (for each profile).
 *
 * Current policy (subject to change):
@@ -52,36 +52,30 @@ import java.util.Objects;
 *    devices is more than one, the rules below will apply.
 * 2) The selected A2DP active device is the one used for AVRCP as well.
 * 3) The HFP active device might be different from the A2DP active device.
 * 4) The Active Device Manager always listens for
 *    ACTION_ACTIVE_DEVICE_CHANGED broadcasts for each profile:
 * 4) The Active Device Manager always listens for ACTION_ACTIVE_DEVICE_CHANGED
 *    broadcasts for each profile:
 *    - BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED for A2DP
 *    - BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED for HFP
 *    - BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED for HearingAid
 *    If such broadcast is received (e.g., triggered indirectly by user
 *    action on the UI), the device in the received broacast is marked
 *    as the current active device for that profile.
 * 5) If there are no connected devices (e.g., during startup, or after all
 * 5) If there is a HearingAid active device, then A2DP and HFP active devices
 *    must be set to null (i.e., A2DP and HFP cannot have active devices).
 *    The reason is because A2DP or HFP cannot be used together with HearingAid.
 * 6) If there are no connected devices (e.g., during startup, or after all
 *    devices have been disconnected, the active device per profile
 *    (either A2DP or HFP) is selected as follows:
 * 5.1) The first connected device (for either A2DP or HFP) is immediately
 *      selected as active for that profile. Assume the first connected device
 *      is for A2DP.
 * 5.2) A timer is started: if the same device is connected for the other
 *      profile as well (HFP in this example) while the timer is running,
 *      and there is no active HFP device yet, that device is selected as
 *      active for HFP as well. The purpose is to select by default the same
 *      device as active for both profiles.
 * 5.3) While the timer is running, all other HFP connected devices are
 *      listed locally, but none of those devices is selected as active.
 * 5.4) While the timer is running, if ACTION_ACTIVE_DEVICE_CHANGED broadcast
 *      is received for HFP, the device contained in the broadcast is
 *      marked as active.
 * 5.5) If the timer expires and no HFP device has been selected as active,
 *      the first HFP connected device is selected as active.
 * 6) If the currently active device (per profile) is disconnected, the
 *    (A2DP/HFP/HearingAid) is selected as follows:
 * 6.1) The last connected HearingAid device is selected as active.
 *      If there is an active A2DP or HFP device, those must be set to null.
 * 6.2) The last connected A2DP or HFP device is selected as active.
 *      However, if there is an active HearingAid device, then the
 *      A2DP or HFP active device is not set (must remain null).
 * 7) If the currently active device (per profile) is disconnected, the
 *    Active Device Manager just marks that the profile has no active device,
 *    but does not attempt to select a new one. Currently, the expectation is
 *    that the user will explicitly select the new active device.
 * 7) If there is already an active device, and the corresponding
 * 8) If there is already an active device, and the corresponding
 *    ACTION_ACTIVE_DEVICE_CHANGED broadcast is received, the device
 *    contained in the broadcast is marked as active. However, if
 *    the contained device is null, the corresponding profile is marked
@@ -93,15 +87,11 @@ class ActiveDeviceManager {

    // Message types for the handler
    private static final int MESSAGE_ADAPTER_ACTION_STATE_CHANGED = 1;
    private static final int MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT = 2;
    private static final int MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED = 3;
    private static final int MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED = 4;
    private static final int MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED = 5;
    private static final int MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED = 6;
    private static final int MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED = 7;

    // Timeouts
    private static final int SELECT_ACTIVE_DEVICE_TIMEOUT_MS = 6000; // 6s
    private static final int MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED = 2;
    private static final int MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED = 3;
    private static final int MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED = 4;
    private static final int MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED = 5;
    private static final int MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED = 6;

    private final AdapterService mAdapterService;
    private final ServiceFactory mFactory;
@@ -155,8 +145,8 @@ class ActiveDeviceManager {
        }
    };

    class ActivePoliceManagerHandler extends Handler {
        ActivePoliceManagerHandler(Looper looper) {
    class ActiveDeviceManagerHandler extends Handler {
        ActiveDeviceManagerHandler(Looper looper) {
            super(looper);
        }

@@ -176,28 +166,16 @@ class ActiveDeviceManager {
                }
                break;

                case MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT: {
                    if (DBG) {
                        Log.d(TAG, "handleMessage(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT)");
                    }
                    // Set the first connected device as active
                    if ((mA2dpActiveDevice == null) && !mA2dpConnectedDevices.isEmpty()
                            && mHearingAidActiveDevice == null) {
                        setA2dpActiveDevice(mA2dpConnectedDevices.get(0));
                    }
                    if ((mHfpActiveDevice == null) && !mHfpConnectedDevices.isEmpty()
                            && mHearingAidActiveDevice == null) {
                        setHfpActiveDevice(mHfpConnectedDevices.get(0));
                    }
                }
                break;

                case MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED: {
                    Intent intent = (Intent) msg.obj;
                    BluetoothDevice device =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                    int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                    if (prevState == nextState) {
                        // Nothing has changed
                        break;
                    }
                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
                        // Device connected
                        if (DBG) {
@@ -206,34 +184,17 @@ class ActiveDeviceManager {
                                    + "device " + device + " connected");
                        }
                        if (mA2dpConnectedDevices.contains(device)) {
                            break;
                            break;      // The device is already connected
                        }
                        if (!hasConnectedClassicDevices() && mHearingAidActiveDevice == null) {
                            // First connected device: select it as active and start the timer
                        mA2dpConnectedDevices.add(device);
                            Message m = obtainMessage(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT);
                            sendMessageDelayed(m, SELECT_ACTIVE_DEVICE_TIMEOUT_MS);
                        if (mHearingAidActiveDevice == null) {
                            // New connected device: select it as active
                            setA2dpActiveDevice(device);
                            break;
                        }
                        mA2dpConnectedDevices.add(device);
                        // Check whether the active device for the other profile is same
                        if ((mA2dpActiveDevice == null) && matchesActiveDevice(device)
                                && mHearingAidActiveDevice == null) {
                            setA2dpActiveDevice(device);
                            break;
                        }
                        // Check whether the active device selection timer is not running
                        if ((mA2dpActiveDevice == null)
                                && !hasMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT)
                                && mHearingAidActiveDevice == null) {
                            setA2dpActiveDevice(mA2dpConnectedDevices.get(0));
                            break;
                        }
                        break;
                    }
                    if ((prevState == BluetoothProfile.STATE_CONNECTED)
                            && (nextState != prevState)) {
                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // Device disconnected
                        if (DBG) {
                            Log.d(TAG,
@@ -256,7 +217,6 @@ class ActiveDeviceManager {
                        Log.d(TAG, "handleMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED): "
                                + "device= " + device);
                    }
                    removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT);
                    if (device != null && !Objects.equals(mA2dpActiveDevice, device)) {
                        setHearingAidActiveDevice(null);
                    }
@@ -271,8 +231,40 @@ class ActiveDeviceManager {
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                    int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                    // TODO: Copy the corresponding logic from the processing of
                    // message MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED
                    if (prevState == nextState) {
                        // Nothing has changed
                        break;
                    }
                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
                        // Device connected
                        if (DBG) {
                            Log.d(TAG,
                                    "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
                                    + "device " + device + " connected");
                        }
                        if (mHfpConnectedDevices.contains(device)) {
                            break;      // The device is already connected
                        }
                        mHfpConnectedDevices.add(device);
                        if (mHearingAidActiveDevice == null) {
                            // New connected device: select it as active
                            setHfpActiveDevice(device);
                            break;
                        }
                        break;
                    }
                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // Device disconnected
                        if (DBG) {
                            Log.d(TAG,
                                    "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
                                    + "device " + device + " disconnected");
                        }
                        mHfpConnectedDevices.remove(device);
                        if (Objects.equals(mHfpActiveDevice, device)) {
                            setHfpActiveDevice(null);
                        }
                    }
                }
                break;

@@ -284,7 +276,6 @@ class ActiveDeviceManager {
                        Log.d(TAG, "handleMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED): "
                                + "device= " + device);
                    }
                    removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT);
                    if (device != null && !Objects.equals(mHfpActiveDevice, device)) {
                        setHearingAidActiveDevice(null);
                    }
@@ -301,7 +292,6 @@ class ActiveDeviceManager {
                        Log.d(TAG, "handleMessage(MESSAGE_HA_ACTION_ACTIVE_DEVICE_CHANGED): "
                                + "device= " + device);
                    }
                    removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT);
                    // Just assign locally the new value
                    mHearingAidActiveDevice = device;
                    if (device != null) {
@@ -326,7 +316,7 @@ class ActiveDeviceManager {

        mHandlerThread = new HandlerThread("BluetoothActiveDeviceManager");
        mHandlerThread.start();
        mHandler = new ActivePoliceManagerHandler(mHandlerThread.getLooper());
        mHandler = new ActiveDeviceManagerHandler(mHandlerThread.getLooper());

        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -393,23 +383,13 @@ class ActiveDeviceManager {
        mHearingAidActiveDevice = device;
    }

    private boolean hasConnectedClassicDevices() {
        return (!mA2dpConnectedDevices.isEmpty() || !mHfpConnectedDevices.isEmpty());
    }

    private boolean matchesActiveDevice(BluetoothDevice device) {
        return (Objects.equals(mA2dpActiveDevice, device)
                || Objects.equals(mHfpActiveDevice, device));
    }

    private void resetState() {
        if (mHandler != null) {
            mHandler.removeMessages(MESSAGE_SELECT_ACTICE_DEVICE_TIMEOUT);
        }
        mA2dpConnectedDevices.clear();
        mA2dpActiveDevice = null;

        mHfpConnectedDevices.clear();
        mHfpActiveDevice = null;

        mHearingAidActiveDevice = null;
    }
}
+40 −4
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManager;
import android.location.LocationManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -102,6 +103,7 @@ public class ScanManager {
    private DisplayManager mDm;

    private ActivityManager mActivityManager;
    private LocationManager mLocationManager;
    private static final int FOREGROUND_IMPORTANCE_CUTOFF =
            ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;

@@ -115,8 +117,6 @@ public class ScanManager {
        }
    }

    ;

    ScanManager(GattService service) {
        mRegularScanClients =
                Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
@@ -128,6 +128,7 @@ public class ScanManager {
        mCurUsedTrackableAdvertisements = 0;
        mDm = (DisplayManager) mService.getSystemService(Context.DISPLAY_SERVICE);
        mActivityManager = (ActivityManager) mService.getSystemService(Context.ACTIVITY_SERVICE);
        mLocationManager = (LocationManager) mService.getSystemService(Context.LOCATION_SERVICE);
    }

    void start() {
@@ -141,6 +142,8 @@ public class ScanManager {
            mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
                    FOREGROUND_IMPORTANCE_CUTOFF);
        }
        IntentFilter locationIntentFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
        mService.registerReceiver(mLocationReceiver, locationIntentFilter);
    }

    void cleanup() {
@@ -170,6 +173,12 @@ public class ScanManager {
            }
            mHandler = null;
        }

        try {
            mService.unregisterReceiver(mLocationReceiver);
        } catch (IllegalArgumentException e) {
            Log.w(TAG, "exception when invoking unregisterReceiver(mLocationReceiver)", e);
        }
    }

    void registerScanner(UUID uuid) {
@@ -313,6 +322,17 @@ public class ScanManager {
                return;
            }

            final boolean locationEnabled = mLocationManager.isLocationEnabled();
            if (!locationEnabled && !isFiltered && !client.legacyForegroundApp) {
                Log.i(TAG, "Cannot start unfiltered scan in location-off. This scan will be"
                        + " resumed when location is on: " + client.scannerId);
                mSuspendedScanClients.add(client);
                if (client.stats != null) {
                    client.stats.recordScanSuspend(client.scannerId);
                }
                return;
            }

            // Begin scan operations.
            if (isBatchClient(client)) {
                mBatchClients.add(client);
@@ -396,7 +416,7 @@ public class ScanManager {
        void handleSuspendScans() {
            for (ScanClient client : mRegularScanClients) {
                if (!mScanNative.isOpportunisticScanClient(client) && (client.filters == null
                        || client.filters.isEmpty())) {
                        || client.filters.isEmpty()) && !client.legacyForegroundApp) {
                    /*Suspend unfiltered scans*/
                    if (client.stats != null) {
                        client.stats.recordScanSuspend(client.scannerId);
@@ -1303,7 +1323,7 @@ public class ScanManager {

                @Override
                public void onDisplayChanged(int displayId) {
                    if (isScreenOn()) {
                    if (isScreenOn() && mLocationManager.isLocationEnabled()) {
                        sendMessage(MSG_RESUME_SCANS, null);
                    } else {
                        sendMessage(MSG_SUSPEND_SCANS, null);
@@ -1324,6 +1344,22 @@ public class ScanManager {
                }
            };

    private BroadcastReceiver mLocationReceiver =
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
                    if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
                        final boolean locationEnabled = mLocationManager.isLocationEnabled();
                        if (locationEnabled && isScreenOn()) {
                            sendMessage(MSG_RESUME_SCANS, null);
                        } else {
                            sendMessage(MSG_SUSPEND_SCANS, null);
                        }
                    }
                }
            };

    private void handleImportanceChange(UidImportance imp) {
        if (imp == null) {
            return;
+42 −28
Original line number Diff line number Diff line
@@ -211,6 +211,9 @@ public class HearingAidService extends ProfileService {
        if (DBG) {
            Log.d(TAG, "connect(): " + device);
        }
        if (device == null) {
            return false;
        }

        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
            return false;
@@ -221,16 +224,35 @@ public class HearingAidService extends ProfileService {
            return false;
        }

        long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
                BluetoothHearingAid.HI_SYNC_ID_INVALID);

        if (hiSyncId != mActiveDeviceHiSyncId) {
            for (BluetoothDevice connectedDevice : getConnectedDevices()) {
                disconnect(connectedDevice);
            }
        }

        for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) {
            if (mDeviceHiSyncIdMap.getOrDefault(storedDevice,
                    BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) {
                synchronized (mStateMachines) {
            HearingAidStateMachine smConnect = getOrCreateStateMachine(device);
            if (smConnect == null) {
                Log.e(TAG, "Cannot connect to " + device + " : no state machine");
                return false;
                    HearingAidStateMachine sm = getOrCreateStateMachine(storedDevice);
                    if (sm == null) {
                        Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
                        continue;
                    }
            smConnect.sendMessage(HearingAidStateMachine.CONNECT);
            return true;
                    sm.sendMessage(HearingAidStateMachine.CONNECT);

                }
                if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID
                        && !device.equals(storedDevice)) {
                    break;
                }
            }
        }
        return true;
    }

    boolean disconnect(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
@@ -242,32 +264,24 @@ public class HearingAidService extends ProfileService {
        }
        long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
                BluetoothHearingAid.HI_SYNC_ID_INVALID);
        synchronized (mStateMachines) {
            HearingAidStateMachine sm = mStateMachines.get(device);
            if (sm == null) {
                Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
            } else {
                sm.sendMessage(HearingAidStateMachine.DISCONNECT);
            }
        }
        if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
            return true;
        }

        for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) {
            if (mDeviceHiSyncIdMap.getOrDefault(storedDevice,
                    BluetoothHearingAid.HI_SYNC_ID_INVALID) != hiSyncId
                    || storedDevice.equals(device)) {
                continue;
            }
                    BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) {
                synchronized (mStateMachines) {
                    HearingAidStateMachine sm = mStateMachines.get(storedDevice);
                    if (sm == null) {
                    Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
                        Log.e(TAG, "Ignored disconnect request for " + device
                                + " : no state machine");
                        continue;
                    }
                    sm.sendMessage(HearingAidStateMachine.DISCONNECT);
                }
                if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID
                        && !device.equals(storedDevice)) {
                    break;
                }
            }
        }
        return true;
    }
+0 −3
Original line number Diff line number Diff line
@@ -1156,9 +1156,6 @@ public class HeadsetService extends ProfileService {
                            stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
                                    0));
                }
                if (mActiveDevice == null) {
                    setActiveDevice(device);
                }
                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET);
            }
            if (fromState == BluetoothProfile.STATE_CONNECTED
+20 −0
Original line number Diff line number Diff line
package com.android.bluetooth.gatt;

import static org.mockito.Mockito.*;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -26,6 +28,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class GattServiceTest {
    private static final int TIMES_UP_AND_DOWN = 3;
    private Context mTargetContext;
    private GattService mService;

@@ -61,6 +64,23 @@ public class GattServiceTest {
        Assert.assertNotNull(GattService.getGattService());
    }

    @Test
    public void testServiceUpAndDown() throws Exception {
        for (int i = 0; i < TIMES_UP_AND_DOWN; i++) {
            GattService gattService = GattService.getGattService();
            TestUtils.stopService(mServiceRule, GattService.class);
            mService = GattService.getGattService();
            Assert.assertNull(mService);
            gattService.cleanup();
            TestUtils.clearAdapterService(mAdapterService);
            reset(mAdapterService);
            TestUtils.setAdapterService(mAdapterService);
            TestUtils.startService(mServiceRule, GattService.class);
            mService = GattService.getGattService();
            Assert.assertNotNull(mService);
        }
    }

    @Test
    public void testParseBatchTimestamp() {
        long timestampNanos = mService.parseTimestampNanos(new byte[]{
Loading