Bug 1322114 - Part 1: Change UI updating flow r=alwu
authorJulian_Chu <walkingice0204@gmail.com>
Wed, 11 Jan 2017 18:24:53 +0800
changeset 375452 63bf33197a074d1a3fff623fdecea63ca7db282a
parent 375451 84923aad340241cf2a60dae38a23d2b39f22bd23
child 375453 daf0f6eccbb6cf6e5f5ab74c99c569e174168ec8
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersalwu
bugs1322114
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1322114 - Part 1: Change UI updating flow r=alwu This patch involve State into MediaControlService. ACTION is behaviour for an intention, and so far we use it for storing media state. Now involve enum to distinguish State from Action. So flow becomes: Action happens -> Change State -> UI reflect state changing. MozReview-Commit-ID: FBHn02tgVO6
mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -41,33 +41,42 @@ public class MediaControlService extends
     public static final String ACTION_PAUSE          = "action_pause";
     public static final String ACTION_STOP           = "action_stop";
     public static final String ACTION_RESUME_BY_AUDIO_FOCUS = "action_resume_audio_focus";
     public static final String ACTION_PAUSE_BY_AUDIO_FOCUS  = "action_pause_audio_focus";
 
     private static final int MEDIA_CONTROL_ID = 1;
     private static final String MEDIA_CONTROL_PREF = "dom.audiochannel.mediaControl";
 
-    private String mActionState = ACTION_STOP;
-
     private MediaSession mSession;
     private MediaController mController;
     private HeadSetStateReceiver mHeadSetStateReceiver;
 
     private PrefsHelper.PrefHandler mPrefsObserver;
     private final String[] mPrefs = { MEDIA_CONTROL_PREF };
 
     private boolean mInitialize = false;
     private boolean mIsMediaControlPrefOn = true;
 
     private WeakReference<Tab> mTabReference = new WeakReference<>(null);
 
     private int minCoverSize;
     private int coverSize;
 
