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.