LOCAL_CERTIFICATE := platform
LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi
+LOCAL_JAVA_LIBRARIES := javax.obex telephony-common libprotobuf-java-2.3.0-micro
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi sap-api-java-static
LOCAL_REQUIRED_MODULES := bluetooth.default
LOCAL_MULTILIB := 32
</provider>
<service
android:process="@string/process"
+ android:name=".sap.SapService"
+ android:enabled="@bool/profile_supported_sap" >
+ <intent-filter>
+ <action android:name="android.bluetooth.IBluetoothSap" />
+ </intent-filter>
+ </service>
+ <service
+ android:process="@string/process"
android:name = ".gatt.GattService"
android:enabled="@bool/profile_supported_gatt">
<intent-filter>
<bool name="pbap_use_profile_for_owner_vcard">true</bool>
<bool name="profile_supported_map">true</bool>
<bool name="profile_supported_avrcp_controller">false</bool>
+ <bool name="profile_supported_sap">true</bool>
</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="bluetooth_sap_notif_title">Bluetooth SIM access</string>
+ <string name="bluetooth_sap_notif_ticker">Bluetooth SIM Access</string>
+ <string name="bluetooth_sap_notif_message">Request client to disconnect?</string>
+ <string name="bluetooth_sap_notif_disconnecting">Waiting for client to disconnect</string>
+ <string name="bluetooth_sap_notif_disconnect_button">Disconnect</string>
+ <string name="bluetooth_sap_notif_force_disconnect_button">Force disconnect</string>
+</resources>
"phonebook_access_permission";
private static final String MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE =
"message_access_permission";
+ private static final String SIM_ACCESS_PERMISSION_PREFERENCE_FILE =
+ "sim_access_permission";
private static final int ADAPTER_SERVICE_TYPE=Service.START_STICKY;
return service.setMessageAccessPermission(device, value);
}
+ public int getSimAccessPermission(BluetoothDevice device) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "getSimAccessPermission() - Not allowed for non-active user");
+ return BluetoothDevice.ACCESS_UNKNOWN;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return BluetoothDevice.ACCESS_UNKNOWN;
+ return service.getSimAccessPermission(device);
+ }
+
+ public boolean setSimAccessPermission(BluetoothDevice device, int value) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "setSimAccessPermission() - Not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) return false;
+ return service.setSimAccessPermission(device, value);
+ }
+
public void sendConnectionStateChange(BluetoothDevice
device, int profile, int state, int prevState) {
AdapterService service = getService();
return editor.commit();
}
+ int getSimAccessPermission(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ SharedPreferences pref = getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE,
+ Context.MODE_PRIVATE);
+ if (!pref.contains(device.getAddress())) {
+ return BluetoothDevice.ACCESS_UNKNOWN;
+ }
+ return pref.getBoolean(device.getAddress(), false)
+ ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_REJECTED;
+ }
+
+ boolean setSimAccessPermission(BluetoothDevice device, int value) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH PRIVILEGED permission");
+ SharedPreferences pref = getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ if (value == BluetoothDevice.ACCESS_UNKNOWN) {
+ editor.remove(device.getAddress());
+ } else {
+ editor.putBoolean(device.getAddress(), value == BluetoothDevice.ACCESS_ALLOWED);
+ }
+ return editor.commit();
+ }
+
void sendConnectionStateChange(BluetoothDevice
device, int profile, int state, int prevState) {
// TODO(BT) permission check?
bsm.start();
return bsm;
}
-
+
public void doQuit() {
quitNow();
}
BluetoothDevice.ACCESS_UNKNOWN);
mAdapterService.setMessageAccessPermission(dev,
BluetoothDevice.ACCESS_UNKNOWN);
+ mAdapterService.setSimAccessPermission(dev,
+ BluetoothDevice.ACCESS_UNKNOWN);
// Set the profile Priorities to undefined
clearProfilePriorty(dev);
}
import com.android.bluetooth.pan.PanService;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.map.BluetoothMapService;
+import com.android.bluetooth.sap.SapService;
public class Config {
private static final String TAG = "AdapterServiceConfig";
BluetoothMapService.class,
HeadsetClientService.class,
AvrcpControllerService.class,
+ SapService.class
};
/**
* Resource flag to indicate whether profile is supported or not.
R.bool.profile_supported_map,
R.bool.profile_supported_hfpclient,
R.bool.profile_supported_avrcp_controller,
+ R.bool.profile_supported_sap,
};
private static Class[] SUPPORTED_PROFILES = new Class[0];
--- /dev/null
+package com.android.bluetooth.sap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.android.btsap.SapApi;
+import org.android.btsap.SapApi.*;
+import com.google.protobuf.micro.*;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * SapMessage is used for incoming and outgoing messages.
+ *
+ * For incoming messages
+ *
+ */
+public class SapMessage {
+
+ public static final String TAG = "SapMessage";
+ public static final boolean DEBUG = SapService.DEBUG;
+ public static final boolean VERBOSE = SapService.VERBOSE;
+ public static final boolean TEST = SapService.PTS_TEST;
+
+ /* Message IDs - SAP specification */
+ public static final int ID_CONNECT_REQ = 0x00;
+ public static final int ID_CONNECT_RESP = 0x01;
+
+ public static final int ID_DISCONNECT_REQ = 0x02;
+ public static final int ID_DISCONNECT_RESP = 0x03;
+ public static final int ID_DISCONNECT_IND = 0x04;
+
+ public static final int ID_TRANSFER_APDU_REQ = 0x05;
+ public static final int ID_TRANSFER_APDU_RESP = 0x06;
+
+ public static final int ID_TRANSFER_ATR_REQ = 0x07;
+ public static final int ID_TRANSFER_ATR_RESP = 0x08;
+
+ public static final int ID_POWER_SIM_OFF_REQ = 0x09;
+ public static final int ID_POWER_SIM_OFF_RESP = 0x0A;
+
+ public static final int ID_POWER_SIM_ON_REQ = 0x0B;
+ public static final int ID_POWER_SIM_ON_RESP = 0x0C;
+
+ public static final int ID_RESET_SIM_REQ = 0x0D;
+ public static final int ID_RESET_SIM_RESP = 0x0E;
+
+ public static final int ID_TRANSFER_CARD_READER_STATUS_REQ = 0x0F;
+ public static final int ID_TRANSFER_CARD_READER_STATUS_RESP = 0x10;
+
+ public static final int ID_STATUS_IND = 0x11;
+ public static final int ID_ERROR_RESP = 0x12;
+
+ public static final int ID_SET_TRANSPORT_PROTOCOL_REQ = 0x13;
+ public static final int ID_SET_TRANSPORT_PROTOCOL_RESP = 0x14;
+
+ /* Message IDs - RIL specific unsolicited */
+ public static final int ID_RIL_BASE = 0x100; // First RIL message id
+ public static final int ID_RIL_UNSOL_CONNECTED = 0x100; // RIL_UNSOL_RIL_CONNECTED
+ public static final int ID_RIL_UNSOL_DISCONNECT_IND = 0x102; // A disconnect ind from RIL will be converted after handled locally
+ public static final int ID_RIL_UNKNOWN = 0x1ff; // All others
+
+ /* Message IDs - RIL specific solicited */
+ public static final int ID_RIL_GET_SIM_STATUS_REQ = 0x200; // RIL_REQUEST_GET_SIM_STATUS
+ /* Test signals used to set the reference ril in test mode */
+ public static final int ID_RIL_SIM_ACCESS_TEST_REQ = 0x201; // RIL_REQUEST_SIM_ACCESS_TEST
+ public static final int ID_RIL_SIM_ACCESS_TEST_RESP = 0x202; // response for RIL_REQUEST_SIM_ACCESS_TEST
+
+ /* Parameter IDs and lengths */
+ public static final int PARAM_MAX_MSG_SIZE_ID = 0x00;
+ public static final int PARAM_MAX_MSG_SIZE_LENGTH = 2;
+
+ public static final int PARAM_CONNECTION_STATUS_ID = 0x01;
+ public static final int PARAM_CONNECTION_STATUS_LENGTH = 1;
+
+ public static final int PARAM_RESULT_CODE_ID = 0x02;
+ public static final int PARAM_RESULT_CODE_LENGTH = 1;
+
+ public static final int PARAM_DISCONNECT_TYPE_ID = 0x03;
+ public static final int PARAM_DISCONNECT_TYPE_LENGTH = 1;
+
+ public static final int PARAM_COMMAND_APDU_ID = 0x04;
+
+ public static final int PARAM_COMMAND_APDU7816_ID = 0x10;
+
+ public static final int PARAM_RESPONSE_APDU_ID = 0x05;
+
+ public static final int PARAM_ATR_ID = 0x06;
+
+ public static final int PARAM_CARD_READER_STATUS_ID = 0x07;
+ public static final int PARAM_CARD_READER_STATUS_LENGTH = 1;
+
+ public static final int PARAM_STATUS_CHANGE_ID = 0x08;
+ public static final int PARAM_STATUS_CHANGE_LENGTH = 1;
+
+ public static final int PARAM_TRANSPORT_PROTOCOL_ID = 0x09;
+ public static final int PARAM_TRANSPORT_PROTOCOL_LENGTH = 1;
+
+ /* Result codes */
+ public static final int RESULT_OK = 0x00;
+ public static final int RESULT_ERROR_NO_REASON = 0x01;
+ public static final int RESULT_ERROR_CARD_NOT_ACCESSIBLE = 0x02;
+ public static final int RESULT_ERROR_CARD_POWERED_OFF = 0x03;
+ public static final int RESULT_ERROR_CARD_REMOVED = 0x04;
+ public static final int RESULT_ERROR_CARD_POWERED_ON = 0x05;
+ public static final int RESULT_ERROR_DATA_NOT_AVAILABLE = 0x06;
+ public static final int RESULT_ERROR_NOT_SUPPORTED = 0x07;
+
+ /* Connection Status codes */
+ public static final int CON_STATUS_OK = 0x00;
+ public static final int CON_STATUS_ERROR_CONNECTION = 0x01;
+ public static final int CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED = 0x02;
+ public static final int CON_STATUS_ERROR_MAX_MSG_SIZE_TOO_SMALL = 0x03;
+ public static final int CON_STATUS_OK_ONGOING_CALL = 0x04;
+
+ /* Disconnection type */
+ public static final int DISC_GRACEFULL = 0x00;
+ public static final int DISC_IMMEDIATE = 0x01;
+ public static final int DISC_FORCED = 0x100; // Used internal only
+ public static final int DISC_RFCOMM = 0x101; // Used internal only
+
+ /* Status Change */
+ public static final int STATUS_UNKNOWN_ERROR = 0x00;
+ public static final int STATUS_CARD_RESET = 0x01;
+ public static final int STATUS_CARD_NOT_ACCESSIBLE = 0x02;
+ public static final int STATUS_CARD_REMOVED = 0x03;
+ public static final int STATUS_CARD_INSERTED = 0x04;
+ public static final int STATUS_RECOVERED = 0x05;
+
+ /* Transport Protocol */
+ public static final int TRANS_PROTO_T0 = 0x00;
+ public static final int TRANS_PROTO_T1 = 0x01;
+
+ /* Test Mode */
+ public static final int TEST_MODE_DISABLE = 0x00;
+ public static final int TEST_MODE_ENABLE = 0x01;
+
+ /* Used to detect uninitialized values */
+ public static final int INVALID_VALUE = -1;
+
+ /* Stuff related to communicating with rild-bt */
+ static final int RESPONSE_SOLICITED = 0;
+ static final int RESPONSE_UNSOLICITED = 1;
+ static AtomicInteger sNextSerial = new AtomicInteger(1);
+
+ // Map<rilSerial, RequestType> - HashTable is synchronized
+ private static Map<Integer, Integer> sOngoingRequests = new Hashtable<Integer, Integer>();
+ private boolean mSendToRil = false; // set to true for messages that needs to go to through the RIL
+ private boolean mClearRilQueue = false; /* set to true for messages that needs to cause the
+ sOngoingRequests to be cleared. */
+
+ /* Instance members */
+ private int mMsgType = INVALID_VALUE; // The SAP message ID
+
+ private int mMaxMsgSize = INVALID_VALUE;
+ private int mConnectionStatus = INVALID_VALUE;
+ private int mResultCode = INVALID_VALUE;
+ private int mDisconnectionType = INVALID_VALUE;
+ private int mCardReaderStatus = INVALID_VALUE;
+ private int mStatusChange = INVALID_VALUE;
+ private int mTransportProtocol = INVALID_VALUE;
+ private int mTestMode = INVALID_VALUE;
+ private byte[] mApdu = null;
+ private byte[] mApdu7816 = null;
+ private byte[] mApduResp = null;
+ private byte[] mAtr = null;
+
+ /**
+ * Create a SapMessage
+ * @param msgType the SAP message type
+ */
+ public SapMessage(int msgType){
+ this.mMsgType = msgType;
+ }
+
+ private static void resetPendingRilMessages() {
+ int numMessages = sOngoingRequests.size();
+ if(numMessages != 0) {
+ Log.w(TAG, "Clearing message queue with size: " + numMessages);
+ sOngoingRequests.clear();
+ }
+ }
+
+ public static int getNumPendingRilMessages() {
+ return sOngoingRequests.size();
+ }
+
+ public int getMsgType() {
+ return mMsgType;
+ }
+
+ public void setMsgType(int msgType) {
+ this.mMsgType = msgType;
+ }
+
+ public int getMaxMsgSize() {
+ return mMaxMsgSize;
+ }
+
+ public void setMaxMsgSize(int maxMsgSize) {
+ this.mMaxMsgSize = maxMsgSize;
+ }
+
+ public int getConnectionStatus() {
+ return mConnectionStatus;
+ }
+
+ public void setConnectionStatus(int connectionStatus) {
+ this.mConnectionStatus = connectionStatus;
+ }
+
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ public void setResultCode(int resultCode) {
+ this.mResultCode = resultCode;
+ }
+
+ public int getDisconnectionType() {
+ return mDisconnectionType;
+ }
+
+ public void setDisconnectionType(int disconnectionType) {
+ this.mDisconnectionType = disconnectionType;
+ }
+
+ public int getCardReaderStatus() {
+ return mCardReaderStatus;
+ }
+
+ public void setCardReaderStatus(int cardReaderStatus) {
+ this.mCardReaderStatus = cardReaderStatus;
+ }
+
+ public int getStatusChange() {
+ return mStatusChange;
+ }
+
+ public void setStatusChange(int statusChange) {
+ this.mStatusChange = statusChange;
+ }
+
+ public int getTransportProtocol() {
+ return mTransportProtocol;
+ }
+
+ public void setTransportProtocol(int transportProtocol) {
+ this.mTransportProtocol = transportProtocol;
+ }
+
+ public byte[] getApdu() {
+ return mApdu;
+ }
+
+ public void setApdu(byte[] apdu) {
+ this.mApdu = apdu;
+ }
+
+ public byte[] getApdu7816() {
+ return mApdu7816;
+ }
+
+ public void setApdu7816(byte[] apdu) {
+ this.mApdu7816 = apdu;
+ }
+
+ public byte[] getApduResp() {
+ return mApduResp;
+ }
+
+ public void setApduResp(byte[] apduResp) {
+ this.mApduResp = apduResp;
+ }
+
+ public byte[] getAtr() {
+ return mAtr;
+ }
+
+ public void setAtr(byte[] atr) {
+ this.mAtr = atr;
+ }
+
+ public boolean getSendToRil() {
+ return mSendToRil;
+ }
+
+ public void setSendToRil(boolean sendToRil) {
+ this.mSendToRil = sendToRil;
+ }
+
+ public boolean getClearRilQueue() {
+ return mClearRilQueue;
+ }
+
+ public void setClearRilQueue(boolean clearRilQueue) {
+ this.mClearRilQueue = clearRilQueue;
+ }
+
+ public int getTestMode() {
+ return mTestMode;
+ }
+
+ public void setTestMode(int testMode) {
+ this.mTestMode = testMode;
+ }
+
+ private int getParamCount() {
+ int paramCount = 0;
+ if(mMaxMsgSize != INVALID_VALUE)
+ paramCount++;
+ if(mConnectionStatus != INVALID_VALUE)
+ paramCount++;
+ if(mResultCode != INVALID_VALUE)
+ paramCount++;
+ if(mDisconnectionType != INVALID_VALUE)
+ paramCount++;
+ if(mCardReaderStatus != INVALID_VALUE)
+ paramCount++;
+ if(mStatusChange != INVALID_VALUE)
+ paramCount++;
+ if(mTransportProtocol != INVALID_VALUE)
+ paramCount++;
+ if(mApdu != null)
+ paramCount++;
+ if(mApdu7816 != null)
+ paramCount++;
+ if(mApduResp != null)
+ paramCount++;
+ if(mAtr != null)
+ paramCount++;
+ return paramCount;
+ }
+
+ /**
+ * Construct a SapMessage based on the incoming rfcomm request.
+ * @param requestType The type of the request
+ * @param is the input stream to read the data from
+ * @return the resulting message, or null if an error occurs
+ */
+ @SuppressWarnings("unused")
+ public static SapMessage readMessage(int requestType, InputStream is) {
+ SapMessage newMessage = new SapMessage(requestType);
+
+ /* Read in all the parameters (if any) */
+ int paramCount;
+ try {
+ paramCount = is.read();
+ skip(is, 2); // Skip the 2 padding bytes
+ if(paramCount > 0) {
+ if(VERBOSE) Log.i(TAG, "Parsing message with paramCount: " + paramCount);
+ if(newMessage.parseParameters(paramCount, is) == false)
+ return null;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ if(DEBUG) Log.i(TAG, "readMessage() Read message: " + getMsgTypeName(requestType));
+
+ /* Validate parameters */
+ switch(requestType) {
+ case ID_CONNECT_REQ:
+ if(newMessage.getMaxMsgSize() == INVALID_VALUE) {
+ Log.e(TAG, "Missing MaxMsgSize parameter in CONNECT_REQ");
+ return null;
+ }
+ break;
+ case ID_TRANSFER_APDU_REQ:
+ if(newMessage.getApdu() == null &&
+ newMessage.getApdu7816() == null) {
+ Log.e(TAG, "Missing Apdu parameter in TRANSFER_APDU_REQ");
+ return null;
+ }
+ newMessage.setSendToRil(true);
+ break;
+ case ID_SET_TRANSPORT_PROTOCOL_REQ:
+ if(newMessage.getTransportProtocol() == INVALID_VALUE) {
+ Log.e(TAG, "Missing TransportProtocol parameter in SET_TRANSPORT_PROTOCOL_REQ");
+ return null;
+ }
+ newMessage.setSendToRil(true);
+ break;
+ case ID_TRANSFER_ATR_REQ: /* No params */
+ case ID_POWER_SIM_OFF_REQ: /* No params */
+ case ID_POWER_SIM_ON_REQ: /* No params */
+ case ID_RESET_SIM_REQ: /* No params */
+ case ID_TRANSFER_CARD_READER_STATUS_REQ: /* No params */
+ newMessage.setSendToRil(true);
+ break;
+ case ID_DISCONNECT_REQ: /* No params */
+ break;
+ default:
+ if(TEST == false) {
+ Log.e(TAG, "Unknown request type");
+ return null;
+ }
+ }
+ return newMessage;
+ }
+
+ /**
+ * Blocking read of an entire array of data.
+ * @param is the input stream to read from
+ * @param buffer the buffer to read into - the length of the buffer will
+ * determine how many bytes will be read.
+ */
+ private static void read(InputStream is, byte[] buffer) throws IOException {
+ int bytesToRead = buffer.length;
+ int bytesRead = 0;
+ int tmpBytesRead;
+ while (bytesRead < bytesToRead) {
+ tmpBytesRead = is.read(buffer, bytesRead, bytesToRead-bytesRead);
+ if(tmpBytesRead == -1)
+ throw new IOException("EOS reached while reading a byte array.");
+ else
+ bytesRead += tmpBytesRead;
+ }
+ }
+
+ /**
+ * Skip a number of bytes in an InputStream.
+ * @param is the input stream
+ * @param count the number of bytes to skip
+ * @throws IOException In case of reaching EOF or a stream error
+ */
+ private static void skip(InputStream is, int count) throws IOException {
+ for(int i = 0; i < count; i++) {
+ is.read(); // Do not use the InputStream.skip as it fails for some stream types
+ }
+ }
+
+ /**
+ * Read the parameters from the stream and update the relevant members.
+ * This function will ensure that all parameters are read from the stream, even
+ * if an error is detected.
+ * @param count the number of parameters to read
+ * @param is the input stream
+ * @return True if all parameters were successfully parsed, False if an error were detected.
+ * @throws IOException
+ */
+ private boolean parseParameters(int count, InputStream is) throws IOException {
+ int paramId;
+ int paramLength;
+ boolean success = true;
+ for(int i = 0; i < count; i++) {
+ paramId = is.read();
+ is.read(); // Skip the reserved byte
+ paramLength = is.read();
+ paramLength = paramLength << 8 | is.read();
+ if(VERBOSE) Log.i(TAG, "parsing paramId: " + paramId + " with length: " + paramLength);
+ switch(paramId) {
+ case PARAM_MAX_MSG_SIZE_ID:
+ if(paramLength != PARAM_MAX_MSG_SIZE_LENGTH) {
+ Log.e(TAG, "Received PARAM_MAX_MSG_SIZE with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ success = false;
+ } else {
+ mMaxMsgSize = is.read();
+ mMaxMsgSize = mMaxMsgSize << 8 | is.read();
+ skip(is, 4 - PARAM_MAX_MSG_SIZE_LENGTH);
+ }
+ break;
+ case PARAM_COMMAND_APDU_ID:
+ mApdu = new byte[paramLength];
+ read(is, mApdu);
+ skip(is, 4 - (paramLength % 4));
+ break;
+ case PARAM_COMMAND_APDU7816_ID:
+ mApdu7816 = new byte[paramLength];
+ read(is, mApdu7816);
+ skip(is, 4 - (paramLength % 4));
+ break;
+ case PARAM_TRANSPORT_PROTOCOL_ID:
+ if(paramLength != PARAM_TRANSPORT_PROTOCOL_LENGTH) {
+ Log.e(TAG, "Received PARAM_TRANSPORT_PROTOCOL with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ success = false;
+ } else {
+ mTransportProtocol = is.read();
+ skip(is, 4 - PARAM_TRANSPORT_PROTOCOL_LENGTH);
+ }
+ break;
+ case PARAM_CONNECTION_STATUS_ID:
+ // not needed - server -> client
+ if(TEST) {
+ if(paramLength != PARAM_CONNECTION_STATUS_LENGTH) {
+ Log.e(TAG, "Received PARAM_CONNECTION_STATUS with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ success = false;
+ } else {
+ mConnectionStatus = is.read();
+ skip(is, 4 - PARAM_CONNECTION_STATUS_LENGTH);
+ }
+ break;
+ } // Fall through if TEST == false
+ case PARAM_CARD_READER_STATUS_ID:
+ // not needed - server -> client
+ if(TEST) {
+ if(paramLength != PARAM_CARD_READER_STATUS_LENGTH) {
+ Log.e(TAG, "Received PARAM_CARD_READER_STATUS with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ success = false;
+ } else {
+ mCardReaderStatus = is.read();
+ skip(is, 4 - PARAM_CARD_READER_STATUS_LENGTH);
+ }
+ break;
+ } // Fall through if TEST == false
+ case PARAM_STATUS_CHANGE_ID:
+ // not needed - server -> client
+ if(TEST) {
+ if(paramLength != PARAM_STATUS_CHANGE_LENGTH) {
+ Log.e(TAG, "Received PARAM_STATUS_CHANGE with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ success = false;
+ } else {
+ mStatusChange = is.read();
+ skip(is, 4 - PARAM_STATUS_CHANGE_LENGTH);
+ }
+ break;
+ } // Fall through if TEST == false
+ case PARAM_RESULT_CODE_ID:
+ // not needed - server -> client
+ if(TEST) {
+ if(paramLength != PARAM_RESULT_CODE_LENGTH) {
+ Log.e(TAG, "Received PARAM_RESULT_CODE with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ success = false;
+ } else {
+ mResultCode = is.read();
+ skip(is, 4 - PARAM_RESULT_CODE_LENGTH);
+ }
+ break;
+ } // Fall through if TEST == false
+ case PARAM_DISCONNECT_TYPE_ID:
+ // not needed - server -> client
+ if(TEST) {
+ if(paramLength != PARAM_DISCONNECT_TYPE_LENGTH) {
+ Log.e(TAG, "Received PARAM_DISCONNECT_TYPE_ID with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ success = false;
+ } else {
+ mDisconnectionType = is.read();
+ skip(is, 4 - PARAM_DISCONNECT_TYPE_LENGTH);
+ }
+ break;
+ } // Fall through if TEST == false
+ case PARAM_RESPONSE_APDU_ID:
+ // not needed - server -> client
+ if(TEST) {
+ mApduResp = new byte[paramLength];
+ read(is, mApduResp);
+ skip(is, 4 - (paramLength % 4));
+ break;
+ } // Fall through if TEST == false
+ case PARAM_ATR_ID:
+ // not needed - server -> client
+ if(TEST) {
+ mAtr = new byte[paramLength];
+ read(is, mAtr);
+ skip(is, 4 - (paramLength % 4));
+ break;
+ } // Fall through if TEST == false
+ default:
+ Log.e(TAG, "Received unknown parameter ID: " + paramId + " length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + (4 - (paramLength % 4)));
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Writes a single value parameter of 1 or 2 bytes in length.
+ * @param os The BufferedOutputStream to write to.
+ * @param id The Parameter ID
+ * @param value The parameter value
+ * @param length The length of the parameter value
+ * @throws IOException if the write to os fails
+ */
+ private static void writeParameter(OutputStream os, int id, int value, int length)
+ throws IOException {
+
+ /* Parameter Header*/
+ os.write(id);
+ os.write(0);
+ os.write(0);
+ os.write(length);
+
+ switch(length) {
+ case 1:
+ os.write(value & 0xff);
+ os.write(0); // Padding
+ os.write(0); // Padding
+ os.write(0); // padding
+ break;
+ case 2:
+ os.write((value >> 8) & 0xff);
+ os.write(value & 0xff);
+ os.write(0); // Padding
+ os.write(0); // padding
+ break;
+ default:
+ throw new IOException("Unable to write value of length: " + length);
+ }
+ }
+
+ /**
+ * Writes a byte[] parameter of any length.
+ * @param os The BufferedOutputStream to write to.
+ * @param id The Parameter ID
+ * @param value The byte array to write, the length will be extracted from the array.
+ * @throws IOException if the write to os fails
+ */
+ private static void writeParameter(OutputStream os, int id, byte[] value) throws IOException {
+
+ /* Parameter Header*/
+ os.write(id);
+ os.write(0); // reserved
+ os.write((value.length >> 8) & 0xff);
+ os.write(value.length & 0xff);
+
+ /* Payload */
+ os.write(value);
+ for(int i = 0, n = 4 - (value.length % 4) ; i < n; i++) {
+ os.write(0); // Padding
+ }
+ }
+
+ public void write(OutputStream os) throws IOException {
+ /* Write the header */
+ os.write(mMsgType);
+ os.write(getParamCount());
+ os.write(0); // padding
+ os.write(0); // padding
+
+ /* write the parameters */
+ if(mConnectionStatus != INVALID_VALUE) {
+ writeParameter(os,PARAM_CONNECTION_STATUS_ID, mConnectionStatus,
+ PARAM_CONNECTION_STATUS_LENGTH);
+ }
+ if(mMaxMsgSize != INVALID_VALUE) {
+ writeParameter(os, PARAM_MAX_MSG_SIZE_ID, mMaxMsgSize,
+ PARAM_MAX_MSG_SIZE_LENGTH);
+ }
+ if(mResultCode != INVALID_VALUE) {
+ writeParameter(os, PARAM_RESULT_CODE_ID, mResultCode,
+ PARAM_RESULT_CODE_LENGTH);
+ }
+ if(mDisconnectionType != INVALID_VALUE && TEST) {
+ writeParameter(os, PARAM_DISCONNECT_TYPE_ID, mDisconnectionType,
+ PARAM_DISCONNECT_TYPE_LENGTH);
+ }
+ if(mCardReaderStatus != INVALID_VALUE) {
+ writeParameter(os, PARAM_CARD_READER_STATUS_ID, mCardReaderStatus,
+ PARAM_CARD_READER_STATUS_LENGTH);
+ }
+ if(mStatusChange != INVALID_VALUE) {
+ writeParameter(os, PARAM_STATUS_CHANGE_ID, mStatusChange,
+ PARAM_STATUS_CHANGE_LENGTH);
+ }
+ if(mTransportProtocol != INVALID_VALUE && TEST) {
+ writeParameter(os, PARAM_TRANSPORT_PROTOCOL_ID, mTransportProtocol,
+ PARAM_TRANSPORT_PROTOCOL_LENGTH);
+ }
+ if(mApdu != null && TEST) {
+ writeParameter(os, PARAM_COMMAND_APDU_ID, mApdu);
+ }
+ if(mApdu7816 != null && TEST) {
+ writeParameter(os, PARAM_COMMAND_APDU7816_ID, mApdu7816);
+ }
+ if(mApduResp != null) {
+ writeParameter(os, PARAM_RESPONSE_APDU_ID, mApduResp);
+ }
+ if(mAtr != null) {
+ writeParameter(os, PARAM_ATR_ID, mAtr);
+ }
+ }
+
+ /***************************************************************************
+ * RILD Interface message conversion functions.
+ ***************************************************************************/
+
+ /**
+ * We use this function to
+ * @param length
+ * @param rawOut
+ * @throws IOException
+ */
+ private void writeLength(int length, CodedOutputStreamMicro out) throws IOException {
+ byte[] dataLength = new byte[4];
+ dataLength[0] = dataLength[1] = 0;
+ dataLength[2] = (byte)((length >> 8) & 0xff);
+ dataLength[3] = (byte)((length) & 0xff);
+ out.writeRawBytes(dataLength);
+ }
+ /**
+ * Write this SAP message as a rild compatible protobuf message.
+ * Solicited Requests are formed as follows:
+ * int type - the rild-bt type
+ * int serial - an number incrementing for each message.
+ */
+ public void writeReqToStream(CodedOutputStreamMicro out) throws IOException {
+
+ int rilSerial = sNextSerial.getAndIncrement();
+ SapApi.MsgHeader msg = new MsgHeader();
+ /* Common variables for all requests */
+ msg.setToken(rilSerial);
+ msg.setType(SapApi.REQUEST);
+ msg.setError(SapApi.RIL_E_UNUSED);
+
+ switch(mMsgType) {
+ case ID_CONNECT_REQ:
+ {
+ SapApi.RIL_SIM_SAP_CONNECT_REQ reqMsg = new RIL_SIM_SAP_CONNECT_REQ();
+ reqMsg.setMaxMessageSize(mMaxMsgSize);
+ msg.setId(SapApi.RIL_SIM_SAP_CONNECT);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_DISCONNECT_REQ:
+ {
+ SapApi.RIL_SIM_SAP_DISCONNECT_REQ reqMsg = new RIL_SIM_SAP_DISCONNECT_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_DISCONNECT);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_TRANSFER_APDU_REQ:
+ {
+ SapApi.RIL_SIM_SAP_APDU_REQ reqMsg = new RIL_SIM_SAP_APDU_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_APDU);
+ if(mApdu != null) {
+ reqMsg.setType(SapApi.RIL_SIM_SAP_APDU_REQ.RIL_TYPE_APDU);
+ reqMsg.setCommand(ByteStringMicro.copyFrom(mApdu));
+ } else if (mApdu7816 != null) {
+ reqMsg.setType(SapApi.RIL_SIM_SAP_APDU_REQ.RIL_TYPE_APDU7816);
+ reqMsg.setCommand(ByteStringMicro.copyFrom(mApdu7816));
+ } else {
+ Log.e(TAG, "Missing Apdu parameter in TRANSFER_APDU_REQ");
+ throw new IllegalArgumentException();
+ }
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_SET_TRANSPORT_PROTOCOL_REQ:
+ {
+ SapApi.RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_REQ reqMsg =
+ new RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_SET_TRANSFER_PROTOCOL);
+
+ if(mTransportProtocol == TRANS_PROTO_T0) {
+ reqMsg.setProtocol(SapApi.RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_REQ.t0);
+ } else if(mTransportProtocol == TRANS_PROTO_T1) {
+ reqMsg.setProtocol(SapApi.RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_REQ.t1);
+ } else {
+ Log.e(TAG, "Missing or invalid TransportProtocol parameter in"+
+ " SET_TRANSPORT_PROTOCOL_REQ: "+ mTransportProtocol );
+ throw new IllegalArgumentException();
+ }
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_TRANSFER_ATR_REQ:
+ {
+ SapApi.RIL_SIM_SAP_TRANSFER_ATR_REQ reqMsg = new RIL_SIM_SAP_TRANSFER_ATR_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_TRANSFER_ATR);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_POWER_SIM_OFF_REQ:
+ {
+ SapApi.RIL_SIM_SAP_POWER_REQ reqMsg = new RIL_SIM_SAP_POWER_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_POWER);
+ reqMsg.setState(false);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_POWER_SIM_ON_REQ:
+ {
+ SapApi.RIL_SIM_SAP_POWER_REQ reqMsg = new RIL_SIM_SAP_POWER_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_POWER);
+ reqMsg.setState(true);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_RESET_SIM_REQ:
+ {
+ SapApi.RIL_SIM_SAP_RESET_SIM_REQ reqMsg = new RIL_SIM_SAP_RESET_SIM_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_RESET_SIM);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ case ID_TRANSFER_CARD_READER_STATUS_REQ:
+ {
+ SapApi.RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_REQ reqMsg =
+ new RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_REQ();
+ msg.setId(SapApi.RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLength(msg.getSerializedSize(), out);
+ msg.writeTo(out);
+ break;
+ }
+ default:
+ if(TEST == false) {
+ Log.e(TAG, "Unknown request type");
+ throw new IllegalArgumentException();
+ }
+ }
+ /* Update the ongoing requests queue */
+ if(mClearRilQueue == true) {
+ resetPendingRilMessages();
+ }
+ // No need to synchronize this, as the HashList is already doing this.
+ sOngoingRequests.put(rilSerial, mMsgType);
+ out.flush();
+ }
+
+ public static SapMessage newInstance(MsgHeader msg) throws IOException {
+ return new SapMessage(msg);
+ }
+
+ private SapMessage(MsgHeader msg) throws IOException {
+ // All header members are "required" hence the hasXxxx() is not needed for those
+ try{
+ switch(msg.getType()){
+ case SapApi.UNSOL_RESPONSE:
+ createUnsolicited(msg);
+ break;
+ case SapApi.RESPONSE:
+ createSolicited(msg);
+ break;
+ default:
+ throw new IOException("Wrong msg header received: Type: " + msg.getType());
+ }
+ } catch (InvalidProtocolBufferMicroException e) {
+ Log.w(TAG, "Error occured parsing a RIL message", e);
+ throw new IOException("Error occured parsing a RIL message");
+ }
+ }
+
+ private void createUnsolicited(MsgHeader msg)
+ throws IOException, InvalidProtocolBufferMicroException {
+ switch(msg.getId()) {
+// TODO:
+// Not sure when we use these? case RIL_UNSOL_RIL_CONNECTED:
+// if(VERBOSE) Log.i(TAG, "RIL_UNSOL_RIL_CONNECTED received, ignoring");
+// msgType = ID_RIL_UNSOL_CONNECTED;
+// break;
+ case SapApi.RIL_SIM_SAP_STATUS:
+ {
+ if(VERBOSE) Log.i(TAG, "RIL_SIM_SAP_STATUS_IND received");
+ RIL_SIM_SAP_STATUS_IND indMsg =
+ RIL_SIM_SAP_STATUS_IND.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_STATUS_IND;
+ if(indMsg.hasStatusChange()) {
+ setStatusChange(indMsg.getStatusChange());
+ if(VERBOSE) Log.i(TAG, "RIL_UNSOL_SIM_SAP_STATUS_IND received value = " + mStatusChange);
+ } else {
+ if(VERBOSE) Log.i(TAG, "Wrong number of parameters in SAP_STATUS_IND, ignoring...");
+ mMsgType = ID_RIL_UNKNOWN;
+ }
+ break;
+ }
+ case SapApi.RIL_SIM_SAP_DISCONNECT:
+ {
+ if(VERBOSE) Log.i(TAG, "RIL_SIM_SAP_DISCONNECT_IND received");
+
+ RIL_SIM_SAP_DISCONNECT_IND indMsg =
+ RIL_SIM_SAP_DISCONNECT_IND.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_RIL_UNSOL_DISCONNECT_IND; // don't use ID_DISCONNECT_IND;
+ if(indMsg.hasDisconnectType()) {
+ setDisconnectionType(indMsg.getDisconnectType());
+ if(VERBOSE) Log.i(TAG, "RIL_UNSOL_SIM_SAP_STATUS_IND received value = "
+ + mDisconnectionType);
+ } else {
+ if(VERBOSE) Log.i(TAG, "Wrong number of parameters in SAP_STATUS_IND, ignoring...");
+ mMsgType = ID_RIL_UNKNOWN;
+ }
+ break;
+ }
+ default:
+ if(VERBOSE) Log.i(TAG, "Unused unsolicited message received, ignoring: " + msg.getId());
+ mMsgType = ID_RIL_UNKNOWN;
+ }
+ }
+
+ private void createSolicited(MsgHeader msg) throws IOException,
+ InvalidProtocolBufferMicroException{
+ /* re-evaluate if we should just ignore these - we could simply catch the exception? */
+ if(msg.hasToken() == false) throw new IOException("Token is missing");
+ if(msg.hasError() == false) throw new IOException("Error code is missing");
+ int serial = msg.getToken();
+ int error = msg.getError();
+ Integer reqType = null;
+ reqType = sOngoingRequests.remove(serial);
+ if(VERBOSE) Log.i(TAG, "RIL SOLICITED serial: " + serial + ", error: " + error
+ + " SapReqType: " + ((reqType== null)?"null":getMsgTypeName(reqType)));
+
+ if(reqType == null) {
+ /* This can happen if we get a resp. for a canceled request caused by a power off,
+ * reset or disconnect
+ */
+ Log.w(TAG, "Solicited response received on a command not initiated - ignoring.");
+ return;
+ }
+ mResultCode = mapRilErrorCode(error);
+
+ switch(reqType) {
+ case ID_CONNECT_REQ:
+ {
+ RIL_SIM_SAP_CONNECT_RSP resMsg =
+ RIL_SIM_SAP_CONNECT_RSP.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_CONNECT_RESP;
+ if(resMsg.hasMaxMessageSize()) {
+ mMaxMsgSize = resMsg.getMaxMessageSize();
+
+ }
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SUCCESS:
+ mConnectionStatus = CON_STATUS_OK;
+ break;
+ case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_CONNECT_OK_CALL_ONGOING:
+ mConnectionStatus = CON_STATUS_OK_ONGOING_CALL;
+ break;
+ case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_CONNECT_FAILURE:
+ mConnectionStatus = CON_STATUS_ERROR_CONNECTION;
+ break;
+ case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_MSG_SIZE_TOO_LARGE:
+ mConnectionStatus = CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED;
+ break;
+ case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_MSG_SIZE_TOO_SMALL:
+ mConnectionStatus = CON_STATUS_ERROR_MAX_MSG_SIZE_TOO_SMALL;
+ break;
+ default:
+ mConnectionStatus = CON_STATUS_ERROR_CONNECTION; // Cannot happen!
+ break;
+ }
+ mResultCode = INVALID_VALUE;
+ if(VERBOSE) Log.v(TAG, " ID_CONNECT_REQ: mMaxMsgSize: " + mMaxMsgSize
+ + " mConnectionStatus: " + mConnectionStatus);
+ break;
+ }
+ case ID_DISCONNECT_REQ:
+ mMsgType = ID_DISCONNECT_RESP;
+ mResultCode = INVALID_VALUE;
+ break;
+ case ID_TRANSFER_APDU_REQ:
+ {
+ RIL_SIM_SAP_APDU_RSP resMsg =
+ RIL_SIM_SAP_APDU_RSP.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_TRANSFER_APDU_RESP;
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_APDU_RSP.RIL_E_SUCCESS:
+ mResultCode = RESULT_OK;
+ /* resMsg.getType is unused as the client knows the type of request used. */
+ if(resMsg.hasApduResponse()){
+ mApduResp = resMsg.getApduResponse().toByteArray();
+ }
+ break;
+ case RIL_SIM_SAP_APDU_RSP.RIL_E_GENERIC_FAILURE:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_ABSENT:
+ mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+ break;
+ case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+ mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+ break;
+ case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_NOT_READY:
+ mResultCode = RESULT_ERROR_CARD_REMOVED;
+ break;
+ default:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ }
+ break;
+ }
+ case ID_SET_TRANSPORT_PROTOCOL_REQ:
+ {
+ RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP resMsg =
+ RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.parseFrom(
+ msg.getPayload().toByteArray());
+ mMsgType = ID_SET_TRANSPORT_PROTOCOL_RESP;
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SUCCESS:
+ mResultCode = RESULT_OK;
+ break;
+ case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_GENERIC_FAILURE:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_ABSENT:
+ mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+ break;
+ case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+ mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+ break;
+ case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_NOT_READY:
+ mResultCode = RESULT_ERROR_CARD_REMOVED;
+ break;
+ default:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ }
+ break;
+ }
+ case ID_TRANSFER_ATR_REQ:
+ {
+ RIL_SIM_SAP_TRANSFER_ATR_RSP resMsg =
+ RIL_SIM_SAP_TRANSFER_ATR_RSP.parseFrom(msg.getPayload().toByteArray());
+ mMsgType =ID_TRANSFER_ATR_RESP;
+ if(resMsg.hasAtr()) {
+ mAtr = resMsg.getAtr().toByteArray();
+ }
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SUCCESS:
+ mResultCode = RESULT_OK;
+ break;
+ case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_GENERIC_FAILURE:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ABSENT:
+ mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+ break;
+ case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+ mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+ break;
+ case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
+ mResultCode = RESULT_ERROR_CARD_POWERED_ON;
+ break;
+ case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_DATA_NOT_AVAILABLE:
+ mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
+ break;
+ default:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ }
+ break;
+ }
+ case ID_POWER_SIM_OFF_REQ:
+ {
+ RIL_SIM_SAP_POWER_RSP resMsg =
+ RIL_SIM_SAP_POWER_RSP.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_POWER_SIM_OFF_RESP;
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SUCCESS:
+ mResultCode = RESULT_OK;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_GENERIC_FAILURE:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ABSENT:
+ mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+ mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
+ mResultCode = RESULT_ERROR_CARD_POWERED_ON;
+ break;
+ default:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ }
+ break;
+ }
+ case ID_POWER_SIM_ON_REQ:
+ {
+ RIL_SIM_SAP_POWER_RSP resMsg =
+ RIL_SIM_SAP_POWER_RSP.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_POWER_SIM_ON_RESP;
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SUCCESS:
+ mResultCode = RESULT_OK;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_GENERIC_FAILURE:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ABSENT:
+ mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+ mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+ break;
+ case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
+ mResultCode = RESULT_ERROR_CARD_POWERED_ON;
+ break;
+ default:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ }
+ break;
+ }
+ case ID_RESET_SIM_REQ:
+ {
+ RIL_SIM_SAP_RESET_SIM_RSP resMsg =
+ RIL_SIM_SAP_RESET_SIM_RSP.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_RESET_SIM_RESP;
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SUCCESS:
+ mResultCode = RESULT_OK;
+ break;
+ case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_GENERIC_FAILURE:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SIM_ABSENT:
+ mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+ break;
+ case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+ mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+ break;
+ default:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ }
+ break;
+ }
+ case ID_TRANSFER_CARD_READER_STATUS_REQ:
+ {
+ RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP resMsg =
+ RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.parseFrom(msg.getPayload().toByteArray());
+ mMsgType = ID_TRANSFER_CARD_READER_STATUS_RESP;
+ switch(resMsg.getResponse()) {
+ case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_SUCCESS:
+ mResultCode = RESULT_OK;
+ if(resMsg.hasCardReaderStatus()) {
+ mCardReaderStatus = resMsg.getCardReaderStatus();
+ } else {
+ mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
+ }
+ break;
+ case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_GENERIC_FAILURE:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_SIM_DATA_NOT_AVAILABLE:
+ mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
+ break;
+ default:
+ mResultCode = RESULT_ERROR_NO_REASON;
+ break;
+ }
+ break;
+ }
+
+ case ID_RIL_SIM_ACCESS_TEST_REQ: // TODO: implement in RILD
+ mMsgType = ID_RIL_SIM_ACCESS_TEST_RESP;
+ break;
+ default:
+ Log.e(TAG, "Unknown request type: " + reqType);
+
+ }
+ }
+
+
+
+ /* Map from RIL header error codes to SAP error codes */
+ private static int mapRilErrorCode(int rilErrorCode) {
+ switch(rilErrorCode) {
+ case SapApi.RIL_E_SUCCESS:
+ return RESULT_OK;
+ case SapApi.RIL_E_CANCELLED:
+ return RESULT_ERROR_NO_REASON;
+ case SapApi.RIL_E_GENERIC_FAILURE:
+ return RESULT_ERROR_NO_REASON;
+ case SapApi.RIL_E_RADIO_NOT_AVAILABLE:
+ return RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+ case SapApi.RIL_E_INVALID_PARAMETER:
+ return RESULT_ERROR_NO_REASON;
+ case SapApi.RIL_E_REQUEST_NOT_SUPPORTED:
+ return RESULT_ERROR_NOT_SUPPORTED;
+ default:
+ return RESULT_ERROR_NO_REASON;
+ }
+ }
+
+
+
+ public static String getMsgTypeName(int msgType) {
+ if(TEST || VERBOSE) {
+ switch (msgType)
+ {
+ case ID_CONNECT_REQ: return "ID_CONNECT_REQ";
+ case ID_CONNECT_RESP: return "ID_CONNECT_RESP";
+ case ID_DISCONNECT_REQ: return "ID_DISCONNECT_REQ";
+ case ID_DISCONNECT_RESP: return "ID_DISCONNECT_RESP";
+ case ID_DISCONNECT_IND: return "ID_DISCONNECT_IND";
+ case ID_TRANSFER_APDU_REQ: return "ID_TRANSFER_APDU_REQ";
+ case ID_TRANSFER_APDU_RESP: return "ID_TRANSFER_APDU_RESP";
+ case ID_TRANSFER_ATR_REQ: return "ID_TRANSFER_ATR_REQ";
+ case ID_TRANSFER_ATR_RESP: return "ID_TRANSFER_ATR_RESP";
+ case ID_POWER_SIM_OFF_REQ: return "ID_POWER_SIM_OFF_REQ";
+ case ID_POWER_SIM_OFF_RESP: return "ID_POWER_SIM_OFF_RESP";
+ case ID_POWER_SIM_ON_REQ: return "ID_POWER_SIM_ON_REQ";
+ case ID_POWER_SIM_ON_RESP: return "ID_POWER_SIM_ON_RESP";
+ case ID_RESET_SIM_REQ: return "ID_RESET_SIM_REQ";
+ case ID_RESET_SIM_RESP: return "ID_RESET_SIM_RESP";
+ case ID_TRANSFER_CARD_READER_STATUS_REQ: return "ID_TRANSFER_CARD_READER_STATUS_REQ";
+ case ID_TRANSFER_CARD_READER_STATUS_RESP: return "ID_TRANSFER_CARD_READER_STATUS_RESP";
+ case ID_STATUS_IND: return "ID_STATUS_IND";
+ case ID_ERROR_RESP: return "ID_ERROR_RESP";
+ case ID_SET_TRANSPORT_PROTOCOL_REQ: return "ID_SET_TRANSPORT_PROTOCOL_REQ";
+ case ID_SET_TRANSPORT_PROTOCOL_RESP: return "ID_SET_TRANSPORT_PROTOCOL_RESP";
+ case ID_RIL_UNSOL_CONNECTED: return "ID_RIL_UNSOL_CONNECTED";
+ case ID_RIL_UNKNOWN: return "ID_RIL_UNKNOWN";
+ case ID_RIL_GET_SIM_STATUS_REQ: return "ID_RIL_GET_SIM_STATUS_REQ";
+ case ID_RIL_SIM_ACCESS_TEST_REQ: return "ID_RIL_SIM_ACCESS_TEST_REQ";
+ case ID_RIL_SIM_ACCESS_TEST_RESP: return "ID_RIL_SIM_ACCESS_TEST_RESP";
+ default: return "Unknown Message Type (" + msgType + ")";
+ }
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+package com.android.bluetooth.sap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.android.btsap.SapApi.MsgHeader;
+
+import com.google.protobuf.micro.CodedInputStreamMicro;
+import com.google.protobuf.micro.CodedOutputStreamMicro;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+public class SapRilReceiver implements Runnable {
+
+ private static final String TAG = "SapRilReceiver";
+ public static final boolean DEBUG = true;
+ public static final boolean VERBOSE = true;
+
+ private static final String SOCKET_NAME_RIL_BT = "sap_uim_socket1";
+ // match with constant in ril.cpp - as in RIL.java
+ private static final int SOCKET_OPEN_RETRY_MILLIS = 4 * 1000;
+
+ LocalSocket mSocket = null;
+ CodedOutputStreamMicro mRilBtOutStream = null;
+ InputStream mRilBtInStream = null;
+ private Handler mSapServerMsgHandler = null;
+
+ public static final int RIL_MAX_COMMAND_BYTES = (8 * 1024);
+ byte[] buffer = new byte[RIL_MAX_COMMAND_BYTES];
+
+ public SapRilReceiver(Handler SapServerMsgHandler) {
+ mSapServerMsgHandler = SapServerMsgHandler;
+ }
+
+ /**
+ * Open the RIL-BT socket in rild. Will continuously try to open the BT socket until
+ * success. (Based on the approach used to open the rild socket in telephony)
+ * @return The socket handle
+ */
+ public static LocalSocket openRilBtSocket() {
+ int retryCount = 0;
+ LocalSocket rilSocket = null;
+
+ for (;;) {
+ LocalSocketAddress address;
+
+ try {
+ rilSocket = new LocalSocket();
+ address = new LocalSocketAddress(SOCKET_NAME_RIL_BT,
+ LocalSocketAddress.Namespace.RESERVED);
+ rilSocket.connect(address);
+ break; // Socket opened
+ } catch (IOException ex){
+ try {
+ if (rilSocket != null) {
+ rilSocket.close();
+ }
+ } catch (IOException ex2) {
+ //ignore failure to close after failure to connect
+ }
+
+ // don't print an error message after the the first time
+ // or after the 8th time
+ if (retryCount == 8) {
+ Log.e (TAG,
+ "Couldn't find '" + SOCKET_NAME_RIL_BT
+ + "' socket after " + retryCount
+ + " times, continuing to retry silently");
+ } else if (retryCount > 0 && retryCount < 8) {
+ Log.i (TAG,
+ "Couldn't find '" + SOCKET_NAME_RIL_BT
+ + "' socket; retrying after timeout");
+ if(VERBOSE) Log.w(TAG, ex);
+ }
+
+ try {
+ Thread.sleep(SOCKET_OPEN_RETRY_MILLIS);
+ } catch (InterruptedException er) {
+ }
+
+ retryCount++;
+ continue;
+ }
+ }
+ return rilSocket;
+ }
+
+
+ public CodedOutputStreamMicro getRilBtOutStream() {
+ return mRilBtOutStream;
+ }
+
+ private void onConnectComplete() {
+ if(mSapServerMsgHandler != null)
+ mSapServerMsgHandler.sendEmptyMessage(SapServer.SAP_MSG_RIL_CONNECT);
+ }
+
+ /**
+ * This will terminate the SapRilReceiver thread, by closing the RIL-BT in-/output
+ * streams.
+ */
+ public void shutdown() {
+ if(DEBUG) Log.i(TAG, "shutdown()");
+
+ /* On Android you need to close the IOstreams using Socket.shutdown*
+ * The IOstream close must not be used, as it some how decouples the
+ * stream from the socket, and when the socket is closed, the pending
+ * reads never return nor throw and exception.
+ * Hence here we use the shutdown method: */
+ if(mSocket != null) {
+ try {
+ mSocket.shutdownOutput();
+ } catch (IOException e) {}
+ try {
+ mSocket.shutdownInput();
+ } catch (IOException e) {}
+ try {
+ mSocket.close();
+ } catch (IOException ex) {
+ if(VERBOSE) Log.e(TAG,"Uncaught exception", ex);
+ }
+ mSocket = null;
+ }
+ }
+
+ /**
+ * Read the message into buffer
+ * @param is
+ * @param buffer
+ * @return the length of the message
+ * @throws IOException
+ */
+ private static int readMessage(InputStream is, byte[] buffer) throws IOException {
+ int countRead;
+ int offset;
+ int remaining;
+ int messageLength;
+
+ // Read in the length of the message
+ offset = 0;
+ remaining = 4;
+ do {
+ countRead = is.read(buffer, offset, remaining);
+
+ if (countRead < 0 ) {
+ Log.e(TAG, "Hit EOS reading message length");
+ return -1;
+ }
+
+ offset += countRead;
+ remaining -= countRead;
+ } while (remaining > 0);
+
+ messageLength = ((buffer[0] & 0xff) << 24)
+ | ((buffer[1] & 0xff) << 16)
+ | ((buffer[2] & 0xff) << 8)
+ | (buffer[3] & 0xff);
+ if(VERBOSE) Log.e(TAG,"Message length found to be: "+messageLength);
+ // Read the message
+ offset = 0;
+ remaining = messageLength;
+ do {
+ countRead = is.read(buffer, offset, remaining);
+
+ if (countRead < 0 ) {
+ Log.e(TAG, "Hit EOS reading message. messageLength=" + messageLength
+ + " remaining=" + remaining);
+ return -1;
+ }
+
+ offset += countRead;
+ remaining -= countRead;
+ } while (remaining > 0);
+
+ return messageLength;
+ }
+
+ /**
+ * The RIL reader thread. Will handle open of the RIL-BT socket, and notify
+ * SapServer when done.
+ */
+ @Override
+ public void run() {
+
+ try {
+ int length = 0;
+ if(VERBOSE) Log.i(TAG, "Starting RilBtReceiverThread...");
+
+ mSocket = openRilBtSocket();
+ mRilBtInStream = mSocket.getInputStream();
+ mRilBtOutStream = CodedOutputStreamMicro.newInstance(mSocket.getOutputStream());
+
+ // Notify the SapServer that we have connected to the RilBtSocket
+ onConnectComplete();
+
+ // The main loop - read messages and forward to SAP server
+ for (;;) {
+ SapMessage sapMsg = null;
+ MsgHeader rilMsg;
+
+
+ if(VERBOSE) Log.i(TAG, "Waiting for incoming message...");
+ length = readMessage(mRilBtInStream, buffer);
+ CodedInputStreamMicro msgStream = CodedInputStreamMicro.newInstance(buffer, 0, length);
+ rilMsg = MsgHeader.parseFrom(msgStream);
+
+ if(VERBOSE) Log.i(TAG, "Message received.");
+
+ sapMsg = SapMessage.newInstance(rilMsg);
+
+ if(sapMsg != null && sapMsg.getMsgType() != SapMessage.INVALID_VALUE)
+ {
+ if(sapMsg.getMsgType() < SapMessage.ID_RIL_BASE) {
+ sendClientMessage(sapMsg);
+ } else {
+ sendRilIndMessage(sapMsg);
+ }
+ } // else simply ignore it
+ }
+ } catch (IOException e) {
+ shutdown(); /* Only needed in case of a connection error */
+ Log.i(TAG, "'" + SOCKET_NAME_RIL_BT + "' socket inputStream closed", e);
+ }
+ finally {
+ Log.i(TAG, "Disconnected from '" + SOCKET_NAME_RIL_BT + "' socket");
+ }
+ }
+
+ /**
+ * Send message to the Sap Server Handler Thread
+ * @param sapMsg The message to send
+ */
+ private void sendClientMessage(SapMessage sapMsg) {
+ Message newMsg = mSapServerMsgHandler.obtainMessage(SapServer.SAP_MSG_RFC_REPLY, sapMsg);
+ mSapServerMsgHandler.sendMessage(newMsg);
+ }
+
+ private void sendRilIndMessage(SapMessage sapMsg) {
+ Message newMsg = mSapServerMsgHandler.obtainMessage(SapServer.SAP_MSG_RIL_IND, sapMsg);
+ mSapServerMsgHandler.sendMessage(newMsg);
+ }
+
+}
--- /dev/null
+package com.android.bluetooth.sap;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+
+import com.android.bluetooth.R;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SyncResult;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+//import com.android.internal.telephony.RIL;
+import com.google.protobuf.micro.CodedOutputStreamMicro;
+
+
+/**
+ * The SapServer uses two threads, one for reading messages from the RFCOMM socket and
+ * one for writing the responses.
+ * Incoming requests are decoded in the "main" SapServer, by the decoder build into SapMEssage.
+ * The relevant RIL calls are made from the message handler thread through the rild-bt socket.
+ * The RIL replies are read in the SapRilReceiver, and passed to the SapServer message handler
+ * to be written to the RFCOMM socket.
+ * All writes to the RFCOMM and rild-bt socket must be synchronized, hence to write e.g. an error
+ * response, send a message to the Sap Handler thread. (There are helper functions to do this)
+ * Communication to the RIL is through an intent, and a BroadcastReceiver.
+ */
+public class SapServer extends Thread implements Callback {
+ private static final String TAG = "SapServer";
+ private static final String TAG_HANDLER = "SapServerHandler";
+ public static final boolean DEBUG = SapService.DEBUG;
+ public static final boolean VERBOSE = SapService.VERBOSE;
+ public static final boolean PTS_TEST = SapService.PTS_TEST;
+
+ private enum SAP_STATE {
+ DISCONNECTED, CONNECTING, CONNECTING_CALL_ONGOING, CONNECTED,
+ CONNECTED_BUSY, DISCONNECTING;
+ }
+
+ private SAP_STATE mState = SAP_STATE.DISCONNECTED;
+
+ private Context mContext = null;
+ /* RFCOMM socket I/O streams */
+ private BufferedOutputStream mRfcommOut = null;
+ private BufferedInputStream mRfcommIn = null;
+ /* The RIL output stream - the input stream is owned by the SapRilReceiver object */
+ private CodedOutputStreamMicro mRilBtOutStream = null;
+ /* References to the SapRilReceiver object */
+ private SapRilReceiver mRilBtReceiver = null;
+ private Thread mRilBtReceiverThread = null;
+ /* The message handler members */
+ private Handler mSapHandler = null;
+ private HandlerThread mHandlerThread = null;
+ /* Reference to the SAP service - which created this instance of the SAP server */
+ private Handler mSapServiceHandler = null;
+
+ /* flag for when user forces disconnect of rfcomm */
+ private boolean mIsLocalInitDisconnect = false;
+ private CountDownLatch mDeinitSignal = new CountDownLatch(1);
+
+ /* Message ID's handled by the message handler */
+ public static final int SAP_MSG_RFC_REPLY = 0x00;
+ public static final int SAP_MSG_RIL_CONNECT = 0x01;
+ public static final int SAP_MSG_RIL_REQ = 0x02;
+ public static final int SAP_MSG_RIL_IND = 0x04;
+
+ public static final String SAP_DISCONNECT_ACTION = "com.android.bluetooth.sap.action.DISCONNECT_ACTION";
+ public static final String SAP_DISCONNECT_TYPE_EXTRA = "com.android.bluetooth.sap.extra.DISCONNECT_TYPE";
+ public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
+ private static final int DISCONNECT_TIMEOUT_IMMEDIATE = 5000; /* ms */
+ private static final int DISCONNECT_TIMEOUT_RFCOMM = 2000; /* ms */
+ private PendingIntent pDiscIntent = null; // Holds a reference to disconnect timeout intents
+ /* These are just used to evaluate the maxMessageSize which we are able to handle in the SAP profile.
+ * The RIL may set other limits, but this will be handled by the SAP connect request send ti the RIL */
+// TODO: REMOVE private static final int MAX_MAX_MESSAGE_SIZE = SapRilReceiver.RIL_MAX_COMMAND_BYTES;
+ /* We store the mMaxMessageSize, as we need a copy of it when the init. sequence completes */
+ private int mMaxMsgSize = 0;
+ /* keep track of the current RIL test mode */
+ private int mTestMode = SapMessage.INVALID_VALUE; // used to set the RIL in test mode
+
+
+ /**
+ * SapServer constructor
+ * @param serviceHandler The handler to send a SapService.MSG_SERVERSESSION_CLOSE when closing
+ * @param inStream The socket input stream
+ * @param outStream The socket output stream
+ */
+ public SapServer(Handler serviceHandler, Context context, InputStream inStream, OutputStream outStream) {
+ mContext = context;
+ mSapServiceHandler = serviceHandler;
+
+ /* Open in- and output streams */
+ mRfcommIn = new BufferedInputStream(inStream);
+ mRfcommOut = new BufferedOutputStream(outStream);
+
+ /* Register for phone state change and the RIL cfm message */
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+ filter.addAction(SAP_DISCONNECT_ACTION);
+ mContext.registerReceiver(mIntentReceiver, filter);
+ }
+
+ /**
+ * This handles the response from RIL.
+ */
+ BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if(intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
+ if(VERBOSE) Log.i(TAG, "ACTION_PHONE_STATE_CHANGED intent received in state "
+ + mState.name()
+ + "PhoneState: "
+ + intent.getStringExtra(TelephonyManager.EXTRA_STATE));
+ if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+ String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+ if(state != null) {
+ if(state.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
+ if(DEBUG) Log.i(TAG, "sending RIL.ACTION_RIL_RECONNECT_OFF_REQ intent");
+ // TODO: Send connect request to RIL
+ SapMessage fakeConReq = new SapMessage(SapMessage.ID_CONNECT_REQ);
+ fakeConReq.setMaxMsgSize(mMaxMsgSize);
+ onConnectRequest(fakeConReq);
+ }
+ }
+ }
+ } else if (intent.getAction().equals(SAP_DISCONNECT_ACTION)
+ && mState != SAP_STATE.DISCONNECTED
+ && mState != SAP_STATE.DISCONNECTING ) {
+
+ int disconnectType = intent.getIntExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,SapMessage.DISC_GRACEFULL);
+ Log.v(TAG, " - Received SAP_DISCONNECT_ACTION type: " + disconnectType);
+ sendDisconnectInd(disconnectType);
+
+ } else {
+ Log.w(TAG, "RIL-BT received unexpected Intent: " + intent.getAction());
+ }
+ }
+ };
+
+ /**
+ * Set RIL driver in test mode - only possible if SapMessage is build with TEST == true
+ * The value set by this function will take effect at the next connect request received
+ * in DISCONNECTED state.
+ * @param testMode Use SapMessage.TEST_MODE_XXX
+ */
+ public void setTestMode(int testMode) {
+ if(SapMessage.TEST) {
+ mTestMode = testMode;
+ }
+ }
+
+ private void sendDisconnectInd(int discType) {
+ if(VERBOSE) Log.v(TAG, "in sendDisconnectInd()");
+
+ if(discType != SapMessage.DISC_FORCED){
+ if(VERBOSE) Log.d(TAG, "Sending disconnect ("+discType+") indication to client");
+ /* Send disconnect to client */
+ SapMessage discInd = new SapMessage(SapMessage.ID_DISCONNECT_IND);
+ discInd.setDisconnectionType(discType);
+ sendClientMessage(discInd);
+
+ /* Handle local disconnect procedures */
+ if (discType == SapMessage.DISC_GRACEFULL)
+ {
+ /* Update the notification to allow the user to initiate a force disconnect */
+ setNotification(SapMessage.DISC_IMMEDIATE, PendingIntent.FLAG_CANCEL_CURRENT);
+
+ } else if (discType == SapMessage.DISC_IMMEDIATE){
+ /* Request an immediate disconnect, but start a timer to force disconnect if the client
+ * do not obey our request. */
+ startDisconnectTimer(SapMessage.DISC_FORCED, DISCONNECT_TIMEOUT_IMMEDIATE);
+ }
+
+ } else {
+ SapMessage msg = new SapMessage(SapMessage.ID_DISCONNECT_REQ);
+ /* Force disconnect of RFCOMM - but first we need to clean up. */
+ clearPendingRilResponses(msg);
+
+ // We simply need to forward to RIL, but not change state to busy - hence send and set message to null.
+ changeState(SAP_STATE.DISCONNECTING);
+ sendRilThreadMessage(msg);
+ mIsLocalInitDisconnect = true;
+ }
+ }
+
+
+ void setNotification(int type, int flags)
+ {
+ String title, text, button, ticker;
+ Notification notification;
+ if(VERBOSE) Log.i(TAG, "setNotification type: " + type);
+ /* put notification up for the user to be able to disconnect from the client*/
+ Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
+ if(type == SapMessage.DISC_GRACEFULL){
+ title = mContext.getString(R.string.bluetooth_sap_notif_title);
+ button = mContext.getString(R.string.bluetooth_sap_notif_disconnect_button);
+ text = mContext.getString(R.string.bluetooth_sap_notif_message);
+ ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
+ }else{
+ title = mContext.getString(R.string.bluetooth_sap_notif_title);
+ button = mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button);
+ text = mContext.getString(R.string.bluetooth_sap_notif_disconnecting);
+ ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
+ }
+ if(!PTS_TEST)
+ {
+ sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, type);
+ PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext, type, sapDisconnectIntent,flags);
+ notification = new Notification.Builder(mContext).setOngoing(true)
+ .addAction(android.R.drawable.stat_sys_data_bluetooth, button, pIntentDisconnect)
+ .setContentTitle(title)
+ .setTicker(ticker)
+ .setContentText(text)
+ .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+ .setAutoCancel(false)
+ .setPriority(Notification.PRIORITY_MAX)
+ .setOnlyAlertOnce(true)
+ .build();
+ }else{
+
+ sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_GRACEFULL);
+ Intent sapForceDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
+ sapForceDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_IMMEDIATE);
+ PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext, SapMessage.DISC_GRACEFULL, sapDisconnectIntent,flags);
+ PendingIntent pIntentForceDisconnect = PendingIntent.getBroadcast(mContext, SapMessage.DISC_IMMEDIATE, sapForceDisconnectIntent,flags);
+ notification = new Notification.Builder(mContext).setOngoing(true)
+ .addAction(android.R.drawable.stat_sys_data_bluetooth, mContext.getString(R.string.bluetooth_sap_notif_disconnect_button), pIntentDisconnect)
+ .addAction(android.R.drawable.stat_sys_data_bluetooth, mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button), pIntentForceDisconnect)
+ .setContentTitle(title)
+ .setTicker(ticker)
+ .setContentText(text)
+ .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+ .setAutoCancel(false)
+ .setPriority(Notification.PRIORITY_MAX)
+ .setOnlyAlertOnce(true)
+ .build();
+ }
+
+
+ notification.flags |= Notification.FLAG_NO_CLEAR |Notification.FLAG_ONLY_ALERT_ONCE; /* cannot be set with the builder */
+
+ NotificationManager notificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ notificationManager.notify(NOTIFICATION_ID, notification);
+ }
+ /**
+ * The SapServer RFCOMM reader thread. Sets up the handler thread and handle
+ * all read from the RFCOMM in-socket. This thread also handle writes to the RIL socket.
+ */
+ @Override
+ public void run() {
+ try {
+ /* SAP is not time critical, hence lowering priority to ensure critical tasks are executed
+ * in a timely manner. */
+ android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
+
+ /* Start the SAP message handler thread */
+ mHandlerThread = new HandlerThread("SapServerHandler", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ Looper sapLooper = mHandlerThread.getLooper(); /* This will return when the looper is ready */
+ mSapHandler = new Handler(sapLooper, this);
+
+ mRilBtReceiver = new SapRilReceiver(mSapHandler);
+ mRilBtReceiverThread = new Thread(mRilBtReceiver, "RilBtReceiver");
+ setNotification(SapMessage.DISC_GRACEFULL,0);
+ boolean done = false;
+ while (!done) {
+ if(VERBOSE) Log.i(TAG, "Waiting for incomming RFCOMM message...");
+ int requestType = mRfcommIn.read();
+ if(requestType == -1) {
+ done = true; // EOF reached
+ } else {
+ SapMessage msg = SapMessage.readMessage(requestType, mRfcommIn);
+ if(msg != null && mState != SAP_STATE.DISCONNECTING)
+ {
+ switch (requestType) {
+ case SapMessage.ID_CONNECT_REQ:
+ if(VERBOSE) Log.d(TAG, "CONNECT_REQ - MaxMsgSize: " + msg.getMaxMsgSize());
+ onConnectRequest(msg);
+ msg = null; /* don't send ril connect yet */
+ break;
+ case SapMessage.ID_DISCONNECT_REQ: /* No params */
+ /*
+ * 1) send RIL_REQUEST_SIM_SAP_DISCONNECT (block for all incoming requests, as they are not
+ * allowed, don't even send an error_resp)
+ * 2) on response disconnect ril socket.
+ * 3) when disconnected send RIL.ACTION_RIL_RECONNECT_OFF_REQ
+ * 4) on RIL.ACTION_RIL_RECONNECT_CFM send SAP_DISCONNECT_RESP to client.
+ * 5) Start RFCOMM disconnect timer
+ * 6.a) on rfcomm disconnect: cancel timer and initiate cleanup
+ * 6.b) on rfcomm disc. timeout: close socket-streams and initiate cleanup */
+ if(VERBOSE) Log.d(TAG, "DISCONNECT_REQ");
+
+ clearPendingRilResponses(msg);
+ // We simply need to forward to RIL, but not change state to busy - hence send and set message to null.
+ changeState(SAP_STATE.DISCONNECTING); /* do not enter disconnecting state for disconnect_ind
+ - we need to obtain normal operation until disconnect is received. */
+ sendRilThreadMessage(msg);
+ msg = null; // don't send twice
+ /*cancel the timer for the hard-disconnect intent*/
+ stopDisconnectTimer();
+ break;
+ case SapMessage.ID_POWER_SIM_OFF_REQ: // Fall through
+ case SapMessage.ID_RESET_SIM_REQ:
+ /* Forward these to the RIL regardless of the state, and clear any pending resp */
+ clearPendingRilResponses(msg);
+ break;
+ default:
+ /* remaining cases just needs to be forwarded to the RIL unless we are in busy state. */
+ if(mState != SAP_STATE.CONNECTED) {
+ Log.w(TAG, "Message received in STATE != CONNECTED - state = " + mState.name());
+ /* We shall only handle one request at the time, hence return error */
+ SapMessage atrReply = new SapMessage(SapMessage.ID_ERROR_RESP);
+ sendClientMessage(atrReply);
+ msg = null;
+ }
+ }
+
+ if(msg != null && msg.getSendToRil() == true) {
+ changeState(SAP_STATE.CONNECTED_BUSY);
+ sendRilThreadMessage(msg);
+ }
+
+ } else { /* An unknown message or in disconnecting state - send error indication */
+ Log.e(TAG, "Unable to parse message.");
+ SapMessage atrReply = new SapMessage(SapMessage.ID_ERROR_RESP);
+ sendClientMessage(atrReply);
+ }
+ }
+ } // end while
+ } catch (NullPointerException e) {
+ Log.w(TAG, e);
+ } catch (IOException e) {
+ /* This is expected during shutdown */
+ Log.i(TAG, "IOException received, this is probably a shutdown signal, cleaning up...");
+ } catch (Exception e) {
+ /* TODO: Change to the needed Exception types when done testing */
+ Log.w(TAG, e);
+ } finally {
+ // Do cleanup even if an exception occurs
+ stopDisconnectTimer();
+ /* In case of e.g. a RFCOMM close while connected:
+ * - Initiate a FORCED shutdown
+ * - Wait for RIL deinit to complete
+ */
+ if(mState != SAP_STATE.DISCONNECTED) {
+ if(mState != SAP_STATE.DISCONNECTING &&
+ mIsLocalInitDisconnect != true) {
+ sendDisconnectInd(SapMessage.DISC_FORCED);
+ }
+ if(DEBUG) Log.i(TAG, "Waiting for deinit to complete");
+ try {
+ mDeinitSignal.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupt received while waitinf for de-init to complete", e);
+ }
+ }
+ mContext.unregisterReceiver(mIntentReceiver);
+ if(mHandlerThread != null) try {
+ mHandlerThread.quit();
+ mHandlerThread.join();
+ } catch (InterruptedException e) {}
+ if(mRilBtReceiverThread != null) try {
+ mRilBtReceiverThread.join();
+ } catch (InterruptedException e) {}
+
+ if(mRfcommIn != null) try {
+ if(VERBOSE) Log.i(TAG, "Closing mRfcommIn...");
+ mRfcommIn.close();
+ } catch (IOException e) {}
+
+ if(mRfcommOut != null) try {
+ if(VERBOSE) Log.i(TAG, "Closing mRfcommOut...");
+ mRfcommOut.close();
+ } catch (IOException e) {}
+
+ if (mSapServiceHandler != null) {
+ Message msg = Message.obtain(mSapServiceHandler);
+ msg.what = SapService.MSG_SERVERSESSION_CLOSE;
+ msg.sendToTarget();
+ if (DEBUG) Log.d(TAG, "MSG_SERVERSESSION_CLOSE sent out.");
+ }
+ Log.i(TAG, "All done exiting thread...");
+ }
+ }
+
+
+ /**
+ * This function needs to determine:
+ * - if the maxMsgSize is acceptable - else reply CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED + new maxMsgSize if too big
+ * - connect to the RIL-BT socket
+ * - if a call is ongoing reply CON_STATUS_OK_ONGOING_CALL.
+ * - if all ok, just respond CON_STATUS_OK.
+ *
+ * @param msg the incoming SapMessage
+ */
+ private void onConnectRequest(SapMessage msg) {
+ SapMessage reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
+
+ if(mState == SAP_STATE.CONNECTING) {
+ /* A connect request might have been rejected because of maxMessageSize negotiation, and
+ * this is a new connect request. Simply forward to RIL, and stay in connecting state.
+ * */
+ reply = null;
+ sendRilMessage(msg);
+ stopDisconnectTimer();
+
+ } else if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.CONNECTING_CALL_ONGOING) {
+ reply.setConnectionStatus(SapMessage.CON_STATUS_ERROR_CONNECTION);
+ } else {
+ // Store the MaxMsgSize for future use
+ mMaxMsgSize = msg.getMaxMsgSize();
+ /* All parameters OK, examine if a call is ongoing and start the RIL-BT listener thread. */
+ if (isCallOngoing() == true) {
+ /* If a call is ongoing we set the state, inform the SAP client and wait for a state change
+ * intent from the TelephonyManager with state IDLE. */
+ changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
+ reply.setConnectionStatus(SapMessage.CON_STATUS_OK_ONGOING_CALL);
+ } else {
+ /* no call is ongoing, initiate the connect sequence:
+ * 1) Start the SapRilReceiver thread (open the rild-bt socket)
+ * 2) Send a RIL_SIM_SAP_CONNECT request to RILD
+ * 3) Send a RIL_SIM_RESET request and a connect confirm to the SAP client */
+ changeState(SAP_STATE.CONNECTING);
+ if(mRilBtReceiverThread != null) {
+ /* Open the RIL socket, and wait for the complete message: SAP_MSG_RIL_CONNECT */
+ mRilBtReceiverThread.start();
+ // Don't send reply yet
+ reply = null;
+ } else {
+ reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
+ reply.setConnectionStatus(SapMessage.CON_STATUS_ERROR_CONNECTION);
+ sendClientMessage(reply);
+ }
+ }
+ }
+ if(reply != null)
+ sendClientMessage(reply);
+ }
+
+ private void clearPendingRilResponses(SapMessage msg) {
+ if(mState == SAP_STATE.CONNECTED_BUSY) {
+ msg.setClearRilQueue(true);
+ }
+ }
+ /**
+ * Send RFCOMM message to the Sap Server Handler Thread
+ * @param sapMsg The message to send
+ */
+ private void sendClientMessage(SapMessage sapMsg) {
+ Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RFC_REPLY, sapMsg);
+ mSapHandler.sendMessage(newMsg);
+ }
+
+ /**
+ * Send a RIL message to the SapServer message handler thread
+ * @param sapMsg
+ */
+ private void sendRilThreadMessage(SapMessage sapMsg) {
+ Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RIL_REQ, sapMsg);
+ mSapHandler.sendMessage(newMsg);
+ }
+
+ /**
+ * Examine if a call is ongoing, by asking the telephony manager
+ * @return false if the phone is IDLE (can be used for SAP), true otherwise.
+ */
+ private boolean isCallOngoing() {
+ TelephonyManager tManager =(TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ if(tManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Change the SAP Server state.
+ * We add thread protection, as we access the state from two threads.
+ * @param newState
+ */
+ private void changeState(SAP_STATE newState) {
+ if(DEBUG) Log.i(TAG_HANDLER,"Changing state from " + mState.name() +
+ " to " + newState.name());
+ synchronized (this) {
+ mState = newState;
+ }
+ }
+
+
+ /*************************************************************************
+ * SAP Server Message Handler Thread Functions
+ *************************************************************************/
+
+ /**
+ * The SapServer message handler thread implements the SAP state machine.
+ * - Handle all outgoing communication to the out-socket. Either replies from the RIL or direct
+ * messages send from the SapServe (e.g. connect_resp).
+ * - Handle all outgoing communication to the RIL-BT socket.
+ * - Handle all replies from the RIL
+ */
+ @Override
+ public boolean handleMessage(Message msg) {
+ if(VERBOSE) Log.i(TAG_HANDLER,"Handling message (ID: " + msg.what + "): " + getMessageName(msg.what));
+
+ SapMessage sapMsg = null;
+
+ switch(msg.what) {
+ case SAP_MSG_RFC_REPLY:
+ sapMsg = (SapMessage) msg.obj;
+ handleRfcommReply(sapMsg);
+ break;
+ case SAP_MSG_RIL_CONNECT:
+ /* The connection to rild-bt have been established. Store the outStream handle
+ * and send the connect request. */
+ mRilBtOutStream = mRilBtReceiver.getRilBtOutStream();
+ if(mTestMode != SapMessage.INVALID_VALUE) {
+ SapMessage rilTestModeReq = new SapMessage(SapMessage.ID_RIL_SIM_ACCESS_TEST_REQ);
+ rilTestModeReq.setTestMode(mTestMode);
+ sendRilMessage(rilTestModeReq);
+ mTestMode = SapMessage.INVALID_VALUE;
+ }
+ SapMessage rilSapConnect = new SapMessage(SapMessage.ID_CONNECT_REQ);
+ rilSapConnect.setMaxMsgSize(mMaxMsgSize);
+ sendRilMessage(rilSapConnect);
+ break;
+ case SAP_MSG_RIL_REQ:
+ sapMsg = (SapMessage) msg.obj;
+ if(sapMsg != null) {
+ sendRilMessage(sapMsg);
+ }
+ break;
+ case SAP_MSG_RIL_IND:
+ sapMsg = (SapMessage) msg.obj;
+ handleRilInd(sapMsg);
+ break;
+ default:
+ /* Message not handled */
+ return false;
+ }
+ return true; // Message handles
+ }
+
+ /**
+ * Close the in/out rfcomm streams, to trigger a shutdown of the SapServer main thread.
+ * Use this after completing the deinit sequence.
+ */
+ private void shutdown() {
+
+ if(DEBUG) Log.i(TAG_HANDLER, "in Shutdown()");
+ try {
+ mRfcommOut.close();
+ } catch (IOException e) {}
+ try {
+ mRfcommIn.close();
+
+ } catch (IOException e) {}
+ mRfcommIn = null;
+ mRfcommOut = null;
+ stopDisconnectTimer();
+ }
+
+ private void startDisconnectTimer(int discType, int timeMs) {
+
+ stopDisconnectTimer();
+ synchronized (this) {
+ Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
+ sapDisconnectIntent.putExtra(SAP_DISCONNECT_TYPE_EXTRA, discType);
+ AlarmManager alarmManager =
+ (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ pDiscIntent = PendingIntent.getBroadcast(mContext,
+ discType,
+ sapDisconnectIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + timeMs, pDiscIntent);
+
+ if(VERBOSE) Log.d(TAG_HANDLER, "Setting alarm for " + timeMs +
+ " ms to activate disconnect type " + discType);
+ }
+ }
+
+ private void stopDisconnectTimer() {
+ synchronized (this) {
+ if(pDiscIntent != null)
+ {
+ AlarmManager alarmManager =
+ (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ alarmManager.cancel(pDiscIntent);
+ pDiscIntent.cancel();
+ if(VERBOSE) {
+ Log.d(TAG_HANDLER, "Canceling disconnect alarm");
+ }
+ pDiscIntent = null;
+ }
+ }
+ }
+
+ /**
+ * Here we handle the replies to the SAP client, normally forwarded directly from the RIL.
+ * We do need to handle some of the messages in the SAP profile, hence we look at the messages
+ * here before they go to the client
+ * @param sapMsg the message to send to the SAP client
+ */
+ private void handleRfcommReply(SapMessage sapMsg) {
+ if(sapMsg != null) {
+
+ if(DEBUG) Log.i(TAG_HANDLER, "handleRfcommReply() handling " + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
+
+ switch(sapMsg.getMsgType()) {
+
+ case SapMessage.ID_CONNECT_RESP:
+ if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
+ // This is successful connect response from RIL/modem.
+ changeState(SAP_STATE.CONNECTED);
+ } else if(sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK_ONGOING_CALL
+ && mState != SAP_STATE.CONNECTING_CALL_ONGOING) {
+ changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
+ } else if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+ // Hold back the connect resp if a call was ongoing when the connect req was received.
+ if(VERBOSE) Log.i(TAG, "Hold back the connect resp, as a call was ongoing when the initial response were sent.");
+ sapMsg = null;
+ } else if(sapMsg.getConnectionStatus() != SapMessage.CON_STATUS_OK) {
+ /* Most likely the peer will try to connect again, hence we keep the connection to RIL open
+ * and stay in connecting state.
+ */
+ // Start timer to do shutdown if a new connect request is not received in time
+ startDisconnectTimer(SapMessage.DISC_FORCED, DISCONNECT_TIMEOUT_RFCOMM);
+ }
+ break;
+ case SapMessage.ID_DISCONNECT_RESP:
+ if(mState == SAP_STATE.DISCONNECTING) {
+ /* Close the RIL-BT output Stream and signal to SapRilReceiver to close down the input stream. */
+ if(DEBUG) Log.i(TAG, "ID_DISCONNECT_RESP received in SAP_STATE.DISCONNECTING" +
+ " - shutdown bt-ril ");
+ /* We need to close down the RilBtReceiverThread before signaling to RILJ
+ * that it can re-open the socket to RILC. */
+ mRilBtReceiver.shutdown();
+ mRilBtOutStream = null;
+ try {
+ mRilBtReceiverThread.join();
+ } catch (InterruptedException e) {
+ Log.e(TAG_HANDLER, "Exception occured while waiting for thread to exit.", e);
+ }
+ mRilBtReceiverThread = null;
+
+ // Wait for the thread to close, as we need the socket to be closed before signaling the RIL to reopen.
+ // Disconnecting
+ // Send the disconnect resp, and wait for the client to close the Rfcomm, but start a
+ // timeout timer, just to be sure. Use alarm, to ensure we wake the host to close the
+ // connection to minimize power consumption.
+ SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
+ changeState(SAP_STATE.DISCONNECTED);
+ sapMsg = disconnectResp;
+ //since we are disconnected we remove the notification
+ NotificationManager notificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(SapServer.NOTIFICATION_ID);
+ } else { /* DISCONNECTED */
+ mDeinitSignal.countDown(); /* Signal deinit complete */
+ if(mIsLocalInitDisconnect == true) {
+ if(VERBOSE) Log.i(TAG_HANDLER, "This is a FORCED disconnect.");
+ /* We needed to force the disconnect, hence no hope for the client to close
+ * the RFCOMM connection, hence we do it here. */
+ shutdown();
+ sapMsg = null;
+ } else {
+ /* The client must disconnect the RFCOMM, but in case it does not, we need to do it.
+ * We start an alarm, and if it triggers, we must send the MSG_SERVERSESSION_CLOSE */
+ if(VERBOSE) Log.i(TAG_HANDLER, "This is a NORMAL disconnect.");
+ startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
+ }
+ }
+ break;
+ case SapMessage.ID_STATUS_IND:
+ /* Some car-kits only "likes" status indication when connected, hence discard
+ * any arriving outside this state */
+ if(mState == SAP_STATE.DISCONNECTED ||
+ mState == SAP_STATE.CONNECTING ||
+ mState == SAP_STATE.DISCONNECTING) {
+ sapMsg = null;
+ }
+ break;
+ default:
+ // Nothing special, just send the message
+ }
+ }
+
+ /* Update state variable based on the number of pending commands. We are only able to
+ * handle one request at the time, except from disconnect, sim off and sim reset.
+ * Hence if one of these are received while in busy state, we might have a crossing
+ * response, hence we must stay in BUSY state if we have an ongoing RIL request. */
+ if(mState == SAP_STATE.CONNECTED_BUSY) {
+ if(SapMessage.getNumPendingRilMessages() == 0) {
+ changeState(SAP_STATE.CONNECTED);
+ }
+ }
+
+ // This is the default case - just send the message to the SAP client.
+ if(sapMsg != null)
+ sendReply(sapMsg);
+ }
+
+ private void handleRilInd(SapMessage sapMsg) {
+ if(sapMsg == null)
+ return;
+
+ switch(sapMsg.getMsgType()) {
+ case SapMessage.ID_DISCONNECT_IND:
+ {
+ if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING){
+ /* we only send disconnect indication to the client if we are actually connected*/
+ SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_IND);
+ reply.setDisconnectionType(sapMsg.getDisconnectionType()) ;
+ sendClientMessage(reply);
+ } else {
+ /* TODO: This was introduced to handle disconnect indication from RIL */
+ sendDisconnectInd(sapMsg.getDisconnectionType());
+ }
+ break;
+ }
+
+ default:
+ if(DEBUG) Log.w(TAG_HANDLER,"Unhandled message - type: " + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
+ }
+ }
+
+ /**
+ * This is only to be called from the handlerThread, else use sendRilThreadMessage();
+ * @param sapMsg
+ */
+ private void sendRilMessage(SapMessage sapMsg) {
+ if(VERBOSE) Log.i(TAG_HANDLER, "sendRilMessage() - " + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
+ try {
+ sapMsg.writeReqToStream(mRilBtOutStream);
+ } catch (IOException e) {
+ Log.e(TAG_HANDLER, "Unable to send message to RIL", e);
+ SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
+ sendClientMessage(errorReply);
+ }
+ }
+
+ /**
+ * Only call this from the sapHandler thread.
+ */
+ private void sendReply(SapMessage msg) {
+ if(VERBOSE) Log.i(TAG_HANDLER, "sendReply() RFCOMM - " + SapMessage.getMsgTypeName(msg.getMsgType()));
+ try {
+ msg.write(mRfcommOut);
+ mRfcommOut.flush();
+ } catch (IOException e) {
+ Log.w(TAG_HANDLER, e);
+ }
+ }
+
+ private static String getMessageName(int messageId) {
+ switch (messageId) {
+ case SAP_MSG_RFC_REPLY:
+ return "SAP_MSG_REPLY";
+ case SAP_MSG_RIL_CONNECT:
+ return "SAP_MSG_RIL_CONNECT";
+ case SAP_MSG_RIL_REQ:
+ return "SAP_MSG_RIL_REQ";
+ case SAP_MSG_RIL_IND:
+ return "SAP_MSG_RIL_IND";
+ default:
+ return "Unknown message ID";
+ }
+ }
+
+}
--- /dev/null
+package com.android.bluetooth.sap;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import android.annotation.TargetApi;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothSap;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.BluetoothSap;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
+
+@TargetApi(Build.VERSION_CODES.ECLAIR)
+public class SapService extends ProfileService {
+
+ private static final String TAG = "SapService";
+ public static final boolean DEBUG = true;
+ public static final boolean VERBOSE = true;
+ public static final boolean PTS_TEST = true;
+
+ /* Message ID's */
+ private static final int START_LISTENER = 1;
+ private static final int USER_TIMEOUT = 2;
+ private static final int SHUTDOWN = 3;
+
+ public static final int MSG_SERVERSESSION_CLOSE = 5000;
+ public static final int MSG_SESSION_ESTABLISHED = 5001;
+ public static final int MSG_SESSION_DISCONNECTED = 5002;
+
+ /* Intent indicating timeout for user confirmation. */
+ public static final String USER_CONFIRM_TIMEOUT_ACTION =
+ "com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT";
+ private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;
+
+ private PowerManager.WakeLock mWakeLock = null;
+ private BluetoothAdapter mAdapter;
+ private SocketAcceptThread mAcceptThread = null;
+ private BluetoothServerSocket mServerSocket = null;
+ private BluetoothSocket mConnSocket = null;
+ private BluetoothDevice mRemoteDevice = null;
+ private static String sRemoteDeviceName = null;
+ private volatile boolean mInterrupted;
+ private int mState;
+ private SapServer mSapServer = null;
+ private AlarmManager mAlarmManager = null;
+ private boolean mRemoveTimeoutMsg = false;
+
+ private boolean mIsWaitingAuthorization = false;
+
+ // package and class name to which we send intent to check message access access permission
+ private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
+ private static final String ACCESS_AUTHORITY_CLASS =
+ "com.android.settings.bluetooth.BluetoothPermissionRequest";
+
+ private static final ParcelUuid[] SAP_UUIDS = {
+ BluetoothUuid.SAP,
+ };
+
+
+ public SapService() {
+ mState = BluetoothSap.STATE_DISCONNECTED;
+ }
+
+ private void startRfcommSocketListener() {
+ if (VERBOSE) Log.v(TAG, "Sap Service startRfcommSocketListener");
+
+ if (mAcceptThread == null) {
+ mAcceptThread = new SocketAcceptThread();
+ mAcceptThread.setName("SapAcceptThread");
+ mAcceptThread.start();
+ }
+ }
+
+ private final boolean initSocket() {
+ if (VERBOSE) Log.v(TAG, "Sap Service initSocket");
+
+ boolean initSocketOK = false;
+ final int CREATE_RETRY_TIME = 10;
+
+ // It's possible that create will fail in some cases. retry for 10 times
+ for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
+ initSocketOK = true;
+ try {
+ // It is mandatory for MSE to support initiation of bonding and
+ // encryption.
+ mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord
+ ("SIM Access", BluetoothUuid.SAP.getUuid());
+
+ } catch (IOException e) {
+ Log.e(TAG, "Error create RfcommServerSocket ", e);
+ initSocketOK = false;
+ }
+ if (!initSocketOK) {
+ // Need to break out of this loop if BT is being turned off.
+ if (mAdapter == null) break;
+ int state = mAdapter.getState();
+ if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
+ (state != BluetoothAdapter.STATE_ON)) {
+ Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
+ break;
+ }
+ try {
+ if (VERBOSE) Log.v(TAG, "wait 300 ms");
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "socketAcceptThread thread was interrupted (3)", e);
+ }
+ } else {
+ break;
+ }
+ }
+ if (mInterrupted) {
+ initSocketOK = false;
+ // close server socket to avoid resource leakage
+ closeServerSocket();
+ }
+
+ if (initSocketOK) {
+ if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
+
+ } else {
+ Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
+ }
+ return initSocketOK;
+ }
+
+ private final synchronized void closeServerSocket() {
+ // exit SocketAcceptThread early
+ if (mServerSocket != null) {
+ try {
+ // this will cause mServerSocket.accept() return early with IOException
+ mServerSocket.close();
+ mServerSocket = null;
+ } catch (IOException ex) {
+ Log.e(TAG, "Close Server Socket error: ", ex);
+ }
+ }
+ }
+ private final synchronized void closeConnectionSocket() {
+ if (mConnSocket != null) {
+ try {
+ mConnSocket.close();
+ mConnSocket = null;
+ } catch (IOException e) {
+ Log.e(TAG, "Close Connection Socket error: ", e);
+ }
+ }
+ }
+
+ private final void closeService() {
+ if (VERBOSE) Log.v(TAG, "SAP Service closeService in");
+
+ // exit initSocket early
+ mInterrupted = true;
+ closeServerSocket();
+
+ if (mAcceptThread != null) {
+ try {
+ mAcceptThread.shutdown();
+ mAcceptThread.join();
+ mAcceptThread = null;
+ } catch (InterruptedException ex) {
+ Log.w(TAG, "mAcceptThread close error", ex);
+ }
+ }
+
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+
+ closeConnectionSocket();
+
+ if (VERBOSE) Log.v(TAG, "SAP Service closeService out");
+ }
+
+ private final void startSapServerSession() throws IOException {
+ if (VERBOSE) Log.v(TAG, "Sap Service startSapServerSession");
+
+ // acquire the wakeLock before start SAP transaction thread
+ // TODO: Do we need this? I guess we will wake when ever incoming data is available?
+ // And/or when a SIM event occurs - same for MAP.
+ // UPDATE: Change to use same approach as for MAP with a timer based wake-lock
+ if (mWakeLock == null) {
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "StartingSapTransaction");
+ mWakeLock.setReferenceCounted(false);
+ mWakeLock.acquire();
+ }
+
+ setState(BluetoothSap.STATE_CONNECTED);
+
+ /* Start the SAP I/O thread and associate with message handler */
+ mSapServer = new SapServer(mSessionStatusHandler, this, mConnSocket.getInputStream(), mConnSocket.getOutputStream());
+ mSapServer.start();
+ /* Warning: at this point we most likely have already handled the initial connect
+ * request from the SAP client, hence we need to be prepared to handle the
+ * response. (the SapHandler should have been started before this point)*/
+
+ if (VERBOSE) {
+ Log.v(TAG, "startSapServerSession() success!");
+ }
+ }
+
+ private void stopSapServerSession() {
+
+ /* When we reach this point, the SapServer is closed down, and the client is
+ * supposed to close the RFCOMM connection. */
+ if (VERBOSE) Log.v(TAG, "SAP Service stopSapServerSession");
+
+ // Release the wake lock if SAP transactions is over
+ /* TODO: Why do we need to hold a wakeLock? I assume the lower layers will wake
+ * the host at incoming data, hence I'm not sure why we need this wake lock?
+ * Any SAP req/ind is triggered either by incoming data from the client, from the
+ * RIL deamon or a user initiated disconnect.
+ * Perhaps we should only hold a wakelock while handling a message?
+ */
+
+ mAcceptThread = null;
+ closeConnectionSocket();
+ closeServerSocket();
+
+ setState(BluetoothSap.STATE_DISCONNECTED);
+
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+
+ // Last SAP transaction is finished, we start to listen for incoming
+ // rfcomm connection again
+ if (mAdapter.isEnabled()) {
+ startRfcommSocketListener();
+ }
+ }
+
+ /**
+ * A thread that runs in the background waiting for remote rfcomm
+ * connect.Once a remote socket connected, this thread shall be
+ * shutdown.When the remote disconnect,this thread shall run again waiting
+ * for next request.
+ */
+ private class SocketAcceptThread extends Thread {
+
+ private boolean stopped = false;
+
+ @Override
+ public void run() {
+ BluetoothServerSocket serverSocket;
+ if (mServerSocket == null) {
+ if (!initSocket()) {
+ return;
+ }
+ }
+
+ while (!stopped) {
+ try {
+ if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
+ serverSocket = mServerSocket;
+ if(serverSocket == null) {
+ Log.w(TAG, "mServerSocket is null");
+ break;
+ }
+ mConnSocket = mServerSocket.accept();
+ if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
+ synchronized (SapService.this) {
+ if (mConnSocket == null) {
+ Log.w(TAG, "mConnSocket is null");
+ break;
+ }
+ mRemoteDevice = mConnSocket.getRemoteDevice();
+ }
+ if (mRemoteDevice == null) {
+ Log.i(TAG, "getRemoteDevice() = null");
+ break;
+ }
+
+ sRemoteDeviceName = mRemoteDevice.getName();
+ // In case getRemoteName failed and return null
+ if (TextUtils.isEmpty(sRemoteDeviceName)) {
+ sRemoteDeviceName = getString(R.string.defaultname);
+ }
+ int permission = mRemoteDevice.getSimAccessPermission();
+
+ if (VERBOSE) Log.v(TAG, "getSimAccessPermission() = " + permission);
+
+ if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ try {
+ if (VERBOSE) Log.v(TAG, "incoming connection accepted from: "
+ + sRemoteDeviceName + " automatically as trusted device");
+ startSapServerSession();
+ } catch (IOException ex) {
+ Log.e(TAG, "catch exception starting obex server session", ex);
+ }
+ } else if (permission != BluetoothDevice.ACCESS_REJECTED){
+ Intent intent = new
+ Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
+ intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+ intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+ BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+ intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
+
+ mIsWaitingAuthorization = true;
+ sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+
+ if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
+ + sRemoteDeviceName);
+
+ } else {
+ // Assuming reject is the stored state - continue to accept new connection.
+ continue;
+ }
+ stopped = true; // job done ,close this thread;
+ } catch (IOException ex) {
+ stopped=true;
+ if (VERBOSE) Log.v(TAG, "Accept exception: ", ex);
+ }
+ }
+ }
+
+ void shutdown() {
+ stopped = true;
+ interrupt();
+ }
+ }
+
+ private final Handler mSessionStatusHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
+
+ switch (msg.what) {
+ case START_LISTENER:
+ if (mAdapter.isEnabled()) {
+ startRfcommSocketListener();
+ }
+ break;
+ case USER_TIMEOUT:
+ if (mIsWaitingAuthorization){
+ sendCancelUserConfirmationIntent(mRemoteDevice);
+ cancelUserTimeoutAlarm();
+ mIsWaitingAuthorization = false;
+ stopSapServerSession(); // And restart RfcommListener if needed
+ }
+ break;
+ case MSG_SERVERSESSION_CLOSE:
+ stopSapServerSession();
+ break;
+ case MSG_SESSION_ESTABLISHED:
+ break;
+ case MSG_SESSION_DISCONNECTED:
+ // handled elsewhere
+ break;
+ case SHUTDOWN:
+ /* Ensure to call close from this handler to avoid starting new stuff
+ because of pending messages */
+ closeService();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private void setState(int state) {
+ setState(state, BluetoothSap.RESULT_SUCCESS);
+ }
+
+ private synchronized void setState(int state, int result) {
+ if (state != mState) {
+ if (DEBUG) Log.d(TAG, "Sap state " + mState + " -> " + state + ", result = "
+ + result);
+ int prevState = mState;
+ mState = state;
+ Intent intent = new Intent(BluetoothSap.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+ sendBroadcast(intent, BLUETOOTH_PERM);
+ AdapterService s = AdapterService.getAdapterService();
+ if (s != null) {
+ s.onProfileConnectionStateChanged(mRemoteDevice, BluetoothProfile.SAP,
+ mState, prevState);
+ }
+ }
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ public BluetoothDevice getRemoteDevice() {
+ return mRemoteDevice;
+ }
+
+ public static String getRemoteDeviceName() {
+ return sRemoteDeviceName;
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ boolean result = false;
+ synchronized (SapService.this) {
+ if (getRemoteDevice().equals(device)) {
+ switch (mState) {
+ case BluetoothSap.STATE_CONNECTED:
+ closeConnectionSocket();
+ setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
+ result = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ synchronized(this) {
+ if (mState == BluetoothSap.STATE_CONNECTED && mRemoteDevice != null) {
+ devices.add(mRemoteDevice);
+ }
+ }
+ return devices;
+ }
+
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+ Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ int connectionState;
+ synchronized (this) {
+ for (BluetoothDevice device : bondedDevices) {
+ ParcelUuid[] featureUuids = device.getUuids();
+ if (!BluetoothUuid.containsAnyUuid(featureUuids, SAP_UUIDS)) {
+ continue;
+ }
+ connectionState = getConnectionState(device);
+ for(int i = 0; i < states.length; i++) {
+ if (connectionState == states[i]) {
+ deviceList.add(device);
+ }
+ }
+ }
+ }
+ return deviceList;
+ }
+
+ public int getConnectionState(BluetoothDevice device) {
+ synchronized(this) {
+ if (getState() == BluetoothSap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
+ return BluetoothProfile.STATE_CONNECTED;
+ } else {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ }
+ }
+
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ Settings.Global.putInt(getContentResolver(),
+ Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
+ priority);
+ if (DEBUG) Log.d(TAG, "Saved priority " + device + " = " + priority);
+ return true;
+ }
+
+ public int getPriority(BluetoothDevice device) {
+ int priority = Settings.Global.getInt(getContentResolver(),
+ Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
+ BluetoothProfile.PRIORITY_UNDEFINED);
+ return priority;
+ }
+
+ @Override
+ protected IProfileServiceBinder initBinder() {
+ return new SapBinder(this);
+ }
+
+ @Override
+ protected boolean start() {
+ Log.v(TAG, "start()");
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ filter.addAction(USER_CONFIRM_TIMEOUT_ACTION);
+
+ try {
+ registerReceiver(mSapReceiver, filter);
+ } catch (Exception e) {
+ Log.w(TAG,"Unable to register sap receiver",e);
+ }
+ mInterrupted = false;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ // start RFCOMM listener
+ mSessionStatusHandler.sendMessage(mSessionStatusHandler
+ .obtainMessage(START_LISTENER));
+ return true;
+ }
+
+ @Override
+ protected boolean stop() {
+ Log.v(TAG, "stop()");
+ try {
+ unregisterReceiver(mSapReceiver);
+ } catch (Exception e) {
+ Log.w(TAG,"Unable to unregister sap receiver",e);
+ }
+ setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
+ sendShutdownMessage();
+ return true;
+ }
+
+ public boolean cleanup() {
+ setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
+ closeService();
+ if(mSessionStatusHandler != null) {
+ mSessionStatusHandler.removeCallbacksAndMessages(null);
+ }
+ return true;
+ }
+
+ private void setUserTimeoutAlarm(){
+ if(DEBUG)Log.d(TAG,"SetUserTimeOutAlarm()");
+ if(mAlarmManager == null){
+ mAlarmManager =(AlarmManager) this.getSystemService (Context.ALARM_SERVICE);
+ }
+ mRemoveTimeoutMsg = true;
+ Intent timeoutIntent =
+ new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+ PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ + USER_CONFIRM_TIMEOUT_VALUE,pIntent);
+ }
+
+ private void cancelUserTimeoutAlarm(){
+ if(DEBUG)Log.d(TAG,"cancelUserTimeOutAlarm()");
+ Intent intent = new Intent(this, SapService.class);
+ PendingIntent sender = PendingIntent.getBroadcast(this, 0, intent, 0);
+ AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
+ alarmManager.cancel(sender);
+ mRemoveTimeoutMsg = false;
+ }
+
+ private void sendCancelUserConfirmationIntent(BluetoothDevice device) {
+ Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
+ intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+ BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
+ sendBroadcast(intent, BLUETOOTH_PERM);
+ }
+
+ private void sendShutdownMessage() {
+ /* Any pending messages are no longer valid.
+ To speed up things, simply delete them. */
+ if (mRemoveTimeoutMsg) {
+ Intent timeoutIntent =
+ new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+ sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
+ mIsWaitingAuthorization = false;
+ cancelUserTimeoutAlarm();
+ }
+ mSessionStatusHandler.removeCallbacksAndMessages(null);
+ // Request release of all resources
+ mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
+ }
+
+ private void sendConnectTimeoutMessage() {
+ if (DEBUG) Log.d(TAG, "sendConnectTimeoutMessage()");
+ if(mSessionStatusHandler != null) {
+ Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT);
+ msg.sendToTarget();
+ } // Can only be null during shutdown
+ }
+
+ private SapBroadcastReceiver mSapReceiver = new SapBroadcastReceiver();
+
+ private class SapBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ if (VERBOSE) Log.v(TAG, "onReceive");
+ String action = intent.getAction();
+ if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ if (state == BluetoothAdapter.STATE_TURNING_OFF) {
+ if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF");
+ sendShutdownMessage();
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ if (DEBUG) Log.d(TAG, "STATE_ON");
+ // start RFCOMM listener
+ mSessionStatusHandler.sendMessage(mSessionStatusHandler
+ .obtainMessage(START_LISTENER));
+ }
+ } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+ Log.v(TAG, " - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
+ if (!mIsWaitingAuthorization) {
+ // this reply is not for us
+ return;
+ }
+
+ mIsWaitingAuthorization = false;
+
+ if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+ BluetoothDevice.CONNECTION_ACCESS_NO) ==
+ BluetoothDevice.CONNECTION_ACCESS_YES) {
+ //bluetooth connection accepted by user
+ if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+ boolean result = mRemoteDevice.setSimAccessPermission(
+ BluetoothDevice.ACCESS_ALLOWED);
+ if (VERBOSE) {
+ Log.v(TAG, "setSimAccessPermission(ACCESS_ALLOWED) result=" + result);
+ } }
+ try {
+ if (mConnSocket != null) {
+ // start obex server and rfcomm connection
+ startSapServerSession();
+ } else {
+ stopSapServerSession();
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Caught the error: ", ex);
+ }
+ } else {
+ if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+ boolean result = mRemoteDevice.setSimAccessPermission(
+ BluetoothDevice.ACCESS_REJECTED);
+ if (VERBOSE) {
+ Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result="
+ + result);
+ }
+ }
+ stopSapServerSession();
+ }
+ } else if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)){
+ if (DEBUG) Log.d(TAG, "USER_CONFIRM_TIMEOUT ACTION Received.");
+ // send us self a message about the timeout.
+ sendConnectTimeoutMessage();
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) &&
+ mIsWaitingAuthorization) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ if (mRemoteDevice == null || device == null) {
+ Log.e(TAG, "Unexpected error!");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG,"ACL disconnected for " + device);
+
+ if (mRemoteDevice.equals(device) && mRemoveTimeoutMsg) {
+ // Send any pending timeout now, as ACL got disconnected.
+ mSessionStatusHandler.removeMessages(USER_TIMEOUT);
+ sendCancelUserConfirmationIntent(mRemoteDevice);
+ mIsWaitingAuthorization = false;
+ mRemoveTimeoutMsg = false;
+ }
+ }
+ }
+ };
+
+ //Binder object: Must be static class or memory leak may occur
+ /**
+ * This class implements the IBluetoothSap interface - or actually it validates the
+ * preconditions for calling the actual functionality in the SapService, and calls it.
+ */
+ private static class SapBinder extends IBluetoothSap.Stub
+ implements IProfileServiceBinder {
+ private SapService mService;
+
+ private SapService getService() {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG,"call not allowed for non-active user");
+ return null;
+ }
+
+ if (mService != null && mService.isAvailable() ) {
+ mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mService;
+ }
+ return null;
+ }
+
+ SapBinder(SapService service) {
+ Log.v(TAG, "SapBinder()");
+ mService = service;
+ }
+
+ public boolean cleanup() {
+ mService = null;
+ return true;
+ }
+
+ public int getState() {
+ Log.v(TAG, "getState()");
+ SapService service = getService();
+ if (service == null) return BluetoothSap.STATE_DISCONNECTED;
+ return getService().getState();
+ }
+
+ public BluetoothDevice getClient() {
+ Log.v(TAG, "getClient()");
+ SapService service = getService();
+ if (service == null) return null;
+ Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
+ return service.getRemoteDevice();
+ }
+
+ public boolean isConnected(BluetoothDevice device) {
+ Log.v(TAG, "isConnected()");
+ SapService service = getService();
+ if (service == null) return false;
+ return (service.getState() == BluetoothSap.STATE_CONNECTED
+ && service.getRemoteDevice().equals(device));
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ Log.v(TAG, "connect()");
+ SapService service = getService();
+ if (service == null) return false;
+ return false;
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ Log.v(TAG, "disconnect()");
+ SapService service = getService();
+ if (service == null) return false;
+ return service.disconnect(device);
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ Log.v(TAG, "getConnectedDevices()");
+ SapService service = getService();
+ if (service == null) return new ArrayList<BluetoothDevice>(0);
+ return service.getConnectedDevices();
+ }
+
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ Log.v(TAG, "getDevicesMatchingConnectionStates()");
+ SapService service = getService();
+ if (service == null) return new ArrayList<BluetoothDevice>(0);
+ return service.getDevicesMatchingConnectionStates(states);
+ }
+
+ public int getConnectionState(BluetoothDevice device) {
+ Log.v(TAG, "getConnectionState()");
+ SapService service = getService();
+ if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+ return service.getConnectionState(device);
+ }
+
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ SapService service = getService();
+ if (service == null) return false;
+ return service.setPriority(device, priority);
+ }
+
+ public int getPriority(BluetoothDevice device) {
+ SapService service = getService();
+ if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+ return service.getPriority(device);
+ }
+ }
+}
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := platform
-LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner telephony-common mms-common
+LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner telephony-common mms-common libprotobuf-java-2.3.0-micro
LOCAL_STATIC_JAVA_LIBRARIES := com.android.emailcommon
# Include all test java files.
--- /dev/null
+package com.android.bluetooth.tests;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.bluetooth.sap.SapMessage;
+import com.android.bluetooth.sap.SapServer;
+
+/**
+ * Test either using the reference ril without a modem, or using a RIL implementing the
+ * BT SAP API, by providing the rild-bt socket as well as the extended API functions for SAP.
+ *
+ */
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class SapServerTest extends AndroidTestCase {
+ protected static String TAG = "SapServerTest";
+ protected static final boolean D = true;
+ private static final boolean rilTestModeEnabled = false; /* Set the RIL driver in test mode, where request stubs are used in stead of forwarding to the modem/sim */
+
+ private Context mContext = null;
+
+ public SapServerTest() {
+ super();
+ }
+
+ private void buildDefaultInitSeq(SapSequencer sequencer) throws IOException {
+ SapMessage connectReq = new SapMessage(SapMessage.ID_CONNECT_REQ);
+ connectReq.setMaxMsgSize(276);
+
+ SapMessage connectResp = new SapMessage(SapMessage.ID_CONNECT_RESP);
+ connectResp.setConnectionStatus(SapMessage.CON_STATUS_OK);
+ connectResp.setMaxMsgSize(0); /* shall be connection status (0) on success */
+
+ SapMessage statusInd = new SapMessage(SapMessage.ID_STATUS_IND);
+ statusInd.setStatusChange(SapMessage.STATUS_CARD_RESET);
+
+ int index = sequencer.addStep(connectReq, connectResp);
+ sequencer.addSubStep(index, null, statusInd);
+
+ }
+ /**
+ * Test that the SapServer is capable of handling a connect request with no call ongoing.
+ */
+ public void testSapServerConnectSimple() {
+ mContext = this.getContext();
+
+ try {
+
+ SapSequencer sequencer = new SapSequencer();
+ if(rilTestModeEnabled) {
+ sequencer.testModeEnable(true);
+ }
+ /* Build a default init sequence */
+ buildDefaultInitSeq(sequencer);
+ SapMessage disconnectReq = new SapMessage(SapMessage.ID_DISCONNECT_REQ);
+ SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
+
+ SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP);
+ resetResp.setResultCode(SapMessage.RESULT_OK);
+
+ int index = sequencer.addStep(disconnectReq, disconnectResp);
+
+ assertTrue(sequencer.run());
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ }
+
+ public void testSapServerApiComplete() {
+ mContext = this.getContext();
+ byte[] dummyBytes = {1, 2, 3, 4};
+
+ /* Select file '2FE2' - observed selected in modem init sequence.
+ * According to spec file '2FE2' is EF_ICCID (Elementary file
+ * ICC identification).
+ */
+
+ byte[] selectFileApdu = {(byte)0xa0, (byte)0xa4, (byte)0x00, (byte)0x00,
+ (byte)0x02, (byte)0x2f, (byte)0xe2};
+
+ /* Command succesfull '9F', length '0F' of response data */
+ byte[] selectFileApduResp = {(byte)0x9f, (byte)0x0f};
+
+ try {
+
+ SapSequencer sequencer = new SapSequencer();
+ if(rilTestModeEnabled) {
+ sequencer.testModeEnable(true);
+ }
+
+ /* Build a default init sequence */
+ buildDefaultInitSeq(sequencer);
+
+ SapMessage powerOffReq = new SapMessage(SapMessage.ID_POWER_SIM_OFF_REQ);
+
+ SapMessage powerOffResp = new SapMessage(SapMessage.ID_POWER_SIM_OFF_RESP);
+ powerOffResp.setResultCode(SapMessage.RESULT_OK);
+ sequencer.addStep(powerOffReq, powerOffResp);
+
+
+ SapMessage powerOnReq = new SapMessage(SapMessage.ID_POWER_SIM_ON_REQ);
+
+ SapMessage powerOnResp = new SapMessage(SapMessage.ID_POWER_SIM_ON_RESP);
+ powerOnResp.setResultCode(SapMessage.RESULT_OK);
+ sequencer.addStep(powerOnReq, powerOnResp);
+
+ SapMessage resetReq = new SapMessage(SapMessage.ID_RESET_SIM_REQ);
+
+ SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP);
+ resetResp.setResultCode(SapMessage.RESULT_OK);
+ int index = sequencer.addStep(resetReq, resetResp);
+
+ /* SapMessage statusInd = new SapMessage(SapMessage.ID_STATUS_IND); */
+ /* statusInd.setStatusChange(SapMessage.STATUS_CARD_RESET); */
+ /* sequencer.addSubStep(index, null, statusInd); */
+
+ SapMessage atrReq = new SapMessage(SapMessage.ID_TRANSFER_ATR_REQ);
+
+ SapMessage atrResp = new SapMessage(SapMessage.ID_TRANSFER_ATR_RESP);
+ atrResp.setResultCode(SapMessage.RESULT_OK);
+ if(rilTestModeEnabled) {
+ /* Use the hard coded return array, must match the test array in RIL */
+ byte[] atr = {1, 2, 3, 4};
+ atrResp.setAtr(atr);
+ } else {
+ atrResp.setAtr(null);
+ }
+ sequencer.addStep(atrReq, atrResp);
+
+
+ SapMessage apduReq = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ);
+ if(rilTestModeEnabled) {
+ apduReq.setApdu(dummyBytes);
+ } else {
+ apduReq.setApdu(selectFileApdu);
+ }
+
+ SapMessage apduResp = new SapMessage(SapMessage.ID_TRANSFER_APDU_RESP);
+ apduResp.setResultCode(SapMessage.RESULT_OK);
+ if(rilTestModeEnabled) {
+ apduResp.setApduResp(dummyBytes);
+ } else {
+ apduResp.setApduResp(selectFileApduResp);
+ }
+ sequencer.addStep(apduReq, apduResp);
+
+
+ SapMessage apdu7816Req = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ);
+ if(rilTestModeEnabled) {
+ apdu7816Req.setApdu7816(dummyBytes);
+ } else {
+ apdu7816Req.setApdu7816(selectFileApdu);
+ }
+
+ SapMessage apdu7816Resp = new SapMessage(SapMessage.ID_TRANSFER_APDU_RESP);
+ apdu7816Resp.setResultCode(SapMessage.RESULT_OK);
+ if(rilTestModeEnabled) {
+ apdu7816Resp.setApduResp(dummyBytes);
+ } else {
+ apdu7816Resp.setApduResp(selectFileApduResp);
+ }
+ sequencer.addStep(apdu7816Req, apdu7816Resp);
+
+ SapMessage transferCardReaderStatusReq = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_REQ);
+
+ SapMessage transferCardReaderStatusResp = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP);
+ transferCardReaderStatusResp.setResultCode(SapMessage.RESULT_OK);
+ sequencer.addStep(transferCardReaderStatusReq, transferCardReaderStatusResp);
+
+ SapMessage setTransportProtocolReq = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ);
+ setTransportProtocolReq.setTransportProtocol(0x01); // T=1
+
+ SapMessage setTransportProtocolResp = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP);
+ setTransportProtocolResp.setResultCode(SapMessage.RESULT_OK);
+ sequencer.addStep(setTransportProtocolReq, setTransportProtocolResp);
+
+ SapMessage disconnectReq = new SapMessage(SapMessage.ID_DISCONNECT_REQ);
+
+ SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
+ sequencer.addStep(disconnectReq, disconnectResp);
+
+ assertTrue(sequencer.run());
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ }
+
+ /**
+ * This test fails if the apdu request generates a response before the reset request is handled.
+ * This happens if the reference ril is used in test mode.
+ */
+ public void testSapServerResetWhileWritingApdu() {
+ mContext = this.getContext();
+ byte[] dummyBytes = {1, 2, 3, 4};
+ int index;
+
+ try {
+
+ SapSequencer sequencer = new SapSequencer();
+ if(rilTestModeEnabled) {
+ sequencer.testModeEnable(true);
+ }
+
+ /* Build a default init sequence */
+ buildDefaultInitSeq(sequencer);
+
+ SapMessage apduReq = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ);
+ apduReq.setApdu(dummyBytes);
+
+ SapMessage apduResp = null; /* expect no response as we send a SIM_RESET before the write APDU completes
+ TODO: Consider adding a real response, and add an optional flag. */
+ index = sequencer.addStep(apduReq, apduResp);
+
+ SapMessage resetReq = new SapMessage(SapMessage.ID_RESET_SIM_REQ);
+ SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP);
+ resetResp.setResultCode(SapMessage.RESULT_OK);
+ sequencer.addSubStep(index, resetReq, resetResp);
+
+ SapMessage statusInd = new SapMessage(SapMessage.ID_STATUS_IND);
+ statusInd.setStatusChange(SapMessage.STATUS_CARD_RESET);
+ sequencer.addSubStep(index, null, statusInd);
+
+ assertTrue(sequencer.run());
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ }
+
+ /**
+ * Test that SapServer can disconnect based on a disconnect intent.
+ * TODO: This test could validate the timeout values.
+ * TODO: We need to add a IAction and add an action to a step, to be able to send
+ * the disconnect intent at the right time.
+ */
+ public void testSapServerTimeouts() {
+ Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
+ sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_IMMEDIATE);
+ mContext = this.getContext();
+
+ try {
+
+ SapSequencer sequencer = new SapSequencer();
+ if(rilTestModeEnabled) {
+ sequencer.testModeEnable(true);
+ }
+ /* Build a default init sequence */
+ buildDefaultInitSeq(sequencer);
+
+ SapMessage disconnectReq = new SapMessage(SapMessage.ID_DISCONNECT_REQ);
+ SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
+
+ SapMessage resetResp = new SapMessage(SapMessage.ID_RESET_SIM_RESP);
+ resetResp.setResultCode(SapMessage.RESULT_OK);
+
+ int index = sequencer.addStep(disconnectReq, disconnectResp);
+
+ assertTrue(sequencer.run());
+
+ mContext.sendBroadcast(sapDisconnectIntent);
+
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ }
+ }
+ public void testSapServerTimeoutsActionDiscIntent() {
+
+ }
+
+ public class SapSequencer implements Callback {
+
+ private final static int MSG_ID_TIMEOUT = 0x01;
+ private final static int TIMEOUT_VALUE = 100*2000; // ms
+ private ArrayList<SeqStep> sequence = null;
+ private HandlerThread handlerThread = null;
+ private Handler messageHandler = null;
+
+ private SapServer sapServer = null;
+
+ private PipedInputStream inStream = null; // Used to write requests to SapServer
+ private PipedOutputStream outStream = null; // Used to read commands from the SapServer
+
+
+ public class SeqStep {
+ public ArrayList<SapMessage> requests = null;
+ public ArrayList<SapMessage> responses = null;
+ public int index = 0; /* requests with same index are executed in parallel (without waiting for a response) */
+ public SeqStep(SapMessage request, SapMessage response) {
+ requests = new ArrayList<SapMessage>();
+ responses = new ArrayList<SapMessage>();
+ this.requests.add(request);
+ this.responses.add(response);
+ }
+
+ public void add(SapMessage request, SapMessage response) {
+ this.requests.add(request);
+ this.responses.add(response);
+ }
+
+ /**
+ * Examine if the step has any expected response.
+ * @return true if one or more of the responses are != null. False otherwise.
+ */
+ public boolean hasResponse() {
+ if(responses == null)
+ return false;
+ for(SapMessage response : responses) {
+ if(response != null)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public SapSequencer() throws IOException {
+ /* Setup the looper thread to handle messages */
+ handlerThread = new HandlerThread("SapTestTimeoutHandler", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ handlerThread.start();
+ Looper testLooper = handlerThread.getLooper();
+ messageHandler = new Handler(testLooper, this);
+
+ /* Initialize members */
+ sequence = new ArrayList<SeqStep>();
+
+ /* Create a SapServer. Fake the BtSocket using piped input/output streams*/
+ inStream = new PipedInputStream(8092);
+ outStream = new PipedOutputStream();
+ sapServer = new SapServer(null, mContext, new PipedInputStream(outStream, 8092), new PipedOutputStream(inStream));
+ sapServer.start();
+ }
+
+ /* TODO:
+ * - Add support for actions ?
+ * */
+
+ /**
+ * Enable/Disable test mode during the next connect request.
+ * @param enable
+ */
+ public void testModeEnable(boolean enable) {
+ if(enable)
+ sapServer.setTestMode(SapMessage.TEST_MODE_ENABLE);
+ else
+ sapServer.setTestMode(SapMessage.TEST_MODE_DISABLE);
+ }
+
+ /**
+ * Add a test step to the sequencer
+ * @param request The request to send to the SAP server
+ * @param response The response to EXPECT from the SAP server
+ * @return The created step index, which can be used when adding events or actions.
+ */
+ public int addStep(SapMessage request, SapMessage response) { // TODO: should we add a step trigger? (in stead of just executing in sequence)
+ SeqStep newStep = new SeqStep(request, response);
+ sequence.add(newStep);
+ return sequence.indexOf(newStep);
+ }
+
+ /**
+ * Add a sub-step to a sequencer step. All requests added to the same index will be send to the
+ * SapServer in the order added before listening for the response.
+ * The response order is not validated - hence for each response received the entire list of
+ * responses in the step will be searched for a match.
+ * @param index the index returned from addStep() to which the sub-step is to be added.
+ * @param request The request to send to the SAP server
+ * @param response The response to EXPECT from the SAP server
+ */
+ public void addSubStep(int index, SapMessage request, SapMessage response) {
+ SeqStep step = sequence.get(index);
+ step.add(request, response);
+ }
+
+
+ /**
+ * Run the sequence, by writing a request and wait for a response. Validate the response
+ * is either the expected response or one of the expected events.
+ * @return
+ */
+ public boolean run() throws IOException {
+ SapMessage inMsg = null;
+ boolean done;
+ for(SeqStep step : sequence) {
+
+ /* Write all requests - if any */
+ if(step.requests != null) {
+ for(SapMessage request : step.requests) {
+ if(request != null) {
+ Log.i(TAG, "Writing request: " + SapMessage.getMsgTypeName(request.getMsgType()));
+ writeSapMessage(request, false); /* write the message without flushing */
+ }
+ }
+ writeSapMessage(null, true); /* flush the pipe */
+ }
+
+ /* Handle and validate all responses - if any */
+ if(step.hasResponse() == true) {
+ done = false;
+ boolean foundMatch = false;
+ SapMessage responseMatch;
+ while(!done) {
+ for(SapMessage response : step.responses) {
+ if(response != null)
+ Log.i(TAG, "Waiting for the response: " + SapMessage.getMsgTypeName(response.getMsgType()));
+ }
+ inMsg = readSapMessage();
+ if(inMsg != null)
+ Log.i(TAG, "Read message: " + SapMessage.getMsgTypeName(inMsg.getMsgType()));
+ else
+ assertTrue("Failed to read message.", false);
+
+ responseMatch = null;
+ for(SapMessage response : step.responses) {
+ if(response != null
+ && inMsg.getMsgType() == response.getMsgType()
+ && compareSapMessages(inMsg, response) == true) {
+ foundMatch = true;
+ responseMatch = response;
+ break;
+ }
+ }
+
+ if(responseMatch != null)
+ step.responses.remove(responseMatch);
+
+ /* If we are expecting no more responses for this step, continue. */
+ if(step.hasResponse() != true) {
+ done = true;
+ }
+ /* Ensure what we received was expected */
+ assertTrue("wrong message received.", foundMatch);
+ }
+ }
+ }
+ return true;
+ }
+
+ private void startTimer() {
+ Message timeoutMessage = messageHandler.obtainMessage(MSG_ID_TIMEOUT);
+ messageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE);
+ }
+
+ private void stopTimer() {
+ messageHandler.removeMessages(MSG_ID_TIMEOUT);
+ }
+
+ /**
+ * Compare two messages by comparing each member variable
+ * @param received message
+ * @param expected message
+ * @return true if equal, else false
+ */
+ private boolean compareSapMessages(SapMessage received, SapMessage expected) {
+ boolean retVal = true;
+ if(expected.getCardReaderStatus() != -1 && received.getCardReaderStatus() != expected.getCardReaderStatus()) {
+ Log.i(TAG, "received.getCardReaderStatus() != expected.getCardReaderStatus() "
+ + received.getCardReaderStatus() +" != " + expected.getCardReaderStatus());
+ retVal = false;
+ }
+ if(received.getConnectionStatus() != expected.getConnectionStatus()) {
+ Log.i(TAG, "received.getConnectionStatus() != expected.getConnectionStatus() "
+ + received.getConnectionStatus() +" != " + expected.getConnectionStatus());
+ retVal = false;
+ }
+ if(received.getDisconnectionType() != expected.getDisconnectionType()) {
+ Log.i(TAG, "received.getDisconnectionType() != expected.getDisconnectionType() "
+ + received.getDisconnectionType() +" != " + expected.getDisconnectionType());
+ retVal = false;
+ }
+ if(received.getMaxMsgSize() != expected.getMaxMsgSize()) {
+ Log.i(TAG, "received.getMaxMsgSize() != expected.getMaxMsgSize() "
+ + received.getMaxMsgSize() +" != " + expected.getMaxMsgSize());
+ retVal = false;
+ }
+ if(received.getMsgType() != expected.getMsgType()) {
+ Log.i(TAG, "received.getMsgType() != expected.getMsgType() "
+ + received.getMsgType() +" != " + expected.getMsgType());
+ retVal = false;
+ }
+ if(received.getResultCode() != expected.getResultCode()) {
+ Log.i(TAG, "received.getResultCode() != expected.getResultCode() "
+ + received.getResultCode() +" != " + expected.getResultCode());
+ retVal = false;
+ }
+ if(received.getStatusChange() != expected.getStatusChange()) {
+ Log.i(TAG, "received.getStatusChange() != expected.getStatusChange() "
+ + received.getStatusChange() +" != " + expected.getStatusChange());
+ retVal = false;
+ }
+ if(received.getTransportProtocol() != expected.getTransportProtocol()) {
+ Log.i(TAG, "received.getTransportProtocol() != expected.getTransportProtocol() "
+ + received.getTransportProtocol() +" != " + expected.getTransportProtocol());
+ retVal = false;
+ }
+ if(!Arrays.equals(received.getApdu(), expected.getApdu())) {
+ Log.i(TAG, "received.getApdu() != expected.getApdu() "
+ + Arrays.toString(received.getApdu()) +" != " + Arrays.toString(expected.getApdu()));
+ retVal = false;
+ }
+ if(!Arrays.equals(received.getApdu7816(), expected.getApdu7816())) {
+ Log.i(TAG, "received.getApdu7816() != expected.getApdu7816() "
+ + Arrays.toString(received.getApdu7816()) +" != " + Arrays.toString(expected.getApdu7816()));
+ retVal = false;
+ }
+ if(expected.getApduResp() != null && !Arrays.equals(received.getApduResp(), expected.getApduResp())) {
+ Log.i(TAG, "received.getApduResp() != expected.getApduResp() "
+ + Arrays.toString(received.getApduResp()) +" != " + Arrays.toString(expected.getApduResp()));
+ retVal = false;
+ }
+ if(expected.getAtr() != null && !Arrays.equals(received.getAtr(), expected.getAtr())) {
+ Log.i(TAG, "received.getAtr() != expected.getAtr() "
+ + Arrays.toString(received.getAtr()) +" != " + Arrays.toString(expected.getAtr()));
+ retVal = false;
+ }
+ return retVal;
+ }
+
+ private SapMessage readSapMessage() throws IOException {
+ startTimer();
+ int requestType = inStream.read();
+ Log.i(TAG,"Received message with type: " + SapMessage.getMsgTypeName(requestType));
+ SapMessage msg = SapMessage.readMessage(requestType, inStream);
+ stopTimer();
+ if(requestType != -1) {
+ return msg;
+ } else
+ {
+ assertTrue("Reached EOF too early...", false);
+ }
+ return msg;
+ }
+
+ private void writeSapMessage(SapMessage message, boolean flush) throws IOException {
+ startTimer();
+ if(message != null)
+ message.write(outStream);
+ if(flush == true)
+ outStream.flush();
+ stopTimer();
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+
+ Log.i(TAG,"Handling message ID: " + msg.what);
+
+ switch(msg.what) {
+ case MSG_ID_TIMEOUT:
+ Log.w(TAG, "Timeout occured!");
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "failed to close inStream", e);
+ }
+ try {
+ outStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, "failed to close outStream", e);
+ }
+ break;
+ default:
+ /* Message not handled */
+ return false;
+ }
+ return true; // Message handles
+ }
+
+ }
+
+}
--- /dev/null
+package com.android.bluetooth.tests;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import org.android.btsap.SapApi;
+import org.android.btsap.SapApi.MsgHeader;
+import org.android.btsap.SapApi.RIL_SIM_SAP_CONNECT_REQ;
+
+import com.google.protobuf.micro.ByteStringMicro;
+import com.google.protobuf.micro.CodedOutputStreamMicro;
+import com.google.protobuf.micro.CodedInputStreamMicro;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+
+public class SapSocketTest extends AndroidTestCase {
+
+ protected static String TAG = "SapSocketTest";
+ protected static final boolean D = true;
+
+ private static final String SOCKET_NAME_RIL_BT = "sap_uim_socket1";
+
+ public SapSocketTest() {
+ super();
+ }
+
+ private void writeLegacyLength(int length, OutputStream rawOut) throws IOException {
+ byte[] dataLength = new byte[4];
+ dataLength[0] = dataLength[1] = 0;
+ dataLength[2] = (byte)((length >> 8) & 0xff);
+ dataLength[3] = (byte)((length) & 0xff);
+ rawOut.write(dataLength);
+ }
+
+ private void dumpMsgHeader(MsgHeader msg){
+ Log.d(TAG,"MsgHeader: ID = " + msg.getId());
+ Log.d(TAG," Type= " + msg.getType());
+ Log.d(TAG," Token= " + msg.getToken());
+ Log.d(TAG," Error= " + msg.getError());
+ Log.d(TAG," Length=" + msg.getSerializedSize());
+ if(msg.hasPayload()){
+ Log.d(TAG,"Payload: Length=" + msg.getPayload().size());
+ Log.d(TAG," Data= " + Arrays.toString(msg.getPayload().toByteArray()));
+ }
+
+ }
+ private void readLegacyLength(InputStream rawIn) throws IOException{
+ byte[] buffer = new byte[4];
+ int countRead;
+ int offset;
+ int remaining;
+ int messageLength;
+
+ // Read in the length of the message
+ offset = 0;
+ remaining = 4;
+ do {
+ countRead = rawIn.read(buffer, offset, remaining);
+
+ if (countRead < 0 ) {
+ Log.e(TAG, "Hit EOS reading message length");
+ return;
+ }
+
+ offset += countRead;
+ remaining -= countRead;
+ } while (remaining > 0);
+
+ messageLength = ((buffer[0] & 0xff) << 24)
+ | ((buffer[1] & 0xff) << 16)
+ | ((buffer[2] & 0xff) << 8)
+ | (buffer[3] & 0xff);
+
+ Log.d(TAG, "The length is: " + messageLength + " - discarding as we do not need it");
+
+ }
+
+/**
+Precondition:
+Add the sap_uim_socket1 to rild in init.rc:
+ socket sap_uim_socket1 stream 666 root bluetooth
+
+Ensure the socket is present in /dev/socket:
+srw-rw-rw- root bluetooth 1970-04-16 06:27 sap_uim_socket1
+
+Build:
+mmm packages/apps/Bluetooth/tests
+
+rebuild with a make in the root folder to get the
+android.test.InstrumentationTestRunner class included.
+
+Run the test(remove line breaks):
+adb shell am instrument -w -e class com.android.bluetooth.
+tests.SapSocketTest#testSapServerConnectSimple com.android.
+bluetooth.tests/android.test.InstrumentationTestRunner
+
+Validate you do not get a permission denied IOException.
+
+Validate you do not get an error in the kernel log:
+type=1400 audit(1404244298.582:25): avc: denied { write }
+for pid=2421 comm="ationTestRunner" name="sap_uim_socket1"
+dev="tmpfs" ino=6703 scontext=u:r:bluetooth:s0
+tcontext=u:object_r:socket_device:s0 tclass=sock_file
+*/
+
+ /**
+ * Precondition: Add the sap_uim_socket1 to rild in init.rc: socket
+ * sap_uim_socket1 stream 666 root bluetooth
+ *
+ * Ensure the socket is present in /dev/socket: srw-rw-rw- root bluetooth
+ * 1970-04-16 06:27 sap_uim_socket1
+ *
+ * Build: mmm packages/apps/Bluetooth/tests
+ *
+ * rebuild with a make in the root folder to get the
+ * android.test.InstrumentationTestRunner class included.
+ *
+ * Run the test(remove line breaks): adb shell am instrument -w -e class
+ * com.android.bluetooth. tests.SapSocketTest#testSapServerConnectSimple
+ * com.android. bluetooth.tests/android.test.InstrumentationTestRunner
+ *
+ * Validate you do not get a permission denied IOException.
+ *
+ * Validate you do not get an error in the kernel log: type=1400
+ * audit(1404244298.582:25): avc: denied { write } for pid=2421
+ * comm="ationTestRunner" name="sap_uim_socket1" dev="tmpfs" ino=6703
+ * scontext=u:r:bluetooth:s0 tcontext=u:object_r:socket_device:s0
+ * tclass=sock_file
+ */
+ public void testSapServerConnectSimple() {
+ LocalSocketAddress address;
+ LocalSocket rilSocket = new LocalSocket();
+ try {
+ address = new LocalSocketAddress(SOCKET_NAME_RIL_BT,
+ LocalSocketAddress.Namespace.RESERVED);
+ rilSocket.connect(address);
+ CodedInputStreamMicro in = CodedInputStreamMicro.newInstance(rilSocket.getInputStream());
+ OutputStream rawOut = rilSocket.getOutputStream();
+ CodedOutputStreamMicro out = CodedOutputStreamMicro.newInstance(rilSocket.getOutputStream());
+ InputStream rawIn = rilSocket.getInputStream();
+ int rilSerial = 1;
+ SapApi.MsgHeader msg = new MsgHeader();
+ /* Common variables for all requests */
+ msg.setToken(rilSerial);
+ msg.setType(SapApi.REQUEST);
+ msg.setError(SapApi.RIL_E_UNUSED);
+ SapApi.RIL_SIM_SAP_CONNECT_REQ reqMsg = new RIL_SIM_SAP_CONNECT_REQ();
+ reqMsg.setMaxMessageSize(1234);
+ msg.setId(SapApi.RIL_SIM_SAP_CONNECT);
+ msg.setPayload(ByteStringMicro.copyFrom(reqMsg.toByteArray()));
+ writeLegacyLength(msg.getSerializedSize(), rawOut);
+ msg.writeTo(out);
+ out.flush();
+ readLegacyLength(rawIn);
+ msg = MsgHeader.parseFrom(in);
+ dumpMsgHeader(msg);
+ assertTrue("Invalid response type", msg.getType()== SapApi.RESPONSE);
+ assertTrue("Invalid response id", msg.getId()== SapApi.RIL_SIM_SAP_CONNECT);
+ } catch (IOException e){
+ Log.e(TAG, "IOException:", e);
+ assertTrue("Failed to connect to the socket " + SOCKET_NAME_RIL_BT + ":" + e, false);
+ } finally {
+ try {
+ rilSocket.close();
+ } catch (IOException e2) {}
+
+ }
+ }
+}