Bug 1322114 - Part 1: Change UI updating flow draft
authorJulian_Chu <walkingice0204@gmail.com>
Wed, 11 Jan 2017 18:24:53 +0800
changeset 461201 e2375d54359b10db31096dcb7b38ed69446297bf
parent 449801 7652a58efa46f1c57c94bba26efc5d53b6184e83
child 461202 58518a2fac51cc4228b5cb93c31d7df733d6cda2
push id41590
push userbmo:walkingice0204@gmail.com
push dateMon, 16 Jan 2017 04:10:36 +0000
bugs1322114
milestone53.0a1
Bug 1322114 - Part 1: Change UI updating flow 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 static 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();
     }