1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.telecom.bluetooth;
18
19import android.bluetooth.BluetoothDevice;
20import android.bluetooth.BluetoothHeadset;
21import android.bluetooth.BluetoothProfile;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.telecom.Log;
27
28import com.android.server.telecom.BluetoothAdapterProxy;
29import com.android.server.telecom.BluetoothHeadsetProxy;
30import com.android.server.telecom.TelecomSystem;
31
32import java.util.Collection;
33import java.util.LinkedHashMap;
34import java.util.LinkedList;
35import java.util.List;
36import java.util.Map;
37import java.util.Objects;
38import java.util.stream.Collectors;
39
40public class BluetoothDeviceManager {
41    private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
42            new BluetoothProfile.ServiceListener() {
43                @Override
44                public void onServiceConnected(int profile, BluetoothProfile proxy) {
45                    Log.startSession("BMSL.oSC");
46                    try {
47                        synchronized (mLock) {
48                            if (profile == BluetoothProfile.HEADSET) {
49                                mBluetoothHeadsetService =
50                                        new BluetoothHeadsetProxy((BluetoothHeadset) proxy);
51                                Log.i(this, "- Got BluetoothHeadset: " + mBluetoothHeadsetService);
52                            } else {
53                                Log.w(this, "Connected to non-headset bluetooth service." +
54                                        " Not changing bluetooth headset.");
55                            }
56                        }
57                    } finally {
58                        Log.endSession();
59                    }
60                }
61
62                @Override
63                public void onServiceDisconnected(int profile) {
64                    Log.startSession("BMSL.oSD");
65                    try {
66                        synchronized (mLock) {
67                            mBluetoothHeadsetService = null;
68                            Log.i(BluetoothDeviceManager.this, "Lost BluetoothHeadset service. " +
69                                    "Removing all tracked devices.");
70                            List<BluetoothDevice> devicesToRemove = new LinkedList<>(
71                                    mConnectedDevicesByAddress.values());
72                            mConnectedDevicesByAddress.clear();
73                            for (BluetoothDevice device : devicesToRemove) {
74                                mBluetoothRouteManager.onDeviceLost(device);
75                            }
76                        }
77                    } finally {
78                        Log.endSession();
79                    }
80                }
81           };
82
83    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
84        @Override
85        public void onReceive(Context context, Intent intent) {
86            Log.startSession("BM.oR");
87            try {
88                String action = intent.getAction();
89
90                if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
91                    int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
92                            BluetoothHeadset.STATE_DISCONNECTED);
93                    BluetoothDevice device =
94                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
95
96                    if (device == null) {
97                        Log.w(BluetoothDeviceManager.this, "Got null device from broadcast. " +
98                                "Ignoring.");
99                        return;
100                    }
101
102                    Log.i(BluetoothDeviceManager.this, "Device %s changed state to %d",
103                            device.getAddress(), bluetoothHeadsetState);
104
105                    synchronized (mLock) {
106                        if (bluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED) {
107                            if (!mConnectedDevicesByAddress.containsKey(device.getAddress())) {
108                                mConnectedDevicesByAddress.put(device.getAddress(), device);
109                                mBluetoothRouteManager.onDeviceAdded(device);
110                            }
111                        } else if (bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTED
112                                || bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTING) {
113                            if (mConnectedDevicesByAddress.containsKey(device.getAddress())) {
114                                mConnectedDevicesByAddress.remove(device.getAddress());
115                                mBluetoothRouteManager.onDeviceLost(device);
116                            }
117                        }
118                    }
119                }
120            } finally {
121                Log.endSession();
122            }
123        }
124    };
125
126    private final LinkedHashMap<String, BluetoothDevice> mConnectedDevicesByAddress =
127            new LinkedHashMap<>();
128    private final TelecomSystem.SyncRoot mLock;
129
130    private BluetoothRouteManager mBluetoothRouteManager;
131    private BluetoothHeadsetProxy mBluetoothHeadsetService;
132
133    public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter,
134            TelecomSystem.SyncRoot lock) {
135        mLock = lock;
136
137        if (bluetoothAdapter != null) {
138            bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
139                    BluetoothProfile.HEADSET);
140        }
141        IntentFilter intentFilter =
142                new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
143        context.registerReceiver(mReceiver, intentFilter);
144    }
145
146    public void setBluetoothRouteManager(BluetoothRouteManager brm) {
147        mBluetoothRouteManager = brm;
148    }
149
150    public int getNumConnectedDevices() {
151        return mConnectedDevicesByAddress.size();
152    }
153
154    public String getMostRecentlyConnectedDevice(String excludeAddress) {
155        String result = null;
156        synchronized (mLock) {
157            for (String addr : mConnectedDevicesByAddress.keySet()) {
158                if (!Objects.equals(addr, excludeAddress)) {
159                    result = addr;
160                }
161            }
162        }
163        return result;
164    }
165
166    public BluetoothHeadsetProxy getHeadsetService() {
167        return mBluetoothHeadsetService;
168    }
169
170    public void setHeadsetServiceForTesting(BluetoothHeadsetProxy bluetoothHeadset) {
171        mBluetoothHeadsetService = bluetoothHeadset;
172    }
173}
174