22
22
import android .content .pm .PackageManager ;
23
23
import android .content .pm .ResolveInfo ;
24
24
import android .graphics .Bitmap ;
25
+ import android .graphics .Color ;
25
26
import android .graphics .drawable .BitmapDrawable ;
26
27
import android .graphics .drawable .Drawable ;
27
28
import android .net .Uri ;
28
29
import android .os .Bundle ;
29
30
import android .os .RemoteException ;
31
+ import android .support .annotation .NonNull ;
30
32
import android .support .annotation .Nullable ;
31
33
import android .support .annotation .StringRes ;
32
34
import android .support .design .widget .Snackbar ;
33
35
import android .support .design .widget .TabLayout ;
36
+ import android .support .v4 .graphics .drawable .DrawableCompat ;
34
37
import android .support .v4 .media .MediaBrowserCompat ;
35
38
import android .support .v4 .media .MediaBrowserServiceCompat ;
36
39
import android .support .v4 .media .MediaMetadataCompat ;
43
46
import android .support .v7 .widget .Toolbar ;
44
47
import android .text .TextUtils ;
45
48
import android .util .Log ;
49
+ import android .util .SparseArray ;
46
50
import android .view .View ;
47
51
import android .view .ViewGroup ;
48
52
import android .widget .EditText ;
53
+ import android .widget .ImageButton ;
49
54
import android .widget .ImageView ;
50
55
import android .widget .Spinner ;
51
56
import android .widget .TextView ;
52
57
import android .widget .Toast ;
53
58
59
+ import java .util .ArrayList ;
60
+ import java .util .Collections ;
61
+ import java .util .Comparator ;
54
62
import java .util .HashMap ;
63
+ import java .util .HashSet ;
55
64
import java .util .List ;
65
+ import java .util .Locale ;
56
66
import java .util .Map ;
57
67
import java .util .Set ;
58
68
@@ -79,7 +89,7 @@ public class MediaAppControllerActivity extends AppCompatActivity {
79
89
private static final String PACKAGE_NAME_EXTRA =
80
90
"com.example.android.mediacontroller.PACKAGE_NAME" ;
81
91
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" ;
83
93
private static final String MEDIA_ID_EXTRA = "com.example.android.mediacontroller.MEDIA_ID" ;
84
94
85
95
// Hint to use the currently loaded app rather than specifying a package.
@@ -113,6 +123,8 @@ public class MediaAppControllerActivity extends AppCompatActivity {
113
123
private TextView mMediaArtistView ;
114
124
private TextView mMediaAlbumView ;
115
125
126
+ private final SparseArray <ImageButton > mActionButtonMap = new SparseArray <>();
127
+
116
128
/**
117
129
* Builds an {@link Intent} to launch this Activity with a set of extras.
118
130
*
@@ -320,6 +332,7 @@ private void setupButtons() {
320
332
findViewById (R .id .action_prepare ).setOnClickListener (preparePlayHandler );
321
333
findViewById (R .id .action_play ).setOnClickListener (preparePlayHandler );
322
334
335
+ mActionButtonMap .clear ();
323
336
final List <Action > mediaActions = Action .createActions (this );
324
337
for (final Action action : mediaActions ) {
325
338
final View button = findViewById (action .getId ());
@@ -332,6 +345,7 @@ public void onClick(View view) {
332
345
}
333
346
}
334
347
});
348
+ mActionButtonMap .put (action .getId (), (ImageButton ) button );
335
349
}
336
350
}
337
351
@@ -377,7 +391,47 @@ private String fetchMediaInfo() {
377
391
final Bitmap art = mediaMetadata .getBitmap (MediaMetadataCompat .METADATA_KEY_ALBUM_ART );
378
392
mMediaAlbumArtView .setImageBitmap (art );
379
393
}
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 ();
381
435
}
382
436
383
437
private String playbackStateToName (final int playbackState ) {
@@ -471,6 +525,23 @@ public void onClick(final View button) {
471
525
}
472
526
473
527
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
+ }
474
545
475
546
@ Override
476
547
public void onConnected () {
@@ -479,25 +550,36 @@ public void onConnected() {
479
550
MediaAppControllerActivity .this ,
480
551
mBrowser .getSessionToken ());
481
552
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 () {
487
555
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 ());
492
582
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
- });
501
583
Log .d (TAG , "MediaControllerCompat created" );
502
584
} catch (RemoteException remoteException ) {
503
585
Log .e (TAG , "Failed to connect with session token: " + remoteException );
@@ -528,5 +610,60 @@ public void onClick(final View view) {
528
610
});
529
611
snackbar .show ();
530
612
}
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
+ }
531
668
}
532
669
}
0 commit comments