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; + } + } + +}