diff --git a/.gitignore b/.gitignore
index 27b23ce..06a9aa0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ lib/java/dbus/Manifest
lib/java/dbus/bin
lib/java/dbus/classes
lib/java/dbus/*.jar
+
+lib/java/android
+cyanogenmod
diff --git a/Android.mk b/Android.mk
index e49fd5f..0d70f54 100644
--- a/Android.mk
+++ b/Android.mk
@@ -20,7 +20,10 @@ LOCAL_PATH := $(call my-dir)
# Build our Java RIL
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := RilOfono
- LOCAL_SRC_FILES := $(call all-java-files-under,src/java) $(call all-java-files-under,lib/java)
+ LOCAL_SRC_FILES := $(call all-java-files-under,src/java) \
+ $(call all-java-files-under,lib/java/dbus) \
+ $(call all-java-files-under,lib/java/debug) \
+ $(call all-java-files-under,lib/java/ofono)
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_CERTIFICATE := platform
diff --git a/README.md b/README.md
index b0ef4ad..18cada4 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,9 @@ The goal of this project is to write an Android RIL daemon implemented on top of
* dexopt/proguard? - see notes in Android.mk
* make sure socket operations on the main thread trigger strict mode exceptions (are Unix sockets a loophole?)
* make dbus exceptions be checked exceptions, so the compiler will find them and I have to handle them
+* SMS send/receive - current implementation is limited when it comes to things like long messages. We could
+concatenate/split PDUs in the RIL to map on to Ofono's nice API, but we will not be able to implement a few things
+that raw PDUs can do. And duplicating the work is ugly anyway, so I think I will just patch Ofono with raw PDU APIs.
# License
diff --git a/mount b/mount
new file mode 100755
index 0000000..28458a7
--- /dev/null
+++ b/mount
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -x
+BASE=$(realpath $(dirname $0))
+
+mount -t overlay overlay -o ro -olowerdir=\
+$BASE/cyanogenmod/12/frameworks/base/core/java:\
+$BASE/cyanogenmod/12/frameworks/base/telephony/java:\
+$BASE/cyanogenmod/12/frameworks/opt/telephony/src/java:\
+$BASE/cyanogenmod/12/libcore/luni/src/main/java:\
+$BASE/cyanogenmod/12/packages/services/Telephony/src\
+ \
+$BASE/lib/java/android/
diff --git a/src/apk.iml b/src/apk.iml
index cae79c1..d681ed8 100644
--- a/src/apk.iml
+++ b/src/apk.iml
@@ -5,21 +5,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -29,6 +14,9 @@
+
+
+
@@ -37,4 +25,4 @@
-
\ No newline at end of file
+
diff --git a/src/java/net/scintill/ril_ofono/AospUtils.java b/src/java/net/scintill/ril_ofono/AospUtils.java
new file mode 100644
index 0000000..69bfc58
--- /dev/null
+++ b/src/java/net/scintill/ril_ofono/AospUtils.java
@@ -0,0 +1,114 @@
+package net.scintill.ril_ofono;
+
+import android.telephony.Rlog;
+
+import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+
+public class AospUtils {
+ private static final String LOG_TAG = "AospUtils";
+
+ /*
+ * Based on com.android.internal.telephony.gsm.SmsMessage in CM12
+ *
+ * Copyright (C) 2006 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.
+ */
+ public static int getEncodingType(int dataCodingScheme) {
+ boolean hasMessageClass = false;
+ boolean userDataCompressed = false;
+
+ int encodingType = ENCODING_UNKNOWN;
+
+ // Look up the data encoding scheme
+ if ((dataCodingScheme & 0x80) == 0) {
+ userDataCompressed = (0 != (dataCodingScheme & 0x20));
+ hasMessageClass = (0 != (dataCodingScheme & 0x10));
+
+ if (userDataCompressed) {
+ Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
+ + "(compression) " + (dataCodingScheme & 0xff));
+ } else {
+ switch ((dataCodingScheme >> 2) & 0x3) {
+ case 0: // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ break;
+
+ case 2: // UCS 2 (16bit)
+ encodingType = ENCODING_16BIT;
+ break;
+
+ case 1: // 8 bit data
+ //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+ //that's stored in 8-bit unpacked format) characters.
+ encodingType = ENCODING_8BIT;
+ break;
+
+ case 3: // reserved
+ Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ + (dataCodingScheme & 0xff));
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+ }
+ } else if ((dataCodingScheme & 0xf0) == 0xf0) {
+ hasMessageClass = true;
+ userDataCompressed = false;
+
+ if (0 == (dataCodingScheme & 0x04)) {
+ // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ } else {
+ // 8 bit data
+ encodingType = ENCODING_8BIT;
+ }
+ } else if ((dataCodingScheme & 0xF0) == 0xC0
+ || (dataCodingScheme & 0xF0) == 0xD0
+ || (dataCodingScheme & 0xF0) == 0xE0) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+
+ // 0xC0 == 7 bit, don't store
+ // 0xD0 == 7 bit, store
+ // 0xE0 == UCS-2, store
+
+ if ((dataCodingScheme & 0xF0) == 0xE0) {
+ encodingType = ENCODING_16BIT;
+ } else {
+ encodingType = ENCODING_7BIT;
+ }
+
+ userDataCompressed = false;
+ boolean active = ((dataCodingScheme & 0x08) == 0x08);
+ // bit 0x04 reserved
+ } else if ((dataCodingScheme & 0xC0) == 0x80) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+ // 0x80..0xBF == Reserved coding groups
+ if (dataCodingScheme == 0x84) {
+ // This value used for KSC5601 by carriers in Korea.
+ encodingType = ENCODING_KSC5601;
+ } else {
+ Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+ + (dataCodingScheme & 0xff));
+ }
+ } else {
+ Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+ + (dataCodingScheme & 0xff));
+ }
+
+ return encodingType;
+ }
+}
diff --git a/src/java/net/scintill/ril_ofono/RilOfono.java b/src/java/net/scintill/ril_ofono/RilOfono.java
index 3966630..625b147 100644
--- a/src/java/net/scintill/ril_ofono/RilOfono.java
+++ b/src/java/net/scintill/ril_ofono/RilOfono.java
@@ -26,19 +26,21 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
+import android.telephony.PhoneNumberUtils;
import android.telephony.Rlog;
import android.telephony.SignalStrength;
+import android.telephony.SmsMessage;
import android.text.TextUtils;
import com.android.internal.telephony.BaseCommands;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsResponse;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
-import com.android.internal.telephony.gsm.SmsMessage;
import com.android.internal.telephony.uicc.AdnRecord;
import com.android.internal.telephony.uicc.IccCardApplicationStatus;
import com.android.internal.telephony.uicc.IccCardStatus;
@@ -65,11 +67,16 @@
import org.ofono.SimManager;
import org.ofono.Struct1;
+import java.io.ByteArrayOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
import static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_EDGE;
@@ -110,7 +117,7 @@ public class RilOfono extends BaseCommands implements CommandsInterface {
public RilOfono(Context context, int preferredNetworkType, int cdmaSubscription, Integer instanceId) {
super(context);
- Rlog.d(TAG, String.format("RilOfono %d starting", BUILD_NUMBER));
+ Rlog.d(TAG, "RilOfono "+BUILD_NUMBER+" starting");
mPhoneType = RILConstants.NO_PHONE;
@@ -136,6 +143,8 @@ public void run() {
mDbus.addSigHandler(NetworkRegistration.PropertyChanged.class, sigHandler);
mDbus.addSigHandler(SimManager.PropertyChanged.class, sigHandler);
mDbus.addSigHandler(org.ofono.Message.PropertyChanged.class, sigHandler);
+ mDbus.addSigHandler(MessageManager.IncomingMessage.class, sigHandler);
+ mDbus.addSigHandler(MessageManager.ImmediateMessage.class, sigHandler);
initProps();
onModemChange(false); // initialize starting state
} catch (DBusException e) {
@@ -144,6 +153,8 @@ public void run() {
}
}
});
+
+ //mMainHandler.postDelayed(new Tests(this), 10000);
}
@Override
@@ -520,6 +531,36 @@ public void sendSMSExpectMore(String smscPDU, String pdu, Message result) {
sendSMS(smscPDU, pdu, result);
}
+ private void handleIncomingMessage(String content, Map info) {
+ String dateStr = (String) info.get("SentTime").getValue();
+ String sender = (String) info.get("Sender").getValue();
+
+ Rlog.d(TAG, "handleIncomingMessage "+sender+" "+dateStr+" "+content); // TODO sensitive
+
+ Date date = Utils.parseOfonoDate(dateStr);
+ if (date == null) {
+ Rlog.e(TAG, "error parsing SMS date "+dateStr);
+ date = new Date();
+ }
+
+ final Object msg = createReceivedMessage(sender, content, date, getProp(info, "Immediate", false));
+ mMainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGsmSmsRegistrant.notifyResult(msg);
+ }
+ });
+ }
+
+ public void handle(MessageManager.IncomingMessage s) {
+ handleIncomingMessage(s.message, s.info);
+ }
+
+ public void handle(MessageManager.ImmediateMessage s) {
+ s.info.put("Immediate", new Variant<>(true));
+ handleIncomingMessage(s.message, s.info);
+ }
+
@Override
public void sendCdmaSms(byte[] pdu, Message response) {
genericTrace(); // XXX NYI
@@ -1459,13 +1500,57 @@ private SmsMessage parseSmsPduStrs(String smscPDUStr, String pduStr) {
smscPDUStr = "00"; // see PduParser; means no smsc
}
try {
- return SmsMessage.createFromPdu(IccUtils.hexStringToBytes(smscPDUStr + pduStr));
+ return SmsMessage.createFromPdu(IccUtils.hexStringToBytes(smscPDUStr + pduStr), SmsConstants.FORMAT_3GPP);
} catch (Throwable t) {
// SmsMessage should have logged information about the error
return null;
}
}
+ private SmsMessage createReceivedMessage(String sender, String contentText, Date date, boolean immediate) {
+ try {
+ // see SmsMessage#parsePdu
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write(new byte[] {
+ 0x00, // null sc address
+ 0x00, // deliver type. no reply path or user header
+ });
+ byte[] bcdSender = PhoneNumberUtils.networkPortionToCalledPartyBCD(sender);
+ os.write((bcdSender.length - 1) * 2); // BCD digit count, excluding TOA.
+ os.write(bcdSender);
+
+ // build a submit pdu so it will encode the message for us
+ // it turned out to not be as convenient as I hoped, but probably still better than
+ // writing/copying here
+ com.android.internal.telephony.gsm.SmsMessage.SubmitPdu submitPduOb =
+ com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, "0", contentText, false);
+ byte[] submitPdu = new byte[1 + submitPduOb.encodedMessage.length];
+ submitPdu[0] = 0x00; // null sc adddr, so it will parse below
+ System.arraycopy(submitPduOb.encodedMessage, 0, submitPdu, 1, submitPduOb.encodedMessage.length);
+ com.android.internal.telephony.gsm.SmsMessage msg =
+ com.android.internal.telephony.gsm.SmsMessage.createFromPdu(submitPdu);
+ if (msg == null) throw new RuntimeException("unable to parse submit pdu to create deliver pdu");
+
+ // finish writing the deliver
+ int dataCodingScheme = callPrivateMethod(msg, Integer.class, "getDataCodingScheme");
+ os.write(new byte[]{
+ 0x00, // protocol identifier
+ (byte) (dataCodingScheme | (immediate ? 0x10 : 0))
+ });
+ os.write(getScTimestamp(date));
+ byte[] payload = msg.getUserData();
+ os.write(AospUtils.getEncodingType(dataCodingScheme) != SmsConstants.ENCODING_7BIT ?
+ payload.length : // octet length
+ // septet length - we can't tell how many meaningful septets there are, but
+ // I think in the case of 7bit it will always be the length of the string
+ contentText.length());
+ os.write(payload);
+ return SmsMessage.createFromPdu(os.toByteArray(), SmsConstants.FORMAT_3GPP);
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
// opposite of IccUtils#bcdToString
private byte[] stringToBcd(String str) {
byte[] ret = new byte[(str.length() / 2) + 1];
@@ -1490,4 +1575,24 @@ public String toDebugString(Object o) {
}
}
+ @SuppressWarnings("unchecked")
+ private static T callPrivateMethod(Object o, Class returnClass, String methodName)
+ throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+
+ Method m = o.getClass().getDeclaredMethod(methodName);
+ m.setAccessible(true);
+ return (T) m.invoke(o);
+ }
+
+ private static byte[] getScTimestamp(Date d) {
+ // opposite of SmsMessage#getSCTimestampMillis()
+ // value is BCD nibble-swapped ymdhmszz (z = zone)
+ SimpleDateFormat fmt = new SimpleDateFormat("ssmmHHddMMyy", Locale.US);
+ fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
+ StringBuilder b = new StringBuilder(fmt.format(d));
+ b.reverse();
+ b.append("00"); // TODO preserve real tz?
+ return IccUtils.hexStringToBytes(b.toString());
+ }
+
}
diff --git a/src/java/net/scintill/ril_ofono/Tests.java b/src/java/net/scintill/ril_ofono/Tests.java
new file mode 100644
index 0000000..e5ee4a0
--- /dev/null
+++ b/src/java/net/scintill/ril_ofono/Tests.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 Joey Hewitt
+ *
+ * This file is part of ril_ofono.
+ *
+ * ril_ofono is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ril_ofono is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ril_ofono. If not, see .
+ */
+
+package net.scintill.ril_ofono;
+
+import android.util.Log;
+
+import org.freedesktop.dbus.Variant;
+import org.ofono.MessageManager;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Tests implements Runnable {
+
+ private static final String TAG = "RilOfono Tests";
+
+ RilOfono mRil;
+
+ public Tests(RilOfono ril) {
+ mRil = ril;
+ }
+
+ @Override
+ public void run() {
+ try {
+ createReceivedMessage("123", "Test123!", new Date(), false);
+ createReceivedMessage("456", "Test 🌠", new Date(System.currentTimeMillis()+1000000), false);
+ createReceivedMessage("777", "Immediate!", new Date(System.currentTimeMillis()), true);
+ } catch (Throwable t) {
+ Log.e(TAG, "Error running tests", t);
+ }
+ }
+
+ private void createReceivedMessage(String sender, String text, Date date, boolean immediate) throws Throwable {
+ Map info = new HashMap<>();
+ info.put("Sender", new Variant<>(sender));
+ info.put("SentTime", new Variant<>(Utils.formatOfonoDate(date)));
+ if (!immediate) {
+ MessageManager.IncomingMessage s = new MessageManager.IncomingMessage("/", text, info);
+ mRil.handle(s);
+ } else {
+ MessageManager.ImmediateMessage s = new MessageManager.ImmediateMessage("/", text, info);
+ mRil.handle(s);
+ }
+ }
+
+}
diff --git a/src/java/net/scintill/ril_ofono/Utils.java b/src/java/net/scintill/ril_ofono/Utils.java
new file mode 100644
index 0000000..abbcf9a
--- /dev/null
+++ b/src/java/net/scintill/ril_ofono/Utils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 Joey Hewitt
+ *
+ * This file is part of ril_ofono.
+ *
+ * ril_ofono is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ril_ofono is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ril_ofono. If not, see .
+ */
+
+package net.scintill.ril_ofono;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Utils {
+
+ static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ");
+
+ public static String formatOfonoDate(Date d) {
+ return FORMAT.format(d);
+ }
+
+ public static Date parseOfonoDate(String s) {
+ try {
+ return FORMAT.parse(s);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+}