Cleanup Radio UI Service callback interface.

Bug: 73950974
Test: build and run radio app
Change-Id: I38da6a69d9b8d50d431831f13f8a048723f3ddf1
diff --git a/src/com/android/car/radio/PlayPauseButton.java b/src/com/android/car/radio/PlayPauseButton.java
index 0e1f82d..2f1cb75 100644
--- a/src/com/android/car/radio/PlayPauseButton.java
+++ b/src/com/android/car/radio/PlayPauseButton.java
@@ -28,8 +28,8 @@
 public class PlayPauseButton extends ImageView {
     private static final String TAG = "Em.PlayPauseButton";
 
-    private final int[] STATE_PLAYING = {R.attr.state_playing};
-    private final int[] STATE_PAUSED = {R.attr.state_paused};
+    private static final int[] STATE_PLAYING = {R.attr.state_playing};
+    private static final int[] STATE_PAUSED = {R.attr.state_paused};
 
     private int mPlaybackState = -1;
 
@@ -40,16 +40,9 @@
     /**
      * Set the current play state of the button.
      *
-     * @param playState One of the values from {@link PlaybackState}. Only
-     *                  {@link PlaybackState#STATE_PAUSED} and {@link PlaybackState#STATE_PLAYING}
-     *                  are valid.
+     * @param playState One of the values from {@link PlaybackState}.
      */
     public void setPlayState(int playState) {
-        if (playState != PlaybackState.STATE_PAUSED && playState != PlaybackState.STATE_PLAYING) {
-            throw new IllegalArgumentException("Playback state should be either "
-                    + "PlaybackState.STATE_PAUSED or PlaybackState.STATE_PLAYING");
-        }
-
         mPlaybackState = playState;
     }
 
@@ -62,11 +55,14 @@
             case PlaybackState.STATE_PLAYING:
                 mergeDrawableStates(drawableState, STATE_PLAYING);
                 break;
+            case PlaybackState.STATE_NONE:
             case PlaybackState.STATE_PAUSED:
+            case PlaybackState.STATE_STOPPED:
+            case PlaybackState.STATE_CONNECTING:
                 mergeDrawableStates(drawableState, STATE_PAUSED);
                 break;
             default:
-                Log.e(TAG, "Unknown PlaybackState: " + mPlaybackState);
+                Log.e(TAG, "Unsupported PlaybackState: " + mPlaybackState);
         }
         if (getBackground() != null) {
             getBackground().setState(drawableState);
diff --git a/src/com/android/car/radio/RadioController.java b/src/com/android/car/radio/RadioController.java
index 713f64b..8bec5d0 100644
--- a/src/com/android/car/radio/RadioController.java
+++ b/src/com/android/car/radio/RadioController.java
@@ -32,7 +32,6 @@
 import android.hardware.radio.RadioManager.ProgramInfo;
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
-import android.media.AudioManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -98,20 +97,13 @@
 
     private final RadioDisplayController mRadioDisplayController;
 
-    /**
-     * Keeps track of if the user has manually muted the radio. This value is used to determine
-     * whether or not to un-mute the radio after an {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}
-     * event has been received.
-     */
-    private boolean mUserHasMuted;
-
     private final RadioStorage mRadioStorage;
 
     private final String mAmBandString;
     private final String mFmBandString;
 
-    private List<ProgramInfoChangeListener> mProgramInfoChangeListeners = new ArrayList<>();
-    private List<RadioServiceConnectionListener> mRadioServiceConnectionListeners =
+    private final List<ProgramInfoChangeListener> mProgramInfoChangeListeners = new ArrayList<>();
+    private final List<RadioServiceConnectionListener> mRadioServiceConnectionListeners =
             new ArrayList<>();
 
     /**
@@ -255,9 +247,6 @@
         try {
             mRadioDisplayController.setSingleChannelDisplay(mRadioBackground);
 
-            // Ensure the play button properly reflects the current mute state.
-            mRadioDisplayController.setPlayPauseButtonState(mRadioManager.isMuted());
-
             // TODO(b/73950974): use callback only
             ProgramInfo current = mRadioManager.getCurrentProgramInfo();
             if (current != null) mCallback.onCurrentProgramInfoChanged(current);
@@ -442,18 +431,6 @@
     }
 
     /**
-     * Closes any active {@link RadioTuner}s and releases audio focus.
-     */
-    private void close() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "close()");
-        }
-
-        // Lost focus, so display that the radio is not playing anymore.
-        mRadioDisplayController.setPlayPauseButtonState(true);
-    }
-
-    /**
      * Closes all active connections in the {@link RadioController}.
      */
     public void shutdown() {
@@ -471,8 +448,6 @@
                 Log.e(TAG, "tuneToRadioChannel(); remote exception: " + e.getMessage());
             }
         }
