OSDN Git Service

Also rely on enabled notification listeners for RemoteController registration
authorJean-Michel Trivi <jmtrivi@google.com>
Sat, 28 Sep 2013 01:37:36 +0000 (18:37 -0700)
committerJean-Michel Trivi <jmtrivi@google.com>
Sat, 5 Oct 2013 00:50:44 +0000 (17:50 -0700)
Registration of a RemoteController may succeed only if:
 - the caller has the MEDIA_CONTENT_CONTROL permission,
 - or if the RemoteController.OnClientUpdateListener it
   registers if one of the enabled notification listeners.

For using the "enabled notification listener" functionality,
 the CL involved:
 - making OnClientUpdateListener an interface so a 3rd-party
   application may have its implementation extend
   NotificationListenerService, which is required for a
   listener to be enabled by the user.
 - add the concept of "enabled" status in an
   IRemoteControlDisplay, so a RemoteController (which
   encapsulates the IRemoteControlDisplay implementation)
   may be registered, but later temporarily disabled by
   the user, as a result of a user action in the security
   settings, or a user switch.
 - making MediaFocusControl, the component tied to
   AudioService, monitor changes in enabled notification
   listeners, and act upon enable/disable changes.

Bug 8209392

Change-Id: Ia8dfa2156c65668b2b0d4ae92048005912652d84

12 files changed:
api/current.txt
cmds/media/src/com/android/commands/media/Media.java
media/java/android/media/AudioManager.java
media/java/android/media/AudioService.java
media/java/android/media/IAudioService.aidl
media/java/android/media/IRemoteControlClient.aidl
media/java/android/media/IRemoteControlDisplay.aidl
media/java/android/media/MediaFocusControl.java
media/java/android/media/RemoteControlClient.java
media/java/android/media/RemoteController.java
packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java
packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java

index ed0d154..981eb1f 100644 (file)
@@ -13352,34 +13352,29 @@ package android.media {
   }
 
   public final class RemoteController {
-    ctor public RemoteController(android.content.Context) throws java.lang.IllegalArgumentException;
-    ctor public RemoteController(android.content.Context, android.os.Looper) throws java.lang.IllegalArgumentException;
-    method public int clearArtworkConfiguration();
+    ctor public RemoteController(android.content.Context, android.media.RemoteController.OnClientUpdateListener) throws java.lang.IllegalArgumentException;
+    ctor public RemoteController(android.content.Context, android.media.RemoteController.OnClientUpdateListener, android.os.Looper) throws java.lang.IllegalArgumentException;
+    method public boolean clearArtworkConfiguration();
     method public android.media.RemoteController.MetadataEditor editMetadata();
     method public long getEstimatedMediaPosition();
-    method public int seekTo(long);
-    method public int sendMediaKeyEvent(android.view.KeyEvent);
-    method public int setArtworkConfiguration(int, int);
-    method public void setOnClientUpdateListener(android.media.RemoteController.OnClientUpdateListener);
-    method public int setSynchronizationMode(int);
-    field public static final int ERROR = -1; // 0xffffffff
-    field public static final int ERROR_BAD_VALUE = -2; // 0xfffffffe
+    method public boolean seekTo(long) throws java.lang.IllegalArgumentException;
+    method public boolean sendMediaKeyEvent(android.view.KeyEvent) throws java.lang.IllegalArgumentException;
+    method public boolean setArtworkConfiguration(int, int) throws java.lang.IllegalArgumentException;
+    method public boolean setSynchronizationMode(int) throws java.lang.IllegalArgumentException;
     field public static final int POSITION_SYNCHRONIZATION_CHECK = 1; // 0x1
     field public static final int POSITION_SYNCHRONIZATION_NONE = 0; // 0x0
-    field public static final int SUCCESS = 0; // 0x0
   }
 
   public class RemoteController.MetadataEditor extends android.media.MediaMetadataEditor {
     method public synchronized void apply();
   }
 
