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 17 package com.google.android.car.kitchensink.bluetooth; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothDevicePicker; 22 import android.bluetooth.BluetoothHeadsetClient; 23 import android.bluetooth.BluetoothHeadsetClientCall; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.os.Bundle; 30 import android.util.Log; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.Button; 35 import android.widget.EditText; 36 import android.widget.TextView; 37 import android.widget.Toast; 38 39 import androidx.annotation.Nullable; 40 import androidx.fragment.app.Fragment; 41 42 import com.google.android.car.kitchensink.R; 43 44 public class BluetoothHeadsetFragment extends Fragment { 45 private static final String TAG = "CAR.BLUETOOTH.KS"; 46 BluetoothAdapter mBluetoothAdapter; 47 BluetoothDevice mPickedDevice; 48 49 TextView mPickedDeviceText; 50 Button mDevicePicker; 51 Button mConnect; 52 Button mDisconnect; 53 Button mScoConnect; 54 Button mScoDisconnect; 55 Button mEnableQuietMode; 56 Button mHoldCall; 57 Button mEnableBVRA; 58 Button mDisableBVRA; 59 Button mStartOutgoingCall; 60 Button mEndOutgoingCall; 61 EditText mOutgoingPhoneNumber; 62 63 BluetoothHeadsetClient mHfpClientProfile; 64 BluetoothHeadsetClientCall mOutgoingCall; 65 66 // Intent for picking a Bluetooth device 67 public static final String DEVICE_PICKER_ACTION = 68 "android.bluetooth.devicepicker.action.LAUNCH"; 69 70 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)71 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 72 @Nullable Bundle savedInstanceState) { 73 View v = inflater.inflate(R.layout.bluetooth_headset, container, false); 74 75 mPickedDeviceText = (TextView) v.findViewById(R.id.bluetooth_device); 76 mDevicePicker = (Button) v.findViewById(R.id.bluetooth_pick_device); 77 mConnect = (Button) v.findViewById(R.id.bluetooth_headset_connect); 78 mDisconnect = (Button) v.findViewById(R.id.bluetooth_headset_disconnect); 79 mScoConnect = (Button) v.findViewById(R.id.bluetooth_sco_connect); 80 mScoDisconnect = (Button) v.findViewById(R.id.bluetooth_sco_disconnect); 81 mEnableQuietMode = (Button) v.findViewById(R.id.bluetooth_quiet_mode_enable); 82 mHoldCall = (Button) v.findViewById(R.id.bluetooth_hold_call); 83 mEnableBVRA = (Button) v.findViewById(R.id.bluetooth_voice_recognition_enable); 84 mDisableBVRA = (Button) v.findViewById(R.id.bluetooth_voice_recognition_disable); 85 mStartOutgoingCall = (Button) v.findViewById(R.id.bluetooth_start_outgoing_call); 86 mEndOutgoingCall = (Button) v.findViewById(R.id.bluetooth_end_outgoing_call); 87 mOutgoingPhoneNumber = (EditText) v.findViewById(R.id.bluetooth_outgoing_phone_number); 88 89 // Pick a bluetooth device 90 mDevicePicker.setOnClickListener(new View.OnClickListener() { 91 @Override 92 public void onClick(View view) { 93 launchDevicePicker(); 94 } 95 }); 96 97 // Connect profile 98 mConnect.setOnClickListener(new View.OnClickListener() { 99 @Override 100 public void onClick(View view) { 101 connect(); 102 } 103 }); 104 105 // Disonnect profile 106 mDisconnect.setOnClickListener(new View.OnClickListener() { 107 @Override 108 public void onClick(View view) { 109 disconnect(); 110 } 111 }); 112 113 // Connect SCO 114 mScoConnect.setOnClickListener(new View.OnClickListener() { 115 @Override 116 public void onClick(View view) { 117 connectSco(); 118 } 119 }); 120 121 // Disconnect SCO 122 mScoDisconnect.setOnClickListener(new View.OnClickListener() { 123 @Override 124 public void onClick(View view) { 125 disconnectSco(); 126 } 127 }); 128 129 // Enable quiet mode 130 mEnableQuietMode.setOnClickListener(new View.OnClickListener() { 131 @Override 132 public void onClick(View view) { 133 mBluetoothAdapter.enableNoAutoConnect(); 134 } 135 }); 136 137 // Place the current call on hold 138 mHoldCall.setOnClickListener(new View.OnClickListener() { 139 @Override 140 public void onClick(View view) { 141 holdCall(); 142 } 143 }); 144 145 // Enable Voice Recognition 146 mEnableBVRA.setOnClickListener(new View.OnClickListener() { 147 @Override 148 public void onClick(View view) { 149 startBVRA(); 150 } 151 }); 152 153 // Disable Voice Recognition 154 mDisableBVRA.setOnClickListener(new View.OnClickListener() { 155 @Override 156 public void onClick(View view) { 157 stopBVRA(); 158 } 159 }); 160 161 // Start a outgoing call 162 mStartOutgoingCall.setOnClickListener(new View.OnClickListener() { 163 @Override 164 public void onClick(View view) { 165 startCall(); 166 } 167 }); 168 169 // Stop a outgoing call 170 mEndOutgoingCall.setOnClickListener(new View.OnClickListener() { 171 @Override 172 public void onClick(View view) { 173 stopCall(); 174 } 175 }); 176 177 return v; 178 } 179 launchDevicePicker()180 void launchDevicePicker() { 181 IntentFilter filter = new IntentFilter(); 182 filter.addAction(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); 183 getContext().registerReceiver(mPickerReceiver, filter); 184 185 Intent intent = new Intent(DEVICE_PICKER_ACTION); 186 intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 187 getContext().startActivity(intent); 188 } 189 connect()190 void connect() { 191 if (mPickedDevice == null) { 192 Log.w(TAG, "Device null when trying to connect sco!"); 193 return; 194 } 195 196 // Check if we have the proxy and connect the device. 197 if (mHfpClientProfile == null) { 198 Log.w(TAG, "HFP Profile proxy not available, cannot connect sco to " + mPickedDevice); 199 return; 200 } 201 mHfpClientProfile.connect(mPickedDevice); 202 } 203 disconnect()204 void disconnect() { 205 if (mPickedDevice == null) { 206 Log.w(TAG, "Device null when trying to connect sco!"); 207 return; 208 } 209 210 // Check if we have the proxy and connect the device. 211 if (mHfpClientProfile == null) { 212 Log.w(TAG, "HFP Profile proxy not available, cannot connect sco to " + mPickedDevice); 213 return; 214 } 215 mHfpClientProfile.disconnect(mPickedDevice); 216 } 217 connectSco()218 void connectSco() { 219 if (mPickedDevice == null) { 220 Log.w(TAG, "Device null when trying to connect sco!"); 221 return; 222 } 223 224 // Check if we have the proxy and connect the device. 225 if (mHfpClientProfile == null) { 226 Log.w(TAG, "HFP Profile proxy not available, cannot connect sco to " + mPickedDevice); 227 return; 228 } 229 mHfpClientProfile.connectAudio(mPickedDevice); 230 } 231 disconnectSco()232 void disconnectSco() { 233 if (mPickedDevice == null) { 234 Log.w(TAG, "Device null when trying to disconnect sco!"); 235 return; 236 } 237 238 if (mHfpClientProfile == null) { 239 Log.w(TAG, "HFP Profile proxy not available, cannot disconnect sco to " + 240 mPickedDevice); 241 return; 242 } 243 mHfpClientProfile.disconnectAudio(mPickedDevice); 244 } 245 holdCall()246 void holdCall() { 247 if (mPickedDevice == null) { 248 Log.w(TAG, "Device null when trying to put the call on hold!"); 249 return; 250 } 251 252 if (mHfpClientProfile == null) { 253 Log.w(TAG, "HFP Profile proxy not available, cannot put the call on hold " + 254 mPickedDevice); 255 return; 256 } 257 mHfpClientProfile.holdCall(mPickedDevice); 258 } 259 startBVRA()260 void startBVRA() { 261 if (mPickedDevice == null) { 262 Log.w(TAG, "Device null when trying to start voice recognition!"); 263 return; 264 } 265 266 // Check if we have the proxy and connect the device. 267 if (mHfpClientProfile == null) { 268 Log.w(TAG, "HFP Profile proxy not available, cannot start voice recognition to " 269 + mPickedDevice); 270 return; 271 } 272 mHfpClientProfile.startVoiceRecognition(mPickedDevice); 273 } 274 stopBVRA()275 void stopBVRA() { 276 if (mPickedDevice == null) { 277 Log.w(TAG, "Device null when trying to stop voice recognition!"); 278 return; 279 } 280 281 // Check if we have the proxy and connect the device. 282 if (mHfpClientProfile == null) { 283 Log.w(TAG, "HFP Profile proxy not available, cannot stop voice recognition to " 284 + mPickedDevice); 285 return; 286 } 287 mHfpClientProfile.stopVoiceRecognition(mPickedDevice); 288 } 289 startCall()290 void startCall() { 291 if (mPickedDevice == null) { 292 Log.w(TAG, "Device null when trying to start voice call!"); 293 return; 294 } 295 296 // Check if we have the proxy and connect the device. 297 if (mHfpClientProfile == null) { 298 Log.w(TAG, "HFP Profile proxy not available, cannot start voice call to " 299 + mPickedDevice); 300 return; 301 } 302 303 if (mOutgoingCall != null) { 304 Log.w(TAG, "Potential on-going call or a stale call " + mOutgoingCall); 305 } 306 307 String number = mOutgoingPhoneNumber.getText().toString(); 308 mOutgoingCall = mHfpClientProfile.dial(mPickedDevice, number); 309 if (mOutgoingCall == null) { 310 Log.w(TAG, "Fail to dial number " + number + ". Make sure profile connect first."); 311 } else { 312 Log.d(TAG, "Succeed in creating outgoing call " + mOutgoingCall + " for number " 313 + number); 314 } 315 } 316 stopCall()317 void stopCall() { 318 if (mPickedDevice == null) { 319 Log.w(TAG, "Device null when trying to stop voice call!"); 320 return; 321 } 322 323 // Check if we have the proxy and connect the device. 324 if (mHfpClientProfile == null) { 325 Log.w(TAG, "HFP Profile proxy not available, cannot stop voice call to " 326 + mPickedDevice); 327 return; 328 } 329 330 if (mOutgoingCall != null) { 331 if (mHfpClientProfile.terminateCall(mPickedDevice, mOutgoingCall)) { 332 Log.d(TAG, "Succeed in terminating outgoing call " + mOutgoingCall); 333 mOutgoingCall = null; 334 } else { 335 Log.d(TAG, "Fail to terminate outgoing call " + mOutgoingCall); 336 } 337 } else { 338 Log.w(TAG, "No outgoing call to terminate"); 339 } 340 } 341 342 343 private final BroadcastReceiver mPickerReceiver = new BroadcastReceiver() { 344 @Override 345 public void onReceive(Context context, Intent intent) { 346 String action = intent.getAction(); 347 348 Log.v(TAG, "mPickerReceiver got " + action); 349 350 if (BluetoothDevicePicker.ACTION_DEVICE_SELECTED.equals(action)) { 351 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 352 if (device == null) { 353 Toast.makeText(getContext(), "No device selected", Toast.LENGTH_SHORT).show(); 354 return; 355 } 356 mPickedDevice = device; 357 String text = device.getName() == null ? 358 device.getAddress() : device.getName() + " " + device.getAddress(); 359 mPickedDeviceText.setText(text); 360 361 // The receiver can now be disabled. 362 getContext().unregisterReceiver(mPickerReceiver); 363 } 364 } 365 }; 366 367 @Override onResume()368 public void onResume() { 369 super.onResume(); 370 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 371 mBluetoothAdapter.getProfileProxy( 372 getContext(), new ProfileServiceListener(), BluetoothProfile.HEADSET_CLIENT); 373 } 374 375 @Override onPause()376 public void onPause() { 377 super.onPause(); 378 } 379 380 class ProfileServiceListener implements BluetoothProfile.ServiceListener { 381 @Override onServiceConnected(int profile, BluetoothProfile proxy)382 public void onServiceConnected(int profile, BluetoothProfile proxy) { 383 Log.d(TAG, "Proxy connected for profile: " + profile); 384 switch (profile) { 385 case BluetoothProfile.HEADSET_CLIENT: 386 mHfpClientProfile = (BluetoothHeadsetClient) proxy; 387 break; 388 default: 389 Log.w(TAG, "onServiceConnected not supported profile: " + profile); 390 } 391 } 392 393 @Override onServiceDisconnected(int profile)394 public void onServiceDisconnected(int profile) { 395 Log.d(TAG, "Proxy disconnected for profile: " + profile); 396 switch (profile) { 397 case BluetoothProfile.HEADSET_CLIENT: 398 mHfpClientProfile = null; 399 break; 400 default: 401 Log.w(TAG, "onServiceDisconnected not supported profile: " + profile); 402 } 403 } 404 } 405 } 406