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