MotoNrEnabler: Add a service to enable 5G via OEM RIL request lineage-20
authordianlujitao <dianlujitao@lineageos.org>
Fri, 12 Jan 2024 14:33:52 +0000 (22:33 +0800)
committerdianlujitao <dianlujitao@lineageos.org>
Fri, 12 Jan 2024 15:22:35 +0000 (23:22 +0800)
Clearing and rebuilding modem cache (e.g. mdmddr, mdm1m9kefs1,
mdm1m9kefs2, carrier, and ddr partitions for MDM devices) is typically
necessary after modem firmware upgrade. However, NR is disabled in
Moto's modem firmware by default, so choosing NR results in "No signal"
after rebuilding modem cache with custom ROMs installed. Users have to
go back to stock once to make it work again, which is a huge pain.

It's observed that stock ROMs control NR via a custom RIL request, and
choose it based on carrier config and user preference (yes they have a
toggle in system settings) after SIM provisioning. Here, we implement a
service to automatically enable NR and DSS in the same approach, so
that users no longer need to go back to stock after upgrading modem
firmware. The implementation is based on telephony-common.jar,
qti-telephony-common.jar and qcom-moto-telephony-ext.jar from nio.
You're suggested to check if the magic numbers and payload formats
match your device's stock ROM before using this service.

Change-Id: I5ecfae93717fe87e6b1c1a75caa674b466403391