+    /**
+     * Internal state of MediaControlService, to indicate it is playing media, or paused...etc.
+     */
+    private State mMediaState = State.STOPPED;
+
+    protected enum State {
+        PLAYING,
+        PAUSED,
+        STOPPED
+    }
+
     @Override
     public void onCreate() {
         initialize();
         mHeadSetStateReceiver = new HeadSetStateReceiver().registerReceiver(getApplicationContext());
     }
 
     @Override
     public void onDestroy() {
@@ -105,47 +114,45 @@ public class MediaControlService extends
 
         final Tab playingTab = mTabReference.get();
         switch (msg) {
             case MEDIA_PLAYING_CHANGE:
                 // The 'MEDIA_PLAYING_CHANGE' would only be received when the
                 // media starts or ends.
                 if (playingTab != tab && tab.isMediaPlaying()) {
                     mTabReference = new WeakReference<>(tab);
-                    notifyControlInterfaceChanged(ACTION_PAUSE);
+                    setState(State.PLAYING);
                 } else if (playingTab == tab && !tab.isMediaPlaying()) {
-                    notifyControlInterfaceChanged(ACTION_STOP);
                     mTabReference = new WeakReference<>(null);
+                    setState(State.STOPPED);
                 }
                 break;
             case MEDIA_PLAYING_RESUME:
                 // user resume the paused-by-control media from page so that we
                 // should make the control interface consistent.
                 if (playingTab == tab && !isMediaPlaying()) {
-                    notifyControlInterfaceChanged(ACTION_PAUSE);
+                    setState(State.PLAYING);
                 }
                 break;
             case CLOSED:
                 if (playingTab == null || playingTab == tab) {
                     // Remove the controls when the playing tab disappeared or was closed.
-                    notifyControlInterfaceChanged(ACTION_STOP);
+                    setState(State.STOPPED);
                 }
                 break;
             case FAVICON:
                 if (playingTab == tab) {
-                    final String actionForPendingIntent = isMediaPlaying() ?
-                        ACTION_PAUSE : ACTION_RESUME;
-                    notifyControlInterfaceChanged(actionForPendingIntent);
+                    setState(isMediaPlaying() ? State.PLAYING : State.PAUSED);
                 }
                 break;
         }
     }
 
     private boolean isMediaPlaying() {
-        return mActionState.equals(ACTION_RESUME);
+        return mMediaState.equals(State.PLAYING);
     }
 
     private void initialize() {
         if (mInitialize ||
             !isAndroidVersionLollopopOrHigher()) {
             return;
         }
 
@@ -161,34 +168,34 @@ public class MediaControlService extends
     }
 
     private void shutdown() {
         if (!mInitialize) {
             return;
         }
 
         Log.d(LOGTAG, "shutdown");
-        notifyControlInterfaceChanged(ACTION_STOP);
+        setState(State.STOPPED);
         PrefsHelper.removeObserver(mPrefsObserver);
 
         Tabs.unregisterOnTabsChangedListener(this);
         mInitialize = false;
         stopSelf();
     }
 
     private boolean isAndroidVersionLollopopOrHigher() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
     }
 
     private void handleIntent(Intent intent) {
         if (intent == null || intent.getAction() == null || !mInitialize) {
             return;
         }
 
-        Log.d(LOGTAG, "HandleIntent, action = " + intent.getAction() + ", actionState = " + mActionState);
+        Log.d(LOGTAG, "HandleIntent, action = " + intent.getAction() + ", mediaState = " + mMediaState);
         switch (intent.getAction()) {
             case ACTION_INIT :
                 // This action is used to create a service and do the initialization,
                 // the actual operation would be executed via control interface's
                 // pending intent.
                 break;
             case ACTION_RESUME :
                 mController.getTransportControls().play();
@@ -212,25 +219,24 @@ public class MediaControlService extends
         mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
             @Override
             public void prefValue(String pref, boolean value) {
                 if (pref.equals(MEDIA_CONTROL_PREF)) {
                     mIsMediaControlPrefOn = value;
 
                     // If media is playing, we just need to create or remove
                     // the media control interface.
-                    if (mActionState.equals(ACTION_RESUME)) {
-                        notifyControlInterfaceChanged(mIsMediaControlPrefOn ?
-                            ACTION_PAUSE : ACTION_STOP);
+                    if (mMediaState.equals(State.PLAYING)) {
+                        setState(mIsMediaControlPrefOn ? State.PLAYING : State.STOPPED);
                     }
 
                     // If turn off pref during pausing, except removing media
                     // interface, we also need to stop the service and notify
                     // gecko about that.
-                    if (mActionState.equals(ACTION_PAUSE) &&
+                    if (mMediaState.equals(State.PAUSED) &&
                         !mIsMediaControlPrefOn) {
                         Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
                         intent.setAction(ACTION_STOP);
                         handleIntent(intent);
                     }
                 }
             }
         };
@@ -244,111 +250,99 @@ public class MediaControlService extends
         mController = new MediaController(getApplicationContext(),
                                           mSession.getSessionToken());
 
         mSession.setCallback(new MediaSession.Callback() {
             @Override
             public void onCustomAction(String action, Bundle extras) {
                 if (action.equals(ACTION_PAUSE_BY_AUDIO_FOCUS)) {
                     Log.d(LOGTAG, "Controller, pause by audio focus changed");
-                    notifyControlInterfaceChanged(ACTION_RESUME);
+                    setState(State.PAUSED);
                 } else if (action.equals(ACTION_RESUME_BY_AUDIO_FOCUS)) {
                     Log.d(LOGTAG, "Controller, resume by audio focus changed");
-                    notifyControlInterfaceChanged(ACTION_PAUSE);
+                    setState(State.PLAYING);
                 }
             }
 
             @Override
             public void onPlay() {
                 Log.d(LOGTAG, "Controller, onPlay");
                 super.onPlay();
-                notifyControlInterfaceChanged(ACTION_PAUSE);
+                setState(State.PLAYING);
                 notifyObservers("MediaControl", "resumeMedia");
                 // To make sure we always own audio focus during playing.
                 AudioFocusAgent.notifyStartedPlaying();
             }
 
             @Override
             public void onPause() {
                 Log.d(LOGTAG, "Controller, onPause");
                 super.onPause();
-                notifyControlInterfaceChanged(ACTION_RESUME);
+                setState(State.PAUSED);
                 notifyObservers("MediaControl", "mediaControlPaused");
                 AudioFocusAgent.notifyStoppedPlaying();
             }
 
             @Override
             public void onStop() {
                 Log.d(LOGTAG, "Controller, onStop");
                 super.onStop();
-                notifyControlInterfaceChanged(ACTION_STOP);
+                setState(State.STOPPED);
                 notifyObservers("MediaControl", "mediaControlStopped");
                 mTabReference = new WeakReference<>(null);
             }
         });
 
     }
 
     private void notifyObservers(String topic, String data) {
         GeckoAppShell.notifyObservers(topic, data);
     }
 
-    private boolean isNeedToRemoveControlInterface(String action) {
-        return action.equals(ACTION_STOP);
+    private boolean isNeedToRemoveControlInterface(State state) {
+        return state.equals(State.STOPPED);
     }
 
-    private void notifyControlInterfaceChanged(final String uiAction) {
+    private void setState(State newState) {
+        mMediaState = newState;
+        onStateChanged();
+    }
+
+    private void onStateChanged() {
         if (!mInitialize) {
             return;
         }
 
-        Log.d(LOGTAG, "notifyControlInterfaceChanged, action = " + uiAction);
+        Log.d(LOGTAG, "onStateChanged, state = " + mMediaState);
 
-        if (isNeedToRemoveControlInterface(uiAction)) {
+        if (isNeedToRemoveControlInterface(mMediaState)) {
             stopForeground(false);
             NotificationManagerCompat.from(this).cancel(MEDIA_CONTROL_ID);
-            setActionState(uiAction);
             return;
         }
 
         if (!mIsMediaControlPrefOn) {
             return;
         }
 
         final Tab tab = mTabReference.get();
 
         if (tab == null) {
             return;
         }
 
-        setActionState(uiAction);
-
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
-                updateNotification(tab, uiAction);
+                updateNotification(tab);
             }
         });
     }
 
-    private void setActionState(final String uiAction) {
-        switch (uiAction) {
-            case ACTION_PAUSE:
-                mActionState = ACTION_RESUME;
-                break;
-            case ACTION_RESUME:
-                mActionState = ACTION_PAUSE;
-                break;
-            case ACTION_STOP:
-                mActionState = ACTION_STOP;
-                break;
-        }
-    }
-
-    private void updateNotification(Tab tab, String action) {
+    protected void updateNotification(Tab tab) {
         ThreadUtils.assertNotOnUiThread();
 
         final Notification.MediaStyle style = new Notification.MediaStyle();
         style.setShowActionsInCompactView(0);
 
         final boolean isPlaying = isMediaPlaying();
         final int visibility = tab.isPrivate() ?
             Notification.VISIBILITY_PRIVATE : Notification.VISIBILITY_PUBLIC;
@@ -356,37 +350,38 @@ public class MediaControlService extends
         final Notification notification = new Notification.Builder(this)
             .setSmallIcon(R.drawable.flat_icon)
             .setLargeIcon(generateCoverArt(tab))
             .setContentTitle(tab.getTitle())
             .setContentText(tab.getURL())
             .setContentIntent(createContentIntent(tab.getId()))
             .setDeleteIntent(createDeleteIntent())
             .setStyle(style)
-            .addAction(createNotificationAction(action))
+            .addAction(createNotificationAction())
             .setOngoing(isPlaying)
             .setShowWhen(false)
             .setWhen(0)
             .setVisibility(visibility)
             .build();
 
         if (isPlaying) {
             startForeground(MEDIA_CONTROL_ID, notification);
         } else {
             stopForeground(false);
             NotificationManagerCompat.from(this)
                 .notify(MEDIA_CONTROL_ID, notification);
         }
     }
 
-    private Notification.Action createNotificationAction(String action) {
-        boolean isPlayAction = action.equals(ACTION_RESUME);
+    private Notification.Action createNotificationAction() {
+        boolean isPlayAction = mMediaState.equals(State.PAUSED);
 
         int icon = isPlayAction ? R.drawable.ic_media_play : R.drawable.ic_media_pause;
         String title = getString(isPlayAction ? R.string.media_play : R.string.media_pause);
+        String action = isPlayAction ? ACTION_RESUME : ACTION_PAUSE;
 
         final Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
         intent.setAction(action);
         final PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
 
         //noinspection deprecation - The new constructor is only for API > 23
         return new Notification.Action.Builder(icon, title, pendingIntent).build();
     }