Merge "Also rely on enabled notification listeners for RemoteController registration" into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
bacb5422bc
@ -13359,34 +13359,29 @@ package android.media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class RemoteController {
|
public final class RemoteController {
|
||||||
ctor public RemoteController(android.content.Context) throws java.lang.IllegalArgumentException;
|
ctor public RemoteController(android.content.Context, android.media.RemoteController.OnClientUpdateListener) throws java.lang.IllegalArgumentException;
|
||||||
ctor public RemoteController(android.content.Context, android.os.Looper) throws java.lang.IllegalArgumentException;
|
ctor public RemoteController(android.content.Context, android.media.RemoteController.OnClientUpdateListener, android.os.Looper) throws java.lang.IllegalArgumentException;
|
||||||
method public int clearArtworkConfiguration();
|
method public boolean clearArtworkConfiguration();
|
||||||
method public android.media.RemoteController.MetadataEditor editMetadata();
|
method public android.media.RemoteController.MetadataEditor editMetadata();
|
||||||
method public long getEstimatedMediaPosition();
|
method public long getEstimatedMediaPosition();
|
||||||
method public int seekTo(long);
|
method public boolean seekTo(long) throws java.lang.IllegalArgumentException;
|
||||||
method public int sendMediaKeyEvent(android.view.KeyEvent);
|
method public boolean sendMediaKeyEvent(android.view.KeyEvent) throws java.lang.IllegalArgumentException;
|
||||||
method public int setArtworkConfiguration(int, int);
|
method public boolean setArtworkConfiguration(int, int) throws java.lang.IllegalArgumentException;
|
||||||
method public void setOnClientUpdateListener(android.media.RemoteController.OnClientUpdateListener);
|
method public boolean setSynchronizationMode(int) throws java.lang.IllegalArgumentException;
|
||||||
method public int setSynchronizationMode(int);
|
|
||||||
field public static final int ERROR = -1; // 0xffffffff
|
|
||||||
field public static final int ERROR_BAD_VALUE = -2; // 0xfffffffe
|
|
||||||
field public static final int POSITION_SYNCHRONIZATION_CHECK = 1; // 0x1
|
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 POSITION_SYNCHRONIZATION_NONE = 0; // 0x0
|
||||||
field public static final int SUCCESS = 0; // 0x0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RemoteController.MetadataEditor extends android.media.MediaMetadataEditor {
|
public class RemoteController.MetadataEditor extends android.media.MediaMetadataEditor {
|
||||||
method public synchronized void apply();
|
method public synchronized void apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract class RemoteController.OnClientUpdateListener {
|
public static abstract interface RemoteController.OnClientUpdateListener {
|
||||||
ctor public RemoteController.OnClientUpdateListener();
|
method public abstract void onClientChange(boolean);
|
||||||
method public void onClientChange(boolean);
|
method public abstract void onClientMetadataUpdate(android.media.RemoteController.MetadataEditor);
|
||||||
method public void onClientMetadataUpdate(android.media.RemoteController.MetadataEditor);
|
method public abstract void onClientPlaybackStateUpdate(int);
|
||||||
method public void onClientPlaybackStateUpdate(int);
|
method public abstract void onClientPlaybackStateUpdate(int, long, long, float);
|
||||||
method public void onClientPlaybackStateUpdate(int, long, long, float);
|
method public abstract void onClientTransportControlUpdate(int);
|
||||||
method public void onClientTransportControlUpdate(int);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ResourceBusyException extends android.media.MediaDrmException {
|
public final class ResourceBusyException extends android.media.MediaDrmException {
|
||||||
|
@ -139,6 +139,11 @@ public class Media extends BaseCommand {
|
|||||||
+ " intent=" + clientMediaIntent + " clearing=" + clearing);
|
+ " intent=" + clientMediaIntent + " clearing=" + clearing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
System.out.println("New enable state= " + (enabled ? "enabled" : "disabled"));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
|
public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
|
||||||
long currentPosMs, float speed) {
|
long currentPosMs, float speed) {
|
||||||
|
@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.media.RemoteController.OnClientUpdateListener;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -2270,7 +2271,9 @@ public class AudioManager {
|
|||||||
* Registers a {@link RemoteController} instance for it to receive media metadata updates
|
* Registers a {@link RemoteController} instance for it to receive media metadata updates
|
||||||
* and playback state information from applications using {@link RemoteControlClient}, and
|
* and playback state information from applications using {@link RemoteControlClient}, and
|
||||||
* control their playback.
|
* 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.
|
* @param rctlr the object to register.
|
||||||
* @return true if the {@link RemoteController} was successfully registered, false if an
|
* @return true if the {@link RemoteController} was successfully registered, false if an
|
||||||
* error occurred, due to an internal system error, or insufficient permissions.
|
* error occurred, due to an internal system error, or insufficient permissions.
|
||||||
@ -2280,14 +2283,17 @@ public class AudioManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
IAudioService service = getService();
|
IAudioService service = getService();
|
||||||
|
final RemoteController.OnClientUpdateListener l = rctlr.getUpdateListener();
|
||||||
|
final ComponentName listenerComponent = new ComponentName(mContext, l.getClass());
|
||||||
try {
|
try {
|
||||||
int[] artworkDimensions = rctlr.getArtworkSize();
|
int[] artworkDimensions = rctlr.getArtworkSize();
|
||||||
boolean reg = service.registerRemoteControlDisplay(rctlr.getRcDisplay(),
|
boolean reg = service.registerRemoteController(rctlr.getRcDisplay(),
|
||||||
artworkDimensions[0]/*w*/, artworkDimensions[1]/*h*/);
|
artworkDimensions[0]/*w*/, artworkDimensions[1]/*h*/,
|
||||||
|
listenerComponent);
|
||||||
rctlr.setIsRegistered(reg);
|
rctlr.setIsRegistered(reg);
|
||||||
return reg;
|
return reg;
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
|
Log.e(TAG, "Dead object in registerRemoteController " + e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2318,6 +2324,7 @@ public class AudioManager {
|
|||||||
* artwork size directly, or
|
* artwork size directly, or
|
||||||
* {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork
|
* {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork
|
||||||
* is not yet needed.
|
* is not yet needed.
|
||||||
|
* <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
|
||||||
* @param rcd the IRemoteControlDisplay
|
* @param rcd the IRemoteControlDisplay
|
||||||
*/
|
*/
|
||||||
public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
|
public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
|
||||||
@ -2328,6 +2335,7 @@ public class AudioManager {
|
|||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
* Registers a remote control display that will be sent information by remote control clients.
|
* 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 rcd
|
||||||
* @param w the maximum width of the expected bitmap. Negative values indicate it is
|
* @param w the maximum width of the expected bitmap. Negative values indicate it is
|
||||||
* useless to send artwork.
|
* useless to send artwork.
|
||||||
|
@ -48,6 +48,7 @@ import android.content.res.XmlResourceParser;
|
|||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.media.MediaPlayer.OnCompletionListener;
|
import android.media.MediaPlayer.OnCompletionListener;
|
||||||
import android.media.MediaPlayer.OnErrorListener;
|
import android.media.MediaPlayer.OnErrorListener;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -87,6 +88,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -4162,17 +4164,13 @@ public class AudioService extends IAudioService.Stub {
|
|||||||
//==========================================================================================
|
//==========================================================================================
|
||||||
// RemoteControlDisplay / RemoteControlClient / Remote info
|
// RemoteControlDisplay / RemoteControlClient / Remote info
|
||||||
//==========================================================================================
|
//==========================================================================================
|
||||||
public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
|
public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
|
||||||
if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
|
ComponentName listenerComp) {
|
||||||
android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
|
return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
|
||||||
|
return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
|
public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
|
||||||
|
@ -133,6 +133,8 @@ interface IAudioService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register an IRemoteControlDisplay.
|
* 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
|
* 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.
|
* at the top of the stack to update the new display with its information.
|
||||||
* @param rcd the IRemoteControlDisplay to register. No effect if null.
|
* @param rcd the IRemoteControlDisplay to register. No effect if null.
|
||||||
@ -142,6 +144,16 @@ interface IAudioService {
|
|||||||
* display doesn't need to receive artwork.
|
* display doesn't need to receive artwork.
|
||||||
*/
|
*/
|
||||||
boolean registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
|
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.
|
* Unregister an IRemoteControlDisplay.
|
||||||
* No effect if the IRemoteControlDisplay hasn't been successfully registered.
|
* No effect if the IRemoteControlDisplay hasn't been successfully registered.
|
||||||
|
@ -56,6 +56,7 @@ oneway interface IRemoteControlClient
|
|||||||
void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
|
void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
|
||||||
void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h);
|
void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h);
|
||||||
void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync);
|
void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync);
|
||||||
|
void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled);
|
||||||
void seekTo(int clientGeneration, long timeMs);
|
void seekTo(int clientGeneration, long timeMs);
|
||||||
void updateMetadata(int clientGeneration, int key, in Rating value);
|
void updateMetadata(int clientGeneration, int key, in Rating value);
|
||||||
}
|
}
|
@ -40,6 +40,12 @@ oneway interface IRemoteControlDisplay
|
|||||||
void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
|
void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
|
||||||
boolean clearing);
|
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.
|
* Sets the playback information (state, position and speed) of a client.
|
||||||
* @param generationId the current generation ID as known by this client
|
* @param generationId the current generation ID as known by this client
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package android.media;
|
package android.media;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.ActivityManager;
|
||||||
import android.app.AppOpsManager;
|
import android.app.AppOpsManager;
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@ -30,6 +31,8 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -45,6 +48,7 @@ import android.speech.RecognizerIntent;
|
|||||||
import android.telephony.PhoneStateListener;
|
import android.telephony.PhoneStateListener;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Slog;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
@ -78,6 +82,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
private final AppOpsManager mAppOps;
|
private final AppOpsManager mAppOps;
|
||||||
private final KeyguardManager mKeyguardManager;
|
private final KeyguardManager mKeyguardManager;
|
||||||
private final AudioService mAudioService;
|
private final AudioService mAudioService;
|
||||||
|
private final NotificationListenerObserver mNotifListenerObserver;
|
||||||
|
|
||||||
protected MediaFocusControl(Looper looper, Context cntxt,
|
protected MediaFocusControl(Looper looper, Context cntxt,
|
||||||
VolumeController volumeCtrl, AudioService as) {
|
VolumeController volumeCtrl, AudioService as) {
|
||||||
@ -111,6 +116,7 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
|
mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
|
||||||
mKeyguardManager =
|
mKeyguardManager =
|
||||||
(KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
|
(KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
|
||||||
|
mNotifListenerObserver = new NotificationListenerObserver();
|
||||||
|
|
||||||
mHasRemotePlayback = false;
|
mHasRemotePlayback = false;
|
||||||
mMainRemoteIsActive = false;
|
mMainRemoteIsActive = false;
|
||||||
@ -124,6 +130,182 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
dumpRCDList(pw);
|
dumpRCDList(pw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==========================================================================================
|
||||||
|
// 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
|
// 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_SEEK_REQUEST = 8;
|
||||||
private static final int MSG_RCC_UPDATE_METADATA = 9;
|
private static final int MSG_RCC_UPDATE_METADATA = 9;
|
||||||
private static final int MSG_RCDISPLAY_INIT_INFO = 10;
|
private static final int MSG_RCDISPLAY_INIT_INFO = 10;
|
||||||
|
private static final int MSG_REEVALUATE_RCD = 11;
|
||||||
|
|
||||||
// sendMsg() flags
|
// sendMsg() flags
|
||||||
/** If the msg is already queued, replace it with this one. */
|
/** 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*/,
|
onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
|
||||||
msg.arg1/*w*/, msg.arg2/*h*/);
|
msg.arg1/*w*/, msg.arg2/*h*/);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MSG_REEVALUATE_RCD:
|
||||||
|
onReevaluateRemoteControlDisplays();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1193,8 +1380,9 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
final DisplayInfoForServer di = (DisplayInfoForServer) displayIterator.next();
|
||||||
pw.println(" IRCD: " + di.mRcDisplay +
|
pw.println(" IRCD: " + di.mRcDisplay +
|
||||||
" -- w:" + di.mArtworkExpectedWidth +
|
" -- w:" + di.mArtworkExpectedWidth +
|
||||||
" -- h:" + di.mArtworkExpectedHeight+
|
" -- h:" + di.mArtworkExpectedHeight +
|
||||||
" -- wantsPosSync:" + di.mWantsPositionSync);
|
" -- wantsPosSync:" + di.mWantsPositionSync +
|
||||||
|
" -- " + (di.mEnabled ? "enabled" : "disabled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1834,11 +2022,13 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
*/
|
*/
|
||||||
private class DisplayInfoForServer implements IBinder.DeathRecipient {
|
private class DisplayInfoForServer implements IBinder.DeathRecipient {
|
||||||
/** may never be null */
|
/** may never be null */
|
||||||
private IRemoteControlDisplay mRcDisplay;
|
private final IRemoteControlDisplay mRcDisplay;
|
||||||
private IBinder mRcDisplayBinder;
|
private final IBinder mRcDisplayBinder;
|
||||||
private int mArtworkExpectedWidth = -1;
|
private int mArtworkExpectedWidth = -1;
|
||||||
private int mArtworkExpectedHeight = -1;
|
private int mArtworkExpectedHeight = -1;
|
||||||
private boolean mWantsPositionSync = false;
|
private boolean mWantsPositionSync = false;
|
||||||
|
private ComponentName mClientNotifListComp;
|
||||||
|
private boolean mEnabled = true;
|
||||||
|
|
||||||
public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
|
public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
|
||||||
if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + 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
|
* Is the remote control display interface already registered
|
||||||
* @param rcd
|
* @param rcd
|
||||||
@ -1937,8 +2144,11 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
* display doesn't need to receive artwork.
|
* display doesn't need to receive artwork.
|
||||||
* @param h the maximum height of the expected bitmap. Negative or zero values indicate this
|
* @param h the maximum height of the expected bitmap. Negative or zero values indicate this
|
||||||
* display doesn't need to receive artwork.
|
* 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+")");
|
if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
|
||||||
synchronized(mAudioFocusLock) {
|
synchronized(mAudioFocusLock) {
|
||||||
synchronized(mRCStack) {
|
synchronized(mRCStack) {
|
||||||
@ -1946,6 +2156,8 @@ public class MediaFocusControl implements OnFinished {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
|
DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
|
||||||
|
di.mEnabled = true;
|
||||||
|
di.mClientNotifListComp = listenerComp;
|
||||||
if (!di.init()) {
|
if (!di.init()) {
|
||||||
if (DEBUG_RC) Log.e(TAG, " error registering RCD");
|
if (DEBUG_RC) Log.e(TAG, " error registering RCD");
|
||||||
return;
|
return;
|
||||||
|
@ -1049,6 +1049,7 @@ public class RemoteControlClient
|
|||||||
private int mArtworkExpectedWidth;
|
private int mArtworkExpectedWidth;
|
||||||
private int mArtworkExpectedHeight;
|
private int mArtworkExpectedHeight;
|
||||||
private boolean mWantsPositionSync = false;
|
private boolean mWantsPositionSync = false;
|
||||||
|
private boolean mEnabled = true;
|
||||||
|
|
||||||
DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
|
DisplayInfoForClient(IRemoteControlDisplay rcd, int w, int h) {
|
||||||
mRcDisplay = rcd;
|
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) {
|
public void seekTo(int generationId, long timeMs) {
|
||||||
// only post messages, we can't block here
|
// only post messages, we can't block here
|
||||||
if (mEventHandler != null) {
|
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_DISPLAY_WANTS_POS_SYNC = 12;
|
||||||
private final static int MSG_UPDATE_METADATA = 13;
|
private final static int MSG_UPDATE_METADATA = 13;
|
||||||
private final static int MSG_REQUEST_METADATA_ARTWORK = 14;
|
private final static int MSG_REQUEST_METADATA_ARTWORK = 14;
|
||||||
|
private final static int MSG_DISPLAY_ENABLE = 15;
|
||||||
|
|
||||||
private class EventHandler extends Handler {
|
private class EventHandler extends Handler {
|
||||||
public EventHandler(RemoteControlClient rcc, Looper looper) {
|
public EventHandler(RemoteControlClient rcc, Looper looper) {
|
||||||
@ -1290,6 +1300,9 @@ public class RemoteControlClient
|
|||||||
case MSG_UPDATE_METADATA:
|
case MSG_UPDATE_METADATA:
|
||||||
onUpdateMetadata(msg.arg1, msg.arg2, msg.obj);
|
onUpdateMetadata(msg.arg1, msg.arg2, msg.obj);
|
||||||
break;
|
break;
|
||||||
|
case MSG_DISPLAY_ENABLE:
|
||||||
|
onDisplayEnable((IRemoteControlDisplay)msg.obj, msg.arg1 == 1);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
|
Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
|
||||||
}
|
}
|
||||||
@ -1315,6 +1328,7 @@ public class RemoteControlClient
|
|||||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||||
|
if (di.mEnabled) {
|
||||||
try {
|
try {
|
||||||
di.mRcDisplay.setPlaybackState(mInternalClientGenId,
|
di.mRcDisplay.setPlaybackState(mInternalClientGenId,
|
||||||
mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
|
mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
|
||||||
@ -1326,6 +1340,7 @@ public class RemoteControlClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendMetadata_syncCacheLock(IRemoteControlDisplay target) {
|
private void sendMetadata_syncCacheLock(IRemoteControlDisplay target) {
|
||||||
if (mCurrentClientGenId == mInternalClientGenId) {
|
if (mCurrentClientGenId == mInternalClientGenId) {
|
||||||
@ -1341,6 +1356,7 @@ public class RemoteControlClient
|
|||||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||||
|
if (di.mEnabled) {
|
||||||
try {
|
try {
|
||||||
di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
|
di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
@ -1350,6 +1366,7 @@ public class RemoteControlClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendTransportControlInfo_syncCacheLock(IRemoteControlDisplay target) {
|
private void sendTransportControlInfo_syncCacheLock(IRemoteControlDisplay target) {
|
||||||
if (mCurrentClientGenId == mInternalClientGenId) {
|
if (mCurrentClientGenId == mInternalClientGenId) {
|
||||||
@ -1367,6 +1384,7 @@ public class RemoteControlClient
|
|||||||
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||||
|
if (di.mEnabled) {
|
||||||
try {
|
try {
|
||||||
di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
|
di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
|
||||||
mTransportControlFlags, mPlaybackPositionCapabilities);
|
mTransportControlFlags, mPlaybackPositionCapabilities);
|
||||||
@ -1378,6 +1396,7 @@ public class RemoteControlClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) {
|
private void sendArtwork_syncCacheLock(IRemoteControlDisplay target, int w, int h) {
|
||||||
// FIXME modify to cache all requested sizes?
|
// FIXME modify to cache all requested sizes?
|
||||||
@ -1438,6 +1457,7 @@ public class RemoteControlClient
|
|||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||||
try {
|
try {
|
||||||
|
if (di.mEnabled) {
|
||||||
if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
|
if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
|
||||||
Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
|
Bitmap artwork = scaleBitmapIfTooBig(mOriginalArtwork,
|
||||||
di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
|
di.mArtworkExpectedWidth, di.mArtworkExpectedHeight);
|
||||||
@ -1445,6 +1465,7 @@ public class RemoteControlClient
|
|||||||
} else {
|
} else {
|
||||||
di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
|
di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
|
Log.e(TAG, "Error when setting metadata, dead display " + di.mRcDisplay, e);
|
||||||
displayIterator.remove();
|
displayIterator.remove();
|
||||||
@ -1578,9 +1599,11 @@ public class RemoteControlClient
|
|||||||
((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
|
((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
|
||||||
di.mArtworkExpectedWidth = w;
|
di.mArtworkExpectedWidth = w;
|
||||||
di.mArtworkExpectedHeight = h;
|
di.mArtworkExpectedHeight = h;
|
||||||
|
if (di.mEnabled) {
|
||||||
if (!sendArtworkToDisplay(di)) {
|
if (!sendArtworkToDisplay(di)) {
|
||||||
displayIterator.remove();
|
displayIterator.remove();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1597,6 +1620,7 @@ public class RemoteControlClient
|
|||||||
// that gets upated, and whether the list has one entry that wants position sync
|
// that gets upated, and whether the list has one entry that wants position sync
|
||||||
while (displayIterator.hasNext()) {
|
while (displayIterator.hasNext()) {
|
||||||
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
|
||||||
|
if (di.mEnabled) {
|
||||||
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
|
||||||
di.mWantsPositionSync = wantsSync;
|
di.mWantsPositionSync = wantsSync;
|
||||||
}
|
}
|
||||||
@ -1604,6 +1628,7 @@ public class RemoteControlClient
|
|||||||
newNeedsPositionSync = true;
|
newNeedsPositionSync = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mNeedsPositionSync = newNeedsPositionSync;
|
mNeedsPositionSync = newNeedsPositionSync;
|
||||||
if (oldNeedsPositionSync != mNeedsPositionSync) {
|
if (oldNeedsPositionSync != mNeedsPositionSync) {
|
||||||
// update needed?
|
// update needed?
|
||||||
@ -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) {
|
private void onSeekTo(int generationId, long timeMs) {
|
||||||
synchronized (mCacheLock) {
|
synchronized (mCacheLock) {
|
||||||
if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
|
if ((mCurrentClientGenId == generationId) && (mPositionUpdateListener != null)) {
|
||||||
|
@ -42,14 +42,14 @@ import android.view.KeyEvent;
|
|||||||
* <p>
|
* <p>
|
||||||
* A RemoteController shall be registered through
|
* A RemoteController shall be registered through
|
||||||
* {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send
|
* {@link AudioManager#registerRemoteController(RemoteController)} in order for the system to send
|
||||||
* media event updates to the listener set in
|
* media event updates to the {@link OnClientUpdateListener} listener set in the class constructor.
|
||||||
* {@link #setOnClientUpdateListener(OnClientUpdateListener)}. This listener is a subclass of
|
* Implement the methods of the interface to receive the information published by the active
|
||||||
* the {@link OnClientUpdateListener} abstract class. Override its methods to receive the
|
* {@link RemoteControlClient} instances.
|
||||||
* information published by the active {@link RemoteControlClient} instances.
|
* <br>By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for
|
||||||
* By default an {@link OnClientUpdateListener} implementation will not receive bitmaps for album
|
* album art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well.
|
||||||
* art. Use {@link #setArtworkConfiguration(int, int)} to receive images as well.
|
|
||||||
* <p>
|
* <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
|
public final class RemoteController
|
||||||
{
|
{
|
||||||
@ -77,30 +77,39 @@ public final class RemoteController
|
|||||||
private PendingIntent mClientPendingIntentCurrent;
|
private PendingIntent mClientPendingIntentCurrent;
|
||||||
private OnClientUpdateListener mOnClientUpdateListener;
|
private OnClientUpdateListener mOnClientUpdateListener;
|
||||||
private PlaybackInfo mLastPlaybackInfo;
|
private PlaybackInfo mLastPlaybackInfo;
|
||||||
private int mLastTransportControlFlags = TRANSPORT_UNKNOWN;
|
|
||||||
private int mArtworkWidth = -1;
|
private int mArtworkWidth = -1;
|
||||||
private int mArtworkHeight = -1;
|
private int mArtworkHeight = -1;
|
||||||
|
private boolean mEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class constructor.
|
* Class constructor.
|
||||||
* @param context non-null the {@link Context}, must be non-null
|
* @param context the {@link Context}, must be non-null.
|
||||||
* @throws java.lang.IllegalArgumentException
|
* @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 {
|
public RemoteController(Context context, OnClientUpdateListener updateListener)
|
||||||
this(context, null);
|
throws IllegalArgumentException {
|
||||||
|
this(context, updateListener, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class constructor.
|
* 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,
|
* @param looper the {@link Looper} on which to run the event loop,
|
||||||
* or null to use the current thread's looper.
|
* or null to use the current thread's looper.
|
||||||
* @param context the {@link Context}, must be non-null
|
|
||||||
* @throws java.lang.IllegalArgumentException
|
* @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) {
|
if (context == null) {
|
||||||
throw new IllegalArgumentException("Invalid null Context");
|
throw new IllegalArgumentException("Invalid null Context");
|
||||||
}
|
}
|
||||||
|
if (updateListener == null) {
|
||||||
|
throw new IllegalArgumentException("Invalid null OnClientUpdateListener");
|
||||||
|
}
|
||||||
if (looper != null) {
|
if (looper != null) {
|
||||||
mEventHandler = new EventHandler(this, looper);
|
mEventHandler = new EventHandler(this, looper);
|
||||||
} else {
|
} else {
|
||||||
@ -111,6 +120,7 @@ public final class RemoteController
|
|||||||
throw new IllegalArgumentException("Calling thread not associated with a looper");
|
throw new IllegalArgumentException("Calling thread not associated with a looper");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mOnClientUpdateListener = updateListener;
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mRcd = new RcDisplay();
|
mRcd = new RcDisplay();
|
||||||
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
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.
|
* 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.
|
* 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
|
* This is typically called whenever a new {@link RemoteControlClient} has been selected
|
||||||
* by the system to have its media information published.
|
* by the system to have its media information published.
|
||||||
* @param clearing true if there is no selected RemoteControlClient and no information
|
* @param clearing true if there is no selected RemoteControlClient and no information
|
||||||
* is available.
|
* 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
|
* It is called when no information is known about the playback progress in the media and
|
||||||
* the playback speed.
|
* the playback speed.
|
||||||
* @param state one of the playback states authorized
|
* @param state one of the playback states authorized
|
||||||
* in {@link RemoteControlClient#setPlaybackState(int)}.
|
* 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
|
* Called whenever the playback state has changed, and playback position
|
||||||
* speed are known.
|
* and speed are known.
|
||||||
* @param state one of the playback states authorized
|
* @param state one of the playback states authorized
|
||||||
* in {@link RemoteControlClient#setPlaybackState(int)}.
|
* in {@link RemoteControlClient#setPlaybackState(int)}.
|
||||||
* @param stateChangeTimeMs the system time at which the state change was reported,
|
* @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}).
|
* playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}).
|
||||||
*/
|
*/
|
||||||
public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
|
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
|
* @param transportControlFlags one of the flags authorized
|
||||||
* in {@link RemoteControlClient#setTransportControlFlags(int)}.
|
* 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)},
|
* See the {@link MediaMetadataEditor#putLong(int, long)},
|
||||||
* {@link MediaMetadataEditor#putString(int, String)},
|
* {@link MediaMetadataEditor#putString(int, String)},
|
||||||
* {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and
|
* {@link MediaMetadataEditor#putBitmap(int, Bitmap)}, and
|
||||||
@ -177,39 +187,9 @@ public final class RemoteController
|
|||||||
* can be queried.
|
* can be queried.
|
||||||
* @param metadataEditor the container of the new metadata.
|
* @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
|
* @hide
|
||||||
@ -256,6 +236,7 @@ public final class RemoteController
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a simulated key event for a media button to be received by the current client.
|
* 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
|
* 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_CLOSE},
|
||||||
* {@link KeyEvent#KEYCODE_MEDIA_EJECT},
|
* {@link KeyEvent#KEYCODE_MEDIA_EJECT},
|
||||||
* or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}.
|
* 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())) {
|
if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) {
|
||||||
Log.e(TAG, "Cannot use sendMediaKeyEvent() for a non-media key event");
|
throw new IllegalArgumentException("not a media key event");
|
||||||
return ERROR_BAD_VALUE;
|
|
||||||
}
|
}
|
||||||
final PendingIntent pi;
|
final PendingIntent pi;
|
||||||
synchronized(mInfoLock) {
|
synchronized(mInfoLock) {
|
||||||
if (!mIsRegistered) {
|
if (!mIsRegistered) {
|
||||||
Log.e(TAG, "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
|
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;
|
pi = mClientPendingIntentCurrent;
|
||||||
}
|
}
|
||||||
@ -301,47 +287,37 @@ public final class RemoteController
|
|||||||
pi.send(mContext, 0, intent);
|
pi.send(mContext, 0, intent);
|
||||||
} catch (CanceledException e) {
|
} catch (CanceledException e) {
|
||||||
Log.e(TAG, "Error sending intent for media button down: ", e);
|
Log.e(TAG, "Error sending intent for media button down: ", e);
|
||||||
return ERROR;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "No-op when sending key click, no receiver right now");
|
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.
|
* Sets the new playback position.
|
||||||
* This method can only be called on a registered RemoteController.
|
* 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.
|
* @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) {
|
if (timeMs < 0) {
|
||||||
return ERROR_BAD_VALUE;
|
throw new IllegalArgumentException("illegal negative time value");
|
||||||
}
|
}
|
||||||
final int genId;
|
final int genId;
|
||||||
synchronized (mGenLock) {
|
synchronized (mGenLock) {
|
||||||
genId = mClientGenerationIdCurrent;
|
genId = mClientGenerationIdCurrent;
|
||||||
}
|
}
|
||||||
mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
|
mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
|
||||||
return SUCCESS;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -350,9 +326,11 @@ public final class RemoteController
|
|||||||
* @param wantBitmap
|
* @param wantBitmap
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @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) {
|
synchronized (mInfoLock) {
|
||||||
if (wantBitmap) {
|
if (wantBitmap) {
|
||||||
if ((width > 0) && (height > 0)) {
|
if ((width > 0) && (height > 0)) {
|
||||||
@ -361,8 +339,7 @@ public final class RemoteController
|
|||||||
mArtworkWidth = width;
|
mArtworkWidth = width;
|
||||||
mArtworkHeight = height;
|
mArtworkHeight = height;
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Invalid dimensions");
|
throw new IllegalArgumentException("Invalid dimensions");
|
||||||
return ERROR_BAD_VALUE;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mArtworkWidth = -1;
|
mArtworkWidth = -1;
|
||||||
@ -375,7 +352,7 @@ public final class RemoteController
|
|||||||
// RemoteController.getArtworkSize() when AudioManager.registerRemoteController()
|
// RemoteController.getArtworkSize() when AudioManager.registerRemoteController()
|
||||||
// is called.
|
// is called.
|
||||||
}
|
}
|
||||||
return SUCCESS;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -383,17 +360,18 @@ public final class RemoteController
|
|||||||
* No bitmaps will be received unless this has been specified.
|
* No bitmaps will be received unless this has been specified.
|
||||||
* @param width the maximum width in pixels
|
* @param width the maximum width in pixels
|
||||||
* @param height the maximum height 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);
|
return setArtworkConfiguration(true, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevents this RemoteController from receiving artwork images.
|
* 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);
|
return setArtworkConfiguration(false, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,20 +398,20 @@ public final class RemoteController
|
|||||||
* Set the playback position synchronization mode.
|
* Set the playback position synchronization mode.
|
||||||
* Must be called on a registered RemoteController.
|
* Must be called on a registered RemoteController.
|
||||||
* @param sync {@link #POSITION_SYNCHRONIZATION_NONE} or {@link #POSITION_SYNCHRONIZATION_CHECK}
|
* @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)) {
|
if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) {
|
||||||
Log.e(TAG, "Unknown synchronization mode");
|
throw new IllegalArgumentException("Unknown synchronization mode " + sync);
|
||||||
return ERROR_BAD_VALUE;
|
|
||||||
}
|
}
|
||||||
if (!mIsRegistered) {
|
if (!mIsRegistered) {
|
||||||
Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
|
Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
|
||||||
return ERROR;
|
return false;
|
||||||
}
|
}
|
||||||
mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
|
mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
|
||||||
POSITION_SYNCHRONIZATION_CHECK == sync);
|
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,
|
public void setPlaybackState(int genId, int state,
|
||||||
long stateChangeTimeMs, long currentPosMs, float speed) {
|
long stateChangeTimeMs, long currentPosMs, float speed) {
|
||||||
if (DEBUG) {
|
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_TRANSPORT_INFO = 2;
|
||||||
private final static int MSG_NEW_METADATA = 3; // msg always has non-null obj parameter
|
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_CLIENT_CHANGE = 4;
|
||||||
|
private final static int MSG_DISPLAY_ENABLE = 5;
|
||||||
|
|
||||||
private class EventHandler extends Handler {
|
private class EventHandler extends Handler {
|
||||||
|
|
||||||
@ -667,6 +651,9 @@ public final class RemoteController
|
|||||||
case MSG_CLIENT_CHANGE:
|
case MSG_CLIENT_CHANGE:
|
||||||
onClientChange(msg.arg1, msg.arg2 == 1);
|
onClientChange(msg.arg1, msg.arg2 == 1);
|
||||||
break;
|
break;
|
||||||
|
case MSG_DISPLAY_ENABLE:
|
||||||
|
onDisplayEnable(msg.arg1 == 1);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Log.e(TAG, "unknown event " + msg.what);
|
Log.e(TAG, "unknown event " + msg.what);
|
||||||
}
|
}
|
||||||
@ -735,7 +722,6 @@ public final class RemoteController
|
|||||||
final OnClientUpdateListener l;
|
final OnClientUpdateListener l;
|
||||||
synchronized(mInfoLock) {
|
synchronized(mInfoLock) {
|
||||||
l = mOnClientUpdateListener;
|
l = mOnClientUpdateListener;
|
||||||
mLastTransportControlFlags = transportControlFlags;
|
|
||||||
}
|
}
|
||||||
if (l != null) {
|
if (l != null) {
|
||||||
l.onClientTransportControlUpdate(transportControlFlags);
|
l.onClientTransportControlUpdate(transportControlFlags);
|
||||||
@ -797,6 +783,11 @@ public final class RemoteController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onDisplayEnable(boolean enabled) {
|
||||||
|
synchronized(mInfoLock) {
|
||||||
|
mEnabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================
|
//==================================================
|
||||||
private static class PlaybackInfo {
|
private static class PlaybackInfo {
|
||||||
@ -818,7 +809,7 @@ public final class RemoteController
|
|||||||
* Used by AudioManager to mark this instance as registered.
|
* Used by AudioManager to mark this instance as registered.
|
||||||
* @param registered
|
* @param registered
|
||||||
*/
|
*/
|
||||||
protected void setIsRegistered(boolean registered) {
|
void setIsRegistered(boolean registered) {
|
||||||
synchronized (mInfoLock) {
|
synchronized (mInfoLock) {
|
||||||
mIsRegistered = registered;
|
mIsRegistered = registered;
|
||||||
}
|
}
|
||||||
@ -829,7 +820,7 @@ public final class RemoteController
|
|||||||
* Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
|
* Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected RcDisplay getRcDisplay() {
|
RcDisplay getRcDisplay() {
|
||||||
return mRcd;
|
return mRcd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -838,11 +829,19 @@ public final class RemoteController
|
|||||||
* Used by AudioManager to read the current artwork dimension
|
* Used by AudioManager to read the current artwork dimension
|
||||||
* @return array containing width (index 0) and height (index 1) of currently set artwork size
|
* @return array containing width (index 0) and height (index 1) of currently set artwork size
|
||||||
*/
|
*/
|
||||||
protected int[] getArtworkSize() {
|
int[] getArtworkSize() {
|
||||||
synchronized (mInfoLock) {
|
synchronized (mInfoLock) {
|
||||||
int[] size = { mArtworkWidth, mArtworkHeight };
|
int[] size = { mArtworkWidth, mArtworkHeight };
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
* Used by AudioManager to access user listener receiving the client update notifications
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
OnClientUpdateListener getUpdateListener() {
|
||||||
|
return mOnClientUpdateListener;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,8 +203,7 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
if (DEBUG) Log.v(TAG, "Create TCV " + this);
|
if (DEBUG) Log.v(TAG, "Create TCV " + this);
|
||||||
mAudioManager = new AudioManager(mContext);
|
mAudioManager = new AudioManager(mContext);
|
||||||
mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
|
mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
|
||||||
mRemoteController = new RemoteController(context);
|
mRemoteController = new RemoteController(context, mRCClientUpdateListener);
|
||||||
mRemoteController.setOnClientUpdateListener(mRCClientUpdateListener);
|
|
||||||
|
|
||||||
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
final DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||||
final int dim = Math.max(dm.widthPixels, dm.heightPixels);
|
final int dim = Math.max(dm.widthPixels, dm.heightPixels);
|
||||||
|
@ -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,
|
public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
|
||||||
boolean clearing) throws RemoteException {
|
boolean clearing) throws RemoteException {
|
||||||
Message msg = mHandler.obtainMessage(MSG_SET_CURRENT_CLIENT_ID,
|
Message msg = mHandler.obtainMessage(MSG_SET_CURRENT_CLIENT_ID,
|
||||||
|
Reference in New Issue
Block a user