MotoNrEnabler/Android.bp [new file with mode: 0644]
MotoNrEnabler/AndroidManifest.xml [new file with mode: 0644]
MotoNrEnabler/src/com/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel.aidl [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/BootCompletedReceiver.kt [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/NrEnablerService.kt [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/NrMode.kt [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomMotoExtTelephonyService.kt [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomNvInfo.kt [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomNvUtils.kt [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomOemConstants.kt [new file with mode: 0644]
MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcrilMsgTunnelConnector.kt [new file with mode: 0644]

diff --git a/MotoNrEnabler/Android.bp b/MotoNrEnabler/Android.bp
new file mode 100644 (file)
index 0000000..b6ef9a4
--- /dev/null
@@ -0,0 +1,18 @@
+//
+// SPDX-FileCopyrightText: 2024 The LineageOS Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+android_app {
+    name: "MotoNrEnabler",
+
+    srcs: [
+        "src/**/*.aidl",
+        "src/**/*.kt",
+    ],
+    libs: ["telephony-common"],
+
+    certificate: "platform",
+    platform_apis: true,
+    system_ext_specific: true,
+}
diff --git a/MotoNrEnabler/AndroidManifest.xml b/MotoNrEnabler/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..d62896e
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     SPDX-FileCopyrightText: 2024 The LineageOS Project
+     SPDX-License-Identifier: Apache-2.0
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.lineageos.motorola.nrenabler"
+    android:sharedUserId="android.uid.phone">
+
+    <uses-permission android:name="com.qualcomm.permission.USE_QCRIL_MSG_TUNNEL" />
+
+    <application
+        android:label="MotoNrEnabler"
+        android:persistent="true"
+        android:directBootAware="true">
+        <service
+            android:name=".NrEnablerService" />
+
+        <receiver
+            android:name=".BootCompletedReceiver"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+</manifest>
diff --git a/MotoNrEnabler/src/com/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel.aidl b/MotoNrEnabler/src/com/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel.aidl
new file mode 100644 (file)
index 0000000..a53f219
--- /dev/null
@@ -0,0 +1,5 @@
+package com.qualcomm.qcrilmsgtunnel;
+
+interface IQcrilMsgTunnel {
+    int sendOemRilRequestRaw(in byte[] request, out byte[] response, in int sub);
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/BootCompletedReceiver.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/BootCompletedReceiver.kt
new file mode 100644 (file)
index 0000000..50191ab
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+
+class BootCompletedReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context, intent: Intent) {
+        Log.d(TAG, "Starting")
+        context.startService(Intent(context, NrEnablerService::class.java))
+    }
+
+    companion object {
+        private const val TAG = "MotoNrEnabler"
+    }
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/NrEnablerService.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/NrEnablerService.kt
new file mode 100644 (file)
index 0000000..4aea5a3
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+import android.app.Service
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Handler
+import android.os.IBinder
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.util.Log
+import java.util.concurrent.atomic.AtomicBoolean
+
+class NrEnablerService : Service() {
+    private lateinit var motoExtService: QcomMotoExtTelephonyService
+    private val handler by lazy { Handler(mainLooper) }
+    private val workingInProgress = AtomicBoolean(false)
+
+    private val repeatWorkOnNRModeAndDSSIfFail = object : Runnable {
+        override fun run() {
+            if (workingInProgress.getAndSet(true))
+                return
+            if (!workOnNRModeAndDSS()) {
+                Log.v(TAG, "workOnNRModeAndDSS failed, retry after 5s")
+                handler.removeCallbacks(this)
+                handler.postDelayed(this, 5000)
+            }
+            workingInProgress.set(false)
+        }
+    }
+
+    private val broadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            if (!workingInProgress.get()) {
+                handler.post(repeatWorkOnNRModeAndDSSIfFail)
+            }
+        }
+    }
+
+    override fun onCreate() {
+        motoExtService = QcomMotoExtTelephonyService(this)
+        registerReceiver(
+            broadcastReceiver, IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+        )
+    }
+
+    private fun workOnNRModeAndDSS(): Boolean {
+        val activeSubs =
+            getSystemService(SubscriptionManager::class.java)?.getActiveSubscriptionInfoList()
+        if (activeSubs.isNullOrEmpty()) {
+            Log.v(TAG, "workOnNRModeAndDSS: no active sub.")
+            return true
+        }
+        for (aSubInfo in activeSubs) {
+            val phoneId = SubscriptionManager.getPhoneId(aSubInfo.subscriptionId)
+            if (!validatePhoneId(phoneId)) {
+                Log.e(TAG, "Invalid phoneId: $phoneId")
+                return false
+            }
+
+            // Moto sets them based on carrier config, but we unconditionally
+            // enable NR and DSS here because maintaining carrier config is
+            // intractable for us.
+            Log.v(TAG, "workOnNRModeAndDSS: setNrModeDisabled for phone ${phoneId}")
+            if (!motoExtService.setNrModeDisabled(phoneId, NrMode.AUTO)) {
+                return false
+            }
+            Log.v(TAG, "workOnNRModeAndDSS: setDSSEnabled for phone ${phoneId}")
+            if (!motoExtService.setDSSEnabled(phoneId, 1.toByte())) {
+                return false
+            }
+        }
+        return true
+    }
+
+    private fun validatePhoneId(phoneId: Int): Boolean {
+        val phoneCount = getSystemService(TelephonyManager::class.java).activeModemCount
+        return phoneId in 0 until phoneCount
+    }
+
+    override fun onBind(intent: Intent?): IBinder? = null
+
+    companion object {
+        private const val TAG = "MotoNrEnabler"
+    }
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/NrMode.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/NrMode.kt
new file mode 100644 (file)
index 0000000..26534e1
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+enum class NrMode(private val id: Int) {
+    AUTO(0),
+    DISABLE_SA(1),
+    DISABLE_NSA(2);
+
+    fun toInt(): Int {
+        return id
+    }
+
+    companion object {
+        fun fromInt(id: Int): NrMode? {
+            for (en in NrMode.values()) {
+                if (en.id == id) {
+                    return en
+                }
+            }
+            return null
+        }
+    }
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomMotoExtTelephonyService.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomMotoExtTelephonyService.kt
new file mode 100644 (file)
index 0000000..5b1e6b8
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+import android.content.Context
+import android.util.Log
+import com.android.internal.telephony.PhoneFactory
+import java.nio.ByteBuffer
+
+class QcomMotoExtTelephonyService(private val context: Context) {
+    private val qcrilMsgTunnelConnector = QcrilMsgTunnelConnector(context)
+
+    fun setNrModeDisabled(phoneId: Int, mode: NrMode): Boolean {
+        val nrModeInModem = getNrModeDisabled(phoneId)
+        Log.v(TAG, "nrModeInModem = $nrModeInModem")
+        if (mode == nrModeInModem) {
+            Log.d(
+                TAG,
+                "setNrModeDisabled equals nrModeInModem:$nrModeInModem, ignore set for phoneID:$phoneId"
+            )
+            return true
+        }
+        val data = ByteArray(9)
+        val buf = ByteBuffer.wrap(data)
+        buf.order(QcomOemConstants.getByteOrderByRequestId(QcomOemConstants.OEM_RIL_REQUEST_SET_NR_DISABLE_MODE))
+        buf.putInt(QcomOemConstants.OEM_RIL_REQUEST_SET_NR_DISABLE_MODE).putInt(1)
+            .put(mode.toInt().toByte())
+        return qcrilMsgTunnelConnector.invokeOemRilRequestRawForPhone(phoneId, data, null) >= 0
+    }
+
+    private fun getNrModeDisabled(phoneId: Int): NrMode? {
+        val data = ByteArray(8)
+        val respData = ByteArray(1)
+        val buf = ByteBuffer.wrap(data)
+        buf.order(QcomOemConstants.getByteOrderByRequestId(QcomOemConstants.OEM_RIL_REQUEST_GET_NR_DISABLE_MODE))
+        buf.putInt(QcomOemConstants.OEM_RIL_REQUEST_GET_NR_DISABLE_MODE)
+        if (qcrilMsgTunnelConnector.invokeOemRilRequestRawForPhone(phoneId, data, respData) >= 0) {
+            return NrMode.fromInt(respData[0].toInt())
+        }
+        return null
+    }
+
+    private fun getDSSEnabled(phoneId: Int): Byte {
+        val rdeNv =
+            qcrilMsgTunnelConnector.getRdeNvValueByElementId(phoneId, QcomNvInfo.RDE_EFS_DSS_I)
+        return (rdeNv?.dataObj as QcomNvInfo.NvGenericDataType?)?.data?.get(0) ?: 2.toByte()
+    }
+
+    fun setDSSEnabled(phoneId: Int, enabled: Byte): Boolean {
+        val prev = getDSSEnabled(phoneId)
+        Log.v(TAG, "previous DSS mode = $prev")
+        if (prev == enabled) {
+            Log.d(TAG, "Skip setDSSEnabled as no change.")
+            return true
+        }
+        return qcrilMsgTunnelConnector.setRdeNvValue(phoneId, QcomNvInfo.RDE_EFS_DSS_I, enabled)
+    }
+
+    companion object {
+        private const val TAG = "MotoNrEnabler: QcomMotoExtTelephonyService"
+    }
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomNvInfo.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomNvInfo.kt
new file mode 100644 (file)
index 0000000..55abde8
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+import android.util.Log
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+object QcomNvInfo {
+    private const val TAG = "MotoNrEnabler: QcomNvInfo"
+    const val RDE_EFS_DSS_I = 10030
+
+    interface NvDataType {
+        fun serialize(buf: ByteBuffer)
+        fun size(): Int
+    }
+
+    class RdeNvValue {
+        var elementId = 0
+        var recordNum = 0
+        var offset = 0
+        var length = 0
+        var dataObj: NvDataType? = null
+        val size: Int
+            get() {
+                return (dataObj?.size() ?: 1) + 16
+            }
+    }
+
+    class NvGenericDataType : NvDataType {
+        var data: ByteArray? = null
+
+        constructor()
+        constructor(byte: Byte) {
+            data = byteArrayOf(byte)
+        }
+
+        override fun serialize(buf: ByteBuffer) {
+            data?.let {
+                buf.put(it)
+            }
+        }
+
+        override fun size(): Int {
+            return data?.size ?: 0
+        }
+    }
+
+    fun getRdeByteOrder(): ByteOrder {
+        return QcomOemConstants.getByteOrderByRequestId(QcomOemConstants.OEM_RIL_REQUEST_CDMA_GET_RDE_ITEM)
+    }
+
+    fun getRdeNvName(elementId: Int): String {
+        return when (elementId) {
+            RDE_EFS_DSS_I -> "RDE_EFS_DSS_I"
+            else -> {
+                Log.w(TAG, "unknown RDE element ID: $elementId")
+                ""
+            }
+        }
+    }
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomNvUtils.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomNvUtils.kt
new file mode 100644 (file)
index 0000000..c28952f
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+import android.util.Log
+import java.nio.BufferUnderflowException
+import java.nio.ByteBuffer
+
+
+object QcomNvUtils {
+    private const val TAG = "MotoNrEnabler: QcomNvUtils"
+
+    private const val DEFAULT_SPC_CODE = "000000"
+    private const val READING_RDE_RESP_BUF_SIZE = 6144
+    private const val WRITING_RESP_BUF_SIZE = 2048
+
+    data class OemHookDataHeader(
+        val reqId: Int,
+        val dataLength: Int,
+        val error: OemHookRespError,
+    ) {
+        val spcLockCode = ByteArray(6)
+        override fun toString(): String {
+            return "reqId = $reqId  dataLength = $dataLength   error = $error  spcLockCode = ${
+                byteArrToStringLog(
+                    spcLockCode
+                )
+            }"
+        }
+
+        companion object {
+            const val SIZE = 18
+        }
+    }
+
+    fun readOemHookRespHeader(reqId: Int, bytes: ByteArray?): OemHookDataHeader? {
+        return bytes?.let {
+            readOemHookRespHeader(
+                ByteBuffer.wrap(it).order(QcomOemConstants.getByteOrderByRequestId(reqId))
+            )
+        }
+    }
+
+    private fun readOemHookRespHeader(buf: ByteBuffer): OemHookDataHeader? {
+        return try {
+            val header = OemHookDataHeader(
+                buf.getInt(),
+                buf.getInt(),
+                OemHookRespError.fromInt(buf.getInt()),
+            )
+            for (i in 0 until header.spcLockCode.size) {
+                header.spcLockCode[i] = buf.get()
+            }
+            Log.d(TAG, "readOemHookRespHeader: $header")
+            header
+        } catch (e: BufferUnderflowException) {
+            Log.w(TAG, "decode RespHeader exception, BufferUnderflowException")
+            null
+        }
+    }
+
+    fun getReadingRdeNvReqData(rdeNv: QcomNvInfo.RdeNvValue): ByteArray {
+        return allocateRdeOemReqData(
+            QcomOemConstants.OEM_RIL_REQUEST_CDMA_GET_RDE_ITEM, rdeNv, DEFAULT_SPC_CODE
+        )
+    }
+
+    fun getWritingRdeNvReqData(rdeNv: QcomNvInfo.RdeNvValue): ByteArray {
+        return allocateRdeOemReqData(
+            QcomOemConstants.OEM_RIL_REQUEST_CDMA_SET_RDE_ITEM, rdeNv, DEFAULT_SPC_CODE
+        )
+    }
+
+    private fun allocateRdeOemReqData(
+        reqId: Int, rdeNv: QcomNvInfo.RdeNvValue, spcCode: String
+    ): ByteArray {
+        val buf = ByteBuffer.allocate(rdeNv.size + OemHookDataHeader.SIZE)
+        buf.order(QcomNvInfo.getRdeByteOrder())
+        writeOemHookReqHeader(
+            buf, reqId, rdeNv.size, OemHookRespError.OEM_RIL_CDMA_SUCCESS, spcCode
+        )
+        buf.putInt(rdeNv.elementId)
+        buf.putInt(rdeNv.recordNum)
+        buf.putInt(rdeNv.offset)
+        rdeNv.dataObj.let {
+            if (it != null) {
+                buf.putInt(it.size())
+                it.serialize(buf)
+            } else {
+                buf.putInt(0)
+                buf.put(0.toByte())
+            }
+        }
+        val data = buf.array()
+        Log.d(
+            TAG,
+            "RDE request for element: ${QcomNvInfo.getRdeNvName(rdeNv.elementId)}  Allocated OemReqData: data = ${
+                byteArrToStringLog(
+                    data
+                )
+            }"
+        )
+        return data
+    }
+
+    fun allocateReadingRdeNvRespBuffer(): ByteArray {
+        return ByteArray(READING_RDE_RESP_BUF_SIZE)
+    }
+
+    fun allocateWritingRdeNvRespBuffer(): ByteArray {
+        return ByteArray(WRITING_RESP_BUF_SIZE)
+    }
+
+    fun decodeReadingRdeNvResult(resultData: ByteArray?): QcomNvInfo.RdeNvValue? {
+        if (resultData == null) {
+            return null
+        }
+        val buf = ByteBuffer.wrap(resultData).order(QcomNvInfo.getRdeByteOrder())
+        return try {
+            val header = readOemHookRespHeader(buf)
+            if (header != null && header.error === OemHookRespError.OEM_RIL_CDMA_SUCCESS) {
+                return deserializeRde(buf)
+            }
+            Log.w(TAG, "decodeReadingRdeNv get error for head")
+            null
+        } catch (e: BufferUnderflowException) {
+            Log.e(TAG, "decodeReadingRdeNvResult: buffer underflow")
+            null
+        }
+    }
+
+    private fun deserializeRde(buf: ByteBuffer): QcomNvInfo.RdeNvValue {
+        val rdeNv = QcomNvInfo.RdeNvValue()
+        rdeNv.elementId = buf.getInt()
+        rdeNv.recordNum = buf.getInt()
+        rdeNv.offset = buf.getInt()
+        rdeNv.length = buf.getInt()
+
+        Log.d(TAG, "decoding response for ${QcomNvInfo.getRdeNvName(rdeNv.elementId)}")
+
+        when (rdeNv.elementId) {
+            QcomNvInfo.RDE_EFS_DSS_I -> {
+                if (rdeNv.length > 0) {
+                    val nvData = QcomNvInfo.NvGenericDataType()
+                    nvData.data = buf.array().copyOfRange(34, 34 + rdeNv.length)
+                    rdeNv.dataObj = nvData
+                }
+            }
+
+            else -> Log.d(TAG, "deserialize unknown elementId (${rdeNv.elementId})")
+        }
+        return rdeNv
+    }
+
+    fun byteArrToStringLog(arr: ByteArray?): String {
+        if (arr == null || arr.isEmpty()) {
+            return "null"
+        }
+        val sb = StringBuilder()
+        for (i in arr) {
+            sb.append(String.format("%02X", i))
+        }
+        return sb.toString()
+    }
+
+    private fun writeOemHookReqHeader(
+        buf: ByteBuffer, reqId: Int, len: Int, err: OemHookRespError, spcLockCode: String
+    ) {
+        writeOemHookReqHeader(buf, reqId, len, err, spcLockCode.toByteArray())
+    }
+
+    private fun writeOemHookReqHeader(
+        buf: ByteBuffer, reqId: Int, len: Int, err: OemHookRespError, spcLockCode: ByteArray
+    ) {
+        buf.putInt(reqId)
+        buf.putInt(len)
+        buf.putInt(err.toInt())
+        for (i in spcLockCode) {
+            buf.put(i)
+        }
+        Log.d(
+            TAG,
+            "writeOemHookReqHeader: reqId = $reqId  dataLength = $len  error = $err  spcLockCode = ${
+                byteArrToStringLog(
+                    spcLockCode
+                )
+            }"
+        )
+    }
+
+    enum class OemHookRespError(private val id: Int) {
+        OEM_RIL_CDMA_SUCCESS(0),
+        OEM_RIL_CDMA_RADIO_NOT_AVAILABLE(1),
+        OEM_RIL_CDMA_NAM_READ_WRITE_FAILURE(2),
+        OEM_RIL_CDMA_NAM_PASSWORD_INCORRECT(3),
+        OEM_RIL_CDMA_NAM_ACCESS_COUNTER_EXCEEDED(4),
+        OEM_RIL_CDMA_GENERIC_FAILURE(5);
+
+        fun toInt(): Int {
+            return id
+        }
+
+        companion object {
+            fun fromInt(id: Int): OemHookRespError {
+                for (en in values()) {
+                    if (en.id == id) {
+                        return en
+                    }
+                }
+                return OEM_RIL_CDMA_GENERIC_FAILURE
+            }
+        }
+    }
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomOemConstants.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcomOemConstants.kt
new file mode 100644 (file)
index 0000000..345fa07
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+import android.util.Log
+import java.nio.ByteOrder
+
+object QcomOemConstants {
+    const val TAG = "MotoNrEnabler: QcomOemConstants"
+
+    private const val OEM_RIL_CDMA_MESSAGE_TYPE_CDMA = 33554432
+    const val OEM_RIL_REQUEST_GET_NR_DISABLE_MODE = 327752
+    const val OEM_RIL_REQUEST_SET_NR_DISABLE_MODE = 327753
+    const val OEM_RIL_REQUEST_CDMA_SET_RDE_ITEM = 33554453
+    const val OEM_RIL_REQUEST_CDMA_GET_RDE_ITEM = 33554454
+
+    fun getByteOrderByRequestId(reqId: Int): ByteOrder {
+        return if (reqId >= OEM_RIL_CDMA_MESSAGE_TYPE_CDMA) {
+            ByteOrder.LITTLE_ENDIAN
+        } else ByteOrder.BIG_ENDIAN
+    }
+
+    fun getRequestName(reqId: Int): String {
+        return when (reqId) {
+            OEM_RIL_REQUEST_CDMA_SET_RDE_ITEM -> "OEM_RIL_REQUEST_CDMA_SET_RDE_ITEM"
+            OEM_RIL_REQUEST_CDMA_GET_RDE_ITEM -> "OEM_RIL_REQUEST_CDMA_GET_RDE_ITEM"
+            else -> {
+                Log.w(TAG, "unknown request ID: $reqId")
+                ""
+            }
+        }
+    }
+}
diff --git a/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcrilMsgTunnelConnector.kt b/MotoNrEnabler/src/org/lineageos/motorola/nrenabler/QcrilMsgTunnelConnector.kt
new file mode 100644 (file)
index 0000000..8d14265
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * SPDX-FileCopyrightText: 2024 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.motorola.nrenabler
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Handler
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Log
+import com.android.internal.telephony.uicc.IccUtils
+import com.qualcomm.qcrilmsgtunnel.IQcrilMsgTunnel
+
+class QcrilMsgTunnelConnector(private val context: Context) {
+    private val handler = Handler(context.mainLooper)
+
+    private var qcrilMsgService: IQcrilMsgTunnel? = null
+    private val qcrilMsgTunnelConnection: ServiceConnection = object : ServiceConnection {
+        override fun onServiceConnected(name: ComponentName, service: IBinder) {
+            Log.d(TAG, "QcrilMsgTunnel Service connected")
+            qcrilMsgService = IQcrilMsgTunnel.Stub.asInterface(service)
+            if (qcrilMsgService == null) {
+                Log.e(TAG, "QcrilMsgTunnelService Connect Failed (onServiceConnected)")
+                return
+            }
+            service.linkToDeath(qcrilMsgServiceDeathRecipient, 0)
+        }
+
+        override fun onServiceDisconnected(name: ComponentName) {
+            Log.e(TAG, "The connection to the service got disconnected unexpectedly!")
+            qcrilMsgService = null
+        }
+    }
+    private val qcrilMsgServiceDeathRecipient = IBinder.DeathRecipient {
+        Log.e(TAG, "QcrilMsgService Died")
+        context.unbindService(qcrilMsgTunnelConnection)
+        handler.postDelayed({ bindToQcrilMsgTunnelService() }, 4000)
+    }
+
+    init {
+        bindToQcrilMsgTunnelService()
+    }
+
+    private fun bindToQcrilMsgTunnelService() {
+        val intent = Intent()
+        intent.setClassName(QCRIL_MSG_TUNNEL_PACKAGE_NAME, QCRIL_MSG_TUNNEL_SERVICE_NAME)
+        Log.d(TAG, "Starting QcrilMsgTunnel Service")
+        context.bindService(intent, qcrilMsgTunnelConnection, Context.BIND_AUTO_CREATE)
+    }
+
+    fun invokeOemRilRequestRawForPhone(phoneId: Int, oemReq: ByteArray?, oemResp: ByteArray?): Int {
+        return qcrilMsgService?.let {
+            Log.d(
+                TAG, "invokeOemRilRequestRawForSubscriber: phoneId = $phoneId oemReq = ${
+                    IccUtils.bytesToHexString(
+                        oemReq
+                    )
+                }"
+            )
+            val rspData = oemResp ?: ByteArray(1)
+            try {
+                val ret = it.sendOemRilRequestRaw(oemReq, rspData, phoneId)
+                Log.d(
+                    TAG, "invokeOemRilRequestRawForSubscriber: phoneId = $phoneId oemResp = ${
+                        IccUtils.bytesToHexString(rspData)
+                    }"
+                )
+                ret
+            } catch (e: RemoteException) {
+                Log.e(TAG, "sendOemRilRequestRaw: Runtime Exception")
+                -1
+            }
+        } ?: run {
+            Log.e(TAG, "QcrilMsgTunnel Service not connected")
+            -1
+        }
+    }
+
+    private fun getRdeNvValueByElementId(
+        phoneId: Int, rdeElementId: Int, recordNum: Int
+    ): QcomNvInfo.RdeNvValue? {
+        if (rdeElementId < 0) {
+            return null
+        }
+        val rdeNv = QcomNvInfo.RdeNvValue()
+        rdeNv.elementId = rdeElementId
+        rdeNv.recordNum = recordNum
+        val reqRdeData: ByteArray = QcomNvUtils.getReadingRdeNvReqData(rdeNv)
+        val respRdeData: ByteArray = QcomNvUtils.allocateReadingRdeNvRespBuffer()
+        return if (invokeOemRilRequestRawForPhone(
+                phoneId, reqRdeData, respRdeData
+            ) < 0
+        ) {
+            null
+        } else QcomNvUtils.decodeReadingRdeNvResult(respRdeData)
+    }
+
+    fun getRdeNvValueByElementId(phoneId: Int, rdeElementId: Int): QcomNvInfo.RdeNvValue? {
+        return getRdeNvValueByElementId(phoneId, rdeElementId, 0)
+    }
+
+    fun setRdeNvValue(phoneId: Int, rdeElementId: Int, value: Byte): Boolean {
+        val data = QcomNvInfo.NvGenericDataType(value)
+        return setRdeNvValue(phoneId, rdeElementId, data)
+    }
+
+    private fun setRdeNvValue(
+        phoneId: Int, rdeElementId: Int, nvData: QcomNvInfo.NvDataType
+    ): Boolean {
+        return setRdeNvValue(phoneId, rdeElementId, 0, nvData)
+    }
+
+    private fun setRdeNvValue(
+        phoneId: Int, rdeElementId: Int, rdeRecordNum: Int, nvData: QcomNvInfo.NvDataType
+    ): Boolean {
+        val nv = QcomNvInfo.RdeNvValue()
+        nv.elementId = rdeElementId
+        nv.recordNum = rdeRecordNum
+        nv.dataObj = nvData
+        return setRdeNvValue(phoneId, nv)
+    }
+
+    private fun setRdeNvValue(phoneId: Int, nv: QcomNvInfo.RdeNvValue): Boolean {
+        val reqData: ByteArray = QcomNvUtils.getWritingRdeNvReqData(nv)
+        val respData: ByteArray = QcomNvUtils.allocateWritingRdeNvRespBuffer()
+        return getWritingRdeNvRespResult(phoneId, reqData, respData)
+    }
+
+    private fun getWritingRdeNvRespResult(
+        phoneId: Int, reqData: ByteArray, respData: ByteArray
+    ): Boolean {
+        return getWritingNvRespResult(
+            phoneId, QcomOemConstants.OEM_RIL_REQUEST_CDMA_SET_RDE_ITEM, reqData, respData
+        )
+    }
+
+    private fun getWritingNvRespResult(
+        phoneId: Int, reqId: Int, reqData: ByteArray, respData: ByteArray
+    ): Boolean {
+        if (invokeOemRilRequestRawForPhone(phoneId, reqData, respData) < 0) {
+            return false
+        }
+        val respHeader = QcomNvUtils.readOemHookRespHeader(reqId, respData) ?: return false
+        Log.d(
+            TAG, "get Writing NV result for ${QcomOemConstants.getRequestName(respHeader.reqId)}"
+        )
+        return respHeader.error == QcomNvUtils.OemHookRespError.OEM_RIL_CDMA_SUCCESS
+    }
+
+    companion object {
+        private const val TAG = "MotoNrEnabler: QcrilMsgTunnelConnector"
+        private const val QCRIL_MSG_TUNNEL_PACKAGE_NAME = "com.qualcomm.qcrilmsgtunnel"
+        private const val QCRIL_MSG_TUNNEL_SERVICE_NAME =
+            "com.qualcomm.qcrilmsgtunnel.QcrilMsgTunnelService"
+    }
+}