• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 /*
18  * Defines utility interface that is used by state machine/service to either send vendor specific AT
19  * command or receive vendor specific response from the native stack.
20  */
21 package com.android.bluetooth.hfpclient;
22 
23 import static android.Manifest.permission.BLUETOOTH_CONNECT;
24 
25 import android.bluetooth.BluetoothAssignedNumbers;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothHeadsetClient;
28 import android.content.Intent;
29 import android.util.Log;
30 
31 import com.android.bluetooth.Utils;
32 
33 import java.util.HashMap;
34 import java.util.Map;
35 import java.util.Objects;
36 
37 class VendorCommandResponseProcessor {
38     private static final String TAG = VendorCommandResponseProcessor.class.getSimpleName();
39 
40     private final HeadsetClientService mService;
41     private final NativeInterface mNativeInterface;
42 
43     // Keys are AT commands (without payload), and values are the company IDs.
44     private static final Map<String, Integer> SUPPORTED_VENDOR_AT_COMMANDS;
45 
46     static {
47         SUPPORTED_VENDOR_AT_COMMANDS = new HashMap<>();
48         SUPPORTED_VENDOR_AT_COMMANDS.put("+XAPL=", BluetoothAssignedNumbers.APPLE);
49         SUPPORTED_VENDOR_AT_COMMANDS.put("+IPHONEACCEV=", BluetoothAssignedNumbers.APPLE);
50         SUPPORTED_VENDOR_AT_COMMANDS.put("+APLSIRI?", BluetoothAssignedNumbers.APPLE);
51         SUPPORTED_VENDOR_AT_COMMANDS.put("+APLEFM=", BluetoothAssignedNumbers.APPLE);
52     }
53 
54     // Keys are AT events (without payload), and values are the company IDs.
55     private static final Map<String, Integer> SUPPORTED_VENDOR_EVENTS;
56 
57     static {
58         SUPPORTED_VENDOR_EVENTS = new HashMap<>();
59         SUPPORTED_VENDOR_EVENTS.put("+APLSIRI:", BluetoothAssignedNumbers.APPLE);
60         SUPPORTED_VENDOR_EVENTS.put("+XAPL=", BluetoothAssignedNumbers.APPLE);
61         SUPPORTED_VENDOR_EVENTS.put("+ANDROID:", BluetoothAssignedNumbers.GOOGLE);
62     }
63 
VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface)64     VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface) {
65         mService = context;
66         mNativeInterface = nativeInterface;
67     }
68 
sendCommand(int vendorId, String atCommand, BluetoothDevice device)69     public boolean sendCommand(int vendorId, String atCommand, BluetoothDevice device) {
70         if (device == null) {
71             Log.w(TAG, "processVendorCommand device is null");
72             return false;
73         }
74 
75         // Do not support more than one command at one line.
76         // We simplify and say no ; allowed as well.
77         int indexOfSemicolon = atCommand.indexOf(';');
78         if (indexOfSemicolon > 0) {
79             Log.e(TAG, "Do not support ; and more than one command:" + atCommand);
80             return false;
81         }
82 
83         // Get command word
84         int indexOfEqual = atCommand.indexOf('=');
85         int indexOfQuestionMark = atCommand.indexOf('?');
86         String commandWord;
87         if (indexOfEqual > 0) {
88             commandWord = atCommand.substring(0, indexOfEqual + 1);
89         } else if (indexOfQuestionMark > 0) {
90             commandWord = atCommand.substring(0, indexOfQuestionMark + 1);
91         } else {
92             commandWord = atCommand;
93         }
94 
95         // replace all white spaces
96         commandWord = commandWord.replaceAll("\\s+", "");
97 
98         if (!Objects.equals(SUPPORTED_VENDOR_AT_COMMANDS.get(commandWord), vendorId)) {
99             Log.e(TAG, "Invalid command " + atCommand + ", " + vendorId + ". Cand=" + commandWord);
100             return false;
101         }
102 
103         if (!mNativeInterface.sendATCmd(
104                 device,
105                 HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD,
106                 0,
107                 0,
108                 atCommand)) {
109             Log.e(TAG, "Failed to send vendor specific at command");
110             return false;
111         }
112         Log.d(TAG, "Send vendor command: " + atCommand);
113         return true;
114     }
115 
getVendorIdFromAtCommand(String atString)116     private static String getVendorIdFromAtCommand(String atString) {
117         // Get event code
118         int indexOfEqual = atString.indexOf('=');
119         int indexOfColon = atString.indexOf(':');
120         String eventCode;
121         if (indexOfEqual > 0) {
122             eventCode = atString.substring(0, indexOfEqual + 1);
123         } else if (indexOfColon > 0) {
124             eventCode = atString.substring(0, indexOfColon + 1);
125         } else {
126             eventCode = atString;
127         }
128 
129         // replace all white spaces
130         eventCode = eventCode.replaceAll("\\s+", "");
131 
132         return eventCode;
133     }
134 
isAndroidAtCommand(String atString)135     public boolean isAndroidAtCommand(String atString) {
136         String eventCode = getVendorIdFromAtCommand(atString);
137         Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
138         if (vendorId == null) {
139             return false;
140         }
141         return vendorId == BluetoothAssignedNumbers.GOOGLE;
142     }
143 
processEvent(String atString, BluetoothDevice device)144     public boolean processEvent(String atString, BluetoothDevice device) {
145         if (device == null) {
146             Log.w(TAG, "processVendorEvent device is null");
147             return false;
148         }
149 
150         String eventCode = getVendorIdFromAtCommand(atString);
151         Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode);
152         if (vendorId == null) {
153             Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
154             return false;
155         } else {
156             broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
157             Log.d(
158                     TAG,
159                     "process vendor event "
160                             + vendorId
161                             + ", "
162                             + eventCode
163                             + ", "
164                             + atString
165                             + " for device"
166                             + device);
167         }
168         return true;
169     }
170 
broadcastVendorSpecificEventIntent( int vendorId, String vendorEventCode, String vendorResponse, BluetoothDevice device)171     private void broadcastVendorSpecificEventIntent(
172             int vendorId, String vendorEventCode, String vendorResponse, BluetoothDevice device) {
173         Log.d(TAG, "broadcastVendorSpecificEventIntent(" + vendorResponse + ")");
174         Intent intent =
175                 new Intent(BluetoothHeadsetClient.ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT);
176         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, vendorId);
177         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE, vendorEventCode);
178         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS, vendorResponse);
179         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
180         mService.sendBroadcast(
181                 intent, BLUETOOTH_CONNECT, Utils.getTempBroadcastOptions().toBundle());
182     }
183 }
184