1 /*
2  * Copyright (C) 2023 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 
17 package com.android.server.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.bluetooth.BluetoothA2dp;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothHearingAid;
25 import android.bluetooth.BluetoothLeAudio;
26 import android.bluetooth.BluetoothProfile;
27 import android.content.Context;
28 
29 import java.util.Objects;
30 
31 /* package */ class BluetoothProfileMonitor {
32 
33     /* package */ static final long GROUP_ID_NO_GROUP = -1L;
34 
35     @NonNull
36     private final ProfileListener mProfileListener = new ProfileListener();
37 
38     @NonNull
39     private final Context mContext;
40     @NonNull
41     private final BluetoothAdapter mBluetoothAdapter;
42 
43     @Nullable
44     private BluetoothA2dp mA2dpProfile;
45     @Nullable
46     private BluetoothHearingAid mHearingAidProfile;
47     @Nullable
48     private BluetoothLeAudio mLeAudioProfile;
49 
50     @Nullable
51     private OnProfileChangedListener mOnProfileChangedListener;
52 
BluetoothProfileMonitor(@onNull Context context, @NonNull BluetoothAdapter bluetoothAdapter)53     BluetoothProfileMonitor(@NonNull Context context,
54             @NonNull BluetoothAdapter bluetoothAdapter) {
55         Objects.requireNonNull(context);
56         Objects.requireNonNull(bluetoothAdapter);
57 
58         mContext = context;
59         mBluetoothAdapter = bluetoothAdapter;
60     }
61 
setOnProfileChangedListener( @onNull OnProfileChangedListener listener)62     /* package */ synchronized void setOnProfileChangedListener(
63             @NonNull OnProfileChangedListener listener) {
64         mOnProfileChangedListener = listener;
65     }
66 
start()67     /* package */ void start() {
68         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
69         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
70         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
71     }
72 
isProfileSupported(int profile, @NonNull BluetoothDevice device)73     /* package */ boolean isProfileSupported(int profile, @NonNull BluetoothDevice device) {
74         BluetoothProfile bluetoothProfile;
75 
76         synchronized (this) {
77             switch (profile) {
78                 case BluetoothProfile.A2DP:
79                     bluetoothProfile = mA2dpProfile;
80                     break;
81                 case BluetoothProfile.LE_AUDIO:
82                     bluetoothProfile = mLeAudioProfile;
83                     break;
84                 case BluetoothProfile.HEARING_AID:
85                     bluetoothProfile = mHearingAidProfile;
86                     break;
87                 default:
88                     throw new IllegalArgumentException(profile
89                             + " is not supported as Bluetooth profile");
90             }
91         }
92 
93         if (bluetoothProfile == null) {
94             return false;
95         }
96 
97         return bluetoothProfile.getConnectedDevices().contains(device);
98     }
99 
getGroupId(int profile, @NonNull BluetoothDevice device)100     /* package */ long getGroupId(int profile, @NonNull BluetoothDevice device) {
101         synchronized (this) {
102             switch (profile) {
103                 case BluetoothProfile.A2DP:
104                     return GROUP_ID_NO_GROUP;
105                 case BluetoothProfile.LE_AUDIO:
106                     return mLeAudioProfile == null ? GROUP_ID_NO_GROUP : mLeAudioProfile.getGroupId(
107                             device);
108                 case BluetoothProfile.HEARING_AID:
109                     return mHearingAidProfile == null
110                             ? GROUP_ID_NO_GROUP : mHearingAidProfile.getHiSyncId(device);
111                 default:
112                     throw new IllegalArgumentException(profile
113                             + " is not supported as Bluetooth profile");
114             }
115         }
116     }
117 
118     /* package */ interface OnProfileChangedListener {
onProfileChange(int profile)119         void onProfileChange(int profile);
120     }
121 
122     private final class ProfileListener implements BluetoothProfile.ServiceListener {
123         @Override
onServiceConnected(int profile, BluetoothProfile proxy)124         public void onServiceConnected(int profile, BluetoothProfile proxy) {
125             OnProfileChangedListener listener;
126 
127             synchronized (BluetoothProfileMonitor.this) {
128                 switch (profile) {
129                     case BluetoothProfile.A2DP:
130                         mA2dpProfile = (BluetoothA2dp) proxy;
131                         break;
132                     case BluetoothProfile.HEARING_AID:
133                         mHearingAidProfile = (BluetoothHearingAid) proxy;
134                         break;
135                     case BluetoothProfile.LE_AUDIO:
136                         mLeAudioProfile = (BluetoothLeAudio) proxy;
137                         break;
138                     default:
139                         return;
140                 }
141 
142                 listener = mOnProfileChangedListener;
143             }
144 
145             if (listener != null) {
146                 listener.onProfileChange(profile);
147             }
148         }
149 
150         @Override
onServiceDisconnected(int profile)151         public void onServiceDisconnected(int profile) {
152             OnProfileChangedListener listener;
153 
154             synchronized (BluetoothProfileMonitor.this) {
155                 switch (profile) {
156                     case BluetoothProfile.A2DP:
157                         mA2dpProfile = null;
158                         break;
159                     case BluetoothProfile.HEARING_AID:
160                         mHearingAidProfile = null;
161                         break;
162                     case BluetoothProfile.LE_AUDIO:
163                         mLeAudioProfile = null;
164                         break;
165                     default:
166                         return;
167                 }
168 
169                 listener = mOnProfileChangedListener;
170             }
171 
172             if (listener != null) {
173                 listener.onProfileChange(profile);
174             }
175         }
176     }
177 
178 }
179