1 /* 2 * Copyright 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 inteface 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 36 class VendorCommandResponseProcessor { 37 38 private static final String TAG = "VendorCommandResponseProcessor"; 39 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 40 41 private final HeadsetClientService mService; 42 private final NativeInterface mNativeInterface; 43 44 // Keys are AT commands (without payload), and values are the company IDs. 45 private static final Map<String, Integer> SUPPORTED_VENDOR_AT_COMMANDS; 46 static { 47 SUPPORTED_VENDOR_AT_COMMANDS = new HashMap<>(); 48 SUPPORTED_VENDOR_AT_COMMANDS.put( 49 "+XAPL=", 50 BluetoothAssignedNumbers.APPLE); 51 SUPPORTED_VENDOR_AT_COMMANDS.put( 52 "+IPHONEACCEV=", 53 BluetoothAssignedNumbers.APPLE); 54 SUPPORTED_VENDOR_AT_COMMANDS.put( 55 "+APLSIRI?", 56 BluetoothAssignedNumbers.APPLE); 57 SUPPORTED_VENDOR_AT_COMMANDS.put( 58 "+APLEFM", 59 BluetoothAssignedNumbers.APPLE); 60 } 61 62 // Keys are AT events (without payload), and values are the company IDs. 63 private static final Map<String, Integer> SUPPORTED_VENDOR_EVENTS; 64 static { 65 SUPPORTED_VENDOR_EVENTS = new HashMap<>(); 66 SUPPORTED_VENDOR_EVENTS.put( 67 "+APLSIRI:", 68 BluetoothAssignedNumbers.APPLE); 69 SUPPORTED_VENDOR_EVENTS.put( 70 "+XAPL=", 71 BluetoothAssignedNumbers.APPLE); 72 } 73 VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface)74 VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface) { 75 mService = context; 76 mNativeInterface = nativeInterface; 77 } 78 sendCommand(int vendorId, String atCommand, BluetoothDevice device)79 public boolean sendCommand(int vendorId, String atCommand, BluetoothDevice device) { 80 if (device == null) { 81 Log.w(TAG, "processVendorCommand device is null"); 82 return false; 83 } 84 85 // Do not support more than one command at one line. 86 // We simplify and say no ; allowed as well. 87 int indexOfSemicolon = atCommand.indexOf(';'); 88 if (indexOfSemicolon > 0) { 89 Log.e(TAG, "Do not support ; and more than one command:" 90 + atCommand); 91 return false; 92 } 93 94 // Get command word 95 int indexOfEqual = atCommand.indexOf('='); 96 int indexOfQuestionMark = atCommand.indexOf('?'); 97 String commandWord; 98 if (indexOfEqual > 0) { 99 commandWord = atCommand.substring(0, indexOfEqual + 1); 100 } else if (indexOfQuestionMark > 0) { 101 commandWord = atCommand.substring(0, indexOfQuestionMark + 1); 102 } else { 103 commandWord = atCommand; 104 } 105 106 // replace all white spaces 107 commandWord = commandWord.replaceAll("\\s+", ""); 108 109 if (SUPPORTED_VENDOR_AT_COMMANDS.get(commandWord) != (Integer) (vendorId)) { 110 Log.e(TAG, "Invalid command " + atCommand + ", " + vendorId + ". Cand=" 111 + commandWord); 112 return false; 113 } 114 115 if (!mNativeInterface.sendATCmd(Utils.getBytesFromAddress(device.getAddress()), 116 HeadsetClientHalConstants 117 .HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD, 118 0, 0, atCommand)) { 119 Log.e(TAG, "Failed to send vendor specific at command"); 120 return false; 121 } 122 logD("Send vendor command: " + atCommand); 123 return true; 124 } 125 processEvent(String atString, BluetoothDevice device)126 public boolean processEvent(String atString, BluetoothDevice device) { 127 if (device == null) { 128 Log.w(TAG, "processVendorEvent device is null"); 129 return false; 130 } 131 132 // Get event code 133 int indexOfEqual = atString.indexOf('='); 134 int indexOfColon = atString.indexOf(':'); 135 String eventCode; 136 if (indexOfEqual > 0) { 137 eventCode = atString.substring(0, indexOfEqual + 1); 138 } else if (indexOfColon > 0) { 139 eventCode = atString.substring(0, indexOfColon + 1); 140 } else { 141 eventCode = atString; 142 } 143 144 // replace all white spaces 145 eventCode = eventCode.replaceAll("\\s+", ""); 146 147 Integer vendorId = SUPPORTED_VENDOR_EVENTS.get(eventCode); 148 if (vendorId == null) { 149 Log.e(TAG, "Invalid response: " + atString + ". " + eventCode); 150 return false; 151 } 152 broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device); 153 logD("process vendor event " + vendorId + ", " + eventCode + ", " 154 + atString + " for device" + device); 155 return true; 156 } 157 broadcastVendorSpecificEventIntent(int vendorId, String vendorEventCode, String vendorResponse, BluetoothDevice device)158 private void broadcastVendorSpecificEventIntent(int vendorId, String vendorEventCode, 159 String vendorResponse, BluetoothDevice device) { 160 logD("broadcastVendorSpecificEventIntent(" + vendorResponse + ")"); 161 Intent intent = new Intent(BluetoothHeadsetClient 162 .ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT); 163 intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, vendorId); 164 intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE, vendorEventCode); 165 intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS, vendorResponse); 166 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 167 mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); 168 } 169 logD(String msg)170 private void logD(String msg) { 171 if (DBG) { 172 Log.d(TAG, msg); 173 } 174 } 175 } 176