-
-        close();
     }
 
     @Override
@@ -515,23 +490,10 @@
             mRadioDisplayController.setChannelIsPreset(mRadioStorage.isPreset(sel));
 
             // Notify that the current radio station has changed.
-            if (mProgramInfoChangeListeners != null) {
-                for (ProgramInfoChangeListener listener : mProgramInfoChangeListeners) {
-                    listener.onProgramInfoChanged(info);
-                }
+            for (ProgramInfoChangeListener listener : mProgramInfoChangeListeners) {
+                listener.onProgramInfoChanged(info);
             }
         }
-
-        @Override
-        public void onRadioMuteChanged(boolean isMuted) {
-            mRadioDisplayController.setPlayPauseButtonState(isMuted);
-        }
-
-        @Override
-        public void onError(int status) {
-            Log.e(TAG, "Radio callback error with status: " + status);
-            close();
-        }
     };
 
     private final View.OnClickListener mBackwardSeekClickListener = new View.OnClickListener() {
@@ -587,11 +549,6 @@
                 } else {
                     mRadioManager.mute();
                 }
-
-                boolean isMuted = mRadioManager.isMuted();
-
-                mUserHasMuted = isMuted;
-                mRadioDisplayController.setPlayPauseButtonState(isMuted);
             } catch (RemoteException e) {
                 Log.e(TAG, "playPauseClickListener(); remote exception: " + e.getMessage());
             }
@@ -638,6 +595,7 @@
                 }
 
                 mRadioDisplayController.setEnabled(true);
