• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.bluetooth.hfp;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothHeadset;
21 import android.bluetooth.BluetoothSinkAudioPolicy;
22 import android.content.ActivityNotFoundException;
23 import android.content.Intent;
24 import android.media.AudioManager;
25 import android.net.Uri;
26 import android.os.PowerManager;
27 import android.telecom.PhoneAccount;
28 import android.telecom.PhoneAccountHandle;
29 import android.telecom.TelecomManager;
30 import android.telephony.TelephonyManager;
31 import android.util.Log;
32 
33 import com.android.bluetooth.telephony.BluetoothInCallService;
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.util.List;
37 
38 /**
39  * Defines system calls that is used by state machine/service to either send or receive messages
40  * from the Android System.
41  */
42 class HeadsetSystemInterface {
43     private static final String TAG = HeadsetSystemInterface.class.getSimpleName();
44 
45     private final HeadsetService mHeadsetService;
46     private final AudioManager mAudioManager;
47     private final HeadsetPhoneState mHeadsetPhoneState;
48     private final PowerManager.WakeLock mVoiceRecognitionWakeLock;
49     private final TelephonyManager mTelephonyManager;
50     private final TelecomManager mTelecomManager;
51 
HeadsetSystemInterface(HeadsetService headsetService)52     HeadsetSystemInterface(HeadsetService headsetService) {
53         if (headsetService == null) {
54             Log.wtf(TAG, "HeadsetService parameter is null");
55         }
56         mHeadsetService = headsetService;
57         mAudioManager = mHeadsetService.getSystemService(AudioManager.class);
58         PowerManager powerManager = mHeadsetService.getSystemService(PowerManager.class);
59         mVoiceRecognitionWakeLock =
60                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition");
61         mVoiceRecognitionWakeLock.setReferenceCounted(false);
62         mHeadsetPhoneState = new com.android.bluetooth.hfp.HeadsetPhoneState(mHeadsetService);
63         mTelephonyManager = mHeadsetService.getSystemService(TelephonyManager.class);
64         mTelecomManager = mHeadsetService.getSystemService(TelecomManager.class);
65     }
66 
getBluetoothInCallServiceInstance()67     private static BluetoothInCallService getBluetoothInCallServiceInstance() {
68         return BluetoothInCallService.getInstance();
69     }
70 
71     /** Stop this system interface */
stop()72     public synchronized void stop() {
73         mHeadsetPhoneState.cleanup();
74     }
75 
76     /**
77      * Get audio manager. Most audio manager operations are pass through and therefore are not
78      * individually managed by this class
79      *
80      * @return audio manager for setting audio parameters
81      */
getAudioManager()82     public AudioManager getAudioManager() {
83         return mAudioManager;
84     }
85 
86     /**
87      * Get wake lock for voice recognition
88      *
89      * @return wake lock for voice recognition
90      */
91     @VisibleForTesting
getVoiceRecognitionWakeLock()92     public PowerManager.WakeLock getVoiceRecognitionWakeLock() {
93         return mVoiceRecognitionWakeLock;
94     }
95 
96     /**
97      * Get HeadsetPhoneState instance to interact with Telephony service
98      *
99      * @return HeadsetPhoneState interface to interact with Telephony service
100      */
101     @VisibleForTesting
getHeadsetPhoneState()102     public HeadsetPhoneState getHeadsetPhoneState() {
103         return mHeadsetPhoneState;
104     }
105 
106     /**
107      * Answer the current incoming call in Telecom service
108      *
109      * @param device the Bluetooth device used for answering this call
110      */
111     @VisibleForTesting
answerCall(BluetoothDevice device)112     public void answerCall(BluetoothDevice device) {
113         Log.d(TAG, "answerCall");
114         if (device == null) {
115             Log.w(TAG, "answerCall device is null");
116             return;
117         }
118         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
119         if (bluetoothInCallService == null) {
120             Log.e(TAG, "Handsfree phone proxy null for answering call");
121             return;
122         }
123         BluetoothSinkAudioPolicy callAudioPolicy = mHeadsetService.getHfpCallAudioPolicy(device);
124         if (callAudioPolicy == null
125                 || callAudioPolicy.getCallEstablishPolicy()
126                         != BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
127             mHeadsetService.setActiveDevice(device);
128         }
129         bluetoothInCallService.answerCall();
130     }
131 
132     /**
133      * Hangup the current call, could either be Telecom call or virtual call
134      *
135      * @param device the Bluetooth device used for hanging up this call
136      */
137     @VisibleForTesting
hangupCall(BluetoothDevice device)138     public void hangupCall(BluetoothDevice device) {
139         if (device == null) {
140             Log.w(TAG, "hangupCall device is null");
141             return;
142         }
143         // Close the virtual call if active. Virtual call should be
144         // terminated for CHUP callback event
145         if (mHeadsetService.isVirtualCallStarted()) {
146             mHeadsetService.stopScoUsingVirtualVoiceCall();
147         } else {
148             BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
149             if (bluetoothInCallService != null) {
150                 bluetoothInCallService.hangupCall();
151             } else {
152                 Log.e(TAG, "Handsfree phone proxy null for hanging up call");
153             }
154         }
155     }
156 
157     /**
158      * Instructs Telecom to play the specified DTMF tone for the current foreground call
159      *
160      * @param dtmf dtmf code
161      * @param device the Bluetooth device that sent this code
162      */
sendDtmf(int dtmf, BluetoothDevice device)163     boolean sendDtmf(int dtmf, BluetoothDevice device) {
164         if (device == null) {
165             Log.w(TAG, "sendDtmf device is null");
166             return false;
167         }
168         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
169         if (bluetoothInCallService != null) {
170             return bluetoothInCallService.sendDtmf(dtmf);
171         } else {
172             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
173         }
174         return false;
175     }
176 
177     /**
178      * Instructs Telecom hold an incoming call
179      *
180      * @param chld index of the call to hold
181      */
182     @VisibleForTesting
processChld(HeadsetService headsetService, int chld)183     public boolean processChld(HeadsetService headsetService, int chld) {
184         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
185         if (bluetoothInCallService != null) {
186             return bluetoothInCallService.processChld(headsetService, chld);
187         } else {
188             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
189         }
190         return false;
191     }
192 
193     /** Check for HD codec for voice call */
194     @VisibleForTesting
isHighDefCallInProgress()195     public boolean isHighDefCallInProgress() {
196         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
197         if (bluetoothInCallService != null) {
198             return bluetoothInCallService.isHighDefCallInProgress();
199         } else {
200             Log.e(TAG, "Handsfree phone proxy null");
201         }
202         return false;
203     }
204 
205     /**
206      * Get the the alphabetic name of current registered operator.
207      *
208      * @return null on error, empty string if not available
209      */
210     @VisibleForTesting
getNetworkOperator()211     public String getNetworkOperator() {
212         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
213         if (bluetoothInCallService == null) {
214             Log.e(TAG, "getNetworkOperator() failed: mBluetoothInCallService is null");
215             return null;
216         }
217         // Should never return null
218         return bluetoothInCallService.getNetworkOperator();
219     }
220 
221     /**
222      * Get the phone number of this device without incall service
223      *
224      * @return empty if unavailable
225      */
getNumberWithoutInCallService()226     private String getNumberWithoutInCallService() {
227         PhoneAccount account = null;
228         String address = "";
229 
230         // Get the label for the default Phone Account.
231         List<PhoneAccountHandle> handles =
232                 mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL);
233         while (handles.iterator().hasNext()) {
234             account = mTelecomManager.getPhoneAccount(handles.iterator().next());
235             break;
236         }
237 
238         if (account != null) {
239             Uri addressUri = account.getAddress();
240 
241             if (addressUri != null) {
242                 address = addressUri.getSchemeSpecificPart();
243             }
244         }
245 
246         if (address.isEmpty()) {
247             address = mTelephonyManager.getLine1Number();
248             if (address == null) address = "";
249         }
250 
251         Log.i(TAG, "get phone number -> '" + address + "'");
252 
253         return address;
254     }
255 
256     /**
257      * Get the phone number of this device
258      *
259      * @return null if unavailable
260      */
261     @VisibleForTesting
getSubscriberNumber()262     public String getSubscriberNumber() {
263         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
264         if (bluetoothInCallService == null) {
265             Log.e(TAG, "getSubscriberNumber() failed: mBluetoothInCallService is null");
266             Log.i(TAG, "Try to get phone number without mBluetoothInCallService.");
267             return getNumberWithoutInCallService();
268         }
269         return bluetoothInCallService.getSubscriberNumber();
270     }
271 
272     /**
273      * Ask the Telecom service to list current list of calls through CLCC response {@link
274      * BluetoothHeadset#clccResponse(int, int, int, int, boolean, String, int)}
275      */
276     @VisibleForTesting
listCurrentCalls(HeadsetService headsetService)277     public boolean listCurrentCalls(HeadsetService headsetService) {
278         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
279         if (bluetoothInCallService == null) {
280             Log.e(TAG, "listCurrentCalls() failed: mBluetoothInCallService is null");
281             return false;
282         }
283         return bluetoothInCallService.listCurrentCalls(headsetService);
284     }
285 
286     /**
287      * Request Telecom service to send an update of the current call state to the headset service
288      * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)}
289      */
290     @VisibleForTesting
queryPhoneState(HeadsetService headsetService)291     public void queryPhoneState(HeadsetService headsetService) {
292         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
293         if (bluetoothInCallService != null) {
294             bluetoothInCallService.queryPhoneState(headsetService);
295         } else {
296             Log.e(TAG, "Handsfree phone proxy null for query phone state");
297         }
298     }
299 
300     /**
301      * Check if we are currently in a phone call
302      *
303      * @return True iff we are in a phone call
304      */
isInCall()305     boolean isInCall() {
306         return ((mHeadsetPhoneState.getNumActiveCall() > 0)
307                 || (mHeadsetPhoneState.getNumHeldCall() > 0)
308                 || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
309                         && (mHeadsetPhoneState.getCallState()
310                                 != HeadsetHalConstants.CALL_STATE_INCOMING)));
311     }
312 
313     /**
314      * Check if there is currently an incoming call
315      *
316      * @return True iff there is an incoming call
317      */
318     @VisibleForTesting
isRinging()319     public boolean isRinging() {
320         return mHeadsetPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING;
321     }
322 
323     /**
324      * Check if call status is idle
325      *
326      * @return true if call state is neither ringing nor in call
327      */
328     @VisibleForTesting
isCallIdle()329     public boolean isCallIdle() {
330         return !isInCall() && !isRinging();
331     }
332 
333     /**
334      * Activate voice recognition on Android system
335      *
336      * @return true if activation succeeds, caller should wait for {@link
337      *     BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then trigger
338      *     {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to
339      *     activate
340      */
341     @VisibleForTesting
activateVoiceRecognition()342     public boolean activateVoiceRecognition() {
343         Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
344         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
345         try {
346             mHeadsetService.startActivity(intent);
347         } catch (ActivityNotFoundException e) {
348             Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent);
349             return false;
350         }
351         return true;
352     }
353 
354     /**
355      * Deactivate voice recognition on Android system
356      *
357      * @return true if activation succeeds, caller should wait for {@link
358      *     BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then trigger
359      *     {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to activate
360      */
361     @VisibleForTesting
deactivateVoiceRecognition()362     public boolean deactivateVoiceRecognition() {
363         // TODO: need a method to deactivate voice recognition on Android
364         return true;
365     }
366 }
367