Principle

This mod will let the intents that use the RUN action do the same URL/stream open flow as the Play Location page does, that page can accept the online music/radio stream instead of the original VIEW intent handles.

Tools required

Reverse engineering tools

  1. apktool

    https://apktool.org

  2. JADX GUI (Optional, but recommend)

    https://github.com/skylot/jadx

  3. Android SDK (To sign apk)

Official APK

I uses arm32 (armv7) version, 2.25.8

https://www.foobar2000.org/android

Modding steps (by v2.25.8)

  1. Decomplie (Change the location where your apktool at)

    D:\apk_reverse\apktools\apktool.bat d -f .\foobar2000-mobile-v2.25.8-arm32.apk
    
  2. Mod the smalis

    1. MainActivity.smali

      Note: Find this method: .method handleIntent

      .method handleIntent(Landroid/content/Intent;)V
          .locals 2
      
          if-nez p1, :cond_0
      
          goto :goto_0
      
          .line 434
          :cond_0
          invoke-virtual {p1}, Landroid/content/Intent;->getAction()Ljava/lang/String;
      
          move-result-object v0
      
          if-nez v0, :cond_1
      
          goto :goto_0
      
          .line 441
          :cond_1
          const-string v1, "com.foobar2000.showplaying"
      
          invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
      
          move-result v1
      
          if-eqz v1, :cond_2
      
          .line 442
          invoke-virtual {p0}, Lcom/foobar2000/foobar2000/MainActivity;->switchToPlayback()V
      
          .line 444
          :cond_2
          const-string v1, "android.intent.action.VIEW"
      
          invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
      
          ## == MOD START ==
          # Let result put to v1 variable, keeps v0 (getAction())
          move-result v1
      
          # Original: cond_3, change to new label
          if-eqz v1, :cond_nc1
      
          # If condition is true, goto another label
          # Not true, will jumps to the new label ":cond_nc1"
          goto :goto_pass1
      
          :cond_nc1
          # Compares the intent is "android.intent.action.RUN" or not
          const-string v1, "android.intent.action.RUN"
      
          # Compare the string
          invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
          # Use v1 to save the result
          move-result v1
      
          # If not equal, returns (Original "cond_3")
          if-eqz v1, :cond_3
      
          # Methods to play Location
          # Copies Original init code
          invoke-static {p0}, Lcom/foobar2000/foobar2000/foobar2000instance;->globalInit(Landroid/content/Context;)V
      
          invoke-static {p0}, Lcom/foobar2000/foobar2000/MainService;->start(Landroid/content/Context;)V
      
          # Get the Intent Data
          invoke-virtual {p1}, Landroid/content/Intent;->getDataString()Ljava/lang/String;
          move-result-object p1
      
          # Instantiate the PlayLocationNSI class
          new-instance v1, Lcom/foobar2000/foobar2000/PlayLocationNSI;
          invoke-direct {v1}, Lcom/foobar2000/foobar2000/PlayLocationNSI;-><init>()V
          # Calls the playLocation, like the Play Location page does.
          invoke-virtual {v1, p1}, Lcom/foobar2000/foobar2000/PlayLocationNSI;->playLocation(Ljava/lang/String;)V
          # Returns
          goto :goto_0
      
          ## == MOD END ==
      
          .line 445
          :goto_pass1
          invoke-static {p0}, Lcom/foobar2000/foobar2000/foobar2000instance;->globalInit(Landroid/content/Context;)V
      
          .line 446
          invoke-static {p0}, Lcom/foobar2000/foobar2000/MainService;->start(Landroid/content/Context;)V
      
          .line 456
          invoke-virtual {p1}, Landroid/content/Intent;->getDataString()Ljava/lang/String;
      
          move-result-object p1
      
          .line 457
          new-instance v0, Ljava/lang/StringBuilder;
      
          const-string v1, "Opening path handed by system: "
      
          invoke-direct {v0, v1}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
      
          invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
      
          move-result-object v0
      
          invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
      
          move-result-object v0
      
          invoke-static {v0}, Lcom/foobar2000/foobar2000/Utility;->consoleOutput(Ljava/lang/String;)V
      
          .line 459
          sget-object v0, Lcom/foobar2000/foobar2000/foobar2000instance;->instance:Lcom/foobar2000/foobar2000/foobar2000instance;
      
          sget-object v1, Lcom/foobar2000/foobar2000/NavStack;->StackMain:Lcom/foobar2000/foobar2000/NavStack;
      
          invoke-virtual {v1}, Lcom/foobar2000/foobar2000/NavStack;->getTopContext()Lcom/foobar2000/foobar2000/Fb2kMenuContext;
      
          move-result-object v1
      
          invoke-virtual {v0, p1, v1}, Lcom/foobar2000/foobar2000/foobar2000instance;->openURL(Ljava/lang/String;Lcom/foobar2000/foobar2000/Fb2kMenuContext;)V
      
          :cond_3
          :goto_0
          return-void
      .end method
      
    2. PlayLocationNSI.smali

      Note: Find this method: .method private doPlay()

      .method private doPlay()V
          .locals 1
      
          .line 69
          invoke-virtual {p0}, Lcom/foobar2000/foobar2000/PlayLocationNSI;->getURL()Ljava/lang/String;
      
          move-result-object v0
      
          .line 70
          invoke-virtual {p0}, Lcom/foobar2000/foobar2000/PlayLocationNSI;->returnToParent()V
      
          .line 71
          # == MOD START ==
          #invoke-direct {p0, v0}, Lcom/foobar2000/foobar2000/PlayLocationNSI;->playLocation(Ljava/lang/String;)V
          # change to invoke-virtual
          invoke-virtual {p0, v0}, Lcom/foobar2000/foobar2000/PlayLocationNSI;->playLocation(Ljava/lang/String;)V
      
          return-void
      .end method
      
      .method private native getLastValue()Ljava/lang/String;
      .end method
      
      # change to public
      .method public native playLocation(Ljava/lang/String;)V
      .end method
      
      # == MOD END ==
      
    3. AndroidManifest.xml

      Note: Find this block: <action android:name="android.intent.action.VIEW"/>

                  <intent-filter>
                      <action android:name="android.intent.action.VIEW"/>
                      <category android:name="android.intent.category.DEFAULT"/>
                      <category android:name="android.intent.category.BROWSABLE"/>
                      <data android:scheme="http"/>
                      <data android:scheme="https"/>
                      <data android:scheme="ftp"/>
                      <data android:scheme="content"/>
                      <data android:mimeType="audio/*"/>
                  </intent-filter>
                  <!-- MOD START -->
                  <!-- Add this filter to accept RUN action -->
                  <intent-filter>
                      <action android:name="android.intent.action.RUN"/>
                      <category android:name="android.intent.category.DEFAULT"/>
                      <data android:scheme="http"/>
                      <data android:scheme="https"/>
                      <data android:scheme="ftp"/>
                      <data android:scheme="content"/>
                      <data android:mimeType="audio/*"/>
                  </intent-filter>
                  <!-- MOD END -->
      
  3. Build the apk

    D:\apk_reverse\apktools\apktool.bat b .\foobar2000-mobile-v2.25.8-arm32
    
  4. Use JADX GUI checks the classes.dex

    Location like this:

    foobar2000-mobile-v2.25.8-arm32\build\apk\classes.dex
    
    1. Check your source code’s com/foobar2000.foobar2000.MainActivity

      Find handleIntent Method, you should see additional if condition like this:

      image.png

    2. Check com/foobar2000.foobar2000.PlayLocationNSI Find playLocation Method, it should be public

      image.png

      Find doPlay() Method, playLocation(url) should displays correctly

      image.png

  5. Sign the apk use debug keystore

    D:\AndroidSDK\build-tools\35.0.0\apksigner.bat sign --ks ~\.android\debug.keystore --ks-pass pass:android --key-pass pass:android --ks-key-alias androiddebugkey .\foobar2000-mobile-v2.25.8-arm32\dist\foobar2000-mobile-v2.25.8-arm32.apk
    
  6. Push and install

    adb.exe install --user 0 .\foobar2000-mobile-v2.25.8-arm32\dist\foobar2000-mobile-v2.25.8-arm32.apk
    

Test and Enjoy!

https://youtube.com/shorts/A_fEDzSxNzY?si=Gl3FGZe1pyOEa5vx