-  public static abstract class RemoteController.OnClientUpdateListener {
-    ctor public RemoteController.OnClientUpdateListener();
-    method public void onClientChange(boolean);
-    method public void onClientMetadataUpdate(android.media.RemoteController.MetadataEditor);
-    method public void onClientPlaybackStateUpdate(int);
-    method public void onClientPlaybackStateUpdate(int, long, long, float);
-    method public void onClientTransportControlUpdate(int);
+  public static abstract interface RemoteController.OnClientUpdateListener {
+    method public abstract void onClientChange(boolean);
+    method public abstract void onClientMetadataUpdate(android.media.RemoteController.MetadataEditor);
+    method public abstract void onClientPlaybackStateUpdate(int);
+    method public abstract void onClientPlaybackStateUpdate(int, long, long, float);
+    method public abstract void onClientTransportControlUpdate(int);
   }
 
   public final class ResourceBusyException extends android.media.MediaDrmException {
index 56af7d6..92c6a51 100644 (file)
@@ -140,6 +140,11 @@ public class Media extends BaseCommand {
         }
 
         @Override
+        public void setEnabled(boolean enabled) {
+            System.out.println("New enable state= " + (enabled ? "enabled" : "disabled"));
+        }
+
+        @Override
         public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
                 long currentPosMs, float speed) {
             System.out.println("New state: id=" + generationId + " state=" + state
index c8ee5ad..680e73a 100644 (file)
@@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.media.RemoteController.OnClientUpdateListener;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -2270,7 +2271,9 @@ public class AudioManager {
      * Registers a {@link RemoteController} instance for it to receive media metadata updates
      * and playback state information from applications using {@link RemoteControlClient}, and
      * control their playback.
-     * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
+     * <p>Registration requires the {@link OnClientUpdateListener} listener to be one of the
+     * enabled notification listeners (see
+     * {@link android.service.notification.NotificationListenerService}).
      * @param rctlr the object to register.
      * @return true if the {@link RemoteController} was successfully registered, false if an
      *     error occurred, due to an internal system error, or insufficient permissions.
@@ -2280,14 +2283,17 @@ public class AudioManager {
             return false;
         }
         IAudioService service = getService();
+        final RemoteController.OnClientUpdateListener l = rctlr.getUpdateListener();
+        final ComponentName listenerComponent = new ComponentName(mContext, l.getClass());
         try {
             int[] artworkDimensions = rctlr.getArtworkSize();
-            boolean reg = service.registerRemoteControlDisplay(rctlr.getRcDisplay(),
-                    artworkDimensions[0]/*w*/, artworkDimensions[1]/*h*/);
+            boolean reg = service.registerRemoteController(rctlr.getRcDisplay(),
+                    artworkDimensions[0]/*w*/, artworkDimensions[1]/*h*/,
+                    listenerComponent);
             rctlr.setIsRegistered(reg);
             return reg;
         } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
+            Log.e(TAG, "Dead object in registerRemoteController " + e);
             return false;
         }
     }
@@ -2318,6 +2324,7 @@ public class AudioManager {
      * artwork size directly, or
      * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork
      * is not yet needed.
+     * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
      * @param rcd the IRemoteControlDisplay
      */
     public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
@@ -2328,6 +2335,7 @@ public class AudioManager {
     /**
      * @hide
      * Registers a remote control display that will be sent information by remote control clients.
+     * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
      * @param rcd
      * @param w the maximum width of the expected bitmap. Negative values indicate it is
      *   useless to send artwork.
index 84ea4c9..e9d644b 100644 (file)
@@ -48,6 +48,7 @@ import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -87,6 +88,7 @@ import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -4147,17 +4149,13 @@ public class AudioService extends IAudioService.Stub {
     //==========================================================================================
     // RemoteControlDisplay / RemoteControlClient / Remote info
     //==========================================================================================
+    public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
+            ComponentName listenerComp) {
+        return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp);
+    }
+
     public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
-        if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
-            mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
-            return true;
-        } else {
-            Log.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
-                    ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
-                    " to register IRemoteControlDisplay");
-            return false;
-        }
+        return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
     }
 
     public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
index e3b87dd..8c05089 100644 (file)
@@ -133,6 +133,8 @@ interface IAudioService {
 
     /**
      * Register an IRemoteControlDisplay.
+     * Success of registration is subject to a check on
+     *   the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission.
      * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
      * at the top of the stack to update the new display with its information.
      * @param rcd the IRemoteControlDisplay to register. No effect if null.
@@ -142,6 +144,16 @@ interface IAudioService {
      *   display doesn't need to receive artwork.
      */
     boolean registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
+
+    /**
+     * Like registerRemoteControlDisplay, but with success being subject to a check on
+     *   the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission, and if it fails,
+     *   success is subject to listenerComp being one of the ENABLED_NOTIFICATION_LISTENERS
+     *   components.
+     */
+    boolean registerRemoteController(in IRemoteControlDisplay rcd, int w, int h,
+            in ComponentName listenerComp);
+
     /**
      * Unregister an IRemoteControlDisplay.
      * No effect if the IRemoteControlDisplay hasn't been successfully registered.
index 999b8ba..aa142d6 100644 (file)
@@ -56,6 +56,7 @@ oneway interface IRemoteControlClient
     void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
     void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h);
     void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync);
+    void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled);
     void seekTo(int clientGeneration, long timeMs);
     void updateMetadata(int clientGeneration, int key, in Rating value);
 }
\ No newline at end of file
index 583f436..1609030 100644 (file)
@@ -41,6 +41,12 @@ oneway interface IRemoteControlDisplay
             boolean clearing);
 
     /**
+     * Sets whether the controls of this display are enabled
+     * @param if false, the display shouldn't any commands
+     */
+    void setEnabled(boolean enabled);
+
+    /**
      * Sets the playback information (state, position and speed) of a client.
      * @param generationId the current generation ID as known by this client
      * @param state the current playback state, one of the following values:
index 2a7a731..d185321 100644 (file)
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
@@ -30,6 +31,8 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -45,6 +48,7 @@ import android.speech.RecognizerIntent;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import java.io.FileDescriptor;
@@ -78,6 +82,7 @@ public class MediaFocusControl implements OnFinished {
     private final AppOpsManager mAppOps;
     private final KeyguardManager mKeyguardManager;
     private final AudioService mAudioService;
+    private final NotificationListenerObserver mNotifListenerObserver;
 
     protected MediaFocusControl(Looper looper, Context cntxt,
             VolumeController volumeCtrl, AudioService as) {
@@ -111,6 +116,7 @@ public class MediaFocusControl implements OnFinished {
         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
         mKeyguardManager =
                 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mNotifListenerObserver = new NotificationListenerObserver();
 
         mHasRemotePlayback = false;
         mMainRemoteIsActive = false;
@@ -125,6 +131,182 @@ public class MediaFocusControl implements OnFinished {
     }
 
     //==========================================================================================
+    // Management of RemoteControlDisplay registration permissions
+    //==========================================================================================
+    private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI =
+            Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
+    private class NotificationListenerObserver extends ContentObserver {
+
+        NotificationListenerObserver() {
+            super(mEventHandler);
+            mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) {
+                return;
+            }
+            if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); }
+            postReevaluateRemoteControlDisplays();
+        }
+    }
+
+    private final static int RCD_REG_FAILURE = 0;
+    private final static int RCD_REG_SUCCESS_PERMISSION = 1;
+    private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2;
+
+    /**
+     * Checks a caller's authorization to register an IRemoteControlDisplay.
+     * Authorization is granted if one of the following is true:
+     * <ul>
+     * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li>
+     * <li>the caller's listener is one of the enabled notification listeners</li>
+     * </ul>
+     * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay
+     *     registration.
+     */
+    private int checkRcdRegistrationAuthorization(ComponentName listenerComp) {
+        // MEDIA_CONTENT_CONTROL permission check
+        if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
+            if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");}
+            return RCD_REG_SUCCESS_PERMISSION;
+        }
+
+        // ENABLED_NOTIFICATION_LISTENERS settings check
+        if (listenerComp != null) {
+            // this call is coming from an app, can't use its identity to read secure settings
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                final int currentUser = ActivityManager.getCurrentUser();
+                final String enabledNotifListeners = Settings.Secure.getStringForUser(
+                        mContext.getContentResolver(),
+                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                        currentUser);
+                if (enabledNotifListeners != null) {
+                    final String[] components = enabledNotifListeners.split(":");
+                    for (int i=0; i<components.length; i++) {
+                        final ComponentName component =
+                                ComponentName.unflattenFromString(components[i]);
+                        if (component != null) {
+                            if (listenerComp.equals(component)) {
+                                if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component +
+                                        " is authorized notification listener"); }
+                                return RCD_REG_SUCCESS_ENABLED_NOTIF;
+                            }
+                        }
+                    }
+                }
+                if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp +
+                        " is not in list of ENABLED_NOTIFICATION_LISTENERS"); }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        return RCD_REG_FAILURE;
+    }
+
+    protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
+            ComponentName listenerComp) {
+        int reg = checkRcdRegistrationAuthorization(listenerComp);
+        if (reg != RCD_REG_FAILURE) {
+            registerRemoteControlDisplay_int(rcd, w, h, listenerComp);
+            return true;
+        } else {
+            Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
+                    ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
+                    " or be an enabled NotificationListenerService for registerRemoteController");
+            return false;
+        }
+    }
+
+    protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
+        int reg = checkRcdRegistrationAuthorization(null);
+        if (reg != RCD_REG_FAILURE) {
+            registerRemoteControlDisplay_int(rcd, w, h, null);
+            return true;
+        } else {
+            Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
+                    ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
+                    " to register IRemoteControlDisplay");
+            return false;
+        }
+    }
+
+    private void postReevaluateRemoteControlDisplays() {
+        sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
+    }
+
+    private void onReevaluateRemoteControlDisplays() {
+        if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); }
+        // read which components are enabled notification listeners
+        final int currentUser = ActivityManager.getCurrentUser();
+        final String enabledNotifListeners = Settings.Secure.getStringForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                currentUser);
+        if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
+        synchronized(mAudioFocusLock) {
+            synchronized(mRCStack) {
+                // check whether the "enable" status of each RCD with a notification listener
+                // has changed
+                final String[] enabledComponents;
+                if (enabledNotifListeners == null) {
+                    enabledComponents = null;
+                } else {
+                    enabledComponents = enabledNotifListeners.split(":");
+                }
+                final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+                while (displayIterator.hasNext()) {
+                    final DisplayInfoForServer di =
+                            (DisplayInfoForServer) displayIterator.next();
+                    if (di.mClientNotifListComp != null) {
+                        boolean wasEnabled = di.mEnabled;
+                        di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
+                                enabledComponents);
+                        if (wasEnabled != di.mEnabled){
+                            try {
+                                // tell the RCD whether it's enabled
+                                di.mRcDisplay.setEnabled(di.mEnabled);
+                                // tell the RCCs about the change for this RCD
+                                enableRemoteControlDisplayForClient_syncRcStack(
+                                        di.mRcDisplay, di.mEnabled);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Error en/disabling RCD: ", e);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param comp a non-null ComponentName
+     * @param enabledArray may be null
+     * @return
+     */
+    private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) {
+        if (enabledArray == null || enabledArray.length == 0) {
+            if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); }
+            return false;
+        }
+        final String compString = comp.flattenToString();
+        for (int i=0; i<enabledArray.length; i++) {
+            if (compString.equals(enabledArray[i])) {
+                if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); }
+                return true;
+            }
+        }
+        if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); }
+        return false;
+    }
+
+    //==========================================================================================
     // Internal event handling
     //==========================================================================================
 
