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