+                mRadioManager.addPlaybackStateListener(mRadioDisplayController);
 
                 if (mRadioErrorDisplay != null) {
                     mRadioErrorDisplay.setVisibility(View.GONE);
diff --git a/src/com/android/car/radio/RadioDisplayController.java b/src/com/android/car/radio/RadioDisplayController.java
index f9b1b7e..9c1179a 100644
--- a/src/com/android/car/radio/RadioDisplayController.java
+++ b/src/com/android/car/radio/RadioDisplayController.java
@@ -17,17 +17,20 @@
 package com.android.car.radio;
 
 import android.content.Context;
-import android.media.session.PlaybackState;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewStub;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.car.radio.audio.IPlaybackStateListener;
+import com.android.car.radio.utils.LocalInterface;
+
 /**
  * Controller that controls the appearance state of various UI elements in the radio.
  */
-public class RadioDisplayController {
+public class RadioDisplayController implements IPlaybackStateListener, LocalInterface {
     private final Context mContext;
 
     private TextView mChannelBand;
@@ -225,21 +228,15 @@
         }
     }
 
-    /**
-     * Sets the current state of the play button. If the given {@code muted} value is {@code true},
-     * then the button display a play icon. If {@code false}, then the button will display a
-     * pause icon.
-     */
-    public void setPlayPauseButtonState(boolean muted) {
+    @Override
+    public void onPlaybackStateChanged(@PlaybackStateCompat.State int state) {
         if (mPlayButton != null) {
-            mPlayButton.setPlayState(muted
-                    ? PlaybackState.STATE_PAUSED : PlaybackState.STATE_PLAYING);
+            mPlayButton.setPlayState(state);
             mPlayButton.refreshDrawableState();
         }
 
         if (mPresetPlayButton != null) {
-            mPresetPlayButton.setPlayState(muted
-                    ? PlaybackState.STATE_PAUSED : PlaybackState.STATE_PLAYING);
+            mPresetPlayButton.setPlayState(state);
             mPresetPlayButton.refreshDrawableState();
         }
     }
diff --git a/src/com/android/car/radio/RadioService.java b/src/com/android/car/radio/RadioService.java
index 5cbe278..e51a2ab 100644
--- a/src/com/android/car/radio/RadioService.java
+++ b/src/com/android/car/radio/RadioService.java
@@ -28,7 +28,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
 
 import androidx.media.MediaBrowserServiceCompat;
@@ -44,6 +43,7 @@
 import com.android.car.radio.service.IRadioCallback;
 import com.android.car.radio.service.IRadioManager;
 import com.android.car.radio.storage.RadioStorage;
+import com.android.car.radio.utils.LocalInterface;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -58,7 +58,7 @@
  *
  * <p>Utilize the {@link RadioBinder} to perform radio operations.
  */
-public class RadioService extends MediaBrowserServiceCompat implements IPlaybackStateListener {
+public class RadioService extends MediaBrowserServiceCompat implements LocalInterface {
 
     private static String TAG = "BcRadioApp.uisrv";
 
@@ -138,8 +138,6 @@
         mRadioStorage.addPresetsChangeListener(mPresetsListener);
         onPresetsChanged();
 
-        mAudioStreamController.addPlaybackStateListener(this);
-
         openRadioBandInternal(mRadioStorage.getStoredRadioBand());
 
         mRadioSuccessfullyInitialized = true;
@@ -216,23 +214,6 @@
         }
     }
 
-    /* TODO(b/73950974): remove onRadioMuteChanged from IRadioCallback,
-     * use IPlaybackStateListener directly.
-     */
-    @Override
-    public void onPlaybackStateChanged(@PlaybackStateCompat.State int state) {
-        boolean muted = state != PlaybackStateCompat.STATE_PLAYING;
-        synchronized (mLock) {
-            for (IRadioCallback callback : mRadioTunerCallbacks) {
-                try {
-                    callback.onRadioMuteChanged(muted);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Mute state change callback failed", e);
-                }
-            }
-        }
-    }
-
     /**
      * Closes any active {@link RadioTuner}s and releases audio focus.
      */
@@ -379,6 +360,16 @@
             return mCurrentProgram;
         }
 
+        @Override
+        public void addPlaybackStateListener(IPlaybackStateListener callback) {
+            mAudioStreamController.addPlaybackStateListener(callback);
+        }
+
+        @Override
+        public void removePlaybackStateListener(IPlaybackStateListener callback) {
+            mAudioStreamController.removePlaybackStateListener(callback);
+        }
+
         /**
          * Returns {@code true} if the radio was able to successfully initialize. A value of
          * {@code false} here could mean that the {@code RadioService} was not able to connect to
@@ -442,14 +433,6 @@
 
                 mReOpenRadioTunerCount++;
             }
-
-            try {
-                for (IRadioCallback callback : mRadioTunerCallbacks) {
-                    callback.onError(status);
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "onError(); Failed to notify IRadioCallbacks: " + e.getMessage());
-            }
         }
 
         @Override
@@ -494,9 +477,4 @@
 
         return super.onStartCommand(intent, flags, startId);
     }
-
-    @Override
-    public IBinder asBinder() {
-        throw new UnsupportedOperationException("Not a binder");
-    }
 }
diff --git a/src/com/android/car/radio/media/TunerSession.java b/src/com/android/car/radio/media/TunerSession.java
index 3b77057..a565046 100644
--- a/src/com/android/car/radio/media/TunerSession.java
+++ b/src/com/android/car/radio/media/TunerSession.java
@@ -23,7 +23,6 @@
 import android.hardware.radio.RadioManager.ProgramInfo;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.RatingCompat;
@@ -39,6 +38,7 @@
 import com.android.car.radio.R;
 import com.android.car.radio.audio.IPlaybackStateListener;
 import com.android.car.radio.service.IRadioManager;
+import com.android.car.radio.utils.LocalInterface;
 import com.android.car.radio.utils.ThrowingRunnable;
 
 import java.util.Objects;
@@ -46,7 +46,8 @@
 /**
  * Implementation of tuner's MediaSession.
  */
-public class TunerSession extends MediaSessionCompat implements IPlaybackStateListener {
+public class TunerSession extends MediaSessionCompat
+        implements IPlaybackStateListener, LocalInterface {
     private static final String TAG = "BcRadioApp.msess";
 
     private final Object mLock = new Object();
@@ -191,9 +192,4 @@
             }
         }
     }