@@ -140,6 +322,7 @@ public class MediaFocusControl implements OnFinished {
     private static final int MSG_RCC_SEEK_REQUEST = 8;
     private static final int MSG_RCC_UPDATE_METADATA = 9;
     private static final int MSG_RCDISPLAY_INIT_INFO = 10;
+    private static final int MSG_REEVALUATE_RCD = 11;
 
     // sendMsg() flags
     /** If the msg is already queued, replace it with this one. */
@@ -221,6 +404,10 @@ public class MediaFocusControl implements OnFinished {
                     onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
                             msg.arg1/*w*/, msg.arg2/*h*/);
                     break;
+
+                case MSG_REEVALUATE_RCD:
+                    onReevaluateRemoteControlDisplays();
+                    break;
             }
         }
     }
@@ -1193,8 +1380,9 @@ public class MediaFocusControl implements OnFinished {
                 final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
                 pw.println("  IRCD: " + di.mRcDisplay +
                         "  -- w:" + di.mArtworkExpectedWidth +
-                        "  -- h:" + di.mArtworkExpectedHeight+
-                        "  -- wantsPosSync:" + di.mWantsPositionSync);
+                        "  -- h:" + di.mArtworkExpectedHeight +
+                        "  -- wantsPosSync:" + di.mWantsPositionSync +
+                        "  -- " + (di.mEnabled ? "enabled" : "disabled"));
             }
         }
     }
