Skip to content

Commit 6d31a3c

Browse files
committed
Add support for 'supported actions'.
The app now displays a list of prepare/play actions that are declared as supported by the connected MediaSession, and colors the various controls red when they corrospond to an action that's not declared as being supported by the MediaSession. Change-Id: I75d8bb325140aed0b4be19f1237a20a851cef024
1 parent 8b9c6d4 commit 6d31a3c

File tree

3 files changed

+167
-20
lines changed

3 files changed

+167
-20
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,24 @@ Another example with UAMP is to perform a search with the term "jazz?" one would
5555

5656
```adb shell am start -n com.example.android.mediacontroller/.MediaAppControllerActivity --es com.example.android.mediacontroller.PACKAGE_NAME "com.example.android.uamp" --es com.example.android.mediacontroller.SEARCH "jazz?"```
5757

58+
Verification
59+
============
60+
61+
This tool displays the supported actions as reported by the MediaSession in the call to
62+
[MediaSessionCompat.setPlaybackState()](https://developer.android.com/reference/android/support/v4/media/session/MediaSessionCompat.html#setPlaybackState(android.support.v4.media.session.PlaybackStateCompat))
63+
as a list of prepare and play actions on the main screen, and by coloring the buttons for actions
64+
that aren't declared as supported red on the controller screen.
65+
66+
See the screenshots below for examples.
67+
5868
Screenshots
5969
===========
6070

6171
![](screenshots/screenshots.png "Controls, URIs, Playback")
6272

6373

6474
License
65-
-------
75+
=======
6676

6777
Copyright 2017 Google Inc. All rights reserved.
6878

mediacontroller/src/main/java/com/example/android/mediacontroller/MediaAppControllerActivity.java

Lines changed: 156 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@
2222
import android.content.pm.PackageManager;
2323
import android.content.pm.ResolveInfo;
2424
import android.graphics.Bitmap;
25+
import android.graphics.Color;
2526
import android.graphics.drawable.BitmapDrawable;
2627
import android.graphics.drawable.Drawable;
2728
import android.net.Uri;
2829
import android.os.Bundle;
2930
import android.os.RemoteException;
31+
import android.support.annotation.NonNull;
3032
import android.support.annotation.Nullable;
3133
import android.support.annotation.StringRes;
3234
import android.support.design.widget.Snackbar;
3335
import android.support.design.widget.TabLayout;
36+
import android.support.v4.graphics.drawable.DrawableCompat;
3437
import android.support.v4.media.MediaBrowserCompat;
3538
import android.support.v4.media.MediaBrowserServiceCompat;
3639
import android.support.v4.media.MediaMetadataCompat;
@@ -43,16 +46,23 @@
4346
import android.support.v7.widget.Toolbar;
4447
import android.text.TextUtils;
4548
import android.util.Log;
49+
import android.util.SparseArray;
4650
import android.view.View;
4751
import android.view.ViewGroup;
4852
import android.widget.EditText;
53+
import android.widget.ImageButton;
4954
import android.widget.ImageView;
5055
import android.widget.Spinner;
5156
import android.widget.TextView;
5257
import android.widget.Toast;
5358

59+
import java.util.ArrayList;
60+
import java.util.Collections;
61+
import java.util.Comparator;
5462
import java.util.HashMap;
63+
import java.util.HashSet;
5564
import java.util.List;
65+
import java.util.Locale;
5666
import java.util.Map;
5767
import java.util.Set;
5868

@@ -79,7 +89,7 @@ public class MediaAppControllerActivity extends AppCompatActivity {
7989
private static final String PACKAGE_NAME_EXTRA =
8090
"com.example.android.mediacontroller.PACKAGE_NAME";
8191
private static final String SEARCH_EXTRA = "com.example.android.mediacontroller.SEARCH";
82-
private static final String URI_EXTRA ="com.example.android.mediacontroller.URI";
92+
private static final String URI_EXTRA = "com.example.android.mediacontroller.URI";
8393
private static final String MEDIA_ID_EXTRA = "com.example.android.mediacontroller.MEDIA_ID";
8494

8595
// Hint to use the currently loaded app rather than specifying a package.
@@ -113,6 +123,8 @@ public class MediaAppControllerActivity extends AppCompatActivity {
113123
private TextView mMediaArtistView;
114124
private TextView mMediaAlbumView;
115125

126+
private final SparseArray<ImageButton> mActionButtonMap = new SparseArray<>();
127+
116128
/**
117129
* Builds an {@link Intent} to launch this Activity with a set of extras.
118130
*
@@ -320,6 +332,7 @@ private void setupButtons() {
320332
findViewById(R.id.action_prepare).setOnClickListener(preparePlayHandler);
321333
findViewById(R.id.action_play).setOnClickListener(preparePlayHandler);
322334

335+
mActionButtonMap.clear();
323336
final List<Action> mediaActions = Action.createActions(this);
324337
for (final Action action : mediaActions) {
325338
final View button = findViewById(action.getId());
@@ -332,6 +345,7 @@ public void onClick(View view) {
332345
}
333346
}
334347
});
348+
mActionButtonMap.put(action.getId(), (ImageButton) button);
335349
}
336350
}
337351

@@ -377,7 +391,47 @@ private String fetchMediaInfo() {
377391
final Bitmap art = mediaMetadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
378392
mMediaAlbumArtView.setImageBitmap(art);
379393
}
380-
return mediaInfos.toString();
394+
395+
final long actions = playbackState.getActions();
396+
397+
if ((actions & PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH) != 0) {
398+
addMediaInfo(mediaInfos, "ACTION_PREPARE_FROM_SEARCH", "Supported");
399+
}
400+
if ((actions & PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH) != 0) {
401+
addMediaInfo(mediaInfos, "ACTION_PLAY_FROM_SEARCH", "Supported");
402+
}
403+
404+
if ((actions & PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID) != 0) {
405+
addMediaInfo(mediaInfos, "ACTION_PREPARE_FROM_MEDIA_ID", "Supported");
406+
}
407+
if ((actions & PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID) != 0) {
408+
addMediaInfo(mediaInfos, "ACTION_PLAY_FROM_MEDIA_ID", "Supported");
409+
}
410+
411+
if ((actions & PlaybackStateCompat.ACTION_PREPARE_FROM_URI) != 0) {
412+
addMediaInfo(mediaInfos, "ACTION_PREPARE_FROM_URI", "Supported");
413+
}
414+
if ((actions & PlaybackStateCompat.ACTION_PLAY_FROM_URI) != 0) {
415+
addMediaInfo(mediaInfos, "ACTION_PLAY_FROM_URI", "Supported");
416+
}
417+
418+
if ((actions & PlaybackStateCompat.ACTION_PREPARE) != 0) {
419+
addMediaInfo(mediaInfos, "ACTION_PREPARE", "Supported");
420+
}
421+
if ((actions & PlaybackStateCompat.ACTION_PLAY) != 0) {
422+
addMediaInfo(mediaInfos, "ACTION_PLAY", "Supported");
423+
}
424+
425+
final StringBuilder stringBuilder = new StringBuilder();
426+
427+
final List<String> sortedKeys = new ArrayList<>();
428+
sortedKeys.addAll(mediaInfos.keySet());
429+
Collections.sort(sortedKeys, new KeyComparator());
430+
431+
for (final String key : sortedKeys) {
432+
stringBuilder.append(key).append(" = ").append(mediaInfos.get(key)).append('\n');
433+
}
434+
return stringBuilder.toString();
381435
}
382436

383437
private String playbackStateToName(final int playbackState) {
@@ -471,6 +525,23 @@ public void onClick(final View button) {
471525
}
472526

473527
private class MyConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
528+
private final SparseArray<Long> mActionViewIdMap;
529+
530+
MyConnectionCallback() {
531+
mActionViewIdMap = new SparseArray<>();
532+
mActionViewIdMap.put(R.id.action_skip_previous,
533+
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
534+
mActionViewIdMap.put(R.id.action_fast_rewind, PlaybackStateCompat.ACTION_REWIND);
535+
mActionViewIdMap.put(R.id.action_resume, PlaybackStateCompat.ACTION_PLAY);
536+
mActionViewIdMap.put(R.id.action_pause, PlaybackStateCompat.ACTION_PAUSE);
537+
mActionViewIdMap.put(R.id.action_stop, PlaybackStateCompat.ACTION_STOP);
538+
mActionViewIdMap.put(R.id.action_fast_forward, PlaybackStateCompat.ACTION_FAST_FORWARD);
539+
mActionViewIdMap.put(R.id.action_skip_next, PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
540+
541+
// They're the same action, but each of the buttons should be colored anyway.
542+
mActionViewIdMap.put(R.id.action_skip_30s_backward, PlaybackStateCompat.ACTION_SEEK_TO);
543+
mActionViewIdMap.put(R.id.action_skip_30s_forward, PlaybackStateCompat.ACTION_SEEK_TO);
544+
}
474545

475546
@Override
476547
public void onConnected() {
@@ -479,25 +550,36 @@ public void onConnected() {
479550
MediaAppControllerActivity.this,
480551
mBrowser.getSessionToken());
481552

482-
mController.registerCallback(new MediaControllerCompat.Callback() {
483-
@Override
484-
public void onPlaybackStateChanged(PlaybackStateCompat playbackState) {
485-
onUpdate();
486-
}
553+
final MediaControllerCompat.Callback callback =
554+
new MediaControllerCompat.Callback() {
487555

488-
@Override
489-
public void onMetadataChanged(MediaMetadataCompat metadata) {
490-
onUpdate();
491-
}
556+
@Override
557+
public void onPlaybackStateChanged(PlaybackStateCompat playbackState) {
558+
onUpdate();
559+
560+
if (playbackState != null) {
561+
showActions(playbackState.getActions());
562+
}
563+
}
564+
565+
@Override
566+
public void onMetadataChanged(MediaMetadataCompat metadata) {
567+
onUpdate();
568+
}
569+
570+
private void onUpdate() {
571+
String mediaInfoStr = fetchMediaInfo();
572+
if (mediaInfoStr != null) {
573+
mMediaInfoText.setText(mediaInfoStr);
574+
}
575+
}
576+
};
577+
mController.registerCallback(callback);
578+
579+
// Force update on connect
580+
callback.onPlaybackStateChanged(mController.getPlaybackState());
581+
callback.onMetadataChanged(mController.getMetadata());
492582

493-
private void onUpdate() {
494-
String newText = "PlaybackState changed!";
495-
String mediaInfoStr = fetchMediaInfo();
496-
if (mediaInfoStr != null) {
497-
mMediaInfoText.setText(newText + "\n" + mediaInfoStr);
498-
}
499-
}
500-
});
501583
Log.d(TAG, "MediaControllerCompat created");
502584
} catch (RemoteException remoteException) {
503585
Log.e(TAG, "Failed to connect with session token: " + remoteException);
@@ -528,5 +610,60 @@ public void onClick(final View view) {
528610
});
529611
snackbar.show();
530612
}
613+
614+
/**
615+
* This updates the buttons on the controller view to show actions that
616+
* aren't included in the declared supported actions in red to more easily
617+
* detect potential bugs.
618+
*
619+
* @param actions The mask of currently supported actions from
620+
* {@see PlaybackStateCompat.getActions()}.
621+
*/
622+
private void showActions(@PlaybackStateCompat.Actions long actions) {
623+
final int count = mActionViewIdMap.size();
624+
for (int i = 0; i < count; ++i) {
625+
final int viewId = mActionViewIdMap.keyAt(i);
626+
final long action = mActionViewIdMap.valueAt(i);
627+
628+
final ImageButton button = mActionButtonMap.get(viewId);
629+
DrawableCompat.setTint(button.getDrawable(), getTint(actions, action));
630+
}
631+
}
632+
633+
private int getTint(@PlaybackStateCompat.Actions long actions,
634+
@PlaybackStateCompat.Actions long checkAction) {
635+
return ((actions & checkAction) != 0)
636+
? Color.WHITE
637+
: Color.RED;
638+
}
639+
}
640+
641+
private static class KeyComparator implements Comparator<String> {
642+
private final Set<String> mCapKeys = new HashSet<>();
643+
644+
@Override
645+
public int compare(String leftSide, String rightSide) {
646+
final boolean leftCaps = isAllCaps(leftSide);
647+
final boolean rightCaps = isAllCaps(rightSide);
648+
649+
if (leftCaps && rightCaps) {
650+
return leftSide.compareTo(rightSide);
651+
} else if (leftCaps) {
652+
return 1;
653+
} else if (rightCaps) {
654+
return -1;
655+
}
656+
return leftSide.compareTo(rightSide);
657+
}
658+
659+
private boolean isAllCaps(@NonNull final String stringToCheck) {
660+
if (mCapKeys.contains(stringToCheck)) {
661+
return true;
662+
} else if (stringToCheck.equals(stringToCheck.toUpperCase(Locale.US))) {
663+
mCapKeys.add(stringToCheck);
664+
return true;
665+
}
666+
return false;
667+
}
531668
}
532669
}

screenshots/screenshots.png

-85 KB
Loading

0 commit comments

Comments
 (0)