diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt
index 37d2334..e360f2d 100644
--- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt
+++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/steps/patch/PatchManifestStep.kt
@@ -6,6 +6,7 @@ import com.meowarex.rlmobile.patcher.steps.StepGroup
import com.meowarex.rlmobile.patcher.steps.base.Step
import com.meowarex.rlmobile.patcher.steps.download.CopyDependenciesStep
import com.meowarex.rlmobile.patcher.util.ManifestPatcher
+import com.meowarex.rlmobile.ui.screens.patchopts.KnownPatch
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
import com.github.diamondminer88.zip.ZipReader
import com.github.diamondminer88.zip.ZipWriter
@@ -26,11 +27,15 @@ class PatchManifestStep(private val options: PatchOptions) : Step() {
?: throw IllegalArgumentException("No manifest found in APK")
container.log("Patching manifest")
+ val enableWazeIntegration =
+ options.patchStates[KnownPatch.WazeIntegration.name] ?: KnownPatch.WazeIntegration.default.isEnabled
+
val patchedManifest = ManifestPatcher.patchManifest(
manifestBytes = manifest,
packageName = options.packageName,
appName = options.appName,
debuggable = options.debuggable,
+ enableWazeIntegration = enableWazeIntegration,
)
container.log("Repacking apk with patched manifest")
diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt
index bc53f2f..81a625e 100644
--- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt
+++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/patcher/util/ManifestPatcher.kt
@@ -18,12 +18,16 @@ object ManifestPatcher {
private const val IS_SPLIT_REQUIRED = "isSplitRequired"
private const val REQUIRED_SPLIT_TYPES = "requiredSplitTypes"
private const val SPLIT_TYPES = "splitTypes"
+ private const val WAZE_PACKAGE = "com.waze"
+ private const val WAZE_INIT_RECEIVER = "radiant.WazeInitReceiver"
+ private const val WAZE_ACTION_INIT = "com.waze.sdk.audio.ACTION_INIT"
fun patchManifest(
manifestBytes: ByteArray,
packageName: String,
appName: String,
debuggable: Boolean,
+ enableWazeIntegration: Boolean,
): ByteArray {
// Extract original package name to rewrite every reference to it
// (permissions, provider authorities) to the new packageName.
@@ -84,6 +88,49 @@ object ManifestPatcher {
}
return when (name) {
+ "queries" -> object : NodeVisitor(nv) {
+ private var hasWazePackage = false
+
+ override fun child(ns: String?, name: String): NodeVisitor {
+ val visitor = super.child(ns, name)
+
+ return if (enableWazeIntegration && name == "package") {
+ object : NodeVisitor(visitor) {
+ override fun attr(
+ ns: String?,
+ name: String?,
+ resourceId: Int,
+ type: Int,
+ value: Any?,
+ ) {
+ if (name == "name" && value == WAZE_PACKAGE) {
+ hasWazePackage = true
+ }
+ super.attr(ns, name, resourceId, type, value)
+ }
+ }
+ } else {
+ visitor
+ }
+ }
+
+ override fun end() {
+ if (enableWazeIntegration && !hasWazePackage) {
+ super.child(null, "package").apply {
+ attr(
+ ANDROID_NAMESPACE,
+ "name",
+ android.R.attr.name,
+ TYPE_STRING,
+ WAZE_PACKAGE,
+ )
+ end()
+ }
+ }
+ super.end()
+ }
+ }
+
"uses-permission" -> object : NodeVisitor(nv) {
override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
if (name != "maxSdkVersion") {
@@ -132,6 +179,7 @@ object ManifestPatcher {
private var addUseEmbeddedDex = true
private var addExtractNativeLibs = true
private var addMetadata = true
+ private var hasWazeInitReceiver = false
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false
@@ -161,6 +209,22 @@ object ManifestPatcher {
return when (name) {
"activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName))
+
+ "receiver" -> object : NodeVisitor(visitor) {
+ override fun attr(
+ ns: String?,
+ name: String,
+ resourceId: Int,
+ type: Int,
+ value: Any?
+ ) {
+ if (name == "name" && value == WAZE_INIT_RECEIVER) {
+ hasWazeInitReceiver = true
+ }
+ super.attr(ns, name, resourceId, type, value)
+ }
+ }
+
"provider" -> object : NodeVisitor(visitor) {
override fun attr(
ns: String?,
@@ -219,6 +283,39 @@ object ManifestPatcher {
0
)
+ if (enableWazeIntegration && !hasWazeInitReceiver) {
+ super.child(null, "receiver").apply {
+ attr(
+ ANDROID_NAMESPACE,
+ "name",
+ android.R.attr.name,
+ TYPE_STRING,
+ WAZE_INIT_RECEIVER,
+ )
+ attr(
+ ANDROID_NAMESPACE,
+ "exported",
+ android.R.attr.exported,
+ TYPE_INT_BOOLEAN,
+ 1,
+ )
+ child(null, "intent-filter").apply {
+ child(null, "action").apply {
+ attr(
+ ANDROID_NAMESPACE,
+ "name",
+ android.R.attr.name,
+ TYPE_STRING,
+ WAZE_ACTION_INIT,
+ )
+ end()
+ }
+ end()
+ }
+ end()
+ }
+ }
+
super.end()
}
}
diff --git a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt
index 212fe07..3f1c27e 100644
--- a/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt
+++ b/Manager/app/src/main/kotlin/com/meowarex/rlmobile/ui/screens/patchopts/KnownPatch.kt
@@ -130,6 +130,17 @@ enum class KnownPatch(
descRes = R.string.patch_debug_menu_unlock_desc,
default = Disabled,
),
+ WazeIntegration(
+ order = 90,
+ fileNames = listOf("waze-media-browser.patch"),
+ extensionFiles = listOf(
+ "radiant/WazeInitReceiver.smali",
+ "radiant/WazeServiceConnection.smali",
+ ),
+ titleRes = R.string.patch_waze_integration_title,
+ descRes = R.string.patch_waze_integration_desc,
+ default = Disabled,
+ ),
LyricsProgressPill(
order = 40,
fileNames = listOf(
diff --git a/Manager/app/src/main/res/values/strings.xml b/Manager/app/src/main/res/values/strings.xml
index 2aaa020..a6db93e 100644
--- a/Manager/app/src/main/res/values/strings.xml
+++ b/Manager/app/src/main/res/values/strings.xml
@@ -286,6 +286,8 @@
Reveals TIDAL\'s hidden Debug Menu in Settings. (Unlocks Feature Flags & the legacy Debug Options)
+ Waze Integration
+ Control TIDAL playback from the Waze app
Enable Legacy UI
()V
+ .locals 0
+
+ invoke-direct {p0}, Landroid/content/BroadcastReceiver;->()V
+
+ return-void
+.end method
+
+
+# virtual methods
+.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V
+ .locals 5
+
+ if-eqz p1, :return
+
+ if-eqz p2, :return
+
+ const-string v0, "com.waze.sdk.audio.ACTION_INIT"
+
+ invoke-virtual {p2}, Landroid/content/Intent;->getAction()Ljava/lang/String;
+
+ move-result-object v1
+
+ invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
+
+ move-result v0
+
+ if-eqz v0, :return
+
+ const-string v0, "token"
+
+ invoke-virtual {p2, v0}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
+
+ move-result-object v0
+
+ if-eqz v0, :return
+
+ invoke-virtual {v0}, Ljava/lang/String;->length()I
+
+ move-result v1
+
+ if-lez v1, :return
+
+ invoke-virtual {p1}, Landroid/content/Context;->getApplicationContext()Landroid/content/Context;
+
+ move-result-object v1
+
+ if-eqz v1, :context_ready
+
+ move-object p1, v1
+
+ :context_ready
+ new-instance v1, Landroid/content/Intent;
+
+ invoke-direct {v1}, Landroid/content/Intent;->()V
+
+ const-string v2, "com.waze"
+
+ const-string v3, "com.waze.sdk.SdkService"
+
+ invoke-virtual {v1, v2, v3}, Landroid/content/Intent;->setClassName(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;
+
+ new-instance v2, Lradiant/WazeServiceConnection;
+
+ invoke-direct {v2, p1, v0}, Lradiant/WazeServiceConnection;->(Landroid/content/Context;Ljava/lang/String;)V
+
+ :try_start_0
+ const/4 v3, 0x1
+
+ invoke-virtual {p1, v1, v2, v3}, Landroid/content/Context;->bindService(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z
+ :try_end_0
+ .catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0
+
+ goto :return
+
+ :catch_0
+ move-exception v4
+
+ :return
+ return-void
+.end method
diff --git a/patches/extension/radiant/WazeServiceConnection.smali b/patches/extension/radiant/WazeServiceConnection.smali
new file mode 100644
index 0000000..74ca8c1
--- /dev/null
+++ b/patches/extension/radiant/WazeServiceConnection.smali
@@ -0,0 +1,222 @@
+.class public final Lradiant/WazeServiceConnection;
+.super Ljava/lang/Object;
+.source "WazeServiceConnection.smali"
+
+# interfaces
+.implements Landroid/content/ServiceConnection;
+
+
+# instance fields
+.field private final context:Landroid/content/Context;
+
+.field private final token:Ljava/lang/String;
+
+
+# direct methods
+.method public constructor (Landroid/content/Context;Ljava/lang/String;)V
+ .locals 0
+
+ invoke-direct {p0}, Ljava/lang/Object;->()V
+
+ iput-object p1, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context;
+
+ iput-object p2, p0, Lradiant/WazeServiceConnection;->token:Ljava/lang/String;
+
+ return-void
+.end method
+
+.method private addOpenMeIntent(Landroid/os/Bundle;)V
+ .locals 4
+
+ iget-object v0, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context;
+
+ invoke-virtual {v0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;
+
+ move-result-object v1
+
+ invoke-virtual {v0}, Landroid/content/Context;->getPackageName()Ljava/lang/String;
+
+ move-result-object v2
+
+ invoke-virtual {v1, v2}, Landroid/content/pm/PackageManager;->getLaunchIntentForPackage(Ljava/lang/String;)Landroid/content/Intent;
+
+ move-result-object v1
+
+ if-eqz v1, :return
+
+ const/high16 v2, 0x10000000
+
+ invoke-virtual {v1, v2}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent;
+
+ const v2, 0xc000000
+
+ const/4 v3, 0x0
+
+ invoke-static {v0, v3, v1, v2}, Landroid/app/PendingIntent;->getActivity(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;
+
+ move-result-object v1
+
+ const-string v2, "openMeIntent"
+
+ invoke-virtual {p1, v2, v1}, Landroid/os/Bundle;->putParcelable(Ljava/lang/String;Landroid/os/Parcelable;)V
+
+ :return
+ return-void
+.end method
+
+.method private createClientMessenger()Landroid/os/Messenger;
+ .locals 3
+
+ new-instance v0, Landroid/os/Handler;
+
+ invoke-static {}, Landroid/os/Looper;->getMainLooper()Landroid/os/Looper;
+
+ move-result-object v1
+
+ invoke-direct {v0, v1}, Landroid/os/Handler;->(Landroid/os/Looper;)V
+
+ new-instance v2, Landroid/os/Messenger;
+
+ invoke-direct {v2, v0}, Landroid/os/Messenger;->(Landroid/os/Handler;)V
+
+ return-object v2
+.end method
+
+.method private createConfigBundle()Landroid/os/Bundle;
+ .locals 3
+
+ new-instance v0, Landroid/os/Bundle;
+
+ invoke-direct {v0}, Landroid/os/Bundle;->()V
+
+ const-string v1, "versionCode"
+
+ const/16 v2, 0xa
+
+ invoke-virtual {v0, v1, v2}, Landroid/os/Bundle;->putInt(Ljava/lang/String;I)V
+
+ invoke-direct {p0, v0}, Lradiant/WazeServiceConnection;->addOpenMeIntent(Landroid/os/Bundle;)V
+
+ return-object v0
+.end method
+
+.method private register(Landroid/os/IBinder;)V
+ .locals 5
+
+ if-eqz p1, :return
+
+ invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
+
+ move-result-object v0
+
+ invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;
+
+ move-result-object v1
+
+ const-string v2, "com.waze.sdk.ISdkService"
+
+ invoke-virtual {v0, v2}, Landroid/os/Parcel;->writeInterfaceToken(Ljava/lang/String;)V
+
+ iget-object v2, p0, Lradiant/WazeServiceConnection;->token:Ljava/lang/String;
+
+ invoke-virtual {v0, v2}, Landroid/os/Parcel;->writeString(Ljava/lang/String;)V
+
+ invoke-direct {p0}, Lradiant/WazeServiceConnection;->createConfigBundle()Landroid/os/Bundle;
+
+ move-result-object v2
+
+ invoke-static {v0, v2}, Lradiant/WazeServiceConnection;->writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V
+
+ invoke-direct {p0}, Lradiant/WazeServiceConnection;->createClientMessenger()Landroid/os/Messenger;
+
+ move-result-object v2
+
+ invoke-static {v0, v2}, Lradiant/WazeServiceConnection;->writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V
+
+ const/4 v2, 0x1
+
+ const/4 v3, 0x0
+
+ invoke-interface {p1, v2, v0, v1, v3}, Landroid/os/IBinder;->transact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
+
+ move-result v4
+
+ if-eqz v4, :cleanup
+
+ invoke-virtual {v1}, Landroid/os/Parcel;->readException()V
+
+ :cleanup
+ invoke-virtual {v1}, Landroid/os/Parcel;->recycle()V
+
+ invoke-virtual {v0}, Landroid/os/Parcel;->recycle()V
+
+ :return
+ return-void
+.end method
+
+.method private safeUnbind()V
+ .locals 1
+
+ :try_start_0
+ iget-object v0, p0, Lradiant/WazeServiceConnection;->context:Landroid/content/Context;
+
+ invoke-virtual {v0, p0}, Landroid/content/Context;->unbindService(Landroid/content/ServiceConnection;)V
+ :try_end_0
+ .catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0
+
+ goto :return
+
+ :catch_0
+ move-exception v0
+
+ :return
+ return-void
+.end method
+
+.method private static writeParcelable(Landroid/os/Parcel;Landroid/os/Parcelable;)V
+ .locals 2
+
+ const/4 v0, 0x0
+
+ if-eqz p1, :null_value
+
+ const/4 v1, 0x1
+
+ invoke-virtual {p0, v1}, Landroid/os/Parcel;->writeInt(I)V
+
+ invoke-interface {p1, p0, v0}, Landroid/os/Parcelable;->writeToParcel(Landroid/os/Parcel;I)V
+
+ return-void
+
+ :null_value
+ invoke-virtual {p0, v0}, Landroid/os/Parcel;->writeInt(I)V
+
+ return-void
+.end method
+
+
+# virtual methods
+.method public onServiceConnected(Landroid/content/ComponentName;Landroid/os/IBinder;)V
+ .locals 1
+
+ :try_start_0
+ invoke-direct {p0, p2}, Lradiant/WazeServiceConnection;->register(Landroid/os/IBinder;)V
+ :try_end_0
+ .catch Ljava/lang/Throwable; {:try_start_0 .. :try_end_0} :catch_0
+
+ goto :unbind
+
+ :catch_0
+ move-exception v0
+
+ :unbind
+ invoke-direct {p0}, Lradiant/WazeServiceConnection;->safeUnbind()V
+
+ return-void
+.end method
+
+.method public onServiceDisconnected(Landroid/content/ComponentName;)V
+ .locals 0
+
+ return-void
+.end method
diff --git a/patches/manifest.json b/patches/manifest.json
index d2d880e..757ca27 100644
--- a/patches/manifest.json
+++ b/patches/manifest.json
@@ -212,6 +212,15 @@
"description": "Inverts the auto-hide behavior on the lyrics screen, playback controls stay visible by default and only hide when you tap them off.",
"default": true
},
+ {
+ "id": "WazeIntegration",
+ "order": 90,
+ "fileNames": ["waze-media-browser.patch"],
+ "extensionFiles": ["radiant/WazeInitReceiver.smali", "radiant/WazeServiceConnection.smali"],
+ "title": "Waze Integration",
+ "description": "Control TIDAL playback from the Waze app",
+ "default": true
+ },
{
"id": "DebugMenuUnlock",
"order": 100,
diff --git a/patches/waze-media-browser.patch b/patches/waze-media-browser.patch
new file mode 100644
index 0000000..1f2c5a4
--- /dev/null
+++ b/patches/waze-media-browser.patch
@@ -0,0 +1,27 @@
+--- a/pb/c.smali
++++ b/pb/c.smali
+@@ -700,0 +701,24 @@
++ iget-object v15, v1, Lcom/aspiro/wamp/util/y$a;->b:Ljava/lang/String;
++
++ const-string/jumbo v14, "com.waze"
++
++ invoke-static {v15, v14}, Lkotlin/jvm/internal/n;->b(Ljava/lang/Object;Ljava/lang/Object;)Z # check caller
++
++ move-result v12
++
++ if-eqz v12, :rl_waze_done
++
++ iget-object v15, v1, Lcom/aspiro/wamp/util/y$a;->d:Ljava/lang/String; # caller cert
++
++ const-string v14, "03:63:7f:6c:5d:8f:60:4e:6f:db:79:a6:ff:bf:a5:78:de:4e:31:8f:8d:a2:2f:c6:10:66:65:24:7f:88:07:d7" # Waze cert
++
++ invoke-static {v15, v14}, Lkotlin/jvm/internal/n;->b(Ljava/lang/Object;Ljava/lang/Object;)Z # check cert
++
++ move-result v12 # cert match
++
++ if-eqz v12, :rl_waze_done # reject mismatch
++
++ goto :goto_9 # trust Waze
++
++ :rl_waze_done
++