Update Bluetooth Client to Support AIDL
Tag: #feature
Bug: 241969533
Test: atest BluetoothInstrumentationTests
Change-Id: I9eec18393f0e60555a712a5029f5c6a4a7d6f5e8
diff --git a/android/app/Android.bp b/android/app/Android.bp
index 4c45dff..bd7bc18 100644
--- a/android/app/Android.bp
+++ b/android/app/Android.bp
@@ -153,6 +153,7 @@
],
static_libs: [
"android.hardware.radio-V1.0-java",
+ "android.hardware.radio.sap-V1-java",
"androidx.core_core",
"androidx.legacy_legacy-support-v4",
"androidx.lifecycle_lifecycle-livedata",
diff --git a/android/app/src/com/android/bluetooth/sap/ISapRilReceiver.java b/android/app/src/com/android/bluetooth/sap/ISapRilReceiver.java
new file mode 100644
index 0000000..01d4c29
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/sap/ISapRilReceiver.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.sap;
+
+import android.hardware.radio.sap.ISap;
+
+/**
+ * ISapRilReceiver is used to send messages
+ */
+public interface ISapRilReceiver extends ISap {
+ /**
+ * Set mSapProxy to null
+ */
+ void resetSapProxy();
+
+ /**
+ * Notify SapServer that this class is ready for shutdown.
+ */
+ void notifyShutdown();
+
+ /**
+ * Notify SapServer that the RIL socket is connected
+ */
+ void sendRilConnectMessage();
+
+ /**
+ * Get mSapProxyLock
+ */
+ Object getSapProxyLock();
+
+ /**
+ * Verifies mSapProxy is not null
+ */
+ boolean isProxyValid();
+}
diff --git a/android/app/src/com/android/bluetooth/sap/SapMessage.java b/android/app/src/com/android/bluetooth/sap/SapMessage.java
index df3c1cf..c88b61b 100644
--- a/android/app/src/com/android/bluetooth/sap/SapMessage.java
+++ b/android/app/src/com/android/bluetooth/sap/SapMessage.java
@@ -1,14 +1,12 @@
package com.android.bluetooth.sap;
-import android.hardware.radio.V1_0.ISap;
-import android.hardware.radio.V1_0.SapApduType;
-import android.hardware.radio.V1_0.SapTransferProtocol;
+import android.hardware.radio.sap.SapApduType;
+import android.hardware.radio.sap.SapTransferProtocol;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.google.protobuf.micro.CodedOutputStreamMicro;
import com.google.protobuf.micro.InvalidProtocolBufferMicroException;
import org.android.btsap.SapApi;
@@ -26,7 +24,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -35,7 +32,6 @@
* SapMessage is used for incoming and outgoing messages.
*
* For incoming messages
- *
*/
public class SapMessage {
@@ -194,6 +190,7 @@
/**
* Create a SapMessage
+ *
* @param msgType the SAP message type
*/
public SapMessage(int msgType) {
@@ -374,8 +371,9 @@
/**
* 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
+ * @param is the input stream to read the data from
* @return the resulting message, or null if an error occurs
*/
@SuppressWarnings("unused")
@@ -443,9 +441,10 @@
/**
* Blocking read of an entire array of data.
- * @param is the input stream to read from
+ *
+ * @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.
+ * determine how many bytes will be read.
*/
private static void read(InputStream is, byte[] buffer) throws IOException {
int bytesToRead = buffer.length;
@@ -463,7 +462,8 @@
/**
* Skip a number of bytes in an InputStream.
- * @param is the input stream
+ *
+ * @param is the input stream
* @param count the number of bytes to skip
* @throws IOException In case of reaching EOF or a stream error
*/
@@ -477,10 +477,10 @@
* 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
+ * @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;
@@ -621,9 +621,10 @@
/**
* 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 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
*/
@@ -656,8 +657,9 @@
/**
* Writes a byte[] parameter of any length.
- * @param os The BufferedOutputStream to write to.
- * @param id The Parameter ID
+ *
+ * @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
*/
@@ -729,18 +731,11 @@
* RILD Interface message conversion functions.
***************************************************************************/
- private ArrayList<Byte> primitiveArrayToContainerArrayList(byte[] arr) {
- ArrayList<Byte> arrayList = new ArrayList<>(arr.length);
- for (byte b : arr) {
- arrayList.add(b);
- }
- return arrayList;
- }
/**
* Send the message by calling corresponding ISap api.
*/
- public void send(ISap sapProxy) throws RemoteException, RuntimeException {
+ public void send(ISapRilReceiver sapProxy) throws RemoteException, RuntimeException {
int rilSerial = sNextSerial.getAndIncrement();
Log.e(TAG, "callISapReq: called for mMsgType " + mMsgType + " rilSerial " + rilSerial);
@@ -763,13 +758,13 @@
}
case ID_TRANSFER_APDU_REQ: {
int type;
- ArrayList<Byte> command;
+ byte[] command;
if (mApdu != null) {
type = SapApduType.APDU;
- command = primitiveArrayToContainerArrayList(mApdu);
+ command = mApdu;
} else if (mApdu7816 != null) {
type = SapApduType.APDU7816;
- command = primitiveArrayToContainerArrayList(mApdu7816);
+ command = mApdu7816;
} else {
Log.e(TAG, "Missing Apdu parameter in TRANSFER_APDU_REQ");
throw new IllegalArgumentException();
@@ -976,7 +971,7 @@
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. */
+ /* resMsg.getType is unused as the client knows the type of request used. */
if (resMsg.hasApduResponse()) {
mApduResp = resMsg.getApduResponse().toByteArray();
}
diff --git a/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java b/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java
index f7eb8f4..acf58ce 100644
--- a/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java
+++ b/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java
@@ -1,31 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.bluetooth.sap;
-import android.hardware.radio.V1_0.ISap;
-import android.hardware.radio.V1_0.ISapCallback;
+import android.hardware.radio.sap.ISap;
+import android.hardware.radio.sap.ISapCallback;
import android.os.Handler;
-import android.os.HwBinder;
+import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.concurrent.atomic.AtomicLong;
-public class SapRilReceiver {
+/**
+ * SapRiilReceiver is the AIDL implementation of ISapRilReceiver
+ */
+public class SapRilReceiver implements ISapRilReceiver {
private static final String TAG = "SapRilReceiver";
public static final boolean DEBUG = true;
public static final boolean VERBOSE = true;
- private static final String SERVICE_NAME_RIL_BT = "slot1";
- // match with constant in ril.cpp - as in RIL.java
- private static final int SOCKET_OPEN_RETRY_MILLIS = 4 * 1000;
+ // todo: add support for slot2 and slot3
+ private static final String HAL_INSTANCE_NAME = ISap.DESCRIPTOR + "/slot1";
SapCallback mSapCallback;
volatile ISap mSapProxy = null;
- Object mSapProxyLock = new Object();
+ final Object mSapProxyLock = new Object();
final AtomicLong mSapProxyCookie = new AtomicLong(0);
final SapProxyDeathRecipient mSapProxyDeathRecipient;
@@ -35,15 +52,129 @@
public static final int RIL_MAX_COMMAND_BYTES = (8 * 1024);
public byte[] buffer = new byte[RIL_MAX_COMMAND_BYTES];
- final class SapProxyDeathRecipient implements HwBinder.DeathRecipient {
+ /**
+ * TRANSFER_APDU_REQ from SAP 1.1 spec 5.1.6
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param type APDU command type
+ * @param command CommandAPDU/CommandAPDU7816 parameter depending on type
+ */
+ @Override
+ public void apduReq(int serial, int type, byte[] command) throws android.os.RemoteException {
+ mSapProxy.apduReq(serial, type, command);
+ }
+
+ /**
+ * CONNECT_REQ from SAP 1.1 spec 5.1.1
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param maxMsgSizeBytes MaxMsgSize to be used for SIM Access Profile connection
+ */
+ @Override
+ public void connectReq(int serial, int maxMsgSizeBytes) throws android.os.RemoteException {
+ mSapProxy.connectReq(serial, maxMsgSizeBytes);
+ }
+
+ /**
+ * DISCONNECT_REQ from SAP 1.1 spec 5.1.3
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void disconnectReq(int serial) throws android.os.RemoteException {
+ mSapProxy.disconnectReq(serial);
+ }
+
+ /**
+ * POWER_SIM_OFF_REQ and POWER_SIM_ON_REQ from SAP 1.1 spec 5.1.10 + 5.1.12
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param powerOn true for on, false for off
+ */
+ @Override
+ public void powerReq(int serial, boolean powerOn) throws android.os.RemoteException {
+ mSapProxy.powerReq(serial, powerOn);
+ }
+
+ /**
+ * RESET_SIM_REQ from SAP 1.1 spec 5.1.14
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void resetSimReq(int serial) throws android.os.RemoteException {
+ mSapProxy.resetSimReq(serial);
+ }
+
+ /**
+ * Set callback that has response and unsolicited indication functions
+ *
+ * @param sapCallback Object containing response and unosolicited indication callbacks
+ */
+ @Override
+ public void setCallback(android.hardware.radio.sap.ISapCallback sapCallback)
+ throws android.os.RemoteException {
+ Log.e(TAG, "setCallback should never be called");
+ }
+
+ /**
+ * SET_TRANSPORT_PROTOCOL_REQ from SAP 1.1 spec 5.1.20
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param transferProtocol Transport Protocol
+ */
+ @Override
+ public void setTransferProtocolReq(int serial, int transferProtocol)
+ throws android.os.RemoteException {
+ mSapProxy.setTransferProtocolReq(serial, transferProtocol);
+ }
+
+ /**
+ * TRANSFER_ATR_REQ from SAP 1.1 spec 5.1.8
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void transferAtrReq(int serial) throws android.os.RemoteException {
+ mSapProxy.transferAtrReq(serial);
+ }
+
+ /**
+ * TRANSFER_CARD_READER_STATUS_REQ from SAP 1.1 spec 5.1.17
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void transferCardReaderStatusReq(int serial) throws android.os.RemoteException {
+ mSapProxy.transferCardReaderStatusReq(serial);
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ Log.e(TAG, "getInterfaceVersion should never be called");
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ Log.e(TAG, "getInterfaceHash should never be called");
+ return "";
+ }
+
+ @Override
+ public android.os.IBinder asBinder() {
+ Log.e(TAG, "asBinder should never be called");
+ return null;
+ }
+
+ final class SapProxyDeathRecipient implements IBinder.DeathRecipient {
@Override
- public void serviceDied(long cookie) {
+ public void binderDied() {
// Deal with service going away
Log.d(TAG, "serviceDied");
// todo: temp hack to send delayed message so that rild is back up by then
- // mSapHandler.sendMessage(mSapHandler.obtainMessage(EVENT_SAP_PROXY_DEAD, cookie));
mSapServerMsgHandler.sendMessageDelayed(
- mSapServerMsgHandler.obtainMessage(SapServer.SAP_PROXY_DEAD, cookie),
+ mSapServerMsgHandler.obtainMessage(SapServer.SAP_PROXY_DEAD, (long) 0),
SapServer.ISAP_GET_SERVICE_DELAY_MILLIS);
}
}
@@ -100,25 +231,25 @@
}
@Override
- public void apduResponse(int token, int resultCode, ArrayList<Byte> apduRsp) {
+ public void apduResponse(int token, int resultCode, byte[] apduRsp) {
Log.d(TAG, "apduResponse: token " + token);
SapService.notifyUpdateWakeLock(mSapServiceHandler);
SapMessage sapMessage = new SapMessage(SapMessage.ID_TRANSFER_APDU_RESP);
sapMessage.setResultCode(resultCode);
if (resultCode == SapMessage.RESULT_OK) {
- sapMessage.setApduResp(arrayListToPrimitiveArray(apduRsp));
+ sapMessage.setApduResp(apduRsp);
}
removeOngoingReqAndSendMessage(token, sapMessage);
}
@Override
- public void transferAtrResponse(int token, int resultCode, ArrayList<Byte> atr) {
+ public void transferAtrResponse(int token, int resultCode, byte[] atr) {
Log.d(TAG, "transferAtrResponse: token " + token + " resultCode " + resultCode);
SapService.notifyUpdateWakeLock(mSapServiceHandler);
SapMessage sapMessage = new SapMessage(SapMessage.ID_TRANSFER_ATR_RESP);
sapMessage.setResultCode(resultCode);
if (resultCode == SapMessage.RESULT_OK) {
- sapMessage.setAtr(arrayListToPrimitiveArray(atr));
+ sapMessage.setAtr(atr);
}
removeOngoingReqAndSendMessage(token, sapMessage);
}
@@ -195,20 +326,39 @@
sapMessage.setResultCode(resultCode);
removeOngoingReqAndSendMessage(token, sapMessage);
}
- }
- public static byte[] arrayListToPrimitiveArray(List<Byte> bytes) {
- byte[] ret = new byte[bytes.size()];
- for (int i = 0; i < ret.length; i++) {
- ret[i] = bytes.get(i);
+ @Override
+ public String getInterfaceHash() {
+ return ISapCallback.HASH;
}
- return ret;
+
+ @Override
+ public int getInterfaceVersion() {
+ return ISapCallback.VERSION;
+ }
}
+ @Override
public Object getSapProxyLock() {
return mSapProxyLock;
}
+ @Override
+ public boolean isProxyValid() {
+ // Only call when synchronized with getSapProxyLock
+ return mSapProxy != null;
+ }
+
+ /**
+ * Check if AIDL is supported
+ */
+ public static boolean isAidlSupported() {
+ return SdkLevel.isAtLeastU() && ServiceManager.isDeclared(HAL_INSTANCE_NAME);
+ }
+
+ /**
+ * Obtain a valid sapProxy
+ */
public ISap getSapProxy() {
synchronized (mSapProxyLock) {
if (mSapProxy != null) {
@@ -216,10 +366,11 @@
}
try {
- mSapProxy = ISap.getService(SERVICE_NAME_RIL_BT);
+ IBinder service = ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME);
+ mSapProxy = ISap.Stub.asInterface(service);
if (mSapProxy != null) {
- mSapProxy.linkToDeath(mSapProxyDeathRecipient,
- mSapProxyCookie.incrementAndGet());
+ service.linkToDeath(mSapProxyDeathRecipient,
+ /* flags= */ 0);
mSapProxy.setCallback(mSapCallback);
} else {
Log.e(TAG, "getSapProxy: mSapProxy == null");
@@ -240,16 +391,17 @@
}
}
+ @Override
public void resetSapProxy() {
synchronized (mSapProxyLock) {
if (DEBUG) Log.d(TAG, "resetSapProxy :" + mSapProxy);
- try {
- if (mSapProxy != null) {
- mSapProxy.unlinkToDeath(mSapProxyDeathRecipient);
- }
- } catch (RemoteException | RuntimeException e) {
- Log.e(TAG, "resetSapProxy: exception: " + e);
+ if (mSapProxy == null) {
+ return;
}
+ if (mSapProxy.asBinder() == null) {
+ Log.e(TAG, "asdf asBinder is null");
+ }
+ mSapProxy.asBinder().unlinkToDeath(mSapProxyDeathRecipient, /* flags= */ 0);
mSapProxy = null;
}
}
@@ -264,25 +416,26 @@
}
}
- /**
- * Notify SapServer that this class is ready for shutdown.
- */
- void notifyShutdown() {
+ @Override
+ public void notifyShutdown() {
if (DEBUG) {
Log.i(TAG, "notifyShutdown()");
}
- // If we are already shutdown, don't bother sending a notification.
synchronized (mSapProxyLock) {
+ // If we are already shutdown, don't bother sending a notification.
if (mSapProxy != null) {
sendShutdownMessage();
}
+ resetSapProxy();
+
+ // todo: rild should be back up since the message was sent with a delay. this is
+ // a hack.
+ getSapProxy();
}
}
- /**
- * Notify SapServer that the RIL socket is connected
- */
- void sendRilConnectMessage() {
+ @Override
+ public void sendRilConnectMessage() {
if (mSapServerMsgHandler != null) {
mSapServerMsgHandler.sendEmptyMessage(SapServer.SAP_MSG_RIL_CONNECT);
}
@@ -290,6 +443,7 @@
/**
* Send reply (solicited) message from the RIL to the Sap Server Handler Thread
+ *
* @param sapMsg The message to send
*/
private void sendClientMessage(SapMessage sapMsg) {
@@ -308,6 +462,7 @@
/**
* Send indication (unsolicited) message from RIL to the Sap Server Handler Thread
+ *
* @param sapMsg The message to send
*/
private void sendRilIndMessage(SapMessage sapMsg) {
diff --git a/android/app/src/com/android/bluetooth/sap/SapRilReceiverHidl.java b/android/app/src/com/android/bluetooth/sap/SapRilReceiverHidl.java
new file mode 100644
index 0000000..30de0bb
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/sap/SapRilReceiverHidl.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.sap;
+
+import android.hardware.radio.V1_0.ISap;
+import android.hardware.radio.V1_0.ISapCallback;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * SapRiilReceiverHidl is the HIDL implementation of ISapRilReceiver
+ */
+public class SapRilReceiverHidl implements ISapRilReceiver {
+ private static final String TAG = "SapRilReceiver";
+ public static final boolean DEBUG = true;
+ public static final boolean VERBOSE = true;
+
+ // todo: add support for slot2 and slot3
+ private static final String SERVICE_NAME_RIL_BT = "slot1";
+
+ SapCallback mSapCallback;
+ volatile ISap mSapProxy = null;
+ final Object mSapProxyLock = new Object();
+ final AtomicLong mSapProxyCookie = new AtomicLong(0);
+ final SapProxyDeathRecipient mSapProxyDeathRecipient;
+
+ private Handler mSapServerMsgHandler = null;
+ private Handler mSapServiceHandler = null;
+
+ public static final int RIL_MAX_COMMAND_BYTES = (8 * 1024);
+ public byte[] buffer = new byte[RIL_MAX_COMMAND_BYTES];
+
+ private ArrayList<Byte> primitiveArrayToContainerArrayList(byte[] arr) {
+ ArrayList<Byte> arrayList = new ArrayList<>(arr.length);
+ for (byte b : arr) {
+ arrayList.add(b);
+ }
+ return arrayList;
+ }
+
+ /**
+ * TRANSFER_APDU_REQ from SAP 1.1 spec 5.1.6
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param type APDU command type
+ * @param command CommandAPDU/CommandAPDU7816 parameter depending on type
+ */
+ @Override
+ public void apduReq(int serial, int type, byte[] command) throws RemoteException {
+ ArrayList<Byte> commandHidl = primitiveArrayToContainerArrayList(command);
+ mSapProxy.apduReq(serial, type, commandHidl);
+
+ }
+
+ /**
+ * CONNECT_REQ from SAP 1.1 spec 5.1.1
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param maxMsgSizeBytes MaxMsgSize to be used for SIM Access Profile connection
+ */
+ @Override
+ public void connectReq(int serial, int maxMsgSizeBytes) throws RemoteException {
+ mSapProxy.connectReq(serial, maxMsgSizeBytes);
+ }
+
+ /**
+ * DISCONNECT_REQ from SAP 1.1 spec 5.1.3
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void disconnectReq(int serial) throws RemoteException {
+ mSapProxy.disconnectReq(serial);
+ }
+
+ /**
+ * POWER_SIM_OFF_REQ and POWER_SIM_ON_REQ from SAP 1.1 spec 5.1.10 + 5.1.12
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param powerOn true for on, false for off
+ */
+ @Override
+ public void powerReq(int serial, boolean powerOn) throws RemoteException {
+ mSapProxy.powerReq(serial, powerOn);
+ }
+
+ /**
+ * RESET_SIM_REQ from SAP 1.1 spec 5.1.14
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void resetSimReq(int serial) throws RemoteException {
+ mSapProxy.resetSimReq(serial);
+ }
+
+ /**
+ * Set callback that has response and unsolicited indication functions
+ *
+ * @param sapCallback Object containing response and unosolicited indication callbacks
+ */
+ @Override
+ public void setCallback(android.hardware.radio.sap.ISapCallback sapCallback)
+ throws RemoteException {
+ Log.e(TAG, "setCallback should never be called");
+ }
+
+ /**
+ * SET_TRANSPORT_PROTOCOL_REQ from SAP 1.1 spec 5.1.20
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ * @param transferProtocol Transport Protocol
+ */
+ @Override
+ public void setTransferProtocolReq(int serial, int transferProtocol) throws RemoteException {
+ mSapProxy.setTransferProtocolReq(serial, transferProtocol);
+ }
+
+ /**
+ * TRANSFER_ATR_REQ from SAP 1.1 spec 5.1.8
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void transferAtrReq(int serial) throws RemoteException {
+ mSapProxy.transferAtrReq(serial);
+ }
+
+ /**
+ * TRANSFER_CARD_READER_STATUS_REQ from SAP 1.1 spec 5.1.17
+ *
+ * @param serial Id to match req-resp. Resp must include same serial.
+ */
+ @Override
+ public void transferCardReaderStatusReq(int serial) throws RemoteException {
+ mSapProxy.transferCardReaderStatusReq(serial);
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ Log.e(TAG, "getInterfaceVersion should never be called");
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ Log.e(TAG, "getInterfaceHash should never be called");
+ return "";
+ }
+
+ @Override
+ public android.os.IBinder asBinder() {
+ Log.e(TAG, "asBinder should never be called");
+ return null;
+ }
+
+ final class SapProxyDeathRecipient implements IHwBinder.DeathRecipient {
+ @Override
+ public void serviceDied(long cookie) {
+ // Deal with service going away
+ Log.d(TAG, "serviceDied");
+ // todo: temp hack to send delayed message so that rild is back up by then
+ // mSapHandler.sendMessage(mSapHandler.obtainMessage(EVENT_SAP_PROXY_DEAD, cookie));
+
+ mSapServerMsgHandler.sendMessageDelayed(
+ mSapServerMsgHandler.obtainMessage(SapServer.SAP_PROXY_DEAD, cookie),
+ SapServer.ISAP_GET_SERVICE_DELAY_MILLIS);
+ }
+ }
+
+ private void sendSapMessage(SapMessage sapMessage) {
+ if (sapMessage.getMsgType() < SapMessage.ID_RIL_BASE) {
+ sendClientMessage(sapMessage);
+ } else {
+ sendRilIndMessage(sapMessage);
+ }
+ }
+
+ private void removeOngoingReqAndSendMessage(int token, SapMessage sapMessage) {
+ Integer reqType = SapMessage.sOngoingRequests.remove(token);
+ if (VERBOSE) {
+ Log.d(TAG, "removeOngoingReqAndSendMessage: token " + token + " reqType " + (
+ reqType == null ? "null" : SapMessage.getMsgTypeName(reqType)));
+ }
+ sendSapMessage(sapMessage);
+ }
+
+ /**
+ * Convert an arrayList to a primitive array
+ *
+ * @param bytes the List to convert
+ */
+ public static byte[] arrayListToPrimitiveArray(List<Byte> bytes) {
+ byte[] ret = new byte[bytes.size()];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = bytes.get(i);
+ }
+ return ret;
+ }
+
+ class SapCallback extends ISapCallback.Stub {
+ @Override
+ public void connectResponse(int token, int sapConnectRsp, int maxMsgSize) {
+ Log.d(TAG, "connectResponse: token " + token + " sapConnectRsp " + sapConnectRsp
+ + " maxMsgSize " + maxMsgSize);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_CONNECT_RESP);
+ sapMessage.setConnectionStatus(sapConnectRsp);
+ if (sapConnectRsp == SapMessage.CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED) {
+ sapMessage.setMaxMsgSize(maxMsgSize);
+ }
+ sapMessage.setResultCode(SapMessage.INVALID_VALUE);
+ removeOngoingReqAndSendMessage(token, sapMessage);
+ }
+
+ @Override
+ public void disconnectResponse(int token) {
+ Log.d(TAG, "disconnectResponse: token " + token);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
+ sapMessage.setResultCode(SapMessage.INVALID_VALUE);
+ removeOngoingReqAndSendMessage(token, sapMessage);
+ }
+
+ @Override
+ public void disconnectIndication(int token, int disconnectType) {
+ Log.d(TAG,
+ "disconnectIndication: token " + token + " disconnectType " + disconnectType);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_RIL_UNSOL_DISCONNECT_IND);
+ sapMessage.setDisconnectionType(disconnectType);
+ sendSapMessage(sapMessage);
+ }
+
+ @Override
+ public void apduResponse(int token, int resultCode, ArrayList<Byte> apduRsp) {
+ Log.d(TAG, "apduResponse: token " + token);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_TRANSFER_APDU_RESP);
+ sapMessage.setResultCode(resultCode);
+ if (resultCode == SapMessage.RESULT_OK) {
+ sapMessage.setApduResp(arrayListToPrimitiveArray(apduRsp));
+ }
+ removeOngoingReqAndSendMessage(token, sapMessage);
+ }
+
+ @Override
+ public void transferAtrResponse(int token, int resultCode, ArrayList<Byte> atr) {
+ Log.d(TAG, "transferAtrResponse: token " + token + " resultCode " + resultCode);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_TRANSFER_ATR_RESP);
+ sapMessage.setResultCode(resultCode);
+ if (resultCode == SapMessage.RESULT_OK) {
+ sapMessage.setAtr(arrayListToPrimitiveArray(atr));
+ }
+ removeOngoingReqAndSendMessage(token, sapMessage);
+ }
+
+ @Override
+ public void powerResponse(int token, int resultCode) {
+ Log.d(TAG, "powerResponse: token " + token + " resultCode " + resultCode);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ Integer reqType = SapMessage.sOngoingRequests.remove(token);
+ if (VERBOSE) {
+ Log.d(TAG, "powerResponse: reqType " + (reqType == null ? "null"
+ : SapMessage.getMsgTypeName(reqType)));
+ }
+ SapMessage sapMessage;
+ if (reqType == SapMessage.ID_POWER_SIM_OFF_REQ) {
+ sapMessage = new SapMessage(SapMessage.ID_POWER_SIM_OFF_RESP);
+ } else if (reqType == SapMessage.ID_POWER_SIM_ON_REQ) {
+ sapMessage = new SapMessage(SapMessage.ID_POWER_SIM_ON_RESP);
+ } else {
+ return;
+ }
+ sapMessage.setResultCode(resultCode);
+ sendSapMessage(sapMessage);
+ }
+
+ @Override
+ public void resetSimResponse(int token, int resultCode) {
+ Log.d(TAG, "resetSimResponse: token " + token + " resultCode " + resultCode);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_RESET_SIM_RESP);
+ sapMessage.setResultCode(resultCode);
+ removeOngoingReqAndSendMessage(token, sapMessage);
+ }
+
+ @Override
+ public void statusIndication(int token, int status) {
+ Log.d(TAG, "statusIndication: token " + token + " status " + status);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_STATUS_IND);
+ sapMessage.setStatusChange(status);
+ sendSapMessage(sapMessage);
+ }
+
+ @Override
+ public void transferCardReaderStatusResponse(int token, int resultCode,
+ int cardReaderStatus) {
+ Log.d(TAG,
+ "transferCardReaderStatusResponse: token " + token + " resultCode " + resultCode
+ + " cardReaderStatus " + cardReaderStatus);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP);
+ sapMessage.setResultCode(resultCode);
+ if (resultCode == SapMessage.RESULT_OK) {
+ sapMessage.setCardReaderStatus(cardReaderStatus);
+ }
+ removeOngoingReqAndSendMessage(token, sapMessage);
+ }
+
+ @Override
+ public void errorResponse(int token) {
+ Log.d(TAG, "errorResponse: token " + token);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ // Since ERROR_RESP isn't supported by createUnsolicited(), keeping behavior same here
+ // SapMessage sapMessage = new SapMessage(SapMessage.ID_ERROR_RESP);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_RIL_UNKNOWN);
+ sendSapMessage(sapMessage);
+ }
+
+ @Override
+ public void transferProtocolResponse(int token, int resultCode) {
+ Log.d(TAG, "transferProtocolResponse: token " + token + " resultCode " + resultCode);
+ SapService.notifyUpdateWakeLock(mSapServiceHandler);
+ SapMessage sapMessage = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP);
+ sapMessage.setResultCode(resultCode);
+ removeOngoingReqAndSendMessage(token, sapMessage);
+ }
+ }
+
+ @Override
+ public Object getSapProxyLock() {
+ return mSapProxyLock;
+ }
+
+ @Override
+ public boolean isProxyValid() {
+ // Only call when synchronized with getSapProxyLock
+ return mSapProxy != null;
+ }
+
+ /**
+ * Obtain a valid sapProxy
+ */
+ public ISap getSapProxy() {
+ synchronized (mSapProxyLock) {
+ if (mSapProxy != null) {
+ return mSapProxy;
+ }
+
+ try {
+ mSapProxy = ISap.getService(SERVICE_NAME_RIL_BT);
+ if (mSapProxy != null) {
+ mSapProxy.linkToDeath(mSapProxyDeathRecipient,
+ mSapProxyCookie.incrementAndGet());
+ mSapProxy.setCallback(mSapCallback);
+ } else {
+ Log.e(TAG, "getSapProxy: mSapProxy == null");
+ }
+ } catch (RemoteException | RuntimeException e) {
+ mSapProxy = null;
+ Log.e(TAG, "getSapProxy: exception: " + e);
+ }
+
+ if (mSapProxy == null) {
+ // if service is not up, treat it like death notification to try to get service
+ // again
+ mSapServerMsgHandler.sendMessageDelayed(
+ mSapServerMsgHandler.obtainMessage(SapServer.SAP_PROXY_DEAD,
+ mSapProxyCookie.get()), SapServer.ISAP_GET_SERVICE_DELAY_MILLIS);
+ }
+ return mSapProxy;
+ }
+ }
+
+ @Override
+ public void resetSapProxy() {
+ synchronized (mSapProxyLock) {
+ if (DEBUG) Log.d(TAG, "resetSapProxy :" + mSapProxy);
+ try {
+ if (mSapProxy != null) {
+ mSapProxy.unlinkToDeath(mSapProxyDeathRecipient);
+ }
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(TAG, "resetSapProxy: exception: " + e);
+ }
+ mSapProxy = null;
+ }
+ }
+
+ public SapRilReceiverHidl(Handler sapServerMsgHandler, Handler sapServiceHandler) {
+ mSapServerMsgHandler = sapServerMsgHandler;
+ mSapServiceHandler = sapServiceHandler;
+ mSapCallback = new SapCallback();
+ mSapProxyDeathRecipient = new SapProxyDeathRecipient();
+ synchronized (mSapProxyLock) {
+ mSapProxy = getSapProxy();
+ }
+ }
+
+ @Override
+ public void notifyShutdown() {
+ if (DEBUG) {
+ Log.i(TAG, "notifyShutdown()");
+ }
+ synchronized (mSapProxyLock) {
+ // If we are already shutdown, don't bother sending a notification.
+ if (mSapProxy != null) {
+ sendShutdownMessage();
+ }
+ resetSapProxy();
+
+ // todo: rild should be back up since the message was sent with a delay. this is
+ // a hack.
+ getSapProxy();
+ }
+ }
+
+ @Override
+ public void sendRilConnectMessage() {
+ if (mSapServerMsgHandler != null) {
+ mSapServerMsgHandler.sendEmptyMessage(SapServer.SAP_MSG_RIL_CONNECT);
+ }
+ }
+
+ /**
+ * Send reply (solicited) message from the RIL 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);
+ }
+
+ /**
+ * Send a shutdown signal to SapServer to indicate the
+ */
+ private void sendShutdownMessage() {
+ if (mSapServerMsgHandler != null) {
+ mSapServerMsgHandler.sendEmptyMessage(SapServer.SAP_RIL_SOCK_CLOSED);
+ }
+ }
+
+ /**
+ * Send indication (unsolicited) message from RIL to the Sap Server Handler Thread
+ *
+ * @param sapMsg The message to send
+ */
+ private void sendRilIndMessage(SapMessage sapMsg) {
+ Message newMsg = mSapServerMsgHandler.obtainMessage(SapServer.SAP_MSG_RIL_IND, sapMsg);
+ mSapServerMsgHandler.sendMessage(newMsg);
+ }
+
+ AtomicLong getSapProxyCookie() {
+ return mSapProxyCookie;
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/sap/SapServer.java b/android/app/src/com/android/bluetooth/sap/SapServer.java
index 9dfa22a..e8217c7 100644
--- a/android/app/src/com/android/bluetooth/sap/SapServer.java
+++ b/android/app/src/com/android/bluetooth/sap/SapServer.java
@@ -12,7 +12,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Icon;
-import android.hardware.radio.V1_0.ISap;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
@@ -67,7 +66,7 @@
private BufferedInputStream mRfcommIn = null;
/* References to the SapRilReceiver object */
@VisibleForTesting
- SapRilReceiver mRilBtReceiver = null;
+ ISapRilReceiver mRilBtReceiver = null;
/* The message handler members */
@VisibleForTesting
Handler mSapHandler = null;
@@ -112,9 +111,10 @@
/**
* 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
+ * @param inStream The socket input stream
+ * @param outStream The socket output stream
*/
public SapServer(Handler serviceHandler, Context context, InputStream inStream,
OutputStream outStream) {
@@ -185,6 +185,7 @@
* 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) {
@@ -271,8 +272,9 @@
PendingIntent pIntentDisconnect =
PendingIntent.getBroadcast(mContext, type, sapDisconnectIntent, flags);
Notification.Action actionDisconnect =
- new Notification.Action.Builder(Icon.createWithResource(mContext,
- android.R.drawable.stat_sys_data_bluetooth), button, pIntentDisconnect).build();
+ new Notification.Action.Builder(Icon.createWithResource(mContext,
+ android.R.drawable.stat_sys_data_bluetooth), button,
+ pIntentDisconnect).build();
notification =
new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL).setOngoing(true)
.addAction(actionDisconnect)
@@ -302,9 +304,10 @@
pIntentDisconnect).build();
Notification.Action actionForceDisconnect =
new Notification.Action.Builder(Icon.createWithResource(mContext,
- android.R.drawable.stat_sys_data_bluetooth),
- mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button),
- pIntentForceDisconnect).build();
+ android.R.drawable.stat_sys_data_bluetooth),
+ mContext.getString(
+ R.string.bluetooth_sap_notif_force_disconnect_button),
+ pIntentForceDisconnect).build();
notification =
new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL).setOngoing(true)
.addAction(actionDisconnect)
@@ -351,7 +354,11 @@
Looper sapLooper = mHandlerThread.getLooper();
mSapHandler = new Handler(sapLooper, this);
- mRilBtReceiver = new SapRilReceiver(mSapHandler, mSapServiceHandler);
+ if (SapRilReceiver.isAidlSupported()) {
+ mRilBtReceiver = new SapRilReceiver(mSapHandler, mSapServiceHandler);
+ } else {
+ mRilBtReceiver = new SapRilReceiverHidl(mSapHandler, mSapServiceHandler);
+ }
boolean done = false;
while (!done) {
if (VERBOSE) {
@@ -383,19 +390,19 @@
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 */
+ /*
+ * 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");
}
@@ -411,20 +418,20 @@
clearPendingRilResponses(msg);
changeState(SAP_STATE.DISCONNECTING);
sendRilThreadMessage(msg);
- /*cancel the timer for the hard-disconnect intent*/
+ /*cancel the timer for the hard-disconnect intent*/
stopDisconnectTimer();
}
msg = null; // No message needs to be sent to RIL
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 */
+ /* Forward these to the RIL regardless of the state, and clear any
+ * pending resp */
clearPendingRilResponses(msg);
break;
case SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ:
- /* The RIL might support more protocols that specified in the SAP,
- * allow only the valid values. */
+ /* The RIL might support more protocols that specified in the SAP,
+ * allow only the valid values. */
if (mState == SAP_STATE.CONNECTED && msg.getTransportProtocol() != 0
&& msg.getTransportProtocol() != 1) {
Log.w(TAG, "Invalid TransportProtocol received:"
@@ -438,8 +445,9 @@
}
// Fall through
default:
- /* Remaining cases just needs to be forwarded to the RIL unless we are
- * in busy state. */
+ /* 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());
@@ -560,11 +568,11 @@
/**
* 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.
+ * - 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
*/
@@ -623,6 +631,7 @@
/**
* Send RFCOMM message to the Sap Server Handler Thread
+ *
* @param sapMsg The message to send
*/
@VisibleForTesting
@@ -633,7 +642,6 @@
/**
* Send a RIL message to the SapServer message handler thread
- * @param sapMsg
*/
@VisibleForTesting
void sendRilThreadMessage(SapMessage sapMsg) {
@@ -643,6 +651,7 @@
/**
* 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.
*/
@VisibleForTesting
@@ -657,7 +666,6 @@
/**
* Change the SAP Server state.
* We add thread protection, as we access the state from two threads.
- * @param newState
*/
@VisibleForTesting
void changeState(SAP_STATE newState) {
@@ -675,10 +683,10 @@
/**
* 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
+ * - 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) {
@@ -695,8 +703,8 @@
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. */
+ /* The connection to rild-bt have been established. Store the outStream handle
+ * and send the connect request. */
if (mTestMode != SapMessage.INVALID_VALUE) {
SapMessage rilTestModeReq =
new SapMessage(SapMessage.ID_RIL_SIM_ACCESS_TEST_REQ);
@@ -724,17 +732,10 @@
startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
break;
case SAP_PROXY_DEAD:
- if ((long) msg.obj == mRilBtReceiver.getSapProxyCookie().get()) {
- mRilBtReceiver.notifyShutdown(); /* Only needed in case of a connection error */
- mRilBtReceiver.resetSapProxy();
-
- // todo: rild should be back up since message was sent with a delay. this is
- // a hack.
- mRilBtReceiver.getSapProxy();
- }
+ mRilBtReceiver.notifyShutdown(); /* Only needed in case of a connection error */
break;
default:
- /* Message not handled */
+ /* Message not handled */
return false;
}
return true; // Message handles
@@ -808,6 +809,7 @@
* 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
*/
@VisibleForTesting
@@ -939,12 +941,13 @@
switch (sapMsg.getMsgType()) {
case SapMessage.ID_RIL_UNSOL_DISCONNECT_IND: {
if (mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING) {
- /* we only send disconnect indication to the client if we are actually connected*/
+ /* 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 */
+ /* TODO: This was introduced to handle disconnect indication from RIL */
sendDisconnectInd(sapMsg.getDisconnectionType());
}
break;
@@ -960,7 +963,6 @@
/**
* This is only to be called from the handlerThread, else use sendRilThreadMessage();
- * @param sapMsg
*/
@VisibleForTesting
void sendRilMessage(SapMessage sapMsg) {
@@ -968,19 +970,15 @@
Log.i(TAG_HANDLER,
"sendRilMessage() - " + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
}
-
- Log.d(TAG_HANDLER, "sendRilMessage: calling getSapProxy");
synchronized (mRilBtReceiver.getSapProxyLock()) {
- ISap sapProxy = mRilBtReceiver.getSapProxy();
- if (sapProxy == null) {
+ if (!mRilBtReceiver.isProxyValid()) {
Log.e(TAG_HANDLER,
- "sendRilMessage: Unable to send message to RIL; sapProxy is null");
+ "sendRiilMessage: Unable to send message to Ril; sapProxy is invalid");
sendClientMessage(new SapMessage(SapMessage.ID_ERROR_RESP));
return;
}
-
try {
- sapMsg.send(sapProxy);
+ sapMsg.send(mRilBtReceiver);
if (VERBOSE) {
Log.d(TAG_HANDLER, "sendRilMessage: sapMsg.callISapReq called successfully");
}
@@ -994,6 +992,7 @@
mRilBtReceiver.resetSapProxy();
}
}
+
}
/**
diff --git a/android/app/tests/unit/src/com/android/bluetooth/sap/SapMessageTest.java b/android/app/tests/unit/src/com/android/bluetooth/sap/SapMessageTest.java
index 55cf35f..3b0b711 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/sap/SapMessageTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/sap/SapMessageTest.java
@@ -42,8 +42,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.hardware.radio.V1_0.ISap;
-import android.hardware.radio.V1_0.SapTransferProtocol;
+import android.hardware.radio.sap.SapTransferProtocol;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -77,10 +76,10 @@
int cardReaderStatus = STATUS_CARD_INSERTED;
int statusChange = 1;
int transportProtocol = TRANS_PROTO_T0;
- byte[] apdu = new byte[] {0x01, 0x02};
- byte[] apdu7816 = new byte[] {0x03, 0x04};
- byte[] apduResp = new byte[] {0x05, 0x06};
- byte[] atr = new byte[] {0x07, 0x08};
+ byte[] apdu = new byte[]{0x01, 0x02};
+ byte[] apdu7816 = new byte[]{0x03, 0x04};
+ byte[] apduResp = new byte[]{0x05, 0x06};
+ byte[] atr = new byte[]{0x07, 0x08};
boolean sendToRil = true;
boolean clearRilQueue = true;
int testMode = TEST_MODE_ENABLE;
@@ -148,10 +147,10 @@
int cardReaderStatus = STATUS_CARD_INSERTED;
int statusChange = 1;
int transportProtocol = TRANS_PROTO_T0;
- byte[] apdu = new byte[] {0x01, 0x02};
- byte[] apdu7816 = new byte[] {0x03, 0x04};
- byte[] apduResp = new byte[] {0x05, 0x06};
- byte[] atr = new byte[] {0x07, 0x08};
+ byte[] apdu = new byte[]{0x01, 0x02};
+ byte[] apdu7816 = new byte[]{0x03, 0x04};
+ byte[] apduResp = new byte[]{0x05, 0x06};
+ byte[] atr = new byte[]{0x07, 0x08};
mMessage.setMsgType(msgType);
mMessage.setMaxMsgSize(maxMsgSize);
@@ -196,10 +195,10 @@
@Test
public void send() throws Exception {
int maxMsgSize = 512;
- byte[] apdu = new byte[] {0x01, 0x02};
- byte[] apdu7816 = new byte[] {0x03, 0x04};
+ byte[] apdu = new byte[]{0x01, 0x02};
+ byte[] apdu7816 = new byte[]{0x03, 0x04};
- ISap sapProxy = mock(ISap.class);
+ ISapRilReceiver sapProxy = mock(ISapRilReceiver.class);
mMessage.setClearRilQueue(true);
mMessage.setMsgType(ID_CONNECT_REQ);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/sap/SapRilReceiverHidlTest.java b/android/app/tests/unit/src/com/android/bluetooth/sap/SapRilReceiverHidlTest.java
new file mode 100644
index 0000000..874bd09
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/sap/SapRilReceiverHidlTest.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.sap;
+
+import static com.android.bluetooth.sap.SapMessage.CON_STATUS_OK;
+import static com.android.bluetooth.sap.SapMessage.DISC_GRACEFULL;
+import static com.android.bluetooth.sap.SapMessage.ID_CONNECT_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_DISCONNECT_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_POWER_SIM_OFF_REQ;
+import static com.android.bluetooth.sap.SapMessage.ID_POWER_SIM_OFF_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_POWER_SIM_ON_REQ;
+import static com.android.bluetooth.sap.SapMessage.ID_POWER_SIM_ON_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_RESET_SIM_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_RIL_UNKNOWN;
+import static com.android.bluetooth.sap.SapMessage.ID_RIL_UNSOL_DISCONNECT_IND;
+import static com.android.bluetooth.sap.SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_STATUS_IND;
+import static com.android.bluetooth.sap.SapMessage.ID_TRANSFER_APDU_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_TRANSFER_ATR_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP;
+import static com.android.bluetooth.sap.SapMessage.RESULT_OK;
+import static com.android.bluetooth.sap.SapMessage.STATUS_CARD_INSERTED;
+import static com.android.bluetooth.sap.SapServer.ISAP_GET_SERVICE_DELAY_MILLIS;
+import static com.android.bluetooth.sap.SapServer.SAP_MSG_RFC_REPLY;
+import static com.android.bluetooth.sap.SapServer.SAP_MSG_RIL_CONNECT;
+import static com.android.bluetooth.sap.SapServer.SAP_MSG_RIL_IND;
+import static com.android.bluetooth.sap.SapServer.SAP_PROXY_DEAD;
+import static com.android.bluetooth.sap.SapServer.SAP_RIL_SOCK_CLOSED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.radio.V1_0.ISap;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class SapRilReceiverHidlTest {
+
+ private static final long TIMEOUT_MS = 1_000;
+
+ private HandlerThread mHandlerThread;
+ private Handler mServerMsgHandler;
+
+ @Spy
+ private TestHandlerCallback mCallback = new TestHandlerCallback();
+
+ @Mock
+ private Handler mServiceHandler;
+
+ @Mock
+ private ISap mSapProxy;
+
+ private SapRilReceiverHidl mReceiver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread("SapRilReceiverTest");
+ mHandlerThread.start();
+
+ mServerMsgHandler = new Handler(mHandlerThread.getLooper(), mCallback);
+ mReceiver = new SapRilReceiverHidl(mServerMsgHandler, mServiceHandler);
+ mReceiver.mSapProxy = mSapProxy;
+ mServerMsgHandler.removeMessages(SAP_PROXY_DEAD);
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ }
+
+ @Test
+ public void getSapProxyLock() {
+ assertThat(mReceiver.getSapProxyLock()).isNotNull();
+ }
+
+ @Test
+ public void resetSapProxy() throws Exception {
+ mReceiver.resetSapProxy();
+
+ assertThat(mReceiver.mSapProxy).isNull();
+ verify(mSapProxy).unlinkToDeath(any());
+ }
+
+ @Test
+ public void notifyShutdown() throws Exception {
+ mReceiver.notifyShutdown();
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_RIL_SOCK_CLOSED), any());
+ }
+
+ @Test
+ public void sendRilConnectMessage() throws Exception {
+ mReceiver.sendRilConnectMessage();
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RIL_CONNECT), any());
+ }
+
+ @Test
+ public void serviceDied() throws Exception {
+ long cookie = 1;
+ mReceiver.mSapProxyDeathRecipient.serviceDied(cookie);
+
+ verify(mCallback, timeout(ISAP_GET_SERVICE_DELAY_MILLIS + TIMEOUT_MS))
+ .receiveMessage(eq(SAP_PROXY_DEAD), argThat(
+ arg -> (arg instanceof Long) && ((Long) arg == cookie)
+ ));
+ }
+
+ @Test
+ public void callback_connectResponse() throws Exception {
+ int token = 1;
+ int sapConnectRsp = CON_STATUS_OK;
+ int maxMsgSize = 512;
+ mReceiver.mSapCallback.connectResponse(token, sapConnectRsp, maxMsgSize);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_CONNECT_RESP
+ && sapMsg.getConnectionStatus() == sapConnectRsp;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_disconnectResponse() throws Exception {
+ int token = 1;
+ mReceiver.mSapCallback.disconnectResponse(token);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_DISCONNECT_RESP;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_disconnectIndication() throws Exception {
+ int token = 1;
+ int disconnectType = DISC_GRACEFULL;
+ mReceiver.mSapCallback.disconnectIndication(token, disconnectType);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RIL_IND), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_RIL_UNSOL_DISCONNECT_IND
+ && sapMsg.getDisconnectionType() == disconnectType;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_apduResponse() throws Exception {
+ int token = 1;
+ int resultCode = RESULT_OK;
+ byte[] apduRsp = new byte[]{0x03, 0x04};
+ ArrayList<Byte> apduRspList = new ArrayList<>();
+ for (byte b : apduRsp) {
+ apduRspList.add(b);
+ }
+
+ mReceiver.mSapCallback.apduResponse(token, resultCode, apduRspList);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_TRANSFER_APDU_RESP
+ && sapMsg.getResultCode() == resultCode
+ && Arrays.equals(sapMsg.getApduResp(), apduRsp);
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_transferAtrResponse() throws Exception {
+ int token = 1;
+ int resultCode = RESULT_OK;
+ byte[] atr = new byte[]{0x03, 0x04};
+ ArrayList<Byte> atrList = new ArrayList<>();
+ for (byte b : atr) {
+ atrList.add(b);
+ }
+
+ mReceiver.mSapCallback.transferAtrResponse(token, resultCode, atrList);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_TRANSFER_ATR_RESP
+ && sapMsg.getResultCode() == resultCode
+ && Arrays.equals(sapMsg.getAtr(), atr);
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_powerResponse_powerOff() throws Exception {
+ int token = 1;
+ int reqType = ID_POWER_SIM_OFF_REQ;
+ int resultCode = RESULT_OK;
+ SapMessage.sOngoingRequests.clear();
+ SapMessage.sOngoingRequests.put(token, reqType);
+
+ mReceiver.mSapCallback.powerResponse(token, resultCode);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_POWER_SIM_OFF_RESP
+ && sapMsg.getResultCode() == resultCode;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_powerResponse_powerOn() throws Exception {
+ int token = 1;
+ int reqType = ID_POWER_SIM_ON_REQ;
+ int resultCode = RESULT_OK;
+ SapMessage.sOngoingRequests.clear();
+ SapMessage.sOngoingRequests.put(token, reqType);
+
+ mReceiver.mSapCallback.powerResponse(token, resultCode);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_POWER_SIM_ON_RESP
+ && sapMsg.getResultCode() == resultCode;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_resetSimResponse() throws Exception {
+ int token = 1;
+ int resultCode = RESULT_OK;
+
+ mReceiver.mSapCallback.resetSimResponse(token, resultCode);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_RESET_SIM_RESP
+ && sapMsg.getResultCode() == resultCode;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_statusIndication() throws Exception {
+ int token = 1;
+ int statusChange = 2;
+
+ mReceiver.mSapCallback.statusIndication(token, statusChange);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_STATUS_IND
+ && sapMsg.getStatusChange() == statusChange;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_transferCardReaderStatusResponse() throws Exception {
+ int token = 1;
+ int resultCode = RESULT_OK;
+ int cardReaderStatus = STATUS_CARD_INSERTED;
+
+ mReceiver.mSapCallback.transferCardReaderStatusResponse(
+ token, resultCode, cardReaderStatus);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_TRANSFER_CARD_READER_STATUS_RESP
+ && sapMsg.getResultCode() == resultCode;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_errorResponse() throws Exception {
+ int token = 1;
+
+ mReceiver.mSapCallback.errorResponse(token);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RIL_IND), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_RIL_UNKNOWN;
+ }
+ }
+ ));
+ }
+
+ @Test
+ public void callback_transferProtocolResponse() throws Exception {
+ int token = 1;
+ int resultCode = RESULT_OK;
+
+ mReceiver.mSapCallback.transferProtocolResponse(token, resultCode);
+
+ verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+ new ArgumentMatcher<Object>() {
+ @Override
+ public boolean matches(Object arg) {
+ if (!(arg instanceof SapMessage)) {
+ return false;
+ }
+ SapMessage sapMsg = (SapMessage) arg;
+ return sapMsg.getMsgType() == ID_SET_TRANSPORT_PROTOCOL_RESP
+ && sapMsg.getResultCode() == resultCode;
+ }
+ }
+ ));
+ }
+
+ public static class TestHandlerCallback implements Handler.Callback {
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ receiveMessage(msg.what, msg.obj);
+ return true;
+ }
+
+ public void receiveMessage(int what, Object obj) {
+ }
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/sap/SapRilReceiverTest.java b/android/app/tests/unit/src/com/android/bluetooth/sap/SapRilReceiverTest.java
index 8299daa..3d0e350 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/sap/SapRilReceiverTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/sap/SapRilReceiverTest.java
@@ -44,14 +44,18 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.hardware.radio.V1_0.ISap;
+import android.hardware.radio.sap.ISap;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Message;
import androidx.test.filters.LargeTest;
@@ -66,7 +70,6 @@
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
-import java.util.ArrayList;
import java.util.Arrays;
@LargeTest
@@ -99,6 +102,7 @@
mServerMsgHandler = new Handler(mHandlerThread.getLooper(), mCallback);
mReceiver = new SapRilReceiver(mServerMsgHandler, mServiceHandler);
mReceiver.mSapProxy = mSapProxy;
+ mServerMsgHandler.removeMessages(SAP_PROXY_DEAD);
}
@After
@@ -113,14 +117,18 @@
@Test
public void resetSapProxy() throws Exception {
+ IBinder mockSapProxyBinder = mock(IBinder.class);
+ when(mReceiver.mSapProxy.asBinder()).thenReturn(mockSapProxyBinder);
mReceiver.resetSapProxy();
assertThat(mReceiver.mSapProxy).isNull();
- verify(mSapProxy).unlinkToDeath(any());
+ verify(mockSapProxyBinder).unlinkToDeath(any(), anyInt());
}
@Test
public void notifyShutdown() throws Exception {
+ IBinder mockSapProxyBinder = mock(IBinder.class);
+ when(mReceiver.mSapProxy.asBinder()).thenReturn(mockSapProxyBinder);
mReceiver.notifyShutdown();
verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_RIL_SOCK_CLOSED), any());
@@ -134,13 +142,12 @@
}
@Test
- public void serviceDied() throws Exception {
- long cookie = 1;
- mReceiver.mSapProxyDeathRecipient.serviceDied(cookie);
+ public void binderDied() throws Exception {
+ mReceiver.mSapProxyDeathRecipient.binderDied();
verify(mCallback, timeout(ISAP_GET_SERVICE_DELAY_MILLIS + TIMEOUT_MS))
.receiveMessage(eq(SAP_PROXY_DEAD), argThat(
- arg -> (arg instanceof Long) && ((Long) arg == cookie)
+ arg -> (arg instanceof Long) && ((Long) arg == 0)
));
}
@@ -211,12 +218,8 @@
int token = 1;
int resultCode = RESULT_OK;
byte[] apduRsp = new byte[]{0x03, 0x04};
- ArrayList<Byte> apduRspList = new ArrayList<>();
- for (byte b : apduRsp) {
- apduRspList.add(b);
- }
- mReceiver.mSapCallback.apduResponse(token, resultCode, apduRspList);
+ mReceiver.mSapCallback.apduResponse(token, resultCode, apduRsp);
verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
new ArgumentMatcher<Object>() {
@@ -239,12 +242,8 @@
int token = 1;
int resultCode = RESULT_OK;
byte[] atr = new byte[]{0x03, 0x04};
- ArrayList<Byte> atrList = new ArrayList<>();
- for (byte b : atr) {
- atrList.add(b);
- }
- mReceiver.mSapCallback.transferAtrResponse(token, resultCode, atrList);
+ mReceiver.mSapCallback.transferAtrResponse(token, resultCode, atr);
verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
new ArgumentMatcher<Object>() {
@@ -430,6 +429,7 @@
return true;
}
- public void receiveMessage(int what, Object obj) {}
+ public void receiveMessage(int what, Object obj) {
+ }
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/sap/SapServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/sap/SapServerTest.java
index b3be8c2..e88c5f1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/sap/SapServerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/sap/SapServerTest.java
@@ -59,7 +59,6 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
-import android.hardware.radio.V1_0.ISap;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
@@ -160,11 +159,9 @@
@Test
public void onConnectRequest_whenStateIsConnecting_callsSendRilMessage() {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
- ISap mockSapProxy = mock(ISap.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
Object lock = new Object();
when(mockReceiver.getSapProxyLock()).thenReturn(lock);
- when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
mSapServer.mRilBtReceiver = mockReceiver;
mSapServer.mSapHandler = mHandler;
@@ -218,26 +215,25 @@
@Test
public void sendRilMessage_success() throws Exception {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
- ISap mockSapProxy = mock(ISap.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
Object lock = new Object();
when(mockReceiver.getSapProxyLock()).thenReturn(lock);
- when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
+ when(mockReceiver.isProxyValid()).thenReturn(true);
mSapServer.mRilBtReceiver = mockReceiver;
mSapServer.mSapHandler = mHandler;
SapMessage msg = mock(SapMessage.class);
mSapServer.sendRilMessage(msg);
- verify(msg).send(mockSapProxy);
+ verify(msg).send(mockReceiver);
}
@Test
public void sendRilMessage_whenSapProxyIsNull_sendsErrorClientMessage() throws Exception {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
Object lock = new Object();
when(mockReceiver.getSapProxyLock()).thenReturn(lock);
- when(mockReceiver.getSapProxy()).thenReturn(null);
+ when(mockReceiver.isProxyValid()).thenReturn(false);
mSapServer.mRilBtReceiver = mockReceiver;
mSapServer.mSapHandler = mHandler;
@@ -250,11 +246,9 @@
@Test
public void sendRilMessage_whenIAEIsThrown_sendsErrorClientMessage() throws Exception {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
Object lock = new Object();
- ISap mockSapProxy = mock(ISap.class);
when(mockReceiver.getSapProxyLock()).thenReturn(lock);
- when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
mSapServer.mRilBtReceiver = mockReceiver;
mSapServer.mSapHandler = mHandler;
@@ -269,11 +263,10 @@
@Test
public void sendRilMessage_whenRemoteExceptionIsThrown_sendsErrorClientMessage()
throws Exception {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
Object lock = new Object();
- ISap mockSapProxy = mock(ISap.class);
when(mockReceiver.getSapProxyLock()).thenReturn(lock);
- when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
+ when(mockReceiver.isProxyValid()).thenReturn(true);
mSapServer.mRilBtReceiver = mockReceiver;
mSapServer.mSapHandler = mHandler;
@@ -544,7 +537,7 @@
@Test
public void handleMessage_forRilConnectMsg_callsSendRilMessage() throws Exception {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
Object lock = new Object();
when(mockReceiver.getSapProxyLock()).thenReturn(lock);
mSapServer.mRilBtReceiver = mockReceiver;
@@ -566,11 +559,9 @@
@Test
public void handleMessage_forRilReqMsg_callsSendRilMessage() throws Exception {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
- ISap mockSapProxy = mock(ISap.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
Object lock = new Object();
when(mockReceiver.getSapProxyLock()).thenReturn(lock);
- when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
mSapServer.mRilBtReceiver = mockReceiver;
mSapServer.mSapHandler = mHandler;
@@ -630,9 +621,8 @@
@Test
public void handleMessage_forProxyDeadMsg_notifiesShutDown() throws Exception {
- SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+ ISapRilReceiver mockReceiver = mock(ISapRilReceiver.class);
AtomicLong cookie = new AtomicLong(23);
- when(mockReceiver.getSapProxyCookie()).thenReturn(cookie);
mSapServer.mRilBtReceiver = mockReceiver;
Message message = Message.obtain();
@@ -643,7 +633,6 @@
mSapServer.handleMessage(message);
verify(mockReceiver).notifyShutdown();
- verify(mockReceiver).resetSapProxy();
} finally {
message.recycle();
}
@@ -709,7 +698,8 @@
return true;
}
- public void receiveMessage(int what, Object obj) {}
+ public void receiveMessage(int what, Object obj) {
+ }
}
}