Merge "freeze screen rotation during memory and app launch test" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 8467400..da1ebae 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15150,6 +15150,7 @@
method public boolean containsKey(java.lang.String);
method public int describeContents();
method public android.graphics.Bitmap getBitmap(java.lang.String);
+ method public android.media.MediaMetadata.Description getDescription();
method public long getLong(java.lang.String);
method public android.media.Rating getRating(java.lang.String);
method public java.lang.String getString(java.lang.String);
@@ -15197,6 +15198,14 @@
method public android.media.MediaMetadata.Builder putText(java.lang.String, java.lang.CharSequence);
}
+ public final class MediaMetadata.Description {
+ method public java.lang.CharSequence getDescription();
+ method public android.graphics.Bitmap getIcon();
+ method public android.net.Uri getIconUri();
+ method public java.lang.CharSequence getSubtitle();
+ method public java.lang.CharSequence getTitle();
+ }
+
public abstract deprecated class MediaMetadataEditor {
method public synchronized void addEditableKey(int);
method public abstract void apply();
@@ -16734,18 +16743,14 @@
public final class MediaSession {
ctor public MediaSession(android.content.Context, java.lang.String);
- method public void addCallback(android.media.session.MediaSession.Callback);
- method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
- method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
- method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback, android.os.Handler);
method public android.media.session.MediaController getController();
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isActive();
method public void release();
- method public void removeCallback(android.media.session.MediaSession.Callback);
- method public void removeTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
method public void sendSessionEvent(java.lang.String, android.os.Bundle);
method public void setActive(boolean);
+ method public void setCallback(android.media.session.MediaSession.Callback);
+ method public void setCallback(android.media.session.MediaSession.Callback, android.os.Handler);
method public void setExtras(android.os.Bundle);
method public void setFlags(int);
method public void setLaunchActivity(android.app.PendingIntent);
@@ -16766,7 +16771,20 @@
public static abstract class MediaSession.Callback {
ctor public MediaSession.Callback();
method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
- method public void onMediaButtonEvent(android.content.Intent);
+ method public void onCustomAction(java.lang.String, android.os.Bundle);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
+ method public void onPlayUri(android.net.Uri, android.os.Bundle);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetRating(android.media.Rating);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToTrack(long);
+ method public void onStop();
}
public static final class MediaSession.Token implements android.os.Parcelable {
@@ -16792,23 +16810,6 @@
method public android.media.session.MediaSession.Track.Builder setExtras(android.os.Bundle);
}
- public static abstract class MediaSession.TransportControlsCallback {
- ctor public MediaSession.TransportControlsCallback();
- method public void onCustomAction(java.lang.String, android.os.Bundle);
- method public void onFastForward();
- method public void onPause();
- method public void onPlay();
- method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
- method public void onPlayUri(android.net.Uri, android.os.Bundle);
- method public void onRewind();
- method public void onSeekTo(long);
- method public void onSetRating(android.media.Rating);
- method public void onSkipToNext();
- method public void onSkipToPrevious();
- method public void onSkipToTrack(long);
- method public void onStop();
- }
-
public final class MediaSessionManager {
method public void addActiveSessionsListener(android.media.session.MediaSessionManager.SessionListener, android.content.ComponentName);
method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 5f6fb6e..5b722e2 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -97,6 +97,11 @@
private Context mContext = null;
private int mMyUid;
+
+ // Since most Providers have only one authority, we keep both a String and a String[] to improve
+ // performance.
+ private String mAuthority;
+ private String[] mAuthorities;
private String mReadPermission;
private String mWritePermission;
private PathPermission[] mPathPermissions;
@@ -193,7 +198,7 @@
public Cursor query(String callingPkg, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
ICancellationSignal cancellationSignal) {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
@@ -211,14 +216,15 @@
@Override
public String getType(Uri uri) {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
return ContentProvider.this.getType(uri);
}
@Override
public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
- int userId = getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return rejectInsert(uri, initialValues);
@@ -233,7 +239,7 @@
@Override
public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
@@ -254,20 +260,22 @@
final int[] userIds = new int[numOperations];
for (int i = 0; i < numOperations; i++) {
ContentProviderOperation operation = operations.get(i);
- userIds[i] = getAndEnforceUserId(operation.getUri());
+ Uri uri = operation.getUri();
+ validateIncomingUri(uri);
+ userIds[i] = getUserIdFromUri(uri);
if (userIds[i] != UserHandle.USER_CURRENT) {
// Removing the user id from the uri.
operation = new ContentProviderOperation(operation, true);
operations.set(i, operation);
}
if (operation.isReadOperation()) {
- if (enforceReadPermission(callingPkg, operation.getUri())
+ if (enforceReadPermission(callingPkg, uri)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
if (operation.isWriteOperation()) {
- if (enforceWritePermission(callingPkg, operation.getUri())
+ if (enforceWritePermission(callingPkg, uri)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
@@ -290,7 +298,7 @@
@Override
public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
@@ -306,7 +314,7 @@
@Override
public int update(String callingPkg, Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
@@ -323,7 +331,7 @@
public ParcelFileDescriptor openFile(
String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
enforceFilePermission(callingPkg, uri, mode);
final String original = setCallingPackage(callingPkg);
@@ -339,7 +347,7 @@
public AssetFileDescriptor openAssetFile(
String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
enforceFilePermission(callingPkg, uri, mode);
final String original = setCallingPackage(callingPkg);
@@ -363,7 +371,7 @@
@Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
}
@@ -371,7 +379,7 @@
@Override
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
- getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
enforceFilePermission(callingPkg, uri, "r");
final String original = setCallingPackage(callingPkg);
@@ -390,7 +398,8 @@
@Override
public Uri canonicalize(String callingPkg, Uri uri) {
- int userId = getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return null;
@@ -405,7 +414,8 @@
@Override
public Uri uncanonicalize(String callingPkg, Uri uri) {
- int userId = getAndEnforceUserId(uri);
+ validateIncomingUri(uri);
+ int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return null;
@@ -620,6 +630,36 @@
}
/**
+ * Change the authorities of the ContentProvider.
+ * This is normally set for you from its manifest information when the provider is first
+ * created.
+ * @hide
+ * @param authorities the semi-colon separated authorities of the ContentProvider.
+ */
+ protected final void setAuthorities(String authorities) {
+ if (authorities.indexOf(';') == -1) {
+ mAuthority = authorities;
+ mAuthorities = null;
+ } else {
+ mAuthority = null;
+ mAuthorities = authorities.split(";");
+ }
+ }
+
+ /** @hide */
+ protected final boolean matchesOurAuthorities(String authority) {
+ if (mAuthority != null) {
+ return mAuthority.equals(authority);
+ }
+ int length = mAuthorities.length;
+ for (int i = 0; i < length; i++) {
+ if (mAuthorities[i].equals(authority)) return true;
+ }
+ return false;
+ }
+
+
+ /**
* Change the permission required to read data from the content
* provider. This is normally set for you from its manifest information
* when the provider is first created.
@@ -1634,6 +1674,7 @@
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
mExported = info.exported;
+ setAuthorities(info.authority);
}
ContentProvider.this.onCreate();
}
@@ -1727,14 +1768,25 @@
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
writer.println("nothing to dump");
}
+
/** @hide */
- private int getAndEnforceUserId(Uri uri) {
- int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT);
+ private void validateIncomingUri(Uri uri) throws SecurityException {
+ String auth = uri.getAuthority();
+ int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
if (userId != UserHandle.USER_CURRENT && userId != mContext.getUserId()) {
throw new SecurityException("trying to query a ContentProvider in user "
- + mContext.getUserId() + "with a uri belonging to user " + userId);
+ + mContext.getUserId() + " with a uri belonging to user " + userId);
}
- return userId;
+ if (!matchesOurAuthorities(getAuthorityWithoutUserId(auth))) {
+ String message = "The authority of the uri " + uri + " does not match the one of the "
+ + "contentProvider: ";
+ if (mAuthority != null) {
+ message += mAuthority;
+ } else {
+ message += mAuthorities;
+ }
+ throw new SecurityException(message);
+ }
}
/** @hide */
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 87d14b9..0ca800f 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -329,7 +329,7 @@
try {
String type = ActivityManagerNative.getDefault().getProviderMimeType(
- url, UserHandle.myUserId());
+ ContentProvider.getUriWithoutUserId(url), resolveUserId(url));
return type;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index d93ca2c..23894ee 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -175,7 +175,9 @@
String[] nativePaths = null;
try {
nativePaths = getWebViewNativeLibraryPaths();
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (Throwable t) {
+ // Log and discard errors at this stage as we must not crash the system server.
+ Log.e(LOGTAG, "error preparing webview native library", t);
}
prepareWebViewInSystemServer(nativePaths);
}
@@ -201,35 +203,37 @@
String[] nativeLibs = null;
try {
nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
- } catch (PackageManager.NameNotFoundException e) {
- }
+ if (nativeLibs != null) {
+ long newVmSize = 0L;
- if (nativeLibs != null) {
- long newVmSize = 0L;
-
- for (String path : nativeLibs) {
- if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
- if (path == null) continue;
- File f = new File(path);
- if (f.exists()) {
- long length = f.length();
- if (length > newVmSize) {
- newVmSize = length;
+ for (String path : nativeLibs) {
+ if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
+ if (path == null) continue;
+ File f = new File(path);
+ if (f.exists()) {
+ long length = f.length();
+ if (length > newVmSize) {
+ newVmSize = length;
+ }
}
}
- }
- if (DEBUG) {
- Log.v(LOGTAG, "Based on library size, need " + newVmSize +
- " bytes of address space.");
+ if (DEBUG) {
+ Log.v(LOGTAG, "Based on library size, need " + newVmSize +
+ " bytes of address space.");
+ }
+ // The required memory can be larger than the file on disk (due to .bss), and an
+ // upgraded version of the library will likely be larger, so always attempt to
+ // reserve twice as much as we think to allow for the library to grow during this
+ // boot cycle.
+ newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
+ Log.d(LOGTAG, "Setting new address space to " + newVmSize);
+ SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
+ Long.toString(newVmSize));
}
- // The required memory can be larger than the file on disk (due to .bss), and an
- // upgraded version of the library will likely be larger, so always attempt to reserve
- // twice as much as we think to allow for the library to grow during this boot cycle.
- newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
- Log.d(LOGTAG, "Setting new address space to " + newVmSize);
- SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
- Long.toString(newVmSize));
+ } catch (Throwable t) {
+ // Log and discard errors at this stage as we must not crash the system server.
+ Log.e(LOGTAG, "error preparing webview native library", t);
}
prepareWebViewInSystemServer(nativeLibs);
}
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index bbf6a3b..74f7a96 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -190,8 +190,8 @@
private static final String[] PREFERRED_DESCRIPTION_ORDER = {
METADATA_KEY_TITLE,
- METADATA_KEY_ALBUM,
METADATA_KEY_ARTIST,
+ METADATA_KEY_ALBUM,
METADATA_KEY_ALBUM_ARTIST,
METADATA_KEY_WRITER,
METADATA_KEY_AUTHOR,
@@ -215,7 +215,6 @@
private static final int METADATA_TYPE_TEXT = 1;
private static final int METADATA_TYPE_BITMAP = 2;
private static final int METADATA_TYPE_RATING = 3;
- private static final int METADATA_TYPE_URI = 4;
private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
static {
@@ -236,16 +235,16 @@
METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_URI);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_URI);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_URI);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
}
private static final SparseArray<String> EDITOR_KEY_MAPPING;
@@ -406,7 +405,6 @@
* Returns a simple description of this metadata for display purposes.
*
* @return A simple description of this metadata.
- * @hide
*/
public @NonNull Description getDescription() {
if (mDescription != null) {
@@ -673,43 +671,86 @@
/**
* A simple form of the metadata that can be used for display.
- *
- * @hide
*/
public final class Description {
/**
* A primary title suitable for display or null.
*/
- public final CharSequence title;
+ private final CharSequence mTitle;
/**
* A subtitle suitable for display or null.
*/
- public final CharSequence subtitle;
+ private final CharSequence mSubtitle;
/**
* A description suitable for display or null.
*/
- public final CharSequence description;
+ private final CharSequence mDescription;
/**
* A bitmap icon suitable for display or null.
*/
- public final Bitmap icon;
+ private final Bitmap mIcon;
/**
* A Uri for an icon suitable for display or null.
*/
- public final Uri iconUri;
+ private final Uri mIconUri;
+
+ /**
+ * Returns the best available title or null.
+ *
+ * @return A title or null.
+ */
+ public @Nullable CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the best available subtitle or null.
+ *
+ * @return A subtitle or null.
+ */
+ public @Nullable CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * Returns the best available description or null.
+ *
+ * @return A description or null.
+ */
+ public @Nullable CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns the best available icon or null.
+ *
+ * @return An icon or null.
+ */
+ public @Nullable Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns the best available icon Uri or null.
+ *
+ * @return An icon uri or null.
+ */
+ public @Nullable Uri getIconUri() {
+ return mIconUri;
+ }
private Description(CharSequence title, CharSequence subtitle, CharSequence description,
Bitmap icon, Uri iconUri) {
- this.title = title;
- this.subtitle = subtitle;
- this.description = description;
- this.icon = icon;
- this.iconUri = iconUri;
+ mTitle = title;
+ mSubtitle = subtitle;
+ mDescription = description;
+ mIcon = icon;
+ mIconUri = iconUri;
}
@Override
public String toString() {
- return title + ", " + subtitle + ", " + description;
+ return mTitle + ", " + mSubtitle + ", " + mDescription;
}
}
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 96c66c5..2a0fd83 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -970,8 +970,7 @@
public final static int RCSE_ID_UNREGISTERED = -1;
// USE_SESSIONS
- private MediaSession.TransportControlsCallback mTransportListener
- = new MediaSession.TransportControlsCallback() {
+ private MediaSession.Callback mTransportListener = new MediaSession.Callback() {
@Override
public void onSeekTo(long pos) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index e3c198e..f6e189a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -179,7 +179,8 @@
}
/**
- * Get the current play queue for this session.
+ * Get the current play queue for this session if one is set. If you only
+ * care about the current item {@link #getMetadata()} should be used.
*
* @return The current play queue or null.
*/
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index cf8e3dd..cf73c2a 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -21,12 +21,10 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
@@ -43,11 +41,11 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import android.view.KeyEvent;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -64,10 +62,8 @@
* create a {@link MediaController} to interact with the session.
* <p>
* To receive commands, media keys, and other events a {@link Callback} must be
- * set with {@link #addCallback(Callback)} and {@link #setActive(boolean)
- * setActive(true)} must be called. To receive transport control commands a
- * {@link TransportControlsCallback} must be set with
- * {@link #addTransportControlsCallback}.
+ * set with {@link #setCallback(Callback)} and {@link #setActive(boolean)
+ * setActive(true)} must be called.
* <p>
* When an app is finished performing playback it must call {@link #release()}
* to clean up the session and notify any controllers.
@@ -85,8 +81,7 @@
/**
* Set this flag on the session to indicate that it handles transport
- * control commands through a {@link TransportControlsCallback}.
- * The callback can be retrieved by calling {@link #addTransportControlsCallback}.
+ * control commands through its {@link Callback}.
*/
public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
@@ -124,12 +119,9 @@
private final ISession mBinder;
private final CallbackStub mCbStub;
- private final ArrayList<CallbackMessageHandler> mCallbacks
- = new ArrayList<CallbackMessageHandler>();
- private final ArrayList<TransportMessageHandler> mTransportCallbacks
- = new ArrayList<TransportMessageHandler>();
-
+ private CallbackMessageHandler mCallback;
private VolumeProvider mVolumeProvider;
+ private PlaybackState mPlaybackState;
private boolean mActive = false;
@@ -177,30 +169,35 @@
}
/**
- * Add a callback to receive updates on for the MediaSession. This includes
- * media button and volume events. The caller's thread will be used to post
- * events.
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls. The caller's thread will be
+ * used to post updates.
+ * <p>
+ * Set the callback to null to stop receiving updates.
*
* @param callback The callback object
*/
- public void addCallback(@NonNull Callback callback) {
- addCallback(callback, null);
+ public void setCallback(@Nullable Callback callback) {
+ setCallback(callback, null);
}
/**
- * Add a callback to receive updates for the MediaSession. This includes
- * media button and volume events.
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls.
+ * <p>
+ * Set the callback to null to stop receiving updates.
*
* @param callback The callback to receive updates on.
* @param handler The handler that events should be posted on.
*/
- public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
+ public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
if (callback == null) {
- throw new IllegalArgumentException("Callback cannot be null");
+ mCallback = null;
+ return;
}
synchronized (mLock) {
- if (getHandlerForCallbackLocked(callback) != null) {
- Log.w(TAG, "Callback is already added, ignoring");
+ if (mCallback != null && mCallback.mCallback == callback) {
+ Log.w(TAG, "Tried to set same callback, ignoring");
return;
}
if (handler == null) {
@@ -208,18 +205,7 @@
}
CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
callback);
- mCallbacks.add(msgHandler);
- }
- }
-
- /**
- * Remove a callback. It will no longer receive updates.
- *
- * @param callback The callback to remove.
- */
- public void removeCallback(@NonNull Callback callback) {
- synchronized (mLock) {
- removeCallbackLocked(callback);
+ mCallback = msgHandler;
}
}
@@ -421,63 +407,12 @@
}
/**
- * Add a callback to receive transport controls on, such as play, rewind, or
- * fast forward.
- *
- * @param callback The callback object
- */
- public void addTransportControlsCallback(@NonNull TransportControlsCallback callback) {
- addTransportControlsCallback(callback, null);
- }
-
- /**
- * Add a callback to receive transport controls on, such as play, rewind, or
- * fast forward. The updates will be posted to the specified handler. If no
- * handler is provided they will be posted to the caller's thread.
- *
- * @param callback The callback to receive updates on
- * @param handler The handler to post the updates on
- */
- public void addTransportControlsCallback(@NonNull TransportControlsCallback callback,
- @Nullable Handler handler) {
- if (callback == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
- synchronized (mLock) {
- if (getTransportControlsHandlerForCallbackLocked(callback) != null) {
- Log.w(TAG, "Callback is already added, ignoring");
- return;
- }
- if (handler == null) {
- handler = new Handler();
- }
- TransportMessageHandler msgHandler = new TransportMessageHandler(handler.getLooper(),
- callback);
- mTransportCallbacks.add(msgHandler);
- }
- }
-
- /**
- * Stop receiving transport controls on the specified callback. If an update
- * has already been posted you may still receive it after this call returns.
- *
- * @param callback The callback to stop receiving updates on
- */
- public void removeTransportControlsCallback(@NonNull TransportControlsCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
- synchronized (mLock) {
- removeTransportControlsCallbackLocked(callback);
- }
- }
-
- /**
* Update the current playback state.
*
* @param state The current state of playback
*/
public void setPlaybackState(@Nullable PlaybackState state) {
+ mPlaybackState = state;
try {
mBinder.setPlaybackState(state);
} catch (RemoteException e) {
@@ -566,138 +501,78 @@
}
private void dispatchPlay() {
- postToTransportCallbacks(TransportMessageHandler.MSG_PLAY);
+ postToCallback(CallbackMessageHandler.MSG_PLAY);
}
private void dispatchPlayUri(Uri uri, Bundle extras) {
- postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_URI, uri, extras);
+ postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
}
private void dispatchPlayFromSearch(String query, Bundle extras) {
- postToTransportCallbacks(TransportMessageHandler.MSG_PLAY_SEARCH, query, extras);
+ postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
}
private void dispatchSkipToTrack(long id) {
- postToTransportCallbacks(TransportMessageHandler.MSG_SKIP_TO_TRACK, id);
+ postToCallback(CallbackMessageHandler.MSG_SKIP_TO_TRACK, id);
}
private void dispatchPause() {
- postToTransportCallbacks(TransportMessageHandler.MSG_PAUSE);
+ postToCallback(CallbackMessageHandler.MSG_PAUSE);
}
private void dispatchStop() {
- postToTransportCallbacks(TransportMessageHandler.MSG_STOP);
+ postToCallback(CallbackMessageHandler.MSG_STOP);
}
private void dispatchNext() {
- postToTransportCallbacks(TransportMessageHandler.MSG_NEXT);
+ postToCallback(CallbackMessageHandler.MSG_NEXT);
}
private void dispatchPrevious() {
- postToTransportCallbacks(TransportMessageHandler.MSG_PREVIOUS);
+ postToCallback(CallbackMessageHandler.MSG_PREVIOUS);
}
private void dispatchFastForward() {
- postToTransportCallbacks(TransportMessageHandler.MSG_FAST_FORWARD);
+ postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD);
}
private void dispatchRewind() {
- postToTransportCallbacks(TransportMessageHandler.MSG_REWIND);
+ postToCallback(CallbackMessageHandler.MSG_REWIND);
}
private void dispatchSeekTo(long pos) {
- postToTransportCallbacks(TransportMessageHandler.MSG_SEEK_TO, pos);
+ postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos);
}
private void dispatchRate(Rating rating) {
- postToTransportCallbacks(TransportMessageHandler.MSG_RATE, rating);
+ postToCallback(CallbackMessageHandler.MSG_RATE, rating);
}
private void dispatchCustomAction(String action, Bundle args) {
- postToTransportCallbacks(TransportMessageHandler.MSG_CUSTOM_ACTION, action, args);
+ postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
}
- private TransportMessageHandler getTransportControlsHandlerForCallbackLocked(
- TransportControlsCallback callback) {
- for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
- TransportMessageHandler handler = mTransportCallbacks.get(i);
- if (callback == handler.mCallback) {
- return handler;
- }
- }
- return null;
+ private void dispatchMediaButton(Intent mediaButtonIntent) {
+ postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
}
- private boolean removeTransportControlsCallbackLocked(TransportControlsCallback callback) {
- for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
- if (callback == mTransportCallbacks.get(i).mCallback) {
- mTransportCallbacks.remove(i);
- return true;
- }
- }
- return false;
- }
-
- private void postToTransportCallbacks(int what, Object obj) {
- synchronized (mLock) {
- for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
- mTransportCallbacks.get(i).post(what, obj);
- }
- }
- }
-
- private void postToTransportCallbacks(int what, Object obj, Bundle args) {
- synchronized (mLock) {
- for (int i = mTransportCallbacks.size() - 1; i >= 0; i--) {
- mTransportCallbacks.get(i).post(what, obj, args);
- }
- }
- }
-
- private void postToTransportCallbacks(int what) {
- postToTransportCallbacks(what, null);
- }
-
- private CallbackMessageHandler getHandlerForCallbackLocked(Callback cb) {
- if (cb == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- CallbackMessageHandler handler = mCallbacks.get(i);
- if (cb == handler.mCallback) {
- return handler;
- }
- }
- return null;
- }
-
- private boolean removeCallbackLocked(Callback cb) {
- if (cb == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- CallbackMessageHandler handler = mCallbacks.get(i);
- if (cb == handler.mCallback) {
- mCallbacks.remove(i);
- return true;
- }
- }
- return false;
+ private void postToCallback(int what) {
+ postToCallback(what, null);
}
private void postCommand(String command, Bundle args, ResultReceiver resultCb) {
Command cmd = new Command(command, args, resultCb);
- synchronized (mLock) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).post(CallbackMessageHandler.MSG_COMMAND, cmd);
- }
- }
+ postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd);
}
- private void postMediaButton(Intent mediaButtonIntent) {
+ private void postToCallback(int what, Object obj) {
+ postToCallback(what, obj, null);
+ }
+
+ private void postToCallback(int what, Object obj, Bundle extras) {
synchronized (mLock) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).post(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
+ if (mCallback != null) {
+ mCallback.post(what, obj, extras);
}
}
}
@@ -791,30 +666,16 @@
}
/**
- * Receives generic commands or updates from controllers and the system.
- * Callbacks may be registered using {@link #addCallback}.
+ * Receives media buttons, transport controls, and commands from controllers
+ * and the system. A callback may be set using {@link #setCallback}.
*/
public abstract static class Callback {
+ private MediaSession mSession;
public Callback() {
}
/**
- * Called when a media button is pressed and this session has the
- * highest priority or a controller sends a media button event to the
- * session. TODO determine if using Intents identical to the ones
- * RemoteControlClient receives is useful
- * <p>
- * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
- * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
- *
- * @param mediaButtonIntent an intent containing the KeyEvent as an
- * extra
- */
- public void onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
- }
-
- /**
* Called when a controller has sent a command to this session.
* The owner of the session may handle custom commands but is not
* required to.
@@ -826,13 +687,81 @@
public void onCommand(@NonNull String command, @Nullable Bundle args,
@Nullable ResultReceiver cb) {
}
- }
- /**
- * Receives transport control commands. Callbacks may be registered using
- * {@link #addTransportControlsCallback}.
- */
- public static abstract class TransportControlsCallback {
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session. The default behavior will call the relevant method if the
+ * action for it was set.
+ * <p>
+ * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+ * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an
+ * extra
+ */
+ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+ if (mSession != null
+ && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+ KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
+ PlaybackState state = mSession.mPlaybackState;
+ long validActions = state == null ? 0 : state.getActions();
+ switch (ke.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
+ onPlay();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
+ onPause();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ onSkipToNext();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+ onSkipToPrevious();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ if ((validActions & PlaybackState.ACTION_STOP) != 0) {
+ onStop();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ onFastForward();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
+ onRewind();
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ boolean isPlaying = state == null ? false
+ : state.getState() == PlaybackState.STATE_PLAYING;
+ boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PLAY)) != 0;
+ boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PAUSE)) != 0;
+ if (isPlaying && canPause) {
+ onPause();
+ } else if (!isPlaying && canPlay) {
+ onPlay();
+ }
+ break;
+ }
+ }
+ }
+ return false;
+ }
/**
* Override to handle requests to begin playback.
@@ -920,6 +849,10 @@
*/
public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
}
+
+ private void setSession(MediaSession session) {
+ mSession = session;
+ }
}
/**
@@ -946,7 +879,7 @@
MediaSession session = mMediaSession.get();
try {
if (session != null) {
- session.postMediaButton(mediaButtonIntent);
+ session.dispatchMediaButton(mediaButtonIntent);
}
} finally {
if (cb != null) {
@@ -1232,44 +1165,6 @@
}
}
- private class CallbackMessageHandler extends Handler {
- private static final int MSG_MEDIA_BUTTON = 1;
- private static final int MSG_COMMAND = 2;
-
- private MediaSession.Callback mCallback;
-
- public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
- super(looper, null, true);
- mCallback = callback;
- }
-
- @Override
- public void handleMessage(Message msg) {
- synchronized (mLock) {
- if (mCallback == null) {
- return;
- }
- switch (msg.what) {
- case MSG_MEDIA_BUTTON:
- mCallback.onMediaButtonEvent((Intent) msg.obj);
- break;
- case MSG_COMMAND:
- Command cmd = (Command) msg.obj;
- mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
- break;
- }
- }
- }
-
- public void post(int what, Object obj) {
- obtainMessage(what, obj).sendToTarget();
- }
-
- public void post(int what, Object obj, int arg1) {
- obtainMessage(what, arg1, 0, obj).sendToTarget();
- }
- }
-
private static final class Command {
public final String command;
public final Bundle extras;
@@ -1282,7 +1177,8 @@
}
}
- private class TransportMessageHandler extends Handler {
+ private class CallbackMessageHandler extends Handler {
+
private static final int MSG_PLAY = 1;
private static final int MSG_PLAY_URI = 2;
private static final int MSG_PLAY_SEARCH = 3;
@@ -1296,12 +1192,14 @@
private static final int MSG_SEEK_TO = 11;
private static final int MSG_RATE = 12;
private static final int MSG_CUSTOM_ACTION = 13;
+ private static final int MSG_MEDIA_BUTTON = 14;
+ private static final int MSG_COMMAND = 15;
- private TransportControlsCallback mCallback;
+ private MediaSession.Callback mCallback;
- public TransportMessageHandler(Looper looper, TransportControlsCallback cb) {
- super(looper);
- mCallback = cb;
+ public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
+ super(looper, null, true);
+ mCallback = callback;
}
public void post(int what, Object obj, Bundle bundle) {
@@ -1318,6 +1216,10 @@
post(what, null);
}
+ public void post(int what, Object obj, int arg1) {
+ obtainMessage(what, arg1, 0, obj).sendToTarget();
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -1359,6 +1261,13 @@
case MSG_CUSTOM_ACTION:
mCallback.onCustomAction((String) msg.obj, msg.getData());
break;
+ case MSG_MEDIA_BUTTON:
+ mCallback.onMediaButtonEvent((Intent) msg.obj);
+ break;
+ case MSG_COMMAND:
+ Command cmd = (Command) msg.obj;
+ mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
+ break;
}
}
}
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index f075ded..a182982 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -29,6 +29,9 @@
import android.media.MediaMetadata;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
+import android.media.Rating;
+import android.media.RemoteControlClient;
+import android.media.RemoteControlClient.MetadataEditor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -229,8 +232,7 @@
}
}
- public void addRccListener(PendingIntent pi,
- MediaSession.TransportControlsCallback listener) {
+ public void addRccListener(PendingIntent pi, MediaSession.Callback listener) {
if (pi == null) {
Log.w(TAG, "Pending intent was null, can't add rcc listener.");
return;
@@ -247,10 +249,7 @@
// This is already the registered listener, ignore
return;
}
- // Otherwise it changed so we need to switch to the new one
- holder.mSession.removeTransportControlsCallback(holder.mRccListener);
}
- holder.mSession.addTransportControlsCallback(listener, mHandler);
holder.mRccListener = listener;
holder.mFlags |= MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
holder.mSession.setFlags(holder.mFlags);
@@ -266,7 +265,6 @@
}
SessionHolder holder = getHolder(pi, false);
if (holder != null && holder.mRccListener != null) {
- holder.mSession.removeTransportControlsCallback(holder.mRccListener);
holder.mRccListener = null;
holder.mFlags &= ~MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS;
holder.mSession.setFlags(holder.mFlags);
@@ -288,8 +286,7 @@
return;
}
if (holder.mMediaButtonListener != null) {
- // Already have this listener registered, but update it anyway as
- // the extras may have changed.
+ // Already have this listener registered
if (DEBUG) {
Log.d(TAG, "addMediaButtonListener already added " + pi);
}
@@ -300,11 +297,8 @@
// set this flag
holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
holder.mSession.setFlags(holder.mFlags);
- holder.mSession.addTransportControlsCallback(holder.mMediaButtonListener, mHandler);
-
- holder.mMediaButtonReceiver = new MediaButtonReceiver(pi, context);
- holder.mSession.addCallback(holder.mMediaButtonReceiver, mHandler);
holder.mSession.setMediaButtonReceiver(pi);
+ holder.update();
if (DEBUG) {
Log.d(TAG, "addMediaButtonListener added " + pi);
}
@@ -316,13 +310,10 @@
}
SessionHolder holder = getHolder(pi, false);
if (holder != null && holder.mMediaButtonListener != null) {
- holder.mSession.removeTransportControlsCallback(holder.mMediaButtonListener);
holder.mFlags &= ~MediaSession.FLAG_HANDLES_MEDIA_BUTTONS;
holder.mSession.setFlags(holder.mFlags);
holder.mMediaButtonListener = null;
- holder.mSession.removeCallback(holder.mMediaButtonReceiver);
- holder.mMediaButtonReceiver = null;
holder.update();
if (DEBUG) {
Log.d(TAG, "removeMediaButtonListener removed " + pi);
@@ -387,22 +378,7 @@
}
}
- private static final class MediaButtonReceiver extends MediaSession.Callback {
- private final PendingIntent mPendingIntent;
- private final Context mContext;
-
- public MediaButtonReceiver(PendingIntent pi, Context context) {
- mPendingIntent = pi;
- mContext = context;
- }
-
- @Override
- public void onMediaButtonEvent(Intent mediaButtonIntent) {
- MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
- }
- }
-
- private static final class MediaButtonListener extends MediaSession.TransportControlsCallback {
+ private static final class MediaButtonListener extends MediaSession.Callback {
private final PendingIntent mPendingIntent;
private final Context mContext;
@@ -412,6 +388,12 @@
}
@Override
+ public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+ MediaSessionLegacyHelper.sendKeyEvent(mPendingIntent, mContext, mediaButtonIntent);
+ return true;
+ }
+
+ @Override
public void onPlay() {
sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
}
@@ -468,10 +450,11 @@
public final MediaSession mSession;
public final PendingIntent mPi;
public MediaButtonListener mMediaButtonListener;
- public MediaButtonReceiver mMediaButtonReceiver;
- public MediaSession.TransportControlsCallback mRccListener;
+ public MediaSession.Callback mRccListener;
public int mFlags;
+ public SessionCallback mCb;
+
public SessionHolder(MediaSession session, PendingIntent pi) {
mSession = session;
mPi = pi;
@@ -479,8 +462,87 @@
public void update() {
if (mMediaButtonListener == null && mRccListener == null) {
+ mSession.setCallback(null);
mSession.release();
+ mCb = null;
mSessions.remove(mPi);
+ } else if (mCb == null) {
+ mCb = new SessionCallback();
+ mSession.setCallback(mCb);
+ }
+ }
+
+ private class SessionCallback extends MediaSession.Callback {
+
+ @Override
+ public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onMediaButtonEvent(mediaButtonIntent);
+ }
+ return true;
+ }
+
+ @Override
+ public void onPlay() {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onPlay();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onPause();
+ }
+ }
+
+ @Override
+ public void onSkipToNext() {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onSkipToNext();
+ }
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onSkipToPrevious();
+ }
+ }
+
+ @Override
+ public void onFastForward() {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onFastForward();
+ }
+ }
+
+ @Override
+ public void onRewind() {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onRewind();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mMediaButtonListener != null) {
+ mMediaButtonListener.onStop();
+ }
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ if (mRccListener != null) {
+ mRccListener.onSeekTo(pos);
+ }
+ }
+
+ @Override
+ public void onSetRating(Rating rating) {
+ if (mRccListener != null) {
+ mRccListener.onSetRating(rating);
+ }
}
}
}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 65bd677..2ad8eae 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -845,20 +845,23 @@
}
/**
- * Add a custom action to the playback state. Actions can be used to expose additional
- * functionality to {@link MediaController MediaControllers} beyond what is offered by the
- * standard transport controls.
+ * Add a custom action to the playback state. Actions can be used to
+ * expose additional functionality to {@link MediaController
+ * MediaControllers} beyond what is offered by the standard transport
+ * controls.
* <p>
- * e.g. start a radio station based on the current item or skip ahead by 30 seconds.
+ * e.g. start a radio station based on the current item or skip ahead by
+ * 30 seconds.
*
- * @param action An identifier for this action. It will be sent back to the
- * {@link MediaSession} through
- * {@link
- * MediaSession.TransportControlsCallback#onCustomAction(String, Bundle)}.
- * @param name The display name for the action. If text is shown with the action or used
- * for accessibility, this is what should be used.
- * @param icon The resource action of the icon that should be displayed for the action. The
- * resource should be in the package of the {@link MediaSession}.
+ * @param action An identifier for this action. It can be sent back to
+ * the {@link MediaSession} through
+ * {@link MediaController.TransportControls#sendCustomAction(String, Bundle)}.
+ * @param name The display name for the action. If text is shown with
+ * the action or used for accessibility, this is what should
+ * be used.
+ * @param icon The resource action of the icon that should be displayed
+ * for the action. The resource should be in the package of
+ * the {@link MediaSession}.
* @return this
*/
public Builder addCustomAction(String action, String name, int icon) {
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index ae6f5bc..97a6b83 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -53,13 +53,25 @@
private static final String PATH_CHANNEL = "channel";
private static final String PATH_PROGRAM = "program";
- private static final String PATH_INPUT = "input";
private static final String PATH_PASSTHROUGH = "passthrough";
/**
+ * An optional query, update or delete URI parameter that allows the caller to specify TV input
+ * ID to filter channels.
+ * @hide
+ */
+ public static final String PARAM_INPUT = "input";
+
+ /**
+ * An optional query, update or delete URI parameter that allows the caller to specify channel
+ * ID to filter programs.
+ * @hide
+ */
+ public static final String PARAM_CHANNEL = "channel";
+
+ /**
* An optional query, update or delete URI parameter that allows the caller to specify start
* time (in milliseconds since the epoch) to filter programs.
- *
* @hide
*/
public static final String PARAM_START_TIME = "start_time";
@@ -67,7 +79,6 @@
/**
* An optional query, update or delete URI parameter that allows the caller to specify end time
* (in milliseconds since the epoch) to filter programs.
- *
* @hide
*/
public static final String PARAM_END_TIME = "end_time";
@@ -76,7 +87,6 @@
* A query, update or delete URI parameter that allows the caller to operate on all or
* browsable-only channels. If set to "true", the rows that contain non-browsable channels are
* not affected.
- *
* @hide
*/
public static final String PARAM_BROWSABLE_ONLY = "browsable_only";
@@ -84,7 +94,6 @@
/**
* A optional query, update or delete URI parameter that allows the caller to specify canonical
* genre to filter programs.
- *
* @hide
*/
public static final String PARAM_CANONICAL_GENRE = "canonical_genre";
@@ -116,17 +125,7 @@
*/
public static final Uri buildChannelUriForPassthroughTvInput(String inputId) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
- .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
- .appendPath(PATH_PASSTHROUGH).build();
- }
-
- /**
- * Returns true, if {@code channelUri} is a channel URI for a passthrough TV input.
- * @hide
- */
- @SystemApi
- public static final boolean isChannelUriForPassthroughTvInput(Uri channelUri) {
- return channelUri.toString().endsWith(PATH_PASSTHROUGH);
+ .appendPath(PATH_PASSTHROUGH).appendPath(inputId).build();
}
/**
@@ -144,7 +143,7 @@
* @param channelUri The URI of the channel whose logo is pointed to.
*/
public static final Uri buildChannelLogoUri(Uri channelUri) {
- if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+ if (!isChannelUriForTunerTvInput(channelUri)) {
throw new IllegalArgumentException("Not a channel: " + channelUri);
}
return Uri.withAppendedPath(channelUri, Channels.Logo.CONTENT_DIRECTORY);
@@ -169,8 +168,8 @@
* @hide
*/
public static final Uri buildChannelsUriForInput(String inputId, boolean browsableOnly) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
- .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
+ return Channels.CONTENT_URI.buildUpon()
+ .appendQueryParameter(PARAM_INPUT, inputId)
.appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
}
@@ -194,8 +193,7 @@
Uri uri;
if (inputId == null) {
- uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
- .appendPath(PATH_CHANNEL).build();
+ uri = Channels.CONTENT_URI;
} else {
uri = buildChannelsUriForInput(inputId, browsableOnly);
}
@@ -217,9 +215,8 @@
* @param channelId The ID of the channel to return programs for.
*/
public static final Uri buildProgramsUriForChannel(long channelId) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
- .appendPath(PATH_CHANNEL).appendPath(String.valueOf(channelId))
- .appendPath(PATH_PROGRAM).build();
+ return Programs.CONTENT_URI.buildUpon()
+ .appendQueryParameter(PARAM_CHANNEL, String.valueOf(channelId)).build();
}
/**
@@ -228,7 +225,7 @@
* @param channelUri The URI of the channel to return programs for.
*/
public static final Uri buildProgramsUriForChannel(Uri channelUri) {
- if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+ if (!isChannelUriForTunerTvInput(channelUri)) {
throw new IllegalArgumentException("Not a channel: " + channelUri);
}
return buildProgramsUriForChannel(ContentUris.parseId(channelUri));
@@ -263,7 +260,7 @@
*/
public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
long endTime) {
- if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+ if (!isChannelUriForTunerTvInput(channelUri)) {
throw new IllegalArgumentException("Not a channel: " + channelUri);
}
return buildProgramsUriForChannel(ContentUris.parseId(channelUri), startTime, endTime);
@@ -279,41 +276,47 @@
return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
}
- /**
- * Extracts the {@link Channels#COLUMN_INPUT_ID} from a given URI.
- *
- * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(String)},
- * {@link #buildChannelsUriForInput(String, boolean)}, or
- * {@link #buildChannelsUriForCanonicalGenre(String, String, boolean)}.
- * @hide
- */
- public static final String getInputId(Uri channelsUri) {
- final List<String> paths = channelsUri.getPathSegments();
- if (paths.size() < 3) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(2))) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- return paths.get(1);
+ private static final boolean isTvUri(Uri uri) {
+ return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+ && AUTHORITY.equals(uri.getAuthority());
+ }
+
+ private static final boolean isTwoSegmentUriStartingWith(Uri uri, String pathSegment) {
+ List<String> pathSegments = uri.getPathSegments();
+ return pathSegments.size() == 2 && pathSegment.equals(pathSegments.get(0));
}
/**
- * Extracts the {@link Channels#_ID} from a given URI.
- *
- * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
- * {@link #buildProgramsUriForChannel(Uri, long, long)}.
+ * Returns true, if {@code uri} is a channel URI.
* @hide
*/
- public static final String getChannelId(Uri programsUri) {
- final List<String> paths = programsUri.getPathSegments();
- if (paths.size() < 3) {
- throw new IllegalArgumentException("Not programs: " + programsUri);
- }
- if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) {
- throw new IllegalArgumentException("Not programs: " + programsUri);
- }
- return paths.get(1);
+ public static final boolean isChannelUri(Uri uri) {
+ return isChannelUriForTunerTvInput(uri) || isChannelUriForPassthroughTvInput(uri);
+ }
+
+ /**
+ * Returns true, if {@code uri} is a channel URI for a tuner TV input.
+ * @hide
+ */
+ public static final boolean isChannelUriForTunerTvInput(Uri uri) {
+ return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_CHANNEL);
+ }
+
+ /**
+ * Returns true, if {@code uri} is a channel URI for a passthrough TV input.
+ * @hide
+ */
+ @SystemApi
+ public static final boolean isChannelUriForPassthroughTvInput(Uri uri) {
+ return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_PASSTHROUGH);
+ }
+
+ /**
+ * Returns true, if {@code uri} is a program URI.
+ * @hide
+ */
+ public static final boolean isProgramUri(Uri uri) {
+ return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_PROGRAM);
}
@@ -1104,7 +1107,6 @@
/**
* Column definitions for the TV programs that the user watched. Applications do not have access
* to this table.
- *
* @hide
*/
public static final class WatchedPrograms implements BaseTvColumns {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c7c7a92..4455901 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8971,8 +8971,10 @@
}
/**
- * Allows app to retrieve the MIME type of a URI without having permission
- * to access its content provider.
+ * Allows apps to retrieve the MIME type of a URI.
+ * If an app is in the same user as the ContentProvider, or if it is allowed to interact across
+ * users, then it does not need permission to access the ContentProvider.
+ * Either, it needs cross-user uri grants.
*
* CTS tests for this functionality can be run with "runtest cts-appsecurity".
*
@@ -8981,12 +8983,22 @@
*/
public String getProviderMimeType(Uri uri, int userId) {
enforceNotIsolatedCaller("getProviderMimeType");
- userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, false, ALLOW_NON_FULL_IN_PROFILE, "getProviderMimeType", null);
final String name = uri.getAuthority();
- final long ident = Binder.clearCallingIdentity();
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+ long ident = 0;
+ boolean clearedIdentity = false;
+ userId = unsafeConvertIncomingUser(userId);
+ if (UserHandle.getUserId(callingUid) != userId) {
+ if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+ callingUid, -1, true) == PackageManager.PERMISSION_GRANTED
+ || checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+ callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+ clearedIdentity = true;
+ ident = Binder.clearCallingIdentity();
+ }
+ }
ContentProviderHolder holder = null;
-
try {
holder = getContentProviderExternalUnchecked(name, null, userId);
if (holder != null) {
@@ -8996,10 +9008,17 @@
Log.w(TAG, "Content provider dead retrieving " + uri, e);
return null;
} finally {
- if (holder != null) {
- removeContentProviderExternalUnchecked(name, null, userId);
+ // We need to clear the identity to call removeContentProviderExternalUnchecked
+ if (!clearedIdentity) {
+ ident = Binder.clearCallingIdentity();
}
- Binder.restoreCallingIdentity(ident);
+ try {
+ if (holder != null) {
+ removeContentProviderExternalUnchecked(name, null, userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
return null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 44b7f01..36dec3e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4635,12 +4635,12 @@
// 2.) we are defering a needed dexopt
// 3.) we are skipping an unneeded dexopt
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
- for (String path : paths) {
- for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
- continue;
- }
+ for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+ if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+ continue;
+ }
+ for (String path : paths) {
try {
// This will return DEXOPT_NEEDED if we either cannot find any odex file for this
// patckage or the one we find does not match the image checksum (i.e. it was
@@ -4661,10 +4661,9 @@
// just result in an error again. Also, don't bother dexopting for other
// paths & ISAs.
return DEX_OPT_FAILED;
- } else {
- performedDexOpt = true;
- pkg.mDexOptPerformed.add(dexCodeInstructionSet);
}
+
+ performedDexOpt = true;
} else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) {
Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
@@ -4676,10 +4675,9 @@
// just result in an error again. Also, don't bother dexopting for other
// paths & ISAs.
return DEX_OPT_FAILED;
- } else {
- performedDexOpt = true;
- pkg.mDexOptPerformed.add(dexCodeInstructionSet);
}
+
+ performedDexOpt = true;
}
// We're deciding to defer a needed dexopt. Don't bother dexopting for other
@@ -4706,6 +4704,13 @@
return DEX_OPT_FAILED;
}
}
+
+ // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
+ // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us
+ // it isn't required. We therefore mark that this package doesn't need dexopt unless
+ // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
+ // it.
+ pkg.mDexOptPerformed.add(dexCodeInstructionSet);
}
// If we've gotten here, we're sure that no error occurred and that we haven't
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index feecfde..890d68d 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -86,10 +86,10 @@
mRouter.setRoutingCallback(new RoutingCallback(), null);
mSession = new MediaSession(mContext, "OneMedia");
- mSession.addCallback(mCallback);
- mSession.addTransportControlsCallback(new TransportCallback());
+ mSession.setCallback(mCallback);
mSession.setPlaybackState(mPlaybackState);
- mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
+ | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
mSession.setMediaRouter(mRouter);
mSession.setActive(true);
}
@@ -230,26 +230,6 @@
private class SessionCb extends MediaSession.Callback {
@Override
- public void onMediaButtonEvent(Intent mediaRequestIntent) {
- if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) {
- KeyEvent event = (KeyEvent) mediaRequestIntent
- .getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- Log.d(TAG, "play button received");
- mRenderer.onPlay();
- break;
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- Log.d(TAG, "pause button received");
- mRenderer.onPause();
- break;
- }
- }
- }
- }
-
- private class TransportCallback extends MediaSession.TransportControlsCallback {
- @Override
public void onPlay() {
mRenderer.onPlay();
}
@@ -315,7 +295,7 @@
updateState(PlaybackState.STATE_NONE);
break;
}
- }
+ }
}
}
}