Merge "Add audio attributes to notificationchannel"
diff --git a/Android.mk b/Android.mk
index 43dbef6..e68b310 100644
--- a/Android.mk
+++ b/Android.mk
@@ -306,6 +306,7 @@
core/java/android/service/wallpaper/IWallpaperService.aidl \
core/java/android/service/chooser/IChooserTargetService.aidl \
core/java/android/service/chooser/IChooserTargetResult.aidl \
+ core/java/android/text/ITextClassificationService.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
core/java/android/view/accessibility/IAccessibilityManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index e941819..805586e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3553,6 +3553,7 @@
method public android.view.WindowManager getWindowManager();
method public boolean hasWindowFocus();
method public void invalidateOptionsMenu();
+ method public boolean isActivityTransitionRunning();
method public boolean isChangingConfigurations();
method public final boolean isChild();
method public boolean isDestroyed();
@@ -22147,7 +22148,10 @@
method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
method public android.media.BufferingParams getDefaultBufferingParams();
+ method public android.media.MediaPlayer.DrmInfo getDrmInfo();
+ method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.Bundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22161,8 +22165,12 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
+ method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
+ method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
method public void reset();
+ method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void seekTo(int, int) throws java.lang.IllegalStateException;
method public void seekTo(int) throws java.lang.IllegalStateException;
method public void selectTrack(int) throws java.lang.IllegalStateException;
@@ -22179,10 +22187,15 @@
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDisplay(android.view.SurfaceHolder);
+ method public void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void setLooping(boolean);
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener, android.os.Handler);
method public void setOnErrorListener(android.media.MediaPlayer.OnErrorListener);
method public void setOnInfoListener(android.media.MediaPlayer.OnInfoListener);
method public void setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener);
@@ -22225,6 +22238,16 @@
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
+ public static final class MediaPlayer.DrmInfo {
+ method public java.lang.String[] getMimes();
+ method public java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public java.util.UUID[] getSupportedSchemes();
+ }
+
+ public static final class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.NoDrmSchemeException(java.lang.String);
+ }
+
public static abstract interface MediaPlayer.OnBufferingUpdateListener {
method public abstract void onBufferingUpdate(android.media.MediaPlayer, int);
}
@@ -22233,6 +22256,19 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
+ public static abstract class MediaPlayer.OnDrmConfigCallback {
+ ctor public MediaPlayer.OnDrmConfigCallback();
+ method public void onDrmConfig(android.media.MediaPlayer);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmInfoListener {
+ method public abstract void onDrmInfo(android.media.MediaPlayer, android.media.MediaPlayer.DrmInfo);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmPreparedListener {
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ }
+
public static abstract interface MediaPlayer.OnErrorListener {
method public abstract boolean onError(android.media.MediaPlayer, int, int);
}
@@ -22261,6 +22297,10 @@
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
+ public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ }
+
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
method public int describeContents();
method public android.media.MediaFormat getFormat();
@@ -49236,6 +49276,7 @@
method public android.graphics.PorterDuff.Mode getSecondaryProgressTintMode();
method public final synchronized void incrementProgressBy(int);
method public final synchronized void incrementSecondaryProgressBy(int);
+ method public boolean isAnimating();
method public synchronized boolean isIndeterminate();
method public void onRestoreInstanceState(android.os.Parcelable);
method public android.os.Parcelable onSaveInstanceState();
diff --git a/api/system-current.txt b/api/system-current.txt
index e4bcf7e..64e9571 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3675,6 +3675,7 @@
method public android.view.WindowManager getWindowManager();
method public boolean hasWindowFocus();
method public void invalidateOptionsMenu();
+ method public boolean isActivityTransitionRunning();
method public boolean isBackgroundVisibleBehind();
method public boolean isChangingConfigurations();
method public final boolean isChild();
@@ -23752,7 +23753,10 @@
method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
method public android.media.BufferingParams getDefaultBufferingParams();
+ method public android.media.MediaPlayer.DrmInfo getDrmInfo();
+ method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.Bundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -23766,8 +23770,12 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
+ method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
+ method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
method public void reset();
+ method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void seekTo(int, int) throws java.lang.IllegalStateException;
method public void seekTo(int) throws java.lang.IllegalStateException;
method public void selectTrack(int) throws java.lang.IllegalStateException;
@@ -23784,10 +23792,15 @@
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDisplay(android.view.SurfaceHolder);
+ method public void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void setLooping(boolean);
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener, android.os.Handler);
method public void setOnErrorListener(android.media.MediaPlayer.OnErrorListener);
method public void setOnInfoListener(android.media.MediaPlayer.OnInfoListener);
method public void setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener);
@@ -23830,6 +23843,16 @@
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
+ public static final class MediaPlayer.DrmInfo {
+ method public java.lang.String[] getMimes();
+ method public java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public java.util.UUID[] getSupportedSchemes();
+ }
+
+ public static final class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.NoDrmSchemeException(java.lang.String);
+ }
+
public static abstract interface MediaPlayer.OnBufferingUpdateListener {
method public abstract void onBufferingUpdate(android.media.MediaPlayer, int);
}
@@ -23838,6 +23861,19 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
+ public static abstract class MediaPlayer.OnDrmConfigCallback {
+ ctor public MediaPlayer.OnDrmConfigCallback();
+ method public void onDrmConfig(android.media.MediaPlayer);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmInfoListener {
+ method public abstract void onDrmInfo(android.media.MediaPlayer, android.media.MediaPlayer.DrmInfo);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmPreparedListener {
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ }
+
public static abstract interface MediaPlayer.OnErrorListener {
method public abstract boolean onError(android.media.MediaPlayer, int, int);
}
@@ -23866,6 +23902,10 @@
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
+ public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ }
+
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
method public int describeContents();
method public android.media.MediaFormat getFormat();
@@ -53003,6 +53043,7 @@
method public android.graphics.PorterDuff.Mode getSecondaryProgressTintMode();
method public final synchronized void incrementProgressBy(int);
method public final synchronized void incrementSecondaryProgressBy(int);
+ method public boolean isAnimating();
method public synchronized boolean isIndeterminate();
method public void onRestoreInstanceState(android.os.Parcelable);
method public android.os.Parcelable onSaveInstanceState();
diff --git a/api/test-current.txt b/api/test-current.txt
index 0006da6..a32300e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3555,6 +3555,7 @@
method public android.view.WindowManager getWindowManager();
method public boolean hasWindowFocus();
method public void invalidateOptionsMenu();
+ method public boolean isActivityTransitionRunning();
method public boolean isChangingConfigurations();
method public final boolean isChild();
method public boolean isDestroyed();
@@ -22239,7 +22240,10 @@
method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
method public android.media.BufferingParams getDefaultBufferingParams();
+ method public android.media.MediaPlayer.DrmInfo getDrmInfo();
+ method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public int getDuration();
+ method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
method public android.os.Bundle getMetrics();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22253,8 +22257,12 @@
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void prepareAsync() throws java.lang.IllegalStateException;
+ method public void prepareDrm(java.util.UUID, android.media.MediaPlayer.OnDrmConfigCallback) throws android.media.MediaPlayer.ProvisioningErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
method public void release();
+ method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
method public void reset();
+ method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void seekTo(int, int) throws java.lang.IllegalStateException;
method public void seekTo(int) throws java.lang.IllegalStateException;
method public void selectTrack(int) throws java.lang.IllegalStateException;
@@ -22271,10 +22279,15 @@
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDisplay(android.view.SurfaceHolder);
+ method public void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException;
method public void setLooping(boolean);
method public void setNextMediaPlayer(android.media.MediaPlayer);
method public void setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener);
method public void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener);
+ method public void setOnDrmInfoListener(android.media.MediaPlayer.OnDrmInfoListener, android.os.Handler);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener);
+ method public void setOnDrmPreparedListener(android.media.MediaPlayer.OnDrmPreparedListener, android.os.Handler);
method public void setOnErrorListener(android.media.MediaPlayer.OnErrorListener);
method public void setOnInfoListener(android.media.MediaPlayer.OnInfoListener);
method public void setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener);
@@ -22317,6 +22330,16 @@
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
+ public static final class MediaPlayer.DrmInfo {
+ method public java.lang.String[] getMimes();
+ method public java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public java.util.UUID[] getSupportedSchemes();
+ }
+
+ public static final class MediaPlayer.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.NoDrmSchemeException(java.lang.String);
+ }
+
public static abstract interface MediaPlayer.OnBufferingUpdateListener {
method public abstract void onBufferingUpdate(android.media.MediaPlayer, int);
}
@@ -22325,6 +22348,19 @@
method public abstract void onCompletion(android.media.MediaPlayer);
}
+ public static abstract class MediaPlayer.OnDrmConfigCallback {
+ ctor public MediaPlayer.OnDrmConfigCallback();
+ method public void onDrmConfig(android.media.MediaPlayer);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmInfoListener {
+ method public abstract void onDrmInfo(android.media.MediaPlayer, android.media.MediaPlayer.DrmInfo);
+ }
+
+ public static abstract interface MediaPlayer.OnDrmPreparedListener {
+ method public abstract void onDrmPrepared(android.media.MediaPlayer, boolean);
+ }
+
public static abstract interface MediaPlayer.OnErrorListener {
method public abstract boolean onError(android.media.MediaPlayer, int, int);
}
@@ -22353,6 +22389,10 @@
method public abstract void onVideoSizeChanged(android.media.MediaPlayer, int, int);
}
+ public static final class MediaPlayer.ProvisioningErrorException extends android.media.MediaDrmException {
+ ctor public MediaPlayer.ProvisioningErrorException(java.lang.String);
+ }
+
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
method public int describeContents();
method public android.media.MediaFormat getFormat();
@@ -49555,6 +49595,7 @@
method public android.graphics.PorterDuff.Mode getSecondaryProgressTintMode();
method public final synchronized void incrementProgressBy(int);
method public final synchronized void incrementSecondaryProgressBy(int);
+ method public boolean isAnimating();
method public synchronized boolean isIndeterminate();
method public void onRestoreInstanceState(android.os.Parcelable);
method public android.os.Parcelable onSaveInstanceState();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1d84ff5..6f95309 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4326,6 +4326,18 @@
}
}
+ /**
+ * Returns whether there are any activity transitions currently running on this
+ * activity. A return value of {@code true} can mean that either an enter or
+ * exit transition is running, including whether the background of the activity
+ * is animating as a part of that transition.
+ *
+ * @return true if a transition is currently running on this activity, false otherwise.
+ */
+ public boolean isActivityTransitionRunning() {
+ return mActivityTransitionState.isTransitionRunning();
+ }
+
private Bundle transferSpringboardActivityOptions(Bundle options) {
if (options == null && (mWindow != null && !mWindow.isActive())) {
final ActivityOptions activityOptions = getActivityOptions();
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index af41db0..1eaf029 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -208,6 +208,7 @@
private ArrayList<Matrix> mSharedElementParentMatrices;
private boolean mSharedElementTransitionComplete;
private boolean mViewsTransitionComplete;
+ private boolean mBackgroundAnimatorComplete;
private ArrayList<View> mStrippedTransitioningViews = new ArrayList<>();
public ActivityTransitionCoordinator(Window window,
@@ -884,6 +885,10 @@
startInputWhenTransitionsComplete();
}
+ protected void backgroundAnimatorComplete() {
+ mBackgroundAnimatorComplete = true;
+ }
+
protected void sharedElementTransitionComplete() {
mSharedElementTransitionComplete = true;
startInputWhenTransitionsComplete();
@@ -966,6 +971,11 @@
}
}
+ public boolean isTransitionRunning() {
+ return !(mViewsTransitionComplete && mSharedElementTransitionComplete &&
+ mBackgroundAnimatorComplete);
+ }
+
private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
private Rect mEpicenter;
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index f2616ff..b8f5a8e 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -336,6 +336,26 @@
}
}
+ public boolean isTransitionRunning() {
+ // Note that *only* enter *or* exit will be running at any given time
+ if (mEnterTransitionCoordinator != null) {
+ if (mEnterTransitionCoordinator.isTransitionRunning()) {
+ return true;
+ }
+ }
+ if (mCalledExitCoordinator != null) {
+ if (mCalledExitCoordinator.isTransitionRunning()) {
+ return true;
+ }
+ }
+ if (mReturnExitCoordinator != null) {
+ if (mReturnExitCoordinator.isTransitionRunning()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void startExitOutTransition(Activity activity, Bundle options) {
mEnterTransitionCoordinator = null;
if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index b569b79..0454acb 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -587,6 +587,7 @@
@Override
public void onAnimationEnd(Animator animation) {
makeOpaque();
+ backgroundAnimatorComplete();
}
});
mBackgroundAnimator.start();
@@ -598,9 +599,13 @@
makeOpaque();
}
});
+ backgroundAnimatorComplete();
} else {
makeOpaque();
+ backgroundAnimatorComplete();
}
+ } else {
+ backgroundAnimatorComplete();
}
}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 2672798..f04eef2 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -201,6 +201,7 @@
public void startExit() {
if (!mIsExitStarted) {
+ backgroundAnimatorComplete();
mIsExitStarted = true;
pauseInput();
ViewGroup decorView = getDecor();
@@ -303,11 +304,13 @@
mIsBackgroundReady = true;
notifyComplete();
}
+ backgroundAnimatorComplete();
}
});
mBackgroundAnimator.setDuration(getFadeDuration());
mBackgroundAnimator.start();
} else {
+ backgroundAnimatorComplete();
mIsBackgroundReady = true;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4be011e..52d7386 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1177,9 +1177,9 @@
= "android.app.action.SHOW_DEVICE_MONITORING_DIALOG";
/**
- * Broadcast Action: Sent after application delegation scopes are changed. The new list of
- * delegation scopes will be sent in an extra identified by the {@link #EXTRA_DELEGATION_SCOPES}
- * key.
+ * Broadcast Action: Sent after application delegation scopes are changed. The new delegation
+ * scopes will be sent in an {@code ArrayList<String>} extra identified by the
+ * {@link #EXTRA_DELEGATION_SCOPES} key.
*
* <p class=”note”> Note: This is a protected intent that can only be sent by the system.</p>
*/
@@ -1188,7 +1188,7 @@
"android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
/**
- * A list of Strings corresponding to the delegation scopes given to an app in the
+ * An {@code ArrayList<String>} corresponding to the delegation scopes given to an app in the
* {@link #ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED} broadcast.
*/
public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
@@ -3669,6 +3669,11 @@
* Granted APIs are determined by {@code scopes}, which is a list of the {@code DELEGATION_*}
* constants.
* <p>
+ * A broadcast with the {@link #ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED} action will be
+ * sent to the {@code delegatePackage} with its new scopes in an {@code ArrayList<String>} extra
+ * under the {@link #EXTRA_DELEGATION_SCOPES} key. The broadcast is sent with the
+ * {@link Intent#FLAG_RECEIVER_REGISTERED_ONLY} flag.
+ * <p>
* Delegated scopes are a per-user state. The delegated access is persistent until it is later
* cleared by calling this method with an empty {@code scopes} list or uninstalling the
* {@code delegatePackage}.
@@ -3704,7 +3709,7 @@
* @throws SecurityException if {@code admin} is not a device or a profile owner.
*/
@NonNull
- public List<String> getDelegatedScopes(@NonNull ComponentName admin,
+ public List<String> getDelegatedScopes(@Nullable ComponentName admin,
@NonNull String delegatedPackage) {
throwIfParentInstance("getDelegatedScopes");
if (mService != null) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 885b42f3..4534767 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1184,6 +1184,25 @@
}
/**
+ * Get the end time of the latest remote device discovery process.
+ * @return the latest time that the bluetooth adapter was/will be in discovery mode,
+ * in milliseconds since the epoch.
+ * This time can be in the future if {@link #startDiscovery()} has been called recently.
+ * @hide
+ */
+ public long getDiscoveryEndMillis() {
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) return mService.getDiscoveryEndMillis();
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return -1;
+ }
+
+ /**
* Start the remote device discovery process.
* <p>The discovery process usually involves an inquiry scan of about 12
* seconds, followed by a page scan of each new device to retrieve its
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 7c5458b..53fef2a 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -52,6 +52,7 @@
boolean startDiscovery();
boolean cancelDiscovery();
boolean isDiscovering();
+ long getDiscoveryEndMillis();
int getAdapterConnectionState();
int getProfileConnectionState(int profile);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 5ac33a1..fa9f394 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -352,8 +352,8 @@
if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
argsForZygote.add("--enable-safemode");
}
- if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
- argsForZygote.add("--enable-debugger");
+ if ((debugFlags & Zygote.DEBUG_ENABLE_JDWP) != 0) {
+ argsForZygote.add("--enable-jdwp");
}
if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
argsForZygote.add("--enable-checkjni");
@@ -367,6 +367,9 @@
if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
argsForZygote.add("--native-debuggable");
}
+ if ((debugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
+ argsForZygote.add("--java-debuggable");
+ }
if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
argsForZygote.add("--enable-assert");
}
@@ -379,9 +382,6 @@
}
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
- //TODO optionally enable debuger
- //argsForZygote.add("--enable-debugger");
-
// --setgroups is a comma-separated list
if (gids != null && gids.length > 0) {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/text/ITextClassificationService.aidl b/core/java/android/text/ITextClassificationService.aidl
new file mode 100644
index 0000000..a73dbf0
--- /dev/null
+++ b/core/java/android/text/ITextClassificationService.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 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.text;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Interface to the text classification service, which grants access to the text classification
+ * LSTM model file.
+ * {@hide}
+ */
+interface ITextClassificationService {
+
+ /**
+ * Request a file descriptor with read-only access to the LSTM model file.
+ * This file descriptor should be closed after the client is done with it.
+ */
+ ParcelFileDescriptor getModelFileFd();
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e8535cdb..597c051 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24768,6 +24768,13 @@
}
/**
+ * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+ */
+ public void setTooltip(@Nullable CharSequence tooltipText) {
+ setTooltipText(tooltipText);
+ }
+
+ /**
* Returns the view's tooltip text.
*
* @return the tooltip text
@@ -24777,6 +24784,14 @@
return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null;
}
+ /**
+ * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+ */
+ @Nullable
+ public CharSequence getTooltip() {
+ return getTooltipText();
+ }
+
private boolean showTooltip(int x, int y, boolean fromLongClick) {
if (mAttachInfo == null) {
return false;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index c424086..0053caa 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1924,7 +1924,7 @@
public Transition getEnterTransition() { return null; }
/**
- * Returns he Transition that will be used to move Views out of the scene when the Window is
+ * Returns the Transition that will be used to move Views out of the scene when the Window is
* preparing to close, for example after a call to
* {@link android.app.Activity#finishAfterTransition()}. The exiting
* Views will be those that are regular Views or ViewGroups that have
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 46324a3..5199b26 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -232,7 +232,7 @@
mDecorView.getLayoutParams();
updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
- p.width, p.height, mAnchoredGravity));
+ p.width, p.height, mAnchoredGravity, false));
update(p.x, p.y, -1, -1, true);
}
}
@@ -1237,7 +1237,7 @@
preparePopup(p);
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
- p.width, p.height, gravity);
+ p.width, p.height, gravity, mAllowScrollingAnchorParent);
updateAboveAnchor(aboveAnchor);
p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
@@ -1529,10 +1529,12 @@
* @param xOffset absolute horizontal offset from the left of the anchor
* @param yOffset absolute vertical offset from the top of the anchor
* @param gravity horizontal gravity specifying popup alignment
+ * @param allowScroll whether the anchor view's parent may be scrolled
+ * when the popup window doesn't fit on screen
* @return true if the popup is translated upwards to fit on screen
*/
private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
- int xOffset, int yOffset, int width, int height, int gravity) {
+ int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
final int anchorHeight = anchor.getHeight();
final int anchorWidth = anchor.getWidth();
if (mOverlapAnchor) {
@@ -1586,7 +1588,7 @@
final int scrollY = anchor.getScrollY();
final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
scrollY + height + anchorHeight + yOffset);
- if (mAllowScrollingAnchorParent && anchor.requestRectangleOnScreen(r, true)) {
+ if (allowScroll && anchor.requestRectangleOnScreen(r, true)) {
// Reset for the new anchor position.
anchor.getLocationInWindow(drawingLocation);
outParams.x = drawingLocation[0] + xOffset;
@@ -2182,15 +2184,19 @@
}
final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
- width, height, gravity);
+ width, height, gravity, mAllowScrollingAnchorParent);
updateAboveAnchor(aboveAnchor);
final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y
|| oldWidth != p.width || oldHeight != p.height;
- // If width and mWidth were both < 0 then we have a MATCH_PARENT/WRAP_CONTENT case.
- // findDropDownPosition will have resolved this to absolute values,
- // but we don't want to update mWidth/mHeight to these absolute values.
- update(p.x, p.y, width < 0 ? width : p.width, height < 0 ? height : p.height, paramsChanged);
+
+ // If width and mWidth were both < 0 then we have a MATCH_PARENT or
+ // WRAP_CONTENT case. findDropDownPosition will have resolved this to
+ // absolute values, but we don't want to update mWidth/mHeight to these
+ // absolute values.
+ final int newWidth = width < 0 ? width : p.width;
+ final int newHeight = height < 0 ? height : p.height;
+ update(p.x, p.y, newWidth, newHeight, paramsChanged);
}
/**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 266ff75..ec2adfb 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -2058,6 +2058,18 @@
}
/**
+ * Returns whether the ProgressBar is animating or not. This is essentially the same
+ * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible,
+ * as indeterminate ProgressBars are always animating, and non-indeterminate
+ * ProgressBars are not animating.
+ *
+ * @return true if the ProgressBar is animating, false otherwise.
+ */
+ public boolean isAnimating() {
+ return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown();
+ }
+
+ /**
* Command for sending an accessibility event.
*/
private class AccessibilityEventSender implements Runnable {
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e1e0a21..59416dd 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -33,7 +33,7 @@
*/
/** enable debugging over JDWP */
- public static final int DEBUG_ENABLE_DEBUGGER = 1;
+ public static final int DEBUG_ENABLE_JDWP = 1;
/** enable JNI checks */
public static final int DEBUG_ENABLE_CHECKJNI = 1 << 1;
/** enable Java programming language "assert" statements */
@@ -46,8 +46,10 @@
public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
/** Always use JIT-ed code. */
public static final int DEBUG_ALWAYS_JIT = 1 << 6;
- /** Make the code debuggable with turning off some optimizations. */
+ /** Make the code native debuggable by turning off some optimizations. */
public static final int DEBUG_NATIVE_DEBUGGABLE = 1 << 7;
+ /** Make the code Java debuggable by turning off some optimizations. */
+ public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = 0;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 83e3cff..8fe374c 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -342,8 +342,9 @@
int[] gids;
/**
- * From --enable-debugger, --enable-checkjni, --enable-assert,
- * --enable-safemode, --generate-debug-info and --enable-jni-logging.
+ * From --enable-jdwp, --enable-checkjni, --enable-assert,
+ * --enable-safemode, --generate-debug-info, --enable-jni-logging,
+ * --java-debuggable, and --native-debuggable.
*/
int debugFlags;
@@ -453,8 +454,8 @@
targetSdkVersionSpecified = true;
targetSdkVersion = Integer.parseInt(
arg.substring(arg.indexOf('=') + 1));
- } else if (arg.equals("--enable-debugger")) {
- debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+ } else if (arg.equals("--enable-jdwp")) {
+ debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
} else if (arg.equals("--enable-safemode")) {
debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
} else if (arg.equals("--enable-checkjni")) {
@@ -465,6 +466,8 @@
debugFlags |= Zygote.DEBUG_ALWAYS_JIT;
} else if (arg.equals("--native-debuggable")) {
debugFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE;
+ } else if (arg.equals("--java-debuggable")) {
+ debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
} else if (arg.equals("--enable-jni-logging")) {
debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
} else if (arg.equals("--enable-assert")) {
@@ -676,14 +679,14 @@
* Applies debugger system properties to the zygote arguments.
*
* If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
- * the debugger state is specified via the "--enable-debugger" flag
+ * the debugger state is specified via the "--enable-jdwp" flag
* in the spawn request.
*
* @param args non-null; zygote spawner args
*/
public static void applyDebuggerSystemProperty(Arguments args) {
if (RoSystemProperties.DEBUGGABLE) {
- args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+ args.debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
}
}
@@ -705,7 +708,7 @@
int peerUid = peer.getUid();
if (args.invokeWith != null && peerUid != 0 &&
- (args.debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) == 0) {
+ (args.debugFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
throw new ZygoteSecurityException("Peer is permitted to specify an"
+ "explicit invoke-with wrapper command only for debuggable"
+ "applications.");
diff --git a/core/res/res/values-mcc208-mnc10/config.xml b/core/res/res/values-mcc208-mnc10/config.xml
index d3640e5..3ed7818 100644
--- a/core/res/res/values-mcc208-mnc10/config.xml
+++ b/core/res/res/values-mcc208-mnc10/config.xml
@@ -31,28 +31,4 @@
<item>[ApnSettingV3]INTERNET NRJ,internetnrj,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4E</item>
</string-array>
- <string-array translatable="false" name="config_operatorConsideredNonRoaming">
- <item>21401</item>
- <item>21402</item>
- <item>21403</item>
- <item>21404</item>
- <item>21405</item>
- <item>21406</item>
- <item>21407</item>
- <item>21408</item>
- <item>21409</item>
- <item>21410</item>
- <item>21411</item>
- <item>21412</item>
- <item>21413</item>
- <item>21414</item>
- <item>21415</item>
- <item>21416</item>
- <item>21417</item>
- <item>21418</item>
- <item>21419</item>
- <item>21420</item>
- <item>21421</item>
- </string-array>
-
</resources>
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 88dde53..62abdefd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -47,6 +47,7 @@
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.BufferingParams;
+import android.media.MediaDrm;
import android.media.MediaFormat;
import android.media.MediaTimeProvider;
import android.media.PlaybackParams;
@@ -60,6 +61,7 @@
import libcore.io.IoBridge;
import libcore.io.Libcore;
+import libcore.io.Streams;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -70,13 +72,20 @@
import java.lang.Runnable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.util.Arrays;
import java.util.BitSet;
+import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
+import java.util.UUID;
import java.util.Vector;
-import java.lang.ref.WeakReference;
+
/**
* MediaPlayer class can be used to control playback
@@ -625,6 +634,17 @@
private int mUsage = -1;
private boolean mBypassInterruptionPolicy;
+ // Modular DRM
+ private UUID mDrmUUID;
+ private final Object mDrmLock = new Object();
+ private DrmInfo mDrmInfo;
+ private boolean mDrmInfoResolved;
+ private boolean mActiveDrmScheme;
+ private boolean mDrmConfigAllowed;
+ private boolean mDrmProvisioningInProgress;
+ private boolean mPrepareDrmInProgress;
+ private ProvisioningThread mDrmProvisioningThread;
+
/**
* Default constructor. Consider using one of the create() methods for
* synchronously instantiating a MediaPlayer from a Uri or resource.
@@ -1866,6 +1886,12 @@
mTimeProvider = null;
}
mOnSubtitleDataListener = null;
+
+ // Modular DRM clean up
+ mOnDrmInfoHandlerDelegate = null;
+ mOnDrmPreparedHandlerDelegate = null;
+ resetDrmState();
+
_release();
}
@@ -1906,6 +1932,8 @@
mIndexTrackPairs.clear();
mInbandTrackIndices.clear();
};
+
+ resetDrmState();
}
private native void _reset();
@@ -2999,6 +3027,7 @@
private static final int MEDIA_INFO = 200;
private static final int MEDIA_SUBTITLE_DATA = 201;
private static final int MEDIA_META_DATA = 202;
+ private static final int MEDIA_DRM_INFO = 210;
private TimeProvider mTimeProvider;
@@ -3037,11 +3066,43 @@
MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
sendMessage(msg2);
}
+
+ // MEDIA_DRM_INFO is fired (if available) before MEDIA_PREPARED.
+ // An empty mDrmInfo indicates prepared is done but the source is not DRM protected.
+ // Setting this before the callback so onPreparedListener can call getDrmInfo to
+ // get the right state
+ mDrmInfoResolved = true;
+
OnPreparedListener onPreparedListener = mOnPreparedListener;
if (onPreparedListener != null)
onPreparedListener.onPrepared(mMediaPlayer);
return;
+ case MEDIA_DRM_INFO:
+ Log.v(TAG, "MEDIA_DRM_INFO " + mOnDrmInfoHandlerDelegate);
+
+ if (msg.obj == null) {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
+ } else if (msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ DrmInfo drmInfo = new DrmInfo(parcel);
+
+ OnDrmInfoHandlerDelegate onDrmInfoHandlerDelegate;
+ synchronized (mDrmLock) {
+ mDrmInfo = drmInfo.makeCopy();
+ // local copy while keeping the lock
+ onDrmInfoHandlerDelegate = mOnDrmInfoHandlerDelegate;
+ }
+
+ // notifying the client outside the lock
+ if (onDrmInfoHandlerDelegate != null) {
+ onDrmInfoHandlerDelegate.notifyClient(drmInfo);
+ }
+ } else {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj NONE; UNEXPECTED" + msg.obj);
+ }
+ return;
+
case MEDIA_PLAYBACK_COMPLETE:
{
mOnCompletionInternalListener.onCompletion(mMediaPlayer);
@@ -3700,6 +3761,988 @@
private OnInfoListener mOnInfoListener;
+ // Modular DRM begin
+
+ /**
+ * Interface definition of a callback to be invoked when the app
+ * can do DRM configuration (get/set properties) before the session
+ * is opened. This facilitates configuration of the properties, like
+ * 'securityLevel', which has to be set after DRM scheme creation but
+ * before the DRM session is opened.
+ *
+ * The only allowed DRM calls in this listener are getDrmPropertyString
+ * and setDrmPropertyString.
+ *
+ */
+ public static abstract class OnDrmConfigCallback
+ {
+ /**
+ * Called to give the app the opportunity to configure DRM before the session is created
+ *
+ * @param mp the {@code MediaPlayer} associated with this callback
+ */
+ public void onDrmConfig(MediaPlayer mp) {}
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the
+ * DRM info becomes available
+ */
+ public interface OnDrmInfoListener
+ {
+ /**
+ * Called to indicate DRM info is available
+ *
+ * @param mp the {@code MediaPlayer} associated with this callback
+ * @param drmInfo DRM info of the source including PSSH, mimes, and subset
+ * of crypto schemes supported by this device
+ */
+ public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM info is
+ * known.
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnDrmInfoListener(OnDrmInfoListener listener)
+ {
+ setOnDrmInfoListener(listener, null);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM info is
+ * known.
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnDrmInfoListener(OnDrmInfoListener listener, Handler handler)
+ {
+ synchronized (mDrmLock) {
+ if (listener != null) {
+ mOnDrmInfoHandlerDelegate = new OnDrmInfoHandlerDelegate(this, listener, handler);
+ } else {
+ mOnDrmInfoHandlerDelegate = null;
+ }
+ } // synchronized
+ }
+
+ private OnDrmInfoHandlerDelegate mOnDrmInfoHandlerDelegate;
+
+ /**
+ * Interface definition of a callback to notify the app when the
+ * DRM is ready for key request/response
+ */
+ public interface OnDrmPreparedListener
+ {
+ /**
+ * Called to notify the app that prepareDrm is finished and ready for key request/response
+ *
+ * @param mp the {@code MediaPlayer} associated with this callback
+ * @param success the result of DRM preparation
+ */
+ public void onDrmPrepared(MediaPlayer mp, boolean success);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM object is prepared.
+ *
+ * @param listener the callback that will be run
+ */
+ public void setOnDrmPreparedListener(OnDrmPreparedListener listener)
+ {
+ setOnDrmPreparedListener(listener, null);
+ }
+
+ /**
+ * Register a callback to be invoked when the DRM object is prepared.
+ *
+ * @param listener the callback that will be run
+ * @param handler the Handler that will receive the callback
+ */
+ public void setOnDrmPreparedListener(OnDrmPreparedListener listener, Handler handler)
+ {
+ synchronized (mDrmLock) {
+ if (listener != null) {
+ mOnDrmPreparedHandlerDelegate = new OnDrmPreparedHandlerDelegate(this,
+ listener, handler);
+ } else {
+ mOnDrmPreparedHandlerDelegate = null;
+ }
+ } // synchronized
+ }
+
+ private OnDrmPreparedHandlerDelegate mOnDrmPreparedHandlerDelegate;
+
+
+ private class OnDrmInfoHandlerDelegate {
+ private MediaPlayer mMediaPlayer;
+ private OnDrmInfoListener mOnDrmInfoListener;
+ private Handler mHandler;
+
+ OnDrmInfoHandlerDelegate(MediaPlayer mp, OnDrmInfoListener listener, Handler handler) {
+ mMediaPlayer = mp;
+ mOnDrmInfoListener = listener;
+
+ // find the looper for our new event handler
+ Looper looper = null;
+ if (handler != null) {
+ looper = handler.getLooper();
+ }
+
+ // construct the event handler with this looper
+ if (looper != null) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ public void handleMessage(Message msg) {
+ DrmInfo drmInfo = (DrmInfo)msg.obj;
+ mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
+ }
+ };
+ }
+ }
+
+ void notifyClient(DrmInfo drmInfo) {
+ if ( mHandler != null ) {
+ Message msg = new Message(); // no message type needed
+ msg.obj = drmInfo;
+ mHandler.sendMessage(msg);
+ }
+ else { // no handler: direct call
+ mOnDrmInfoListener.onDrmInfo(mMediaPlayer, drmInfo);
+ }
+ }
+ }
+
+ private class OnDrmPreparedHandlerDelegate {
+ private MediaPlayer mMediaPlayer;
+ private OnDrmPreparedListener mOnDrmPreparedListener;
+ private Handler mHandler;
+
+ OnDrmPreparedHandlerDelegate(MediaPlayer mp, OnDrmPreparedListener listener,
+ Handler handler) {
+ mMediaPlayer = mp;
+ mOnDrmPreparedListener = listener;
+
+ // find the looper for our new event handler
+ Looper looper = null;
+ if (handler != null) {
+ looper = handler.getLooper();
+ }
+
+ // construct the event handler with this looper
+ if (looper != null) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ public void handleMessage(Message msg) {
+ boolean success = (msg.arg1 == 0) ? false : true;
+ mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, success);
+ }
+ };
+ }
+ }
+
+ void notifyClient(boolean success) {
+ if ( mHandler != null ) {
+ Message msg = new Message(); // no message type needed
+ msg.arg1 = success ? 1 : 0;
+ mHandler.sendMessage(msg);
+ }
+ else { // no handler: direct call
+ mOnDrmPreparedListener.onDrmPrepared(mMediaPlayer, success);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the DRM Info associated with the current source
+ *
+ * @throws IllegalStateException if called before prepare()
+ */
+ public DrmInfo getDrmInfo()
+ {
+ DrmInfo drmInfo = null;
+
+ // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
+ // regardless below returns drmInfo anyway instead of raising an exception
+ synchronized (mDrmLock) {
+ if (!mDrmInfoResolved && mDrmInfo == null) {
+ final String msg = "The Player has not been prepared yet";
+ Log.v(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmInfo != null) {
+ drmInfo = mDrmInfo.makeCopy();
+ }
+ } // synchronized
+
+ return drmInfo;
+ }
+
+ private native void _prepareDrm(@NonNull byte[] uuid, int mode)
+ throws UnsupportedSchemeException, ResourceBusyException, NotProvisionedException;
+
+ /**
+ * Prepares the DRM for the current source
+ * <p>
+ * If {@code OnDrmConfigCallback} is registered, it will be called half-way into
+ * preparation to allow configuration of the DRM properties before opening the
+ * DRM session. Note that the callback is called synchronously in the thread that called
+ * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
+ * and {@code setDrmPropertyString} calls and refrain from any lengthy operation.
+ * <p>
+ * If the device has not been provisioned before, this call also provisions the device
+ * which involves accessing the provisioning server and can take a variable time to
+ * complete depending on the network connectivity.
+ * If OnDrmPreparedListener is registered, prepareDrm() runs in non-blocking
+ * mode by launching the provisioning in the background and returning. The listener
+ * will be called when provisioning and preperation has finished. If a
+ * OnDrmPreparedListener is not registered, prepareDrm() waits till provisioning
+ * and preperation has finished, i.e., runs in blocking mode.
+ * <p>
+ * If OnDrmPreparedListener is registered, it is called to indicated the DRM session
+ * being ready regardless of blocking or non-blocking mode. The application should
+ * not make any assumption about its call sequence (e.g., before or after prepareDrm
+ * returns) or the thread context that will execute the listener.
+ * <p>
+ *
+ * @param uuid The UUID of the crypto scheme.
+ *
+ * @throws IllegalStateException if called before prepare(), or there exists a Drm already
+ * @throws UnsupportedSchemeException if the crypto scheme is not supported
+ * @throws ResourceBusyException if required DRM resources are in use
+ * @throws ProvisioningErrorException if provisioning is required but an attempt failed
+ */
+ public void prepareDrm(@NonNull UUID uuid, OnDrmConfigCallback configCallback)
+ throws UnsupportedSchemeException,
+ ResourceBusyException, ProvisioningErrorException
+ {
+ boolean allDoneWithoutProvisioning = false;
+ // get a snapshot as we'll use them outside the lock
+ OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate = null;
+
+ synchronized (mDrmLock) {
+
+ // only allowing if tied to a protected source; might releax for releasing offline keys
+ if (mDrmInfo == null) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: " +
+ "The player must be prepared and DRM " +
+ "info be retrieved before this call.", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mActiveDrmScheme) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " +
+ "an active DRM scheme with %s.", uuid, mDrmUUID);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mPrepareDrmInProgress) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: There is already " +
+ "a pending prepareDrm call.", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmProvisioningInProgress) {
+ final String msg = String.format("prepareDrm(%s): Unexpectd: Provisioning is " +
+ "already in progress.", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ mPrepareDrmInProgress = true;
+ // local copy while the lock is held
+ onDrmPreparedHandlerDelegate = mOnDrmPreparedHandlerDelegate;
+
+ if (configCallback != null) {
+ try {
+ boolean allowOpenSession = false; // just pre-openSession
+ _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+ } catch (IllegalStateException e) {
+ final String msg = String.format("prepareDrm(): Wrong usage: The player must " +
+ "be in prepared state to call prepareDrm().");
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ } catch (NotProvisionedException e) { // the pre-config step won't raise this
+ final String msg = String.format("prepareDrm: Unexpected " +
+ "NotProvisionedException here.");
+ Log.e(TAG, msg);
+ throw new ProvisioningErrorException(msg);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("prepareDrm: Exception %s", e));
+ throw e;
+ } finally {
+ mPrepareDrmInProgress = false;
+ }
+ }
+
+ mDrmConfigAllowed = true;
+ } // synchronized
+
+
+ // call the callback outside the lock
+ if (configCallback != null) {
+ configCallback.onDrmConfig(this);
+ }
+
+ synchronized (mDrmLock) {
+ mDrmConfigAllowed = false;
+
+ try {
+ boolean allowOpenSession = true; // all in
+ _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+
+ mPrepareDrmInProgress = false;
+
+ allDoneWithoutProvisioning = true;
+ } catch (IllegalStateException e) {
+ final String msg = String.format("prepareDrm(%s): Wrong usage: The player must be" +
+ " in prepared state to call prepareDrm().", uuid);
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, String.format("prepareDrm: NotProvisionedException"));
+
+ // handle provisioning internally
+ boolean result = HandleProvisioninig(uuid);
+
+ // if blocking mode, we're already done;
+ // if non-blocking mode, we attempted to launch background provisioning
+ if (result == false) {
+ final String msg =
+ String.format("prepareDrm: Provisioning was required but failed.");
+ Log.e(TAG, msg);
+ throw new ProvisioningErrorException(msg);
+ }
+
+ // nothing else to do;
+ // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
+ } catch (Exception e) {
+ Log.w(TAG, String.format("prepareDrm: Exception %s", e));
+ throw e;
+ } finally {
+ mPrepareDrmInProgress = false;
+ }
+ } // synchronized
+
+
+ // if finished successfully without provisioning, call the callback outside the lock
+ if (allDoneWithoutProvisioning) {
+ if (onDrmPreparedHandlerDelegate != null)
+ onDrmPreparedHandlerDelegate.notifyClient(true /*success*/);
+ }
+
+ }
+
+
+ private native void _releaseDrm();
+
+ /**
+ * Releases the DRM session
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session to release
+ */
+ public void releaseDrm()
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, String.format("releaseDrm(%s): No active DRM scheme to release."));
+ throw new NoDrmSchemeException("releaseDrm: No active DRM scheme to release.");
+ } else {
+ _releaseDrm();
+
+ mActiveDrmScheme = false;
+ }
+ } // synchronized
+ }
+
+
+ @NonNull
+ private native MediaDrm.KeyRequest _getKeyRequest(@NonNull byte[] scope,
+ @Nullable String mimeType, @MediaDrm.KeyType int keyType,
+ @Nullable Map<String, String> optionalParameters)
+ throws NotProvisionedException;
+
+ /**
+ * A key request/response exchange occurs between the app and a license server
+ * to obtain or release keys used to decrypt encrypted content.
+ * <p>
+ * getKeyRequest() is used to obtain an opaque key request byte array that is
+ * delivered to the license server. The opaque key request byte array is returned
+ * in KeyRequest.data. The recommended URL to deliver the key request to is
+ * returned in KeyRequest.defaultUrl.
+ * <p>
+ * After the app has received the key request response from the server,
+ * it should deliver to the response to the DRM engine plugin using the method
+ * {@link #provideKeyResponse}.
+ *
+ * @param scope may be a container-specific initialization data or a keySetId,
+ * depending on the specified keyType.
+ * When the keyType is KEY_TYPE_STREAMING or KEY_TYPE_OFFLINE, scope should be set to
+ * the container-specific initialization data. Its meaning is interpreted based on the
+ * mime type provided in the mimeType parameter. It could contain, for example,
+ * the content ID, key ID or other data obtained from the content metadata that is
+ * required in generating the key request.
+ * When the keyType is KEY_TYPE_RELEASE, scope should be set to the keySetId of
+ * the keys being released.
+ *
+ * @param mimeType identifies the mime type of the content
+ *
+ * @param keyType specifes the type of the request. The request may be to acquire
+ * keys for streaming or offline content, or to release previously acquired
+ * keys, which are identified by a keySetId.
+ *
+ * @param optionalParameters are included in the key request message to
+ * allow a client application to provide additional message parameters to the server.
+ * This may be {@code null} if no additional parameters are to be sent.
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ */
+ @NonNull
+ public MediaDrm.KeyRequest getKeyRequest(@NonNull byte[] scope, @Nullable String mimeType,
+ @MediaDrm.KeyType int keyType, @Nullable Map<String, String> optionalParameters)
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException"));
+ throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ return _getKeyRequest(scope, mimeType, keyType, optionalParameters);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, String.format("getKeyRequest NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here."));
+ throw new IllegalStateException("getKeyRequest: Unexpected provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, String.format("getKeyRequest Exception %s", e));
+ throw e;
+ }
+
+ } // synchronized
+ }
+
+
+ @Nullable
+ private native byte[] _provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
+ throws DeniedByServerException;
+
+ /**
+ * A key response is received from the license server by the app, then it is
+ * provided to the DRM engine plugin using provideKeyResponse. When the
+ * response is for an offline key request, a key-set identifier is returned that
+ * can be used to later restore the keys to a new session with the method
+ * {@ link # restoreKeys}.
+ * When the response is for a streaming or release request, null is returned.
+ *
+ * @param keySetId When the response is for a release request, keySetId identifies
+ * the saved key associated with the release request (i.e., the same keySetId
+ * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the
+ * response is for either streaming or offline key requests.
+ *
+ * @param response the byte array response from the server
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ * @throws DeniedByServerException if the response indicates that the
+ * server rejected the request
+ */
+ public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
+ throws NoDrmSchemeException, DeniedByServerException
+ {
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, String.format("getKeyRequest NoDrmSchemeException"));
+ throw new NoDrmSchemeException("getKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ return _provideKeyResponse(keySetId, response);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("provideKeyResponse Exception %s", e));
+ throw e;
+ }
+ } // synchronized
+ }
+
+
+ private native void _restoreKeys(@NonNull byte[] keySetId);
+
+ /**
+ * Restore persisted offline keys into a new session. keySetId identifies the
+ * keys to load, obtained from a prior call to {@link #provideKeyResponse}.
+ *
+ * @param keySetId identifies the saved key set to restore
+ */
+ public void restoreKeys(@NonNull byte[] keySetId)
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme) {
+ Log.w(TAG, String.format("restoreKeys NoDrmSchemeException"));
+ throw new NoDrmSchemeException("restoreKeys: Has to set a DRM scheme first.");
+ }
+
+ try {
+ _restoreKeys(keySetId);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("restoreKeys Exception %s", e));
+ throw e;
+ }
+
+ } // synchronized
+ }
+
+
+ @NonNull
+ private native String _getDrmPropertyString(@NonNull String propertyName);
+
+ /**
+ * Read a DRM engine plugin String property value, given the property name string.
+ * <p>
+ * @param propertyName the property name
+ *
+ * Standard fields names are:
+ * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
+ * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+ */
+ @NonNull
+ public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
+ throws NoDrmSchemeException
+ {
+ String value;
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme && !mDrmConfigAllowed) {
+ Log.w(TAG, String.format("getDrmPropertyString NoDrmSchemeException"));
+ throw new NoDrmSchemeException("getDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ value = _getDrmPropertyString(propertyName);
+ } catch (Exception e) {
+ Log.w(TAG, String.format("getDrmPropertyString Exception %s", e));
+ throw e;
+ }
+ } // synchronized
+
+ return value;
+ }
+
+ private native void _setDrmPropertyString(@NonNull String propertyName, @NonNull String value);
+
+ /**
+ * Set a DRM engine plugin String property value.
+ * <p>
+ * @param propertyName the property name
+ * @param value the property value
+ *
+ * Standard fields names are:
+ * {link #PROPERTY_VENDOR}, {link #PROPERTY_VERSION},
+ * {link #PROPERTY_DESCRIPTION}, {link #PROPERTY_ALGORITHMS}
+ */
+ public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
+ @NonNull String value)
+ throws NoDrmSchemeException
+ {
+ synchronized (mDrmLock) {
+
+ if ( !mActiveDrmScheme && !mDrmConfigAllowed ) {
+ Log.w(TAG, String.format("setDrmPropertyString NoDrmSchemeException"));
+ throw new NoDrmSchemeException("setDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ _setDrmPropertyString(propertyName, value);
+ } catch ( Exception e ) {
+ Log.w(TAG, String.format("setDrmPropertyString Exception %s", e));
+ throw e;
+ }
+ } // synchronized
+ }
+
+ public static final class DrmInfo {
+ private Map<UUID, byte[]> mapPssh;
+ private UUID[] supportedSchemes;
+ // TODO: Won't need this in final release. Only keeping it for the existing test app.
+ private String[] mimes;
+
+ public Map<UUID, byte[]> getPssh() {
+ return mapPssh;
+ }
+ public UUID[] getSupportedSchemes() {
+ return supportedSchemes;
+ }
+ // TODO: Won't need this in final release. Only keeping it for the existing test app.
+ public String[] getMimes() {
+ return mimes;
+ }
+
+ private DrmInfo(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes, String[] Mimes) {
+ mapPssh = Pssh;
+ supportedSchemes = SupportedSchemes;
+ mimes = Mimes;
+ }
+
+ private DrmInfo(Parcel parcel) {
+ Log.v(TAG, "DrmInfo(" + parcel + ") size " + parcel.dataSize());
+
+ int psshsize = parcel.readInt();
+ byte[] pssh = new byte[psshsize];
+ parcel.readByteArray(pssh);
+
+ Log.v(TAG, "DrmInfo() PSSH: " + arrToHex(pssh));
+ mapPssh = parsePSSH(pssh, psshsize);
+ Log.v(TAG, "DrmInfo() PSSH: " + mapPssh);
+
+ int supportedDRMsCount = parcel.readInt();
+ supportedSchemes = new UUID[supportedDRMsCount];
+ for (int i = 0; i < supportedDRMsCount; i++) {
+ byte[] uuid = new byte[16];
+ parcel.readByteArray(uuid);
+
+ supportedSchemes[i] = bytesToUUID(uuid);
+
+ Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " +
+ supportedSchemes[i]);
+ }
+
+ // TODO: Won't need this in final release. Only keeping it for the test app.
+ mimes = parcel.readStringArray();
+ int mimeCount = mimes.length;
+ Log.v(TAG, "DrmInfo() mime: " + Arrays.toString(mimes));
+
+ Log.v(TAG, "DrmInfo() Parcel psshsize: " + psshsize +
+ " supportedDRMsCount: " + supportedDRMsCount +
+ " mimeCount: " + mimeCount);
+ }
+
+ private DrmInfo makeCopy() {
+ return new DrmInfo(this.mapPssh, this.supportedSchemes, this.mimes);
+ }
+
+ private String arrToHex(byte[] bytes) {
+ String out = "0x";
+ for (int i = 0; i < bytes.length; i++) {
+ out += String.format("%02x", bytes[i]);
+ }
+
+ return out;
+ }
+
+ private UUID bytesToUUID(byte[] uuid) {
+ long msb = 0, lsb = 0;
+ for (int i = 0; i < 8; i++) {
+ msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) );
+ lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) );
+ }
+
+ return new UUID(msb, lsb);
+ }
+
+ private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
+ Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
+
+ final int UUID_SIZE = 16;
+ final int DATALEN_SIZE = 4;
+
+ int len = psshsize;
+ int numentries = 0;
+ int i = 0;
+
+ while (len > 0) {
+ if (len < UUID_SIZE) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "UUID: (%d < 16) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE);
+ UUID uuid = bytesToUUID(subset);
+ i += UUID_SIZE;
+ len -= UUID_SIZE;
+
+ // get data length
+ if (len < 4) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "datalen: (%d < 4) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE);
+ int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ?
+ ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) |
+ ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) :
+ ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) |
+ ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ;
+ i += DATALEN_SIZE;
+ len -= DATALEN_SIZE;
+
+ if (len < datalen) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "data: (%d < %d) pssh: %d", len, datalen, psshsize));
+ return null;
+ }
+
+ byte[] data = Arrays.copyOfRange(pssh, i, i+datalen);
+
+ // skip the data
+ i += datalen;
+ len -= datalen;
+
+ Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
+ numentries, uuid, arrToHex(data), psshsize));
+ numentries++;
+ result.put(uuid, data);
+ }
+
+ return result;
+ }
+
+ }; // DrmInfo
+
+ /**
+ * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class NoDrmSchemeException extends MediaDrmException {
+ public NoDrmSchemeException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when the device requires DRM provisioning but the provisioning attempt has
+ * failed (for example: network timeout, provisioning server error).
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class ProvisioningErrorException extends MediaDrmException {
+ public ProvisioningErrorException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ // Modular DRM helpers
+
+ private class ProvisioningThread extends Thread
+ {
+ public static final int TIMEOUT_MS = 60000;
+
+ private UUID uuid;
+ private String urlStr;
+ private byte[] response;
+ private Object drmLock;
+ private OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate;
+ private MediaPlayer mediaPlayer;
+ private boolean succeeded;
+ private boolean finished;
+ public boolean succeeded() {
+ return succeeded;
+ }
+
+ public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
+ UUID uuid, MediaPlayer mediaPlayer) {
+ // lock is held by the caller
+ drmLock = mediaPlayer.mDrmLock;
+ onDrmPreparedHandlerDelegate = mediaPlayer.mOnDrmPreparedHandlerDelegate;
+ this.mediaPlayer = mediaPlayer;
+
+ urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
+ this.uuid = uuid;
+
+ Log.v(TAG, String.format("HandleProvisioninig: Thread is initialised url: %s", urlStr));
+ return this;
+ }
+
+ public void run() {
+
+ boolean provisioningSucceeded = false;
+ try {
+ URL url = new URL(urlStr);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ try {
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(false);
+ connection.setDoInput(true);
+ connection.setConnectTimeout(TIMEOUT_MS);
+ connection.setReadTimeout(TIMEOUT_MS);
+
+ connection.connect();
+ response = Streams.readFully(connection.getInputStream());
+
+ Log.v(TAG, String.format("HandleProvisioninig: Thread run response %d %s",
+ response.length, response));
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread run connect %s url: %s",
+ e, url));
+ } finally {
+ connection.disconnect();
+ }
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread run openConnection %s", e));
+ }
+
+ if (response != null) {
+ try {
+ MediaDrm drm = new MediaDrm(uuid);
+ drm.provideProvisionResponse(response);
+ drm.release();
+ Log.v(TAG, String.format("HandleProvisioninig: Thread run " +
+ "newDrm+provideProvisionResponse SUCCEEDED!"));
+
+ provisioningSucceeded = true;
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread run " +
+ "newDrm+provideProvisionResponse %s", e));
+ }
+ }
+
+ // non-blocking mode needs the lock
+ if (onDrmPreparedHandlerDelegate != null) {
+
+ synchronized (drmLock) {
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ }
+ mediaPlayer.mDrmProvisioningInProgress = false;
+ mediaPlayer.mPrepareDrmInProgress = false;
+ }
+
+ // calling the callback outside the lock
+ onDrmPreparedHandlerDelegate.notifyClient(succeeded);
+ } else { // blocking mode already has the lock
+
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ }
+ mediaPlayer.mDrmProvisioningInProgress = false;
+ mediaPlayer.mPrepareDrmInProgress = false;
+ }
+
+ finished = true;
+ } // run()
+
+ } // ProvisioningThread
+
+ private boolean HandleProvisioninig(UUID uuid)
+ {
+ // the lock is already held by the caller
+
+ if (mDrmProvisioningInProgress) {
+ Log.e(TAG, String.format("HandleProvisioninig: Unexpected mDrmProvisioningInProgress"));
+ return false;
+ }
+
+ MediaDrm.ProvisionRequest provReq = null;
+ try {
+ MediaDrm drm = new MediaDrm(uuid);
+ provReq = drm.getProvisionRequest();
+ drm.release();
+ } catch (Exception e) {
+ Log.e(TAG, String.format("HandleProvisioninig: getProvisionRequest failed with %s", e));
+ return false;
+ }
+
+ Log.v(TAG, String.format("HandleProvisioninig provReq: data %s url %s",
+ (provReq != null) ? provReq.getData() : "-",
+ (provReq != null) ? provReq.getDefaultUrl() : "://")
+ );
+
+ // networking in a background thread
+ mDrmProvisioningInProgress = true;
+
+ mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
+ mDrmProvisioningThread.start();
+
+ boolean result = false;
+
+ // non-blocking
+ if (mOnDrmPreparedHandlerDelegate != null) {
+ result = true;
+ } else {
+ // if blocking mode, wait till provisioning is done
+ try {
+ mDrmProvisioningThread.join();
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: Thread.join Exception %s", e));
+ }
+ result = mDrmProvisioningThread.succeeded();
+ // no longer need the thread
+ mDrmProvisioningThread = null;
+ }
+
+ return result;
+ }
+
+ private boolean resumePrepareDrm(UUID uuid)
+ {
+ // mDrmLock is guaranteed to be held
+ boolean success = false;
+ try {
+ boolean allowOpenSession = true; // resuming
+ _prepareDrm(getByteArrayFromUUID(uuid), allowOpenSession ? 1 : 0);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+
+ success = true;
+ } catch (Exception e) {
+ Log.w(TAG, String.format("HandleProvisioninig: " +
+ "Thread run _prepareDrm resume failed with %s", e));
+ }
+
+ return success;
+ }
+
+ private void resetDrmState()
+ {
+ synchronized (mDrmLock) {
+ mDrmInfoResolved = false;
+ mDrmInfo = null;
+
+ if (mDrmProvisioningThread != null) {
+ // timeout; relying on HttpUrlConnection
+ try {
+ mDrmProvisioningThread.join();
+ }
+ catch (InterruptedException e) {
+ Log.w(TAG, String.format("resetDrmState: ProvThread.join Exception %s", e));
+ }
+ mDrmProvisioningThread = null;
+ }
+
+ mPrepareDrmInProgress = false;
+ } // synchronized
+ }
+
+ private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+
+ byte[] uuidBytes = new byte[16];
+ for (int i = 0; i < 8; ++i) {
+ uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
+ uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
+ }
+
+ return uuidBytes;
+ }
+
+ // Modular DRM end
+
/*
* Test whether a given video scaling mode is supported.
*/
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index af59d81..5e8135f 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -55,6 +55,90 @@
#include <binder/IServiceManager.h>
#include "android_util_Binder.h"
+
+// Modular DRM begin
+#include <media/drm/DrmAPI.h>
+
+#define FIND_CLASS(var, className) \
+var = env->FindClass(className); \
+LOG_FATAL_IF(! (var), "Unable to find class " className);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
+
+#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
+var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
+LOG_FATAL_IF(! (var), "Unable to find method " fieldName);
+
+#define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \
+LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
+
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+struct RequestFields {
+ jfieldID data;
+ jfieldID defaultUrl;
+ jfieldID requestType;
+};
+
+struct HashmapFields {
+ jmethodID init;
+ jmethodID get;
+ jmethodID put;
+ jmethodID entrySet;
+};
+
+struct SetFields {
+ jmethodID iterator;
+};
+
+struct IteratorFields {
+ jmethodID next;
+ jmethodID hasNext;
+};
+
+struct EntryFields {
+ jmethodID getKey;
+ jmethodID getValue;
+};
+
+struct KeyTypes {
+ jint kKeyTypeStreaming;
+ jint kKeyTypeOffline;
+ jint kKeyTypeRelease;
+};
+
+static KeyTypes gKeyTypes;
+
+struct KeyRequestTypes {
+ jint kKeyRequestTypeInitial;
+ jint kKeyRequestTypeRenewal;
+ jint kKeyRequestTypeRelease;
+};
+
+static KeyRequestTypes gKeyRequestTypes;
+
+struct StateExceptionFields {
+ jmethodID init;
+ jclass classId;
+};
+
+struct drm_fields_t {
+ RequestFields keyRequest;
+ HashmapFields hashmap;
+ SetFields set;
+ IteratorFields iterator;
+ EntryFields entry;
+ StateExceptionFields stateException;
+ jclass stringClassId;
+};
+
+static drm_fields_t gFields;
+
+// Modular DRM end
+
// ----------------------------------------------------------------------------
using namespace android;
@@ -953,6 +1037,55 @@
env->DeleteLocalRef(clazz);
gBufferingParamsFields.init(env);
+
+ // Modular DRM
+ FIND_CLASS(clazz, "android/media/MediaDrm");
+ if (clazz) {
+ jfieldID field;
+ GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
+ gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I");
+ gKeyTypes.kKeyTypeOffline = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I");
+ gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field);
+
+ env->DeleteLocalRef(clazz);
+ } else {
+ ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
+ "get clazz android/media/MediaDrm");
+ }
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
+ if (clazz) {
+ GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B");
+ GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
+ GET_FIELD_ID(gFields.keyRequest.requestType, clazz, "mRequestType", "I");
+
+ jfieldID field;
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I");
+ gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I");
+ gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I");
+ gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field);
+
+ env->DeleteLocalRef(clazz);
+ } else {
+ ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
+ "get clazz android/media/MediaDrm$KeyRequest");
+ }
+
+ FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
+ if (clazz) {
+ GET_METHOD_ID(gFields.stateException.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ gFields.stateException.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+ env->DeleteLocalRef(clazz);
+ } else {
+ ALOGE("JNI getKeyRequest android_media_MediaPlayer_native_init couldn't "
+ "get clazz android/media/MediaDrm$MediaDrmStateException");
+ }
+
gPlaybackParamsFields.init(env);
gSyncParamsFields.init(env);
}
@@ -1126,6 +1259,441 @@
;
}
+/////////////////////////////////////////////////////////////////////////////////////
+// Modular DRM begin
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static void throwDrmStateException(JNIEnv *env, const char *msg, status_t err)
+{
+ ALOGE("Illegal DRM state exception: %s (%d)", msg, err);
+
+ jobject exception = env->NewObject(gFields.stateException.classId,
+ gFields.stateException.init, static_cast<int>(err),
+ env->NewStringUTF(msg));
+ env->Throw(static_cast<jthrowable>(exception));
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static bool throwDrmExceptionAsNecessary(JNIEnv *env, status_t err, const char *msg = NULL)
+{
+ const char *drmMessage = "Unknown DRM Msg";
+
+ switch (err) {
+ case ERROR_DRM_UNKNOWN:
+ drmMessage = "General DRM error";
+ break;
+ case ERROR_DRM_NO_LICENSE:
+ drmMessage = "No license";
+ break;
+ case ERROR_DRM_LICENSE_EXPIRED:
+ drmMessage = "License expired";
+ break;
+ case ERROR_DRM_SESSION_NOT_OPENED:
+ drmMessage = "Session not opened";
+ break;
+ case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
+ drmMessage = "Not initialized";
+ break;
+ case ERROR_DRM_DECRYPT:
+ drmMessage = "Decrypt error";
+ break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ drmMessage = "Unsupported scheme or data format";
+ break;
+ case ERROR_DRM_TAMPER_DETECTED:
+ drmMessage = "Invalid state";
+ break;
+ default:
+ break;
+ }
+
+ String8 vendorMessage;
+ if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
+ vendorMessage = String8::format("DRM vendor-defined error: %d", err);
+ drmMessage = vendorMessage.string();
+ }
+
+ if (err == BAD_VALUE) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+ return true;
+ } else if (err == ERROR_DRM_NOT_PROVISIONED) {
+ jniThrowException(env, "android/media/NotProvisionedException", msg);
+ return true;
+ } else if (err == ERROR_DRM_RESOURCE_BUSY) {
+ jniThrowException(env, "android/media/ResourceBusyException", msg);
+ return true;
+ } else if (err == ERROR_DRM_DEVICE_REVOKED) {
+ jniThrowException(env, "android/media/DeniedByServerException", msg);
+ return true;
+ } else if (err == DEAD_OBJECT) {
+ jniThrowException(env, "android/media/MediaDrmResetException",
+ "mediaserver died");
+ return true;
+ } else if (err != OK) {
+ String8 errbuf;
+ if (drmMessage != NULL) {
+ if (msg == NULL) {
+ msg = drmMessage;
+ } else {
+ errbuf = String8::format("%s: %s", msg, drmMessage);
+ msg = errbuf.string();
+ }
+ }
+ throwDrmStateException(env, msg, err);
+ return true;
+ }
+ return false;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static jbyteArray VectorToJByteArray(JNIEnv *env, Vector<uint8_t> const &vector)
+{
+ size_t length = vector.size();
+ jbyteArray result = env->NewByteArray(length);
+ if (result != NULL) {
+ env->SetByteArrayRegion(result, 0, length, (jbyte *)vector.array());
+ }
+ return result;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray)
+{
+ Vector<uint8_t> vector;
+ size_t length = env->GetArrayLength(byteArray);
+ vector.insertAt((size_t)0, length);
+ env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray());
+ return vector;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static String8 JStringToString8(JNIEnv *env, jstring const &jstr)
+{
+ String8 result;
+
+ const char *s = env->GetStringUTFChars(jstr, NULL);
+ if (s) {
+ result = s;
+ env->ReleaseStringUTFChars(jstr, s);
+ }
+ return result;
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env,
+ jobject &hashMap, bool* pIsOK)
+{
+ jclass clazz = gFields.stringClassId;
+ KeyedVector<String8, String8> keyedVector;
+ *pIsOK = true;
+
+ jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet);
+ if (entrySet) {
+ jobject iterator = env->CallObjectMethod(entrySet, gFields.set.iterator);
+ if (iterator) {
+ jboolean hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
+ while (hasNext) {
+ jobject entry = env->CallObjectMethod(iterator, gFields.iterator.next);
+ if (entry) {
+ jobject obj = env->CallObjectMethod(entry, gFields.entry.getKey);
+ if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "HashMap key is not a String");
+ env->DeleteLocalRef(entry);
+ *pIsOK = false;
+ break;
+ }
+ jstring jkey = static_cast<jstring>(obj);
+
+ obj = env->CallObjectMethod(entry, gFields.entry.getValue);
+ if (obj == NULL || !env->IsInstanceOf(obj, clazz)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "HashMap value is not a String");
+ env->DeleteLocalRef(entry);
+ *pIsOK = false;
+ break;
+ }
+ jstring jvalue = static_cast<jstring>(obj);
+
+ String8 key = JStringToString8(env, jkey);
+ String8 value = JStringToString8(env, jvalue);
+ keyedVector.add(key, value);
+
+ env->DeleteLocalRef(jkey);
+ env->DeleteLocalRef(jvalue);
+ hasNext = env->CallBooleanMethod(iterator, gFields.iterator.hasNext);
+ }
+ env->DeleteLocalRef(entry);
+ }
+ env->DeleteLocalRef(iterator);
+ }
+ env->DeleteLocalRef(entrySet);
+ }
+ return keyedVector;
+}
+
+static void android_media_MediaPlayer_prepareDrm(JNIEnv *env, jobject thiz,
+ jbyteArray uuidObj, jint mode)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (uuidObj == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj);
+
+ if (uuid.size() != 16) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalArgumentException",
+ "invalid UUID size, expected 16 bytes");
+ return;
+ }
+
+ status_t err = mp->prepareDrm(uuid.array(), mode);
+ if (err != OK) {
+ if (err == INVALID_OPERATION) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalStateException",
+ "The player is not prepared yet.");
+ } else if (err == ERROR_DRM_CANNOT_HANDLE) {
+ jniThrowException(
+ env,
+ "android/media/UnsupportedSchemeException",
+ "Failed to instantiate drm object.");
+ } else {
+ throwDrmExceptionAsNecessary(env, err, "Failed to prepare DRM scheme");
+ }
+ }
+}
+
+static void android_media_MediaPlayer_releaseDrm(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ status_t err = mp->releaseDrm();
+ if (err != OK) {
+ if (err == INVALID_OPERATION) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalStateException",
+ "The player is not prepared yet.");
+ }
+ }
+}
+
+static jobject android_media_MediaPlayer_getKeyRequest(JNIEnv *env, jobject thiz, jbyteArray jscope,
+ jstring jmimeType, jint jkeyType, jobject joptParams)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ Vector<uint8_t> scope;
+ if (jscope != NULL) {
+ scope = JByteArrayToVector(env, jscope);
+ }
+
+ String8 mimeType;
+ if (jmimeType != NULL) {
+ mimeType = JStringToString8(env, jmimeType);
+ }
+
+ DrmPlugin::KeyType keyType;
+ if (jkeyType == gKeyTypes.kKeyTypeStreaming) {
+ keyType = DrmPlugin::kKeyType_Streaming;
+ } else if (jkeyType == gKeyTypes.kKeyTypeOffline) {
+ keyType = DrmPlugin::kKeyType_Offline;
+ } else if (jkeyType == gKeyTypes.kKeyTypeRelease) {
+ keyType = DrmPlugin::kKeyType_Release;
+ } else {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType");
+ return NULL;
+ }
+
+ KeyedVector<String8, String8> optParams;
+ if (joptParams != NULL) {
+ bool isOK;
+ optParams = HashMapToKeyedVector(env, joptParams, &isOK);
+ if (!isOK) {
+ return NULL;
+ }
+ }
+
+ Vector<uint8_t> request;
+ String8 defaultUrl;
+ DrmPlugin::KeyRequestType keyRequestType;
+ status_t err = mp->getKeyRequest(scope, mimeType, keyType, optParams, request, defaultUrl,
+ keyRequestType);
+
+ if (throwDrmExceptionAsNecessary(env, err, "Failed to get key request")) {
+ return NULL;
+ }
+
+ ALOGV("JNI getKeyRequest err %d request %d url %s keyReqType %d",
+ err, (int)request.size(), defaultUrl.string(), (int)keyRequestType);
+
+ // Fill out return obj
+ jclass clazz;
+ FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
+
+ jobject keyObj = NULL;
+
+ if (clazz) {
+ keyObj = env->AllocObject(clazz);
+ jbyteArray jrequest = VectorToJByteArray(env, request);
+ env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest);
+
+ jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+ env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl);
+
+ switch (keyRequestType) {
+ case DrmPlugin::kKeyRequestType_Initial:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeInitial);
+ break;
+ case DrmPlugin::kKeyRequestType_Renewal:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeRenewal);
+ break;
+ case DrmPlugin::kKeyRequestType_Release:
+ env->SetIntField(keyObj, gFields.keyRequest.requestType,
+ gKeyRequestTypes.kKeyRequestTypeRelease);
+ break;
+ default:
+ throwDrmStateException(env, "MediaPlayer/DRM plugin failure: unknown "
+ "key request type", ERROR_DRM_UNKNOWN);
+ break;
+ }
+ }
+
+ return keyObj;
+}
+
+static jbyteArray android_media_MediaPlayer_provideKeyResponse(JNIEnv *env, jobject thiz,
+ jbyteArray jreleaseKeySetId, jbyteArray jresponse)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ if (jresponse == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "key response is null");
+ return NULL;
+ }
+
+ Vector<uint8_t> releaseKeySetId;
+ if (jreleaseKeySetId != NULL) {
+ releaseKeySetId = JByteArrayToVector(env, jreleaseKeySetId);
+ }
+
+ Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
+ Vector<uint8_t> keySetId;
+
+ status_t err = mp->provideKeyResponse(releaseKeySetId, response, keySetId);
+
+ if (throwDrmExceptionAsNecessary(env, err, "Failed to handle key response")) {
+ return NULL;
+ }
+ return VectorToJByteArray(env, keySetId);
+}
+
+static void android_media_MediaPlayer_restoreKeys(JNIEnv *env, jobject thiz, jbyteArray jkeySetId)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (jkeySetId == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "invalid keyType");
+ return;
+ }
+
+ Vector<uint8_t> keySetId;
+ keySetId = JByteArrayToVector(env, jkeySetId);
+
+ status_t err = mp->restoreKeys(keySetId);
+
+ ALOGV("JNI restoreKeys err %d ", err);
+ throwDrmExceptionAsNecessary(env, err, "Failed to restore keys");
+}
+
+static jstring android_media_MediaPlayer_getDrmPropertyString(JNIEnv *env, jobject thiz,
+ jstring jname)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ if (jname == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "property name String is null");
+ return NULL;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ String8 value;
+
+ status_t err = mp->getDrmPropertyString(name, value);
+
+ ALOGV("JNI getPropertyString err %d", err);
+
+ if (throwDrmExceptionAsNecessary(env, err, "Failed to get property")) {
+ return NULL;
+ }
+
+ return env->NewStringUTF(value.string());
+}
+
+static void android_media_MediaPlayer_setDrmPropertyString(JNIEnv *env, jobject thiz,
+ jstring jname, jstring jvalue)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (jname == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "property name String is null");
+ return;
+ }
+
+ if (jvalue == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "property value String is null");
+ return;
+ }
+
+ String8 name = JStringToString8(env, jname);
+ String8 value = JStringToString8(env, jvalue);
+
+ status_t err = mp->setDrmPropertyString(name, value);
+
+ ALOGV("JNI setPropertyString err %d", err);
+ throwDrmExceptionAsNecessary(env, err, "Failed to set property");
+}
+// Modular DRM end
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -1179,6 +1747,15 @@
{"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData},
{"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint},
{"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer},
+ // Modular DRM
+ { "_prepareDrm", "([BI)V", (void *)android_media_MediaPlayer_prepareDrm },
+ { "_releaseDrm", "()V", (void *)android_media_MediaPlayer_releaseDrm },
+ { "_getKeyRequest", "([BLjava/lang/String;ILjava/util/Map;)" "Landroid/media/MediaDrm$KeyRequest;",
+ (void *)android_media_MediaPlayer_getKeyRequest },
+ { "_provideKeyResponse", "([B[B)[B", (void *)android_media_MediaPlayer_provideKeyResponse },
+ { "_getDrmPropertyString", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_MediaPlayer_getDrmPropertyString },
+ { "_setDrmPropertyString", "(Ljava/lang/String;Ljava/lang/String;)V",(void *)android_media_MediaPlayer_setDrmPropertyString },
+ { "_restoreKeys", "([B)V", (void *)android_media_MediaPlayer_restoreKeys },
};
// This function only registers the native methods
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 115c622..2fb6843 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -135,6 +135,10 @@
mAdapter.setDiscoverableTimeout(timeout);
}
+ public long getDiscoveryEndMillis() {
+ return mAdapter.getDiscoveryEndMillis();
+ }
+
public void setName(String name) {
mAdapter.setName(name);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 140ae9b..7731228 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3708,7 +3708,8 @@
}
int debugFlags = 0;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
- debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+ debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
+ debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
// Also turn on CheckJNI for debuggable apps. It's quite
// awkward to turn on otherwise.
debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
diff --git a/services/core/java/com/android/server/text/TextClassificationService.java b/services/core/java/com/android/server/text/TextClassificationService.java
new file mode 100644
index 0000000..9358238
--- /dev/null
+++ b/services/core/java/com/android/server/text/TextClassificationService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.text;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.text.ITextClassificationService;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Text classification service.
+ * This is used to provide access to the text classification LSTM model file.
+ */
+public class TextClassificationService extends ITextClassificationService.Stub {
+
+ private static final String LOG_TAG = "TextClassificationService";
+
+ public static final class Lifecycle extends SystemService {
+
+ private TextClassificationService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new TextClassificationService();
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mService);
+ } catch (Throwable t) {
+ // Starting this service is not critical to the running of this device and should
+ // therefore not crash the device. If it fails, log the error and continue.
+ Slog.e(LOG_TAG, "Could not start the TextClassificationService.", t);
+ }
+ }
+ }
+
+ @Override
+ public synchronized ParcelFileDescriptor getModelFileFd() throws RemoteException {
+ try {
+ return ParcelFileDescriptor.open(
+ new File("/etc/assistant/smart-selection.model"),
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (Throwable t) {
+ Slog.e(LOG_TAG, "Error retrieving an fd to the text classification model file.", t);
+ throw new RemoteException(t.getMessage());
+ }
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d91b473..3c1d274 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4896,12 +4896,13 @@
// Notify delegate package of updates.
final Intent intent = new Intent(
DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
- // Only call receivers registered in the manifest (don’t wake app if not running).
+ // Only call receivers registered with Context#registerReceiver (don’t wake delegate).
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
// Limit components this intent resolves to to the delegate package.
intent.setPackage(delegatePackage);
// Include the list of delegated scopes as an extra.
- intent.putExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes.toArray());
+ intent.putStringArrayListExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES,
+ (ArrayList<String>) scopes);
// Send the broadcast.
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c3ef23b..712441c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -100,6 +100,7 @@
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.text.TextClassificationService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.tv.TvRemoteService;
@@ -951,6 +952,12 @@
traceEnd();
}
+ if (!disableNonCoreServices) {
+ traceBeginAndSlog("StartTextClassificationService");
+ mSystemServiceManager.startService(TextClassificationService.Lifecycle.class);
+ traceEnd();
+ }
+
if (!disableNetwork) {
traceBeginAndSlog("StartNetworkScoreService");
try {