-
-    @Override
-    public IBinder asBinder() {
-        throw new UnsupportedOperationException("Not a binder");
-    }
 }
diff --git a/src/com/android/car/radio/service/IRadioCallback.aidl b/src/com/android/car/radio/service/IRadioCallback.aidl
index d1ecf93..95bab45 100644
--- a/src/com/android/car/radio/service/IRadioCallback.aidl
+++ b/src/com/android/car/radio/service/IRadioCallback.aidl
@@ -29,19 +29,4 @@
      * @param info The current program info.
      */
     void onCurrentProgramInfoChanged(in RadioManager.ProgramInfo info);
-
-    /**
-     * Called when the mute state of the radio has changed.
-     *
-     * @param isMuted {@code true} if the radio is muted.
-     */
-    void onRadioMuteChanged(boolean isMuted);
-
-    /**
-     * Called when the radio has encountered an error.
-     *
-     * @param status One of the error states in {@link RadioManager}. For example,
-     *               {@link RadioManager#ERROR_HARDWARE_FAILURE}.
-     */
-    void onError(int status);
 }
diff --git a/src/com/android/car/radio/service/IRadioManager.aidl b/src/com/android/car/radio/service/IRadioManager.aidl
index ba005ba..4902c4b 100644
--- a/src/com/android/car/radio/service/IRadioManager.aidl
+++ b/src/com/android/car/radio/service/IRadioManager.aidl
@@ -19,6 +19,7 @@
 import android.hardware.radio.RadioManager;
 
 import com.android.car.broadcastradio.support.Program;
+import com.android.car.radio.audio.IPlaybackStateListener;
 import com.android.car.radio.service.IRadioCallback;
 
 /**
@@ -96,6 +97,16 @@
     RadioManager.ProgramInfo getCurrentProgramInfo();
 
     /**
+     * Adds {@link IPlaybackStateListener} listener for play/pause notifications.
+     */
+    void addPlaybackStateListener(in IPlaybackStateListener callback);
+
+    /**
+     * Removes {@link IPlaybackStateListener} listener.
+     */
+    void removePlaybackStateListener(in IPlaybackStateListener callback);
+
+    /**
      * Returns {@code true} if the radio was able to successfully initialize. A value of
      * {@code false} here could mean that the {@code RadioService} was not able to connect to
      * the {@link RadioManager} or there were no radio modules on the current device.
diff --git a/src/com/android/car/radio/utils/LocalInterface.java b/src/com/android/car/radio/utils/LocalInterface.java
new file mode 100644
index 0000000..ddc9fa7
--- /dev/null
+++ b/src/com/android/car/radio/utils/LocalInterface.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.radio.utils;
+
+import android.os.IBinder;
+import android.os.IInterface;
+
+/**
+ * Marks given I-Class as not-a-binder.
+ *
+ * It saves some copy-pasting for interfaces that are not meant to be used cross-process.
+ */
+public interface LocalInterface extends IInterface {
+    /**
+     * Dummy implementation of {@link IInterface#asBinder}.
+     */
+    default IBinder asBinder() {
+        throw new UnsupportedOperationException("Not a binder");
+    }
+}