@@ -1834,11 +2022,13 @@ public class MediaFocusControl implements OnFinished {
      */
     private class DisplayInfoForServer implements IBinder.DeathRecipient {
         /** may never be null */
-        private IRemoteControlDisplay mRcDisplay;
-        private IBinder mRcDisplayBinder;
+        private final IRemoteControlDisplay mRcDisplay;
+        private final IBinder mRcDisplayBinder;
         private int mArtworkExpectedWidth = -1;
         private int mArtworkExpectedHeight = -1;
         private boolean mWantsPositionSync = false;
+        private ComponentName mClientNotifListComp;
+        private boolean mEnabled = true;
 
         public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
             if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
@@ -1911,6 +2101,23 @@ public class MediaFocusControl implements OnFinished {
         }
     }
 
+    private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd,
+            boolean enabled) {
+        // let all the remote control clients know whether the given display is enabled
+        //   (so the remote control stack traversal order doesn't matter).
+        final Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+        while(stackIterator.hasNext()) {
+            RemoteControlStackEntry rcse = stackIterator.next();
+            if(rcse.mRcClient != null) {
+                try {
+                    rcse.mRcClient.enableRemoteControlDisplay(rcd, enabled);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error connecting RCD to client: ", e);
+                }
+            }
+        }
+    }
+
     /**
      * Is the remote control display interface already registered
      * @param rcd
@@ -1937,8 +2144,11 @@ public class MediaFocusControl implements OnFinished {
      *   display doesn't need to receive artwork.
      * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
      *   display doesn't need to receive artwork.
+     * @param listenerComp the component for the listener interface, may be null if it's not needed
+     *   to verify it belongs to one of the enabled notification listeners
      */
-    protected void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
+    private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h,
+            ComponentName listenerComp) {
         if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
         synchronized(mAudioFocusLock) {
             synchronized(mRCStack) {
@@ -1946,6 +2156,8 @@ public class MediaFocusControl implements OnFinished {
                     return;
                 }
                 DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
+                di.mEnabled = true;
+                di.mClientNotifListComp = listenerComp;
                 if (!di.init()) {
                     if (DEBUG_RC) Log.e(TAG, " error registering RCD");
                     return;
index 497b8b3..0c00aba 100644 (file)
@@ -1049,6 +1049,7 @@ public class RemoteControlClient
         private int mArtworkExpectedWidth;
         private int mArtworkExpectedHeight;
         private boolean mWantsPositionSync = false;
+        private boolean mEnabled = true;
 
         DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
             mRcDisplay = rcd;
@@ -1165,6 +1166,14 @@ public class RemoteControlClient
             }
         }
 
+        public void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled) {
+            // only post messages, we can't block here
+            if ((mEventHandler != null) && (rcd != null)) {
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(
+                        MSG_DISPLAY_ENABLE, enabled ? 1 : 0, 0/*arg2 ignored*/, rcd));
+            }
+        }
+
         public void seekTo(int generationId, long timeMs) {
             // only post messages, we can't block here
             if (mEventHandler != null) {
@@ -1227,6 +1236,7 @@ public class RemoteControlClient
     private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12;
     private final static int MSG_UPDATE_METADATA = 13;
     private final static int MSG_REQUEST_METADATA_ARTWORK = 14;
+    private final static int MSG_DISPLAY_ENABLE = 15;
 
     private class EventHandler extends Handler {
         public EventHandler(RemoteControlClient rcc, Looper looper) {
@@ -1290,6 +1300,9 @@ public class RemoteControlClient
                 case MSG_UPDATE_METADATA:
                     onUpdateMetadata(msg.arg1, msg.arg2, msg.obj);
                     break;
+                case MSG_DISPLAY_ENABLE:
+                    onDisplayEnable((IRemoteControlDisplay)msg.obj, msg.arg1 == 1);
+                    break;
                 default:
                     Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
             }
@@ -1315,13 +1328,15 @@ public class RemoteControlClient
             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
             while (displayIterator.hasNext()) {
                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
-                try {
-                    di.mRcDisplay.setPlaybackState(mInternalClientGenId,
-                            mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
-                            mPlaybackSpeed);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
-                    displayIterator.remove();
+                if (di.mEnabled) {
+                    try {
+                        di.mRcDisplay.setPlaybackState(mInternalClientGenId,
+                                mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
+                                mPlaybackSpeed);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
+                        displayIterator.remove();
+                    }
                 }
             }
         }
@@ -1341,11 +1356,13 @@ public class RemoteControlClient
             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
             while (displayIterator.hasNext()) {
                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
-                try {
-                    di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
-                    displayIterator.remove();
+                if (di.mEnabled) {
+                    try {
+                        di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error in setMetadata(), dead display " + di.mRcDisplay, e);
+                        displayIterator.remove();
+                    }
                 }
             }
         }
@@ -1367,13 +1384,15 @@ public class RemoteControlClient
             final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
             while (displayIterator.hasNext()) {
                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
-                try {
-                    di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
-                            mTransportControlFlags, mPlaybackPositionCapabilities);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
-                            e);
-                    displayIterator.remove();
+                if (di.mEnabled) {
+                    try {
+                        di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
+                                mTransportControlFlags, mPlaybackPositionCapabilities);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error in setTransportControlFlags(), dead display " + di.mRcDisplay,
+                                e);
+                        displayIterator.remove();
+                    }
                 }
             }
         }
