Merge "RemoteController class to expose IRemoteControlDisplay features" into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
15bcc323cb
@@ -2107,6 +2107,13 @@
|
||||
android:description="@string/permdesc_captureSecureVideoOutput"
|
||||
android:protectionLevel="signature|system" />
|
||||
|
||||
<!--@hide Allows an application to know what content is playing and control its playback.
|
||||
<p>Not for use by third-party applications due to privacy of media consumption</p> -->
|
||||
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
|
||||
android:label="@string/permlab_mediaContentControl"
|
||||
android:description="@string/permdesc_mediaContentControl"
|
||||
android:protectionLevel="signature|system" />
|
||||
|
||||
<!-- Required to be able to disable the device (very dangerous!).
|
||||
<p>Not for use by third-party applications.. -->
|
||||
<permission android:name="android.permission.BRICK"
|
||||
|
||||
@@ -1432,6 +1432,11 @@
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_captureSecureVideoOutput">Allows the app to capture and redirect secure video output.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_mediaContentControl">control media playback and metadata access</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_mediaContentControl">Allows the app to control media playback and access the media information (title, author...).</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_modifyAudioSettings">change your audio settings</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
|
||||
@@ -442,6 +442,19 @@ public class AudioManager {
|
||||
return sService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* @param KeyEvent
|
||||
*/
|
||||
protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
|
||||
IAudioService service = getService();
|
||||
try {
|
||||
service.dispatchMediaKeyEvent(keyEvent);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "dispatchMediaKeyEvent threw exception ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -2233,6 +2246,49 @@ public class AudioManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param rctlr
|
||||
* @return true if the {@link RemoteController} was successfully registered, false if an
|
||||
* error occurred, due to an internal system error, or insufficient permissions.
|
||||
*/
|
||||
public boolean registerRemoteController(RemoteController rctlr) {
|
||||
if (rctlr == null) {
|
||||
return false;
|
||||
}
|
||||
IAudioService service = getService();
|
||||
try {
|
||||
boolean reg = service.registerRemoteControlDisplay(rctlr.getRcDisplay(),
|
||||
// passing a negative value for art work width and height
|
||||
// as they are still unknown at this stage
|
||||
/*w*/-1, /*h*/ -1);
|
||||
rctlr.setIsRegistered(reg);
|
||||
return reg;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param rctlr
|
||||
*/
|
||||
public void unregisterRemoteController(RemoteController rctlr) {
|
||||
if (rctlr == null) {
|
||||
return;
|
||||
}
|
||||
IAudioService service = getService();
|
||||
try {
|
||||
service.unregisterRemoteControlDisplay(rctlr.getRcDisplay());
|
||||
rctlr.setIsRegistered(false);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Registers a remote control display that will be sent information by remote control clients.
|
||||
@@ -2263,8 +2319,6 @@ public class AudioManager {
|
||||
}
|
||||
IAudioService service = getService();
|
||||
try {
|
||||
// passing a negative value for art work width and height as they are unknown at
|
||||
// this stage
|
||||
service.registerRemoteControlDisplay(rcd, w, h);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
|
||||
@@ -2357,13 +2411,15 @@ public class AudioManager {
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Notify the user of a RemoteControlClient that it should update its metadata
|
||||
* Notify the user of a RemoteControlClient that it should update its metadata with the
|
||||
* new value for the given key.
|
||||
* @param generationId the RemoteControlClient generation counter for which this request is
|
||||
* issued. Requests for an older generation than current one will be ignored.
|
||||
* @param key the metadata key for which a new value exists
|
||||
* @param value the new metadata value
|
||||
*/
|
||||
public void updateRemoteControlClientMetadata(int generationId, int key, long value) {
|
||||
public void updateRemoteControlClientMetadata(int generationId, int key,
|
||||
Rating value) {
|
||||
IAudioService service = getService();
|
||||
try {
|
||||
service.updateRemoteControlClientMetadata(generationId, key, value);
|
||||
|
||||
@@ -4143,8 +4143,17 @@ public class AudioService extends IAudioService.Stub {
|
||||
//==========================================================================================
|
||||
// RemoteControlDisplay / RemoteControlClient / Remote info
|
||||
//==========================================================================================
|
||||
public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
|
||||
mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
|
||||
@@ -4190,7 +4199,7 @@ public class AudioService extends IAudioService.Stub {
|
||||
mMediaFocusControl.setRemoteControlClientPlaybackPosition(generationId, timeMs);
|
||||
}
|
||||
|
||||
public void updateRemoteControlClientMetadata(int generationId, int key, long value) {
|
||||
public void updateRemoteControlClientMetadata(int generationId, int key, Rating value) {
|
||||
mMediaFocusControl.updateRemoteControlClientMetadata(generationId, key, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.media.IRemoteControlClient;
|
||||
import android.media.IRemoteControlDisplay;
|
||||
import android.media.IRemoteVolumeObserver;
|
||||
import android.media.IRingtonePlayer;
|
||||
import android.media.Rating;
|
||||
import android.net.Uri;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
@@ -140,7 +141,7 @@ interface IAudioService {
|
||||
* @param h the maximum height of the expected bitmap. Negative or zero values indicate this
|
||||
* display doesn't need to receive artwork.
|
||||
*/
|
||||
oneway void registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
|
||||
boolean registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
|
||||
/**
|
||||
* Unregister an IRemoteControlDisplay.
|
||||
* No effect if the IRemoteControlDisplay hasn't been successfully registered.
|
||||
@@ -178,13 +179,14 @@ interface IAudioService {
|
||||
*/
|
||||
void setRemoteControlClientPlaybackPosition(int generationId, long timeMs);
|
||||
/**
|
||||
* Notify the user of a RemoteControlClient that it should update its metadata
|
||||
* Notify the user of a RemoteControlClient that it should update its metadata with the
|
||||
* new value for the given key.
|
||||
* @param generationId the RemoteControlClient generation counter for which this request is
|
||||
* issued. Requests for an older generation than current one will be ignored.
|
||||
* @param key the metadata key for which a new value exists
|
||||
* @param value the new metadata value
|
||||
*/
|
||||
void updateRemoteControlClientMetadata(int generationId, int key, long value);
|
||||
void updateRemoteControlClientMetadata(int generationId, int key, in Rating value);
|
||||
|
||||
/**
|
||||
* Do not use directly, use instead
|
||||
|
||||
@@ -138,7 +138,7 @@ public class MediaFocusControl implements OnFinished {
|
||||
private static final int MSG_PROMOTE_RCC = 6;
|
||||
private static final int MSG_RCC_NEW_PLAYBACK_STATE = 7;
|
||||
private static final int MSG_RCC_SEEK_REQUEST = 8;
|
||||
private static final int MSG_RCC_UPDATE_METADATA_LONG = 9;
|
||||
private static final int MSG_RCC_UPDATE_METADATA = 9;
|
||||
|
||||
// sendMsg() flags
|
||||
/** If the msg is already queued, replace it with this one. */
|
||||
@@ -206,9 +206,9 @@ public class MediaFocusControl implements OnFinished {
|
||||
msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */);
|
||||
break;
|
||||
|
||||
case MSG_RCC_UPDATE_METADATA_LONG:
|
||||
onUpdateRemoteControlClientMetadataLong(msg.arg1 /*genId*/, msg.arg2 /*key*/,
|
||||
((Long)msg.obj).longValue() /* value */);
|
||||
case MSG_RCC_UPDATE_METADATA:
|
||||
onUpdateRemoteControlClientMetadata(msg.arg1 /*genId*/, msg.arg2 /*key*/,
|
||||
(Rating) msg.obj /* value */);
|
||||
break;
|
||||
|
||||
case MSG_PROMOTE_RCC:
|
||||
@@ -720,11 +720,7 @@ public class MediaFocusControl implements OnFinished {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
|
||||
if (keyEvent == null) {
|
||||
return false;
|
||||
}
|
||||
final int keyCode = keyEvent.getKeyCode();
|
||||
protected static boolean isMediaKeyCode(int keyCode) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_MUTE:
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
@@ -740,11 +736,17 @@ public class MediaFocusControl implements OnFinished {
|
||||
case KeyEvent.KEYCODE_MEDIA_CLOSE:
|
||||
case KeyEvent.KEYCODE_MEDIA_EJECT:
|
||||
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
|
||||
break;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
|
||||
if (keyEvent == null) {
|
||||
return false;
|
||||
}
|
||||
return MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2080,24 +2082,21 @@ public class MediaFocusControl implements OnFinished {
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateRemoteControlClientMetadata(int genId, int key, long value) {
|
||||
sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA_LONG, SENDMSG_QUEUE,
|
||||
genId /* arg1 */, key /* arg2 */, Long.valueOf(value) /* obj */, 0 /* delay */);
|
||||
protected void updateRemoteControlClientMetadata(int genId, int key, Rating value) {
|
||||
sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA, SENDMSG_QUEUE,
|
||||
genId /* arg1 */, key /* arg2 */, value /* obj */, 0 /* delay */);
|
||||
}
|
||||
|
||||
private void onUpdateRemoteControlClientMetadataLong(int genId, int key, long value) {
|
||||
if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadataLong(genId=" + genId +
|
||||
", what=" + key + ",val=" + value + ")");
|
||||
private void onUpdateRemoteControlClientMetadata(int genId, int key, Rating value) {
|
||||
if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadata(genId=" + genId +
|
||||
", what=" + key + ",rating=" + value + ")");
|
||||
synchronized(mRCStack) {
|
||||
synchronized(mCurrentRcLock) {
|
||||
if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) {
|
||||
try {
|
||||
switch (key) {
|
||||
case RemoteControlClient.MetadataEditor.RATING_KEY_BY_USER:
|
||||
// TODO handle rating update, placeholder code here that sends
|
||||
// an unrated percent-based rating
|
||||
mCurrentRcClient.updateMetadata(genId, key,
|
||||
Rating.newUnratedRating(Rating.RATING_PERCENTAGE));
|
||||
case MediaMetadataEditor.RATING_KEY_BY_USER:
|
||||
mCurrentRcClient.updateMetadata(genId, key, value);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "unhandled metadata key " + key + " update for RCC "
|
||||
|
||||
@@ -75,6 +75,16 @@ public final class Rating implements Parcelable {
|
||||
mRatingValue = rating;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public String toString () {
|
||||
return "Rating:style=" + mRatingStyle + " rating="
|
||||
+ (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return mRatingStyle;
|
||||
|
||||
@@ -715,7 +715,7 @@ public class RemoteControlClient
|
||||
* Implement this interface to receive metadata updates after registering your listener
|
||||
* through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}.
|
||||
*/
|
||||
public interface OnMetadataUpdateListener {
|
||||
public interface OnMetadataUpdateListener {
|
||||
/**
|
||||
* Called on the implementer to notify that the metadata field for the given key has
|
||||
* been updated to the new value.
|
||||
|
||||
796
media/java/android/media/RemoteController.java
Normal file
796
media/java/android/media/RemoteController.java
Normal file
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.IRemoteControlDisplay;
|
||||
import android.media.MediaMetadataEditor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* The RemoteController class is used to control media playback, display and update media metadata
|
||||
* and playback status, published by applications using the {@link RemoteControlClient} class.
|
||||
* <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 #setBitmapConfiguration(boolean, int, int)} to receive images as well.
|
||||
* <p>
|
||||
* A RemoteController can also be used without being registered, when it is only meant to send
|
||||
* media key events (for play or stop events for instance),
|
||||
* with {@link #sendMediaKeyEvent(KeyEvent)}.
|
||||
*/
|
||||
public class RemoteController
|
||||
{
|
||||
private final static int MAX_BITMAP_DIMENSION = 512;
|
||||
private final static int TRANSPORT_UNKNOWN = 0;
|
||||
private RcDisplay mRcd;
|
||||
private final static String TAG = "RemoteController";
|
||||
private final static boolean DEBUG = false;
|
||||
private final static Object mGenLock = new Object();
|
||||
private final static Object mInfoLock = new Object();
|
||||
private Context mContext;
|
||||
private AudioManager mAudioManager;
|
||||
private MetadataEditor mMetadataEditor;
|
||||
|
||||
/**
|
||||
* Synchronized on mGenLock
|
||||
*/
|
||||
private int mClientGenerationIdCurrent = 0;
|
||||
|
||||
/**
|
||||
* Synchronized on mInfoLock
|
||||
*/
|
||||
private boolean mIsRegistered = false;
|
||||
private PendingIntent mClientPendingIntentCurrent;
|
||||
private OnClientUpdateListener mOnClientUpdateListener;
|
||||
private PlaybackInfo mLastPlaybackInfo;
|
||||
private int mLastTransportControlFlags = TRANSPORT_UNKNOWN;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param ctxt non-null {@link Context}
|
||||
* @throws java.lang.IllegalArgumentException
|
||||
*/
|
||||
public RemoteController(Context ctxt) throws IllegalArgumentException {
|
||||
if (ctxt == null) {
|
||||
throw new IllegalArgumentException("Invalid null Context");
|
||||
}
|
||||
Looper looper;
|
||||
if ((looper = Looper.myLooper()) != null) {
|
||||
mEventHandler = new EventHandler(this, looper);
|
||||
} else if ((looper = Looper.getMainLooper()) != null) {
|
||||
mEventHandler = new EventHandler(this, looper);
|
||||
} else {
|
||||
mEventHandler = null;
|
||||
Log.e(TAG, "RemoteController() couldn't find main application thread");
|
||||
}
|
||||
mContext = ctxt;
|
||||
mRcd = new RcDisplay();
|
||||
mAudioManager = (AudioManager) ctxt.getSystemService(Context.AUDIO_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param looper
|
||||
* @param ctxt non-null {@link Context}
|
||||
* @throws java.lang.IllegalArgumentException
|
||||
*/
|
||||
public RemoteController(Looper looper, Context ctxt) throws IllegalArgumentException {
|
||||
if (ctxt == null) {
|
||||
throw new IllegalArgumentException("Invalid null Context");
|
||||
}
|
||||
mEventHandler = new EventHandler(this, looper);
|
||||
mContext = ctxt;
|
||||
mRcd = new RcDisplay();
|
||||
mAudioManager = (AudioManager) ctxt.getSystemService(Context.AUDIO_SERVICE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
*/
|
||||
public static abstract class OnClientUpdateListener {
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param clearing
|
||||
*/
|
||||
public void onClientReset(boolean clearing) { }
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param state
|
||||
*/
|
||||
public void onClientPlaybackStateUpdate(int state) { }
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param state
|
||||
* @param stateChangeTimeMs
|
||||
* @param currentPosMs
|
||||
* @param speed
|
||||
*/
|
||||
public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
|
||||
long currentPosMs, float speed) { }
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param transportControlFlags
|
||||
* @param posCapabilities
|
||||
*/
|
||||
public void onClientTransportControlUpdate(int transportControlFlags) { }
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param metadataEditor
|
||||
*/
|
||||
public void onClientMetadataUpdate(MetadataEditor metadataEditor) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param l
|
||||
*/
|
||||
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
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* Send a simulated key event for a media button.
|
||||
* May be used without registering the RemoteController
|
||||
* with {@link AudioManager#registerRemoteController(RemoteController)}. To simulate a key
|
||||
* press, you must first send a KeyEvent built with a {@link KeyEvent#ACTION_DOWN} action, then
|
||||
* another event with the {@link KeyEvent#ACTION_UP} action.
|
||||
* <p> When used from a registered RemoteController, the key event will be sent to the
|
||||
* application currently promoted to publish its media metadata and playback state (there may be
|
||||
* none under some circumstances). With an unregistered RemoteController, the key event will be
|
||||
* sent to the current media key event consumer
|
||||
* (see {@link AudioManager#registerMediaButtonEventReceiver(PendingIntent)}).
|
||||
* @param keyEvent a {@link KeyEvent} instance whose key code is one of
|
||||
* {@link KeyEvent.KEYCODE_MUTE},
|
||||
* {@link KeyEvent.KEYCODE_HEADSETHOOK},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_PLAY},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_PAUSE},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_STOP},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_NEXT},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_PREVIOUS},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_REWIND},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_RECORD},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_FAST_FORWARD},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_CLOSE},
|
||||
* {@link KeyEvent.KEYCODE_MEDIA_EJECT},
|
||||
* or {@link KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK}.
|
||||
*/
|
||||
public int sendMediaKeyEvent(KeyEvent keyEvent) {
|
||||
if (!MediaFocusControl.isMediaKeyCode(keyEvent.getKeyCode())) {
|
||||
Log.e(TAG, "Cannot use sendMediaKeyEvent() for a non-media key event");
|
||||
return ERROR_BAD_VALUE;
|
||||
}
|
||||
boolean registered = false;
|
||||
final PendingIntent pi;
|
||||
synchronized(mInfoLock) {
|
||||
registered = mIsRegistered;
|
||||
pi = mClientPendingIntentCurrent;
|
||||
}
|
||||
if (registered) {
|
||||
if (pi != null) {
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
|
||||
try {
|
||||
pi.send(mContext, 0, intent);
|
||||
} catch (CanceledException e) {
|
||||
Log.e(TAG, "Error sending intent for media button down: ", e);
|
||||
return ERROR;
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "No-op when sending key click, no receiver right now");
|
||||
return ERROR;
|
||||
}
|
||||
} else {
|
||||
mAudioManager.dispatchMediaKeyEvent(keyEvent);
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// Error codes
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* Successful operation.
|
||||
*/
|
||||
public static final int SUCCESS = 0;
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* Unspecified error.
|
||||
*/
|
||||
public static final int ERROR = -1;
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* Operation failed due to bad parameter value.
|
||||
*/
|
||||
public static final int ERROR_BAD_VALUE = -2;
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* @param timeMs
|
||||
* @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
|
||||
*/
|
||||
public int seekTo(long timeMs) {
|
||||
if (timeMs < 0) {
|
||||
return ERROR_BAD_VALUE;
|
||||
}
|
||||
final int genId;
|
||||
synchronized (mGenLock) {
|
||||
genId = mClientGenerationIdCurrent;
|
||||
}
|
||||
mAudioManager.setRemoteControlClientPlaybackPosition(genId, timeMs);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* must be called on a registered RemoteController
|
||||
* @param wantBitmap
|
||||
* @param width
|
||||
* @param height
|
||||
* @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
|
||||
*/
|
||||
public int setBitmapConfiguration(boolean wantBitmap, int width, int height) {
|
||||
synchronized (mInfoLock) {
|
||||
if (!mIsRegistered) {
|
||||
Log.e(TAG, "Cannot specify bitmap configuration on unregistered RemoteController");
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
if (wantBitmap) {
|
||||
if ((width > 0) && (height > 0)) {
|
||||
if (width > MAX_BITMAP_DIMENSION) { width = MAX_BITMAP_DIMENSION; }
|
||||
if (height > MAX_BITMAP_DIMENSION) { height = MAX_BITMAP_DIMENSION; }
|
||||
mAudioManager.remoteControlDisplayUsesBitmapSize(mRcd, width, height);
|
||||
} else {
|
||||
Log.e(TAG, "Invalid dimensions");
|
||||
return ERROR_BAD_VALUE;
|
||||
}
|
||||
} else {
|
||||
mAudioManager.remoteControlDisplayUsesBitmapSize(mRcd, -1, -1);
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* must be called on a registered RemoteController
|
||||
* @param width
|
||||
* @param height
|
||||
* @return {@link #SUCCESS}, {@link #ERROR} or {@link #ERROR_BAD_VALUE}
|
||||
*/
|
||||
public int setBitmapConfiguration(int width, int height) {
|
||||
return setBitmapConfiguration(true, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* must be called on a registered RemoteController
|
||||
* @return {@link #SUCCESS}, {@link #ERROR}
|
||||
*/
|
||||
public int setBitmapConfigurationNone() {
|
||||
return setBitmapConfiguration(false, -1, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* Default playback position synchronization mode where the RemoteControlClient is not
|
||||
* asked regularly for its playback position to see if it has drifted from the estimated
|
||||
* position.
|
||||
*/
|
||||
public static final int POSITION_SYNCHRONIZATION_NONE = 0;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* The playback position synchronization mode where the RemoteControlClient instances which
|
||||
* expose their playback position to the framework, will be regularly polled to check
|
||||
* whether any drift has been noticed between their estimated position and the one they report.
|
||||
* Note that this mode should only ever be used when needing to display very accurate playback
|
||||
* position, as regularly polling a RemoteControlClient for its position may have an impact
|
||||
* on battery life (if applicable) when this query will trigger network transactions in the
|
||||
* case of remote playback.
|
||||
*/
|
||||
public static final int POSITION_SYNCHRONIZATION_CHECK = 1;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* 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}
|
||||
*/
|
||||
public int setSynchronizationMode(int sync) {
|
||||
if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) {
|
||||
Log.e(TAG, "Unknown synchronization mode");
|
||||
return ERROR_BAD_VALUE;
|
||||
}
|
||||
if (!mIsRegistered) {
|
||||
Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
|
||||
return ERROR;
|
||||
}
|
||||
mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
|
||||
POSITION_SYNCHRONIZATION_CHECK == sync);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* Creates a {@link MetadataEditor} for updating metadata values of the editable keys of
|
||||
* the current {@link RemoteControlClient}.
|
||||
* @return a new MetadataEditor instance.
|
||||
*/
|
||||
public MetadataEditor editMetadata() {
|
||||
MetadataEditor editor = new MetadataEditor();
|
||||
editor.mEditorMetadata = new Bundle();
|
||||
editor.mEditorArtwork = null;
|
||||
editor.mMetadataChanged = true;
|
||||
editor.mArtworkChanged = true;
|
||||
editor.mEditableKeys = 0;
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
* Used to read the metadata published by a {@link RemoteControlClient}, or send a
|
||||
* {@link RemoteControlClient} new values for keys that can be edited.
|
||||
*/
|
||||
public class MetadataEditor extends MediaMetadataEditor {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected MetadataEditor() { }
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected MetadataEditor(Bundle metadata, long editableKeys) {
|
||||
mEditorMetadata = metadata;
|
||||
mEditableKeys = editableKeys;
|
||||
mEditorArtwork = null;
|
||||
mMetadataChanged = true;
|
||||
mArtworkChanged = true;
|
||||
mApplied = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* CANDIDATE FOR PUBLIC API
|
||||
*/
|
||||
public synchronized void apply() {
|
||||
// "applying" a metadata bundle in RemoteController is only for sending edited
|
||||
// key values back to the RemoteControlClient, so here we only care about the only
|
||||
// editable key we support: RATING_KEY_BY_USER
|
||||
if (!mMetadataChanged) {
|
||||
return;
|
||||
}
|
||||
final int genId;
|
||||
synchronized(mGenLock) {
|
||||
genId = mClientGenerationIdCurrent;
|
||||
}
|
||||
synchronized(mInfoLock) {
|
||||
if (mEditorMetadata.containsKey(
|
||||
String.valueOf(MediaMetadataEditor.RATING_KEY_BY_USER))) {
|
||||
Rating rating = (Rating) getObject(
|
||||
MediaMetadataEditor.RATING_KEY_BY_USER, null);
|
||||
mAudioManager.updateRemoteControlClientMetadata(genId,
|
||||
MediaMetadataEditor.RATING_KEY_BY_USER,
|
||||
rating);
|
||||
} else {
|
||||
Log.e(TAG, "no metadata to apply");
|
||||
}
|
||||
// NOT setting mApplied to true as this type of MetadataEditor will be applied
|
||||
// multiple times, whenever the user of a RemoteController needs to change the
|
||||
// metadata (e.g. user changes the rating of a song more than once during playback)
|
||||
mApplied = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//==================================================
|
||||
// Implementation of IRemoteControlDisplay interface
|
||||
private class RcDisplay extends IRemoteControlDisplay.Stub {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setCurrentClientId(int genId, PendingIntent clientMediaIntent,
|
||||
boolean clearing) {
|
||||
boolean isNew = false;
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
mClientGenerationIdCurrent = genId;
|
||||
isNew = true;
|
||||
}
|
||||
}
|
||||
if (clientMediaIntent != null) {
|
||||
sendMsg(mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE,
|
||||
genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/);
|
||||
}
|
||||
if (isNew || clearing) {
|
||||
sendMsg(mEventHandler, MSG_CLIENT_RESET, SENDMSG_REPLACE,
|
||||
genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setPlaybackState(int genId, int state,
|
||||
long stateChangeTimeMs, long currentPosMs, float speed) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "> new playback state: genId="+genId
|
||||
+ " state="+ state
|
||||
+ " changeTime="+ stateChangeTimeMs
|
||||
+ " pos=" + currentPosMs
|
||||
+ "ms speed=" + speed);
|
||||
}
|
||||
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final PlaybackInfo playbackInfo =
|
||||
new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed);
|
||||
sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
|
||||
genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setTransportControlInfo(int genId, int transportControlFlags,
|
||||
int posCapabilities) {
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
|
||||
genId /*arg1*/, transportControlFlags /*arg2*/,
|
||||
null /*obj*/, 0 /*delay*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setMetadata(int genId, Bundle metadata) {
|
||||
if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); }
|
||||
if (metadata == null) {
|
||||
return;
|
||||
}
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
|
||||
genId /*arg1*/, 0 /*arg2*/,
|
||||
metadata /*obj*/, 0 /*delay*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setArtwork(int genId, Bitmap artwork) {
|
||||
if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); }
|
||||
if (artwork == null) {
|
||||
return;
|
||||
}
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Bundle metadata = new Bundle(1);
|
||||
metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork);
|
||||
sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
|
||||
genId /*arg1*/, 0 /*arg2*/,
|
||||
metadata /*obj*/, 0 /*delay*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) {
|
||||
if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); }
|
||||
if ((metadata == null) && (artwork == null)) {
|
||||
return;
|
||||
}
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (metadata == null) {
|
||||
metadata = new Bundle(1);
|
||||
}
|
||||
if (artwork != null) {
|
||||
metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
|
||||
artwork);
|
||||
}
|
||||
sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
|
||||
genId /*arg1*/, 0 /*arg2*/,
|
||||
metadata /*obj*/, 0 /*delay*/);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================
|
||||
// Event handling
|
||||
private EventHandler mEventHandler;
|
||||
private final static int MSG_NEW_PENDING_INTENT = 0;
|
||||
private final static int MSG_NEW_PLAYBACK_INFO = 1;
|
||||
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_RESET = 4;
|
||||
|
||||
private class EventHandler extends Handler {
|
||||
|
||||
public EventHandler(RemoteController rc, Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch(msg.what) {
|
||||
case MSG_NEW_PENDING_INTENT:
|
||||
onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj);
|
||||
break;
|
||||
case MSG_NEW_PLAYBACK_INFO:
|
||||
onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj);
|
||||
break;
|
||||
case MSG_NEW_TRANSPORT_INFO:
|
||||
onNewTransportInfo(msg.arg1, msg.arg2);
|
||||
break;
|
||||
case MSG_NEW_METADATA:
|
||||
onNewMetadata(msg.arg1, (Bundle)msg.obj);
|
||||
break;
|
||||
case MSG_CLIENT_RESET:
|
||||
onClientReset(msg.arg1, msg.arg2 == 1);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "unknown event " + msg.what);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** If the msg is already queued, replace it with this one. */
|
||||
private static final int SENDMSG_REPLACE = 0;
|
||||
/** If the msg is already queued, ignore this one and leave the old. */
|
||||
private static final int SENDMSG_NOOP = 1;
|
||||
/** If the msg is already queued, queue this one and leave the old. */
|
||||
private static final int SENDMSG_QUEUE = 2;
|
||||
|
||||
private static void sendMsg(Handler handler, int msg, int existingMsgPolicy,
|
||||
int arg1, int arg2, Object obj, int delayMs) {
|
||||
if (handler == null) {
|
||||
Log.e(TAG, "null event handler, will not deliver message " + msg);
|
||||
return;
|
||||
}
|
||||
if (existingMsgPolicy == SENDMSG_REPLACE) {
|
||||
handler.removeMessages(msg);
|
||||
} else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
|
||||
return;
|
||||
}
|
||||
handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
|
||||
}
|
||||
|
||||
private void onNewPendingIntent(int genId, PendingIntent pi) {
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
synchronized(mInfoLock) {
|
||||
mClientPendingIntentCurrent = pi;
|
||||
}
|
||||
}
|
||||
|
||||
private void onNewPlaybackInfo(int genId, PlaybackInfo pi) {
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final OnClientUpdateListener l;
|
||||
synchronized(mInfoLock) {
|
||||
l = this.mOnClientUpdateListener;
|
||||
mLastPlaybackInfo = pi;
|
||||
}
|
||||
if (l != null) {
|
||||
if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
|
||||
l.onClientPlaybackStateUpdate(pi.mState);
|
||||
} else {
|
||||
l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs,
|
||||
pi.mSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onNewTransportInfo(int genId, int transportControlFlags) {
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final OnClientUpdateListener l;
|
||||
synchronized(mInfoLock) {
|
||||
l = mOnClientUpdateListener;
|
||||
mLastTransportControlFlags = transportControlFlags;
|
||||
}
|
||||
if (l != null) {
|
||||
l.onClientTransportControlUpdate(transportControlFlags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param genId
|
||||
* @param metadata guaranteed to be always non-null
|
||||
*/
|
||||
private void onNewMetadata(int genId, Bundle metadata) {
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final OnClientUpdateListener l;
|
||||
final MetadataEditor metadataEditor;
|
||||
// prepare the received Bundle to be used inside a MetadataEditor
|
||||
final long editableKeys = metadata.getLong(
|
||||
String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0);
|
||||
if (editableKeys != 0) {
|
||||
metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK));
|
||||
}
|
||||
synchronized(mInfoLock) {
|
||||
l = mOnClientUpdateListener;
|
||||
if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) {
|
||||
if (mMetadataEditor.mEditorMetadata != metadata) {
|
||||
// existing metadata, merge existing and new
|
||||
mMetadataEditor.mEditorMetadata.putAll(metadata);
|
||||
}
|
||||
} else {
|
||||
mMetadataEditor = new MetadataEditor(metadata, editableKeys);
|
||||
}
|
||||
metadataEditor = mMetadataEditor;
|
||||
}
|
||||
if (l != null) {
|
||||
l.onClientMetadataUpdate(metadataEditor);
|
||||
}
|
||||
}
|
||||
|
||||
private void onClientReset(int genId, boolean clearing) {
|
||||
synchronized(mGenLock) {
|
||||
if (mClientGenerationIdCurrent != genId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final OnClientUpdateListener l;
|
||||
synchronized(mInfoLock) {
|
||||
l = mOnClientUpdateListener;
|
||||
}
|
||||
if (l != null) {
|
||||
l.onClientReset(clearing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==================================================
|
||||
private static class PlaybackInfo {
|
||||
int mState;
|
||||
long mStateChangeTimeMs;
|
||||
long mCurrentPosMs;
|
||||
float mSpeed;
|
||||
|
||||
PlaybackInfo(int state, long stateChangeTimeMs, long currentPosMs, float speed) {
|
||||
mState = state;
|
||||
mStateChangeTimeMs = stateChangeTimeMs;
|
||||
mCurrentPosMs = currentPosMs;
|
||||
mSpeed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Used by AudioManager to mark this instance as registered.
|
||||
* @param registered
|
||||
*/
|
||||
protected void setIsRegistered(boolean registered) {
|
||||
synchronized (mInfoLock) {
|
||||
mIsRegistered = registered;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
|
||||
* @return
|
||||
*/
|
||||
protected RcDisplay getRcDisplay() {
|
||||
return mRcd;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
|
||||
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
|
||||
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
|
||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
||||
|
||||
<application android:label="@string/app_name"
|
||||
android:process="com.android.systemui"
|
||||
|
||||
Reference in New Issue
Block a user