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