@@ -1438,12 +1457,14 @@ public class RemoteControlClient
             while (displayIterator.hasNext()) {
                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
                 try {
-                    if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
-                        Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
-                                di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
-                        di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
-                    } else {
-                        di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+                    if (di.mEnabled) {
+                        if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
+                            Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
+                                    di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
+                            di.mRcDisplay.setAllMetadata(mInternalClientGenId, mMetadata, artwork);
+                        } else {
+                            di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
+                        }
                     }
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
@@ -1578,8 +1599,10 @@ public class RemoteControlClient
                         ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
                     di.mArtworkExpectedWidth = w;
                     di.mArtworkExpectedHeight = h;
-                    if (!sendArtworkToDisplay(di)) {
-                        displayIterator.remove();
+                    if (di.mEnabled) {
+                        if (!sendArtworkToDisplay(di)) {
+                            displayIterator.remove();
+                        }
                     }
                     break;
                 }
@@ -1597,11 +1620,13 @@ public class RemoteControlClient
             //  that gets upated, and whether the list has one entry that wants position sync
             while (displayIterator.hasNext()) {
                 final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
-                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
-                    di.mWantsPositionSync = wantsSync;
-                }
-                if (di.mWantsPositionSync) {
-                    newNeedsPositionSync = true;
+                if (di.mEnabled) {
+                    if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+                        di.mWantsPositionSync = wantsSync;
+                    }
+                    if (di.mWantsPositionSync) {
+                        newNeedsPositionSync = true;
+                    }
                 }
             }
             mNeedsPositionSync = newNeedsPositionSync;
@@ -1612,6 +1637,19 @@ public class RemoteControlClient
         }
     }
 
