1

I'm working on an app which needs to resume audio playback after the device was rebooted.

I found out that simply sending the media play button may not start the same app the user was using. (I have spotify running, yet sending the play button after a reboot opens Google Music)

Is there a way to get details about the app playing media (before reboot) ?

I need something like app name/activity name/package name .etc. (So I can launch the same app before sending play button)

Machavity
  • 30,841
  • 27
  • 92
  • 100
Madushan
  • 6,977
  • 31
  • 79

2 Answers2

7

UPDATE 2: This uses the queryBroadcastReceivers API which may be subject to package visibility filtering on Android 11 and higher. This means you will have to declare QUERY_ALL_PACKAGES permission and will have to justify its use if/when publishing to Play Store. Android T seems to deprecate this API in favor of a slightly different one, but the current invocation should still work.

UPDATE: Below method requires you to implement an AccessibilityService for which Google Play now has strict requirements and a separate review process, and the app must justify the use of it. If you implement the below, there is a likelyhood your app may be rejected by Play Store for abusing this API.


While not perfect, I found a way to identify the current app playing music.

My approach is to exploit the fact that music players must post an 'ongoing' notification in order to prevent being killed by the system, and they usually have a Broadcast Receiver to handle media buttons.

Technically music apps following android conventions have to have an intent-filter for CATEGORY_MUSIC_APP (or the deprecated MediaStore.INTENT_ACTION_MUSIC_PLAYER) for atleast one of its activities, but I noticed that the current version of iHeartRadio app decided not to bother.

So here's my approach

  • Have an Accessibility Service registered to monitor android notifications
  • (Music players must post an 'ongoing' notification to prevent from being killed by android when under CPU or memory pressure).

Accessibility Service must have atleast android:accessibilityEventTypes="typeNotificationStateChanged" registered and enabled explicitly by the user.

Service must handle notification event as follows

@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        if (accessibilityEvent.getEventType() ==
           AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
            String packageName = accessibilityEvent.getPackageName().toString();

            AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
            boolean musicPlaying = am.isMusicActive();
            
            if (!musicPlaying) {
                Log.i("NOTIFICATION", "no one is playing music. (anymore...)");
               return; // clear saved package
            }
            
            PackageManager pm = getPackageManager();    
            Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);                
            i.setPackage(packageName);    

            List<ResolveInfo> a = pm.queryBroadcastReceivers(i, PackageManager.MATCH_ALL);

            if (a.size() > 0)
                Log.i("NOTIFICATION", "Music is played by: " + packageName);
                // save packageName to somewhere
            else
                Log.i("NOTIFICATION", "irrelevant notification from package: " + packageName);
        }
    }

Later when the device is rebooted, start the default activity and press the play button.

Intent i = new Intent(Intent.ACTION_VIEW);
i.addCategory(Intent.CATEGORY_DEFAULT); 
i.setPackage(savedPackage);
startActivity(i);

AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

 am.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
 am.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY));

 // may be fire another intent for home/launcher. so user doesn't have to

Assumptions/Caveats

  • Music app directly goes to an activity that is ready to play (iHeartRadio asks to login if you haven't logged in to their services on that device)
  • Music app saves the last playback state. If this isn't the case, it may start playback from beginning of a track/playlist instead of where the user left off.
  • Accuracy may vary
  • Spotify posts the notification when the user moves away from activity or screen turns off, and updates the same notification when playback state changes (We're not notified about existing notification changes. You won't be notified of playback starting, until spotify activity is paused.)
  • Google Music posts the notification as soon as it's launched and may not be playing music (another app may be playing music. Can cause false positives.)
  • iHeartRadio causes multiple notification changes in a row.

You can also get the Notification from the event, and list actions. If the title of any Action is "Play" then that app isn't playing music and simply posting its notification like Google Music explained above.

(Having the option to start 'Play' means it's not playing right now.) Also, the string 'Play' is not standard and may vary by app and/or language. Assuming the string is the same or similar for most apps to support accessibility .etc. And make sure you check for actions being null. If the notification uses a custom layout (eg: Spotify) this is null. (Might also mean Spotify cannot be used by blind users since Accessibility Services cannot control it using Notification Actions.)


UPDATE: You may also use an app like Automate to handle this if you don't want to build it into your own app.

Madushan
  • 6,977
  • 31
  • 79
0

need permission

<uses-permission android:name="android.permission.READ_LOGS" />
new Thread(new Runnable() {
    @Override
    public void run() {
        Process mLogcatProc = null;
        BufferedReader reader = null;
        try {
            //获取logcat日志信息
            mLogcatProc = Runtime.getRuntime().exec(new String[] { "logcat","MediaFocusControl:I *:S" });
            reader = new BufferedReader(new InputStreamReader(mLogcatProc.getInputStream()));

            String line;

            while ((line = reader.readLine()) != null) {

                if (line.contains("MediaFocusControl: requestAudioFocus()")) {
                    String[] strArr = line.split(" ");
                    for (String str:
                         strArr) {
                        if (str.contains("callingPack")){
                            System.out.println(str);
                            Test.packageName = str.split("=")[1];
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}).start();
Elikill58
  • 4,050
  • 24
  • 23
  • 45
  • I'm sure this works on some older devices or the emulator, but this is not viable since READ_LOGS permission is not granted on a real device you can buy without modifications or rooting unless your code is signed by firmware signing key or is a preinstalled system app. It apparently doesn't work any Android version above 4.1. Also reading logs this way isn't well supported, so prefer using something a bit more robust. See https://stackoverflow.com/a/45270606/975887 and https://developer.android.com/reference/android/Manifest.permission#READ_LOGS – Madushan Feb 24 '22 at 08:20