+    /** pre-condition rcd != null */
+    private void onDisplayEnable(IRemoteControlDisplay rcd, boolean enable) {
+        synchronized(mCacheLock) {
+            final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
+            while (displayIterator.hasNext()) {
+                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+                if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+                    di.mEnabled = enable;
+                }
+            }
+        }
+    }
+
     private void onSeekTo(int generationId, long timeMs) {
         synchronized (mCacheLock) {
             if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
index 22f6343..c6d6296 100644 (file)
@@ -42,14 +42,14 @@ import android.view.KeyEvent;
  * <p>
  * A RemoteController shall be registered through
  * {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send
- * media event updates to the listener set in
- * {@link #setOnClientUpdateListener(OnClientUpdateListener)}. This listener is a subclass of
- * the {@link OnClientUpdateListener} abstract class. Override its methods to receive the
- * information published by the active {@link RemoteControlClient} instances.
- * By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for album
- * art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well.
+ * media event updates to the {@link OnClientUpdateListener} listener set in the class constructor.
+ * Implement the methods of the interface to receive the information published by the active
+ * {@link RemoteControlClient} instances.
+ * <br>By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for
+ * album art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well.
  * <p>
- * Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
+ * Registration requires the {@link OnClientUpdateListener} listener to be one of the enabled
+ * notification listeners (see {@link android.service.notification.NotificationListenerService}).
  */
 public final class RemoteController
 {
@@ -77,30 +77,39 @@ public final class RemoteController
     private PendingIntent mClientPendingIntentCurrent;
     private OnClientUpdateListener mOnClientUpdateListener;
     private PlaybackInfo mLastPlaybackInfo;
-    private int mLastTransportControlFlags = TRANSPORT_UNKNOWN;
     private int mArtworkWidth = -1;
     private int mArtworkHeight = -1;
+    private boolean mEnabled = true;
 
     /**
      * Class constructor.
-     * @param context non-null the {@link Context}, must be non-null
-     * @throws java.lang.IllegalArgumentException
+     * @param context the {@link Context}, must be non-null.
+     * @param updateListener the listener to be called whenever new client information is available,
+     *     must be non-null.
+     * @throws IllegalArgumentException
      */
-    public RemoteController(Context context) throws IllegalArgumentException {
-        this(context, null);
+    public RemoteController(Context context, OnClientUpdateListener updateListener)
+            throws IllegalArgumentException {
+        this(context, updateListener, null);
     }
 
     /**
      * Class constructor.
+     * @param context the {@link Context}, must be non-null.
+     * @param updateListener the listener to be called whenever new client information is available,
+     *     must be non-null.
      * @param looper the {@link Looper} on which to run the event loop,
      *     or null to use the current thread's looper.
-     * @param context the {@link Context}, must be non-null
      * @throws java.lang.IllegalArgumentException
      */
-    public RemoteController(Context context, Looper looper) throws IllegalArgumentException {
+    public RemoteController(Context context, OnClientUpdateListener updateListener, Looper looper)
+            throws IllegalArgumentException {
         if (context == null) {
             throw new IllegalArgumentException("Invalid null Context");
         }
+        if (updateListener == null) {
+            throw new IllegalArgumentException("Invalid null OnClientUpdateListener");
+        }
         if (looper != null) {
             mEventHandler = new EventHandler(this, looper);
         } else {
@@ -111,6 +120,7 @@ public final class RemoteController
                 throw new IllegalArgumentException("Calling thread not associated with a looper");
             }
         }
+        mOnClientUpdateListener = updateListener;
         mContext = context;
         mRcd = new RcDisplay();
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -125,31 +135,31 @@ public final class RemoteController
 
 
     /**
-     * An abstract class definition for the callbacks to be invoked whenever media events, metadata
+     * Interface definition for the callbacks to be invoked whenever media events, metadata
      * and playback status are available.
      */
-    public static abstract class OnClientUpdateListener {
+    public interface OnClientUpdateListener {
         /**
-         * The method called whenever all information previously received through the other
+         * Called whenever all information, previously received through the other
          * methods of the listener, is no longer valid and is about to be refreshed.
          * This is typically called whenever a new {@link RemoteControlClient} has been selected
          * by the system to have its media information published.
          * @param clearing true if there is no selected RemoteControlClient and no information
          *     is available.
          */
-        public void onClientChange(boolean clearing) { }
+        public void onClientChange(boolean clearing);
 
         /**
-         * The method called whenever the playback state has changed.
+         * Called whenever the playback state has changed.
          * It is called when no information is known about the playback progress in the media and
          * the playback speed.
          * @param state one of the playback states authorized
          *     in {@link RemoteControlClient#setPlaybackState(int)}.
          */
-        public void onClientPlaybackStateUpdate(int state) { }
+        public void onClientPlaybackStateUpdate(int state);
         /**
-         * The method called whenever the playback state has changed, and playback position and
-         * speed are known.
+         * Called whenever the playback state has changed, and playback position
+         * and speed are known.
          * @param state one of the playback states authorized
          *     in {@link RemoteControlClient#setPlaybackState(int)}.
          * @param stateChangeTimeMs the system time at which the state change was reported,
@@ -161,15 +171,15 @@ public final class RemoteController
          *    playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}).
          */
         public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
-                long currentPosMs, float speed) { }
+                long currentPosMs, float speed);
         /**
-         * The method called whenever the transport control flags have changed.
+         * Called whenever the transport control flags have changed.
          * @param transportControlFlags one of the flags authorized
          *     in {@link RemoteControlClient#setTransportControlFlags(int)}.
          */
-        public void onClientTransportControlUpdate(int transportControlFlags) { }
+        public void onClientTransportControlUpdate(int transportControlFlags);
         /**
-         * The method called whenever new metadata is available.
+         * Called whenever new metadata is available.
          * See the {@link MediaMetadataEditor#putLong(int, long)},
          *  {@link MediaMetadataEditor#putString(int, String)},
          *  {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and
@@ -177,39 +187,9 @@ public final class RemoteController
          *  can be queried.
          * @param metadataEditor the container of the new metadata.
          */
-        public void onClientMetadataUpdate(MetadataEditor metadataEditor) { }
+        public void onClientMetadataUpdate(MetadataEditor metadataEditor);
     };
 
-    /**
-     * Sets the listener to be called whenever new client information is available.
-     * This method can only be called on a registered RemoteController.
-     * @param l the update listener to be called.
-     */
-    public void setOnClientUpdateListener(OnClientUpdateListener l) {
-        synchronized(mInfoLock) {
-            mOnClientUpdateListener = l;
-            if (!mIsRegistered) {
-                // since the object is not registered, it hasn't received any information from
-                // RemoteControlClients yet, so we can exit here.
-                return;
-            }
-            if (mLastPlaybackInfo != null) {
-                sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
-                        mClientGenerationIdCurrent /*arg1*/, 0,
-                        mLastPlaybackInfo /*obj*/, 0 /*delay*/);
-            }
-            if (mLastTransportControlFlags != TRANSPORT_UNKNOWN) {
-                sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
-                        mClientGenerationIdCurrent /*arg1*/, mLastTransportControlFlags /*arg2*/,
-                        null /*obj*/, 0 /*delay*/);
-            }
-            if (mMetadataEditor != null) {
-                sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
-                        mClientGenerationIdCurrent /*arg1*/, 0 /*arg2*/,
-                        mMetadataEditor /*obj*/, 0 /*delay*/);
-            }
-        }
-    }
 
     /**
      * @hide
@@ -256,6 +236,7 @@ public final class RemoteController
         return -1;
     }
 
+
     /**
      * Send a simulated key event for a media button to be received by the current client.
      * To simulate a key press, you must first send a KeyEvent built with
@@ -280,17 +261,22 @@ public final class RemoteController
      *     {@link KeyEvent#KEYCODE_MEDIA_CLOSE},
      *     {@link KeyEvent#KEYCODE_MEDIA_EJECT},
      *     or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}.
+     * @return true if the event was successfully sent, false otherwise.
+     * @throws IllegalArgumentException
      */
-    public int sendMediaKeyEvent(KeyEvent keyEvent) {
+    public boolean sendMediaKeyEvent(KeyEvent keyEvent) throws IllegalArgumentException {
         if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) {
-            Log.e(TAG, "Cannot use sendMediaKeyEvent() for a non-media key event");
-            return ERROR_BAD_VALUE;
+            throw new IllegalArgumentException("not a media key event");
         }
         final PendingIntent pi;
         synchronized(mInfoLock) {
             if (!mIsRegistered) {
                 Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
-                return ERROR;
+                return false;
+            }
+            if (!mEnabled) {
+                Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController");
+                return false;
             }
             pi = mClientPendingIntentCurrent;
         }
@@ -301,47 +287,37 @@ public final class RemoteController
                 pi.send(mContext, 0, intent);
             } catch (CanceledException e) {
                 Log.e(TAG, "Error sending intent for media button down: ", e);
-                return ERROR;
+                return false;
             }
         } else {
             Log.i(TAG, "No-op when sending key click, no receiver right now");
-            return ERROR;
+            return false;
         }
-        return SUCCESS;
+        return true;
     }
 
 
-    // Error codes
-    /**
-     * Successful operation.
-     */
-    public  static final int SUCCESS            = 0;
-    /**
-     * Unspecified error.
-     */
-    public  static final int ERROR              = -1;
-    /**
-     * Operation failed due to bad parameter value.
-     */
-    public  static final int ERROR_BAD_VALUE    = -2;
-
-
     /**
      * Sets the new playback position.
      * This method can only be called on a registered RemoteController.
      * @param timeMs a 0 or positive value for the new playback position, expressed in ms.
-     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
+     * @return true if the command to set the playback position was successfully sent.
+     * @throws IllegalArgumentException
      */
-    public int seekTo(long timeMs) {
+    public boolean seekTo(long timeMs) throws IllegalArgumentException {
+        if (!mEnabled) {
+            Log.e(TAG, "Cannot use seekTo() from a disabled RemoteController");
+            return false;
+        }
         if (timeMs < 0) {
-            return ERROR_BAD_VALUE;
+            throw new IllegalArgumentException("illegal negative time value");
         }
         final int genId;
         synchronized (mGenLock) {
             genId = mClientGenerationIdCurrent;
         }
         mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
-        return SUCCESS;
+        return true;
     }
 
 
@@ -350,9 +326,11 @@ public final class RemoteController
      * @param wantBitmap
      * @param width
      * @param height
-     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
+     * @return true if successful
+     * @throws IllegalArgumentException
      */
-    public int setArtworkConfiguration(boolean wantBitmap, int width, int height) {
+    public boolean setArtworkConfiguration(boolean wantBitmap, int width, int height)
+            throws IllegalArgumentException {
         synchronized (mInfoLock) {
             if (wantBitmap) {
                 if ((width > 0) && (height > 0)) {
@@ -361,8 +339,7 @@ public final class RemoteController
                     mArtworkWidth = width;
                     mArtworkHeight = height;
                 } else {
-                    Log.e(TAG, "Invalid dimensions");
-                    return ERROR_BAD_VALUE;
+                    throw new IllegalArgumentException("Invalid dimensions");
                 }
             } else {
                 mArtworkWidth = -1;
@@ -375,7 +352,7 @@ public final class RemoteController
               //    RemoteController.getArtworkSize() when AudioManager.registerRemoteController()
               //    is called.
         }
-        return SUCCESS;
+        return true;
     }
 
     /**
@@ -383,17 +360,18 @@ public final class RemoteController
      * No bitmaps will be received unless this has been specified.
      * @param width the maximum width in pixels
      * @param height  the maximum height in pixels
-     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
+     * @return true if the artwork dimension was successfully set.
+     * @throws IllegalArgumentException
      */
-    public int setArtworkConfiguration(int width, int height) {
+    public boolean setArtworkConfiguration(int width, int height) throws IllegalArgumentException {
         return setArtworkConfiguration(true, width, height);
     }
 
     /**
      * Prevents this RemoteController from receiving artwork images.
-     * @return {@link #SUCCESS}, {@link #ERROR}
+     * @return true if receiving artwork images was successfully disabled.
      */
-    public int clearArtworkConfiguration() {
+    public boolean clearArtworkConfiguration() {
         return setArtworkConfiguration(false, -1, -1);
     }
 
@@ -420,20 +398,20 @@ public final class RemoteController
      * Set the playback position synchronization mode.
      * Must be called on a registered RemoteController.
      * @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK}
-     * @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
+     * @return true if the synchronization mode was successfully set.
+     * @throws IllegalArgumentException
      */
-    public int setSynchronizationMode(int sync) {
+    public boolean setSynchronizationMode(int sync) throws IllegalArgumentException {
         if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) {
-            Log.e(TAG, "Unknown synchronization mode");
-            return ERROR_BAD_VALUE;
+            throw new IllegalArgumentException("Unknown synchronization mode " + sync);
         }
         if (!mIsRegistered) {
             Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
-            return ERROR;
+            return false;
         }
         mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
                 POSITION_SYNCHRONIZATION_CHECK == sync);
-        return SUCCESS;
+        return true;
     }
 
 
@@ -548,6 +526,11 @@ public final class RemoteController
             }
         }
 
+        public void setEnabled(boolean enabled) {
+            sendMsg(mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE,
+                    enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/);
+        }
+
         public void setPlaybackState(int genId, int state,
                 long stateChangeTimeMs, long currentPosMs, float speed) {
             if (DEBUG) {
@@ -642,6 +625,7 @@ public final class RemoteController
     private final static int MSG_NEW_TRANSPORT_INFO = 2;
     private final static int MSG_NEW_METADATA       = 3; // msg always has non-null obj parameter
     private final static int MSG_CLIENT_CHANGE      = 4;
+    private final static int MSG_DISPLAY_ENABLE     = 5;
 
     private class EventHandler extends Handler {
 
@@ -667,6 +651,9 @@ public final class RemoteController
                 case MSG_CLIENT_CHANGE:
                     onClientChange(msg.arg1, msg.arg2 == 1);
                     break;
+                case MSG_DISPLAY_ENABLE:
+                    onDisplayEnable(msg.arg1 == 1);
+                    break;
                 default:
                     Log.e(TAG, "unknown event " + msg.what);
             }
@@ -735,7 +722,6 @@ public final class RemoteController
         final OnClientUpdateListener l;
         synchronized(mInfoLock) {
             l = mOnClientUpdateListener;
-            mLastTransportControlFlags = transportControlFlags;
         }
         if (l != null) {
             l.onClientTransportControlUpdate(transportControlFlags);
@@ -797,6 +783,11 @@ public final class RemoteController
         }
     }
 
+    private void onDisplayEnable(boolean enabled) {
+        synchronized(mInfoLock) {
+            mEnabled = enabled;
+        }
+    }
 
     //==================================================
     private static class PlaybackInfo {
@@ -818,7 +809,7 @@ public final class RemoteController
      * Used by AudioManager to mark this instance as registered.
      * @param registered
      */
-    protected void setIsRegistered(boolean registered) {
+    void setIsRegistered(boolean registered) {
         synchronized (mInfoLock) {
             mIsRegistered = registered;
         }
@@ -829,7 +820,7 @@ public final class RemoteController
      * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
      * @return
      */
-    protected RcDisplay getRcDisplay() {
+    RcDisplay getRcDisplay() {
         return mRcd;
     }
 
@@ -838,11 +829,19 @@ public final class RemoteController
      * Used by AudioManager to read the current artwork dimension
      * @return array containing width (index 0) and height (index 1) of currently set artwork size
      */
-    protected int[] getArtworkSize() {
+    int[] getArtworkSize() {
         synchronized (mInfoLock) {
             int[] size = { mArtworkWidth, mArtworkHeight };
             return size;
         }
     }
 
+    /**
+     * @hide
+     * Used by AudioManager to access user listener receiving the client update notifications
+     * @return
+     */
+    OnClientUpdateListener getUpdateListener() {
+        return mOnClientUpdateListener;
+    }
 }
index 83d8ab1..e8c4c23 100644 (file)
@@ -203,8 +203,7 @@ public class KeyguardTransportControlView extends FrameLayout {
         if (DEBUG) Log.v(TAG, "Create TCV " + this);
         mAudioManager = new AudioManager(mContext);
         mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
-        mRemoteController = new RemoteController(context);
-        mRemoteController.setOnClientUpdateListener(mRCClientUpdateListener);
+        mRemoteController = new RemoteController(context, mRCClientUpdateListener);
 
         final DisplayMetrics dm = context.getResources().getDisplayMetrics();
         final int dim = Math.max(dm.widthPixels, dm.heightPixels);
index 734f517..76deb77 100644 (file)
@@ -232,6 +232,10 @@ public class KeyguardUpdateMonitor {
 
         }
 
+        public void setEnabled(boolean enabled) {
+            // no-op: this RemoteControlDisplay is not subject to being disabled.
+        }
+
         public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
                 boolean clearing) throws RemoteException {
             Message msg = mHandler.obtainMessage(MSG_SET_CURRENT_CLIENT_ID,