summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioManager.java7
-rw-r--r--media/java/android/media/AudioService.java141
-rw-r--r--media/java/android/media/AudioSystem.java10
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/MediaPlayer.java74
-rw-r--r--media/java/android/media/MediaRecorder.java6
-rw-r--r--media/java/android/media/MediaRouter.java4
-rw-r--r--media/java/android/media/VolumeProvider.java15
-rw-r--r--media/java/android/media/session/ISessionCallback.aidl2
-rw-r--r--media/java/android/media/session/ISessionController.aidl2
-rw-r--r--media/java/android/media/session/ISessionManager.aidl2
-rw-r--r--media/java/android/media/session/MediaController.java41
-rw-r--r--media/java/android/media/session/MediaSession.java54
-rw-r--r--media/java/android/media/session/MediaSessionLegacyHelper.java16
-rw-r--r--media/java/android/media/session/MediaSessionManager.java52
-rw-r--r--media/java/android/media/tv/TvContentRating.java245
-rw-r--r--media/java/android/media/tv/TvContract.java81
17 files changed, 513 insertions, 241 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c65961d..ef95c11 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2954,15 +2954,12 @@ public class AudioManager {
* Set Hdmi Cec system audio mode.
*
* @param on whether to be on system audio mode
- * @param device out device type to be used for system audio mode.
- * Ignored if {@code on} is {@code false}
- * @param name name of system audio device
* @return output device type. 0 (DEVICE_NONE) if failed to set device.
* @hide
*/
- public int setHdmiSystemAudioSupported(boolean on, int device, String name) {
+ public int setHdmiSystemAudioSupported(boolean on) {
try {
- return getService().setHdmiSystemAudioSupported(on, device, name);
+ return getService().setHdmiSystemAudioSupported(on);
} catch (RemoteException e) {
Log.w(TAG, "Error setting system audio mode", e);
return AudioSystem.DEVICE_NONE;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index ab63145..9d5fe23 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -393,10 +393,16 @@ public class AudioService extends IAudioService.Stub {
// Indicates the mode used for SCO audio connection. The mode is virtual call if the request
// originated from an app targeting an API version before JB MR2 and raw audio after that.
private int mScoAudioMode;
+ // SCO audio mode is undefined
+ private static final int SCO_MODE_UNDEFINED = -1;
// SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
private static final int SCO_MODE_VIRTUAL_CALL = 0;
// SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
private static final int SCO_MODE_RAW = 1;
+ // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
+ private static final int SCO_MODE_VR = 2;
+
+ private static final int SCO_MODE_MAX = 2;
// Current connection state indicated by bluetooth headset
private int mScoConnectionState;
@@ -2117,7 +2123,7 @@ public class AudioService extends IAudioService.Stub {
public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
int scoAudioMode =
(targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
- SCO_MODE_VIRTUAL_CALL : SCO_MODE_RAW;
+ SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
startBluetoothScoInt(cb, scoAudioMode);
}
@@ -2272,14 +2278,28 @@ public class AudioService extends IAudioService.Stub {
mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
if (mScoAudioState == SCO_STATE_INACTIVE) {
mScoAudioMode = scoAudioMode;
+ if (scoAudioMode == SCO_MODE_UNDEFINED) {
+ mScoAudioMode = new Integer(Settings.Global.getInt(
+ mContentResolver,
+ "bluetooth_sco_channel_"+
+ mBluetoothHeadsetDevice.getAddress(),
+ SCO_MODE_VIRTUAL_CALL));
+ if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ }
+ }
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- boolean status;
+ boolean status = false;
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.connectAudio();
- } else {
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.startVoiceRecognition(
+ mBluetoothHeadsetDevice);
}
+
if (status) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
} else {
@@ -2302,13 +2322,17 @@ public class AudioService extends IAudioService.Stub {
mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- boolean status;
+ boolean status = false;
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.disconnectAudio();
- } else {
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice);
}
+
if (!status) {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
@@ -2502,17 +2526,23 @@ public class AudioService extends IAudioService.Stub {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.connectAudio();
- } else {
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.startVoiceRecognition(
+ mBluetoothHeadsetDevice);
}
break;
case SCO_STATE_DEACTIVATE_REQ:
if (mScoAudioMode == SCO_MODE_RAW) {
status = mBluetoothHeadset.disconnectAudio();
- } else {
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice);
}
break;
case SCO_STATE_DEACTIVATE_EXT_REQ:
@@ -3792,8 +3822,7 @@ public class AudioService extends IAudioService.Stub {
}
if (mHdmiTvClient != null) {
- setHdmiSystemAudioSupported(mHdmiSystemAudioSupported,
- mHdmiSystemAudioOutputDevice, "");
+ setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
}
// indicate the end of reconfiguration phase to audio HAL
@@ -4774,113 +4803,25 @@ public class AudioService extends IAudioService.Stub {
private boolean mHdmiSystemAudioSupported = false;
// Set only when device is tv.
private HdmiTvClient mHdmiTvClient;
- private int mHdmiSystemAudioOutputDevice = AudioSystem.DEVICE_NONE;
- private int[] mSpeakerGains;
@Override
- public int setHdmiSystemAudioSupported(boolean on, int device, String name) {
+ public int setHdmiSystemAudioSupported(boolean on) {
if (mHdmiTvClient == null) {
Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
return AudioSystem.DEVICE_NONE;
}
- if (on && !checkHdmiSystemAudioOutput(device)) {
- return AudioSystem.DEVICE_NONE;
- }
-
synchronized (mHdmiTvClient) {
- if (on) {
- mHdmiSystemAudioOutputDevice = device;
- }
if (mHdmiSystemAudioSupported == on) {
return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
}
mHdmiSystemAudioSupported = on;
- updateHdmiSystemAudioVolumeLocked(on);
+ AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
+ on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : AudioSystem.FORCE_NONE);
}
return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
}
- private boolean checkHdmiSystemAudioOutput(int device) {
- if ((device & AudioSystem.DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO) == 0) {
- Log.w(TAG, "Unsupported Hdmi-Cec system audio output:" + device);
- return false;
- }
-
- int streamDevice = AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC);
- // If other devices except for system audio and speaker are available,
- // fails to start system audio mode.
- if ((streamDevice & ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER) != 0) {
- Log.w(TAG, "Should turn off other devices before starting system audio:"
- + streamDevice);
- return false;
- }
- if (AudioSystem.getDeviceConnectionState(device, "") !=
- AudioSystem.DEVICE_STATE_AVAILABLE) {
- Log.w(TAG, "Output device is not connected:" + device);
- return false;
- }
- return true;
- }
-
- private void updateHdmiSystemAudioVolumeLocked(boolean on) {
- AudioDevicePort speaker = findAudioDevicePort(AudioSystem.DEVICE_OUT_SPEAKER);
- if (speaker == null) {
- Log.w(TAG, "Has no speaker output.");
- return;
- }
-
- AudioPortConfig portConfig = speaker.activeConfig();
- AudioGainConfig gainConfig = portConfig.gain();
- int[] newGains;
- // When system audio is on, backup original gains and mute all channels of speaker by
- // setting gains to 0; otherwise, restore gains of speaker.
- if (on) {
- if (gainConfig == null) {
- Log.w(TAG, "Speaker has no gain control.");
- return;
- }
- // Back up original gains.
- mSpeakerGains = Arrays.copyOf(gainConfig.values(), gainConfig.values().length);
- // Set all gains to 0.
- newGains = new int[gainConfig.values().length];
- } else {
- if (mSpeakerGains == null) {
- Log.w(TAG, "mSpeakerGains should not be null.");
- return;
- }
- newGains = Arrays.copyOf(mSpeakerGains, mSpeakerGains.length);
- }
-
- gainConfig = gainConfig.mGain.buildConfig(gainConfig.mode(),
- gainConfig.channelMask(),
- newGains,
- gainConfig.rampDurationMs());
-
- AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- if (AudioSystem.SUCCESS != audioManager.setAudioPortGain(speaker, gainConfig)) {
- Log.w(TAG, "Failed to update audio port config.");
- }
- }
-
- private AudioDevicePort findAudioDevicePort(int type) {
- ArrayList<AudioPort> devicePorts = new ArrayList<>();
- AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- int status = audioManager.listAudioDevicePorts(devicePorts);
- if (status != AudioSystem.SUCCESS) {
- Log.w(TAG, "Failed to list up all audio ports");
- return null;
- }
-
- for (AudioPort port : devicePorts) {
- AudioDevicePort devicePort = (AudioDevicePort) port;
- if (devicePort.type() == type) {
- return devicePort;
- }
- }
- return null;
- }
-
//==========================================================================================
// Camera shutter sound policy.
// config_camera_sound_forced configuration option in config.xml defines if the camera shutter
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 72367c8..8e2ca95 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -254,6 +254,7 @@ public class AudioSystem
public static final int DEVICE_OUT_HDMI_ARC = 0x40000;
public static final int DEVICE_OUT_SPDIF = 0x80000;
public static final int DEVICE_OUT_FM = 0x100000;
+ public static final int DEVICE_OUT_AUX_LINE = 0x200000;
public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
@@ -278,6 +279,7 @@ public class AudioSystem
DEVICE_OUT_HDMI_ARC |
DEVICE_OUT_SPDIF |
DEVICE_OUT_FM |
+ DEVICE_OUT_AUX_LINE |
DEVICE_OUT_DEFAULT);
public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP |
DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
@@ -287,7 +289,7 @@ public class AudioSystem
DEVICE_OUT_BLUETOOTH_SCO_CARKIT);
public static final int DEVICE_OUT_ALL_USB = (DEVICE_OUT_USB_ACCESSORY |
DEVICE_OUT_USB_DEVICE);
- public static final int DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO = (DEVICE_OUT_LINE |
+ public static final int DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO = (DEVICE_OUT_AUX_LINE |
DEVICE_OUT_HDMI_ARC |
DEVICE_OUT_SPDIF);
public static final int DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER =
@@ -440,7 +442,8 @@ public class AudioSystem
public static final int FORCE_DIGITAL_DOCK = 9;
public static final int FORCE_NO_BT_A2DP = 10;
public static final int FORCE_SYSTEM_ENFORCED = 11;
- private static final int NUM_FORCE_CONFIG = 12;
+ public static final int FORCE_HDMI_SYSTEM_AUDIO_ENFORCED = 12;
+ private static final int NUM_FORCE_CONFIG = 13;
public static final int FORCE_DEFAULT = FORCE_NONE;
// usage for setForceUse, must match AudioSystem::force_use
@@ -449,7 +452,8 @@ public class AudioSystem
public static final int FOR_RECORD = 2;
public static final int FOR_DOCK = 3;
public static final int FOR_SYSTEM = 4;
- private static final int NUM_FORCE_USE = 5;
+ public static final int FOR_HDMI_SYSTEM_AUDIO = 5;
+ private static final int NUM_FORCE_USE = 6;
// usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
public static final int SYNC_EVENT_NONE = 0;
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4f7021e..7318660 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -201,7 +201,7 @@ interface IAudioService {
void disableSafeMediaVolume();
- int setHdmiSystemAudioSupported(boolean on, int device, String name);
+ int setHdmiSystemAudioSupported(boolean on);
boolean registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
oneway void unregisterAudioPolicyAsync(in IBinder cb);
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index ab65ba0..b15bd69 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -378,6 +378,13 @@ import java.lang.ref.WeakReference;
* <td>Successful invoke of this method in a valid state does not change
* the state. Calling this method in an invalid state transfers the
* object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>setAudioAttributes </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state. In order for the
+ * target audio attributes type to become effective, this method must be called before
+ * prepare() or prepareAsync().</p></td></tr>
* <tr><td>setAudioSessionId </p></td>
* <td>{Idle} </p></td>
* <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
@@ -787,6 +794,10 @@ public class MediaPlayer implements SubtitleController.Listener
* <p>When done with the MediaPlayer, you should call {@link #release()},
* to free the resources. If not released, too many MediaPlayer instances will
* result in an exception.</p>
+ * <p>Note that since {@link #prepare()} is called automatically in this method,
+ * you cannot change the audio stream type (see {@link #setAudioStreamType(int)}), audio
+ * session ID (see {@link #setAudioSessionId(int)}) or audio attributes
+ * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p>
*
* @param context the Context to use
* @param uri the Uri from which to get the datasource
@@ -802,6 +813,10 @@ public class MediaPlayer implements SubtitleController.Listener
* <p>When done with the MediaPlayer, you should call {@link #release()},
* to free the resources. If not released, too many MediaPlayer instances will
* result in an exception.</p>
+ * <p>Note that since {@link #prepare()} is called automatically in this method,
+ * you cannot change the audio stream type (see {@link #setAudioStreamType(int)}), audio
+ * session ID (see {@link #setAudioSessionId(int)}) or audio attributes
+ * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p>
*
* @param context the Context to use
* @param uri the Uri from which to get the datasource
@@ -809,9 +824,30 @@ public class MediaPlayer implements SubtitleController.Listener
* @return a MediaPlayer object, or null if creation failed
*/
public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder) {
+ int s = AudioSystem.newAudioSessionId();
+ return create(context, uri, holder, null, s > 0 ? s : 0);
+ }
+
+ /**
+ * Same factory method as {@link #create(Context, Uri, SurfaceHolder)} but that lets you specify
+ * the audio attributes and session ID to be used by the new MediaPlayer instance.
+ * @param context the Context to use
+ * @param uri the Uri from which to get the datasource
+ * @param holder the SurfaceHolder to use for displaying the video, may be null.
+ * @param audioAttributes the {@link AudioAttributes} to be used by the media player.
+ * @param audioSessionId the audio session ID to be used by the media player,
+ * see {@link AudioManager#allocateAudioSessionId()} to obtain a new session.
+ * @return a MediaPlayer object, or null if creation failed
+ */
+ public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
+ AudioAttributes audioAttributes, int audioSessionId) {
try {
MediaPlayer mp = new MediaPlayer();
+ final AudioAttributes aa = audioAttributes != null ? audioAttributes :
+ new AudioAttributes.Builder().build();
+ mp.setAudioAttributes(aa);
+ mp.setAudioSessionId(audioSessionId);
mp.setDataSource(context, uri);
if (holder != null) {
mp.setDisplay(holder);
@@ -840,6 +876,10 @@ public class MediaPlayer implements SubtitleController.Listener
* <p>When done with the MediaPlayer, you should call {@link #release()},
* to free the resources. If not released, too many MediaPlayer instances will
* result in an exception.</p>
+ * <p>Note that since {@link #prepare()} is called automatically in this method,
+ * you cannot change the audio stream type (see {@link #setAudioStreamType(int)}), audio
+ * session ID (see {@link #setAudioSessionId(int)}) or audio attributes
+ * (see {@link #setAudioAttributes(AudioAttributes)} of the new MediaPlayer.</p>
*
* @param context the Context to use
* @param resid the raw resource id (<var>R.raw.&lt;something></var>) for
@@ -847,11 +887,34 @@ public class MediaPlayer implements SubtitleController.Listener
* @return a MediaPlayer object, or null if creation failed
*/
public static MediaPlayer create(Context context, int resid) {
+ int s = AudioSystem.newAudioSessionId();
+ return create(context, resid, null, s > 0 ? s : 0);
+ }
+
+ /**
+ * Same factory method as {@link #create(Context, int)} but that lets you specify the audio
+ * attributes and session ID to be used by the new MediaPlayer instance.
+ * @param context the Context to use
+ * @param resid the raw resource id (<var>R.raw.&lt;something></var>) for
+ * the resource to use as the datasource
+ * @param audioAttributes the {@link AudioAttributes} to be used by the media player.
+ * @param audioSessionId the audio session ID to be used by the media player,
+ * see {@link AudioManager#allocateAudioSessionId()} to obtain a new session.
+ * @return a MediaPlayer object, or null if creation failed
+ */
+ public static MediaPlayer create(Context context, int resid,
+ AudioAttributes audioAttributes, int audioSessionId) {
try {
AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
if (afd == null) return null;
MediaPlayer mp = new MediaPlayer();
+
+ final AudioAttributes aa = audioAttributes != null ? audioAttributes :
+ new AudioAttributes.Builder().build();
+ mp.setAudioAttributes(aa);
+ mp.setAudioSessionId(audioSessionId);
+
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
mp.prepare();
@@ -1454,16 +1517,15 @@ public class MediaPlayer implements SubtitleController.Listener
private native boolean setParameter(int key, Parcel value);
/**
- * @hide
- * CANDIDATE FOR PUBLIC API
- * Must call this method before prepare() or
- * prepareAsync() in order for the audio attributes to become effective
- * thereafter.
+ * Sets the audio attributes for this MediaPlayer.
+ * See {@link AudioAttributes} for how to build and configure an instance of this class.
+ * You must call this method before {@link #prepare()} or {@link #prepareAsync()} in order
+ * for the audio attributes to become effective thereafter.
* @param attributes a non-null set of audio attributes
*/
public void setAudioAttributes(AudioAttributes attributes) throws IllegalArgumentException {
if (attributes == null) {
- final String msg = "Cannot set audio attributes to null";
+ final String msg = "Cannot set AudioAttributes to null";
throw new IllegalArgumentException(msg);
}
Parcel pattributes = Parcel.obtain();
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 3917bf1..59307d0 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -299,6 +299,9 @@ public class MediaRecorder
/** @hide H.264/AAC data encapsulated in MPEG2/TS */
public static final int OUTPUT_FORMAT_MPEG2TS = 8;
+
+ /** VP8/VORBIS data in a WEBM container */
+ public static final int WEBM = 9;
};
/**
@@ -321,6 +324,8 @@ public class MediaRecorder
public static final int HE_AAC = 4;
/** Enhanced Low Delay AAC (AAC-ELD) audio codec */
public static final int AAC_ELD = 5;
+ /** Ogg Vorbis audio codec */
+ public static final int VORBIS = 6;
}
/**
@@ -336,6 +341,7 @@ public class MediaRecorder
public static final int H263 = 1;
public static final int H264 = 2;
public static final int MPEG_4_SP = 3;
+ public static final int VP8 = 4;
}
/**
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 3336694..7d1de24 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -2249,12 +2249,12 @@ public class MediaRouter {
}
@Override
- public void onAdjustVolumeBy(final int delta) {
+ public void onAdjustVolume(final int direction) {
sStatic.mHandler.post(new Runnable() {
@Override
public void run() {
if (mVcb != null) {
- mVcb.vcb.onVolumeUpdateRequest(mVcb.route, delta);
+ mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
}
}
});
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index d151e66..9bda1d4 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -32,14 +32,14 @@ public abstract class VolumeProvider {
/**
* The volume control uses relative adjustment via
- * {@link #onAdjustVolumeBy(int)}. Attempts to set the volume to a specific
+ * {@link #onAdjustVolume(int)}. Attempts to set the volume to a specific
* value should be ignored.
*/
public static final int VOLUME_CONTROL_RELATIVE = 1;
/**
* The volume control uses an absolute value. It may be adjusted using
- * {@link #onAdjustVolumeBy(int)} or set directly using
+ * {@link #onAdjustVolume(int)} or set directly using
* {@link #onSetVolumeTo(int)}.
*/
public static final int VOLUME_CONTROL_ABSOLUTE = 2;
@@ -104,12 +104,13 @@ public abstract class VolumeProvider {
}
/**
- * Override to handle requests to adjust the volume of the current
- * output.
- *
- * @param delta The amount to change the volume
+ * Override to handle requests to adjust the volume of the current output.
+ * Direction will be one of {@link AudioManager#ADJUST_LOWER},
+ * {@link AudioManager#ADJUST_RAISE}, {@link AudioManager#ADJUST_SAME}.
+ *
+ * @param direction The direction to change the volume in.
*/
- public void onAdjustVolumeBy(int delta) {
+ public void onAdjustVolume(int direction) {
}
/**
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index e554e27..39391b6 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -39,6 +39,6 @@ oneway interface ISessionCallback {
void onRate(in Rating rating);
// These callbacks are for volume handling
- void onAdjustVolumeBy(int delta);
+ void onAdjustVolume(int direction);
void onSetVolumeTo(int value);
}
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index 6cf5ef2..b555220 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -41,7 +41,7 @@ interface ISessionController {
MediaSessionInfo getSessionInfo();
long getFlags();
ParcelableVolumeInfo getVolumeAttributes();
- void adjustVolumeBy(int delta, int flags);
+ void adjustVolume(int direction, int flags);
void setVolumeTo(int value, int flags);
IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback);
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index dce84d4..95c2d61 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -31,7 +31,7 @@ interface ISessionManager {
ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
List<IBinder> getSessions(in ComponentName compName, int userId);
void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
- void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags);
+ void dispatchAdjustVolume(int suggestedStream, int delta, int flags);
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
int userId);
void removeSessionsListener(in IActiveSessionsListener listener);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index cc8b31a..7fedd82 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -66,28 +66,27 @@ public final class MediaController {
private final TransportControls mTransportControls;
- private MediaController(ISessionController sessionBinder) {
- mSessionBinder = sessionBinder;
- mTransportControls = new TransportControls();
- }
-
/**
+ * Call for creating a MediaController directly from a binder. Should only
+ * be used by framework code.
+ *
* @hide
*/
- public static MediaController fromBinder(ISessionController sessionBinder) {
- return new MediaController(sessionBinder);
+ public MediaController(ISessionController sessionBinder) {
+ if (sessionBinder == null) {
+ throw new IllegalArgumentException("Session token cannot be null");
+ }
+ mSessionBinder = sessionBinder;
+ mTransportControls = new TransportControls();
}
/**
- * Get a new media controller from a session token which may have
- * been obtained from another process. If successful the controller returned
- * will be connected to the session that generated the token.
+ * Create a new MediaController from a session's token.
*
- * @param token The session token to control.
- * @return A controller for the session or null if inaccessible.
+ * @param token The token for the session.
*/
- public static MediaController fromToken(@NonNull MediaSession.Token token) {
- return fromBinder(token.getBinder());
+ public MediaController(@NonNull MediaSession.Token token) {
+ this(token.getBinder());
}
/**
@@ -235,19 +234,21 @@ public final class MediaController {
}
/**
- * Adjust the volume of the stream or output this session is playing on.
- * Negative values will lower the volume. The command will be ignored if it
- * does not support {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+ * Adjust the volume of the stream or output this session is playing on. The
+ * direction must be one of {@link AudioManager#ADJUST_LOWER},
+ * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+ * The command will be ignored if the session does not support
+ * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
* {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
* {@link AudioManager} may be used to affect the handling.
*
* @see #getVolumeInfo()
- * @param delta The number of steps to adjust the volume by.
+ * @param direction The direction to adjust the volume in.
* @param flags Any flags to pass with the command.
*/
- public void adjustVolumeBy(int delta, int flags) {
+ public void adjustVolume(int direction, int flags) {
try {
- mSessionBinder.adjustVolumeBy(delta, flags);
+ mSessionBinder.adjustVolume(direction, flags);
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 34997bd..4841360 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaMetadata;
@@ -37,10 +38,13 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.telephony.DctConstants.Activity;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -59,8 +63,9 @@ import java.util.List;
* create a {@link MediaController} to interact with the session.
* <p>
* To receive commands, media keys, and other events a {@link Callback} must be
- * set with {@link #addCallback(Callback)}. To receive transport control
- * commands a {@link TransportControlsCallback} must be set with
+ * set with {@link #addCallback(Callback)} and {@link #setActive(boolean)
+ * setActive(true)} must be called. To receive transport control commands a
+ * {@link TransportControlsCallback} must be set with
* {@link #addTransportControlsCallback}.
* <p>
* When an app is finished performing playback it must call {@link #release()}
@@ -119,18 +124,45 @@ public final class MediaSession {
private boolean mActive = false;
/**
+ * Creates a new session. The session will automatically be registered with
+ * the system but will not be published until {@link #setActive(boolean)
+ * setActive(true)} is called. You must call {@link #release()} when
+ * finished with the session.
+ *
+ * @param context The context to use to create the session.
+ * @param tag A short name for debugging purposes.
+ */
+ public MediaSession(@NonNull Context context, @NonNull String tag) {
+ this(context, tag, UserHandle.myUserId());
+ }
+
+ /**
+ * Creates a new session as the specified user. To create a session as a
+ * user other than your own you must hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission.
+ *
+ * @param context The context to use to create the session.
+ * @param tag A short name for debugging purposes.
+ * @param userId The user id to create the session as.
* @hide
*/
- public MediaSession(ISession binder, CallbackStub cbStub) {
- mBinder = binder;
- mCbStub = cbStub;
- ISessionController controllerBinder = null;
+ public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
+ if (context == null) {
+ throw new IllegalArgumentException("context cannot be null.");
+ }
+ if (TextUtils.isEmpty(tag)) {
+ throw new IllegalArgumentException("tag cannot be null or empty");
+ }
+ mCbStub = new CallbackStub();
+ MediaSessionManager manager = (MediaSessionManager) context
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
- controllerBinder = mBinder.getController();
+ mBinder = manager.createSession(mCbStub, tag, userId);
+ mSessionToken = new Token(mBinder.getController());
} catch (RemoteException e) {
- throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
+ throw new RuntimeException("Remote error creating session.", e);
}
- mSessionToken = new Token(controllerBinder);
}
/**
@@ -837,11 +869,11 @@ public final class MediaSession {
}
@Override
- public void onAdjustVolumeBy(int delta) {
+ public void onAdjustVolume(int direction) {
MediaSession session = mMediaSession.get();
if (session != null) {
if (session.mVolumeProvider != null) {
- session.mVolumeProvider.onAdjustVolumeBy(delta);
+ session.mVolumeProvider.onAdjustVolume(direction);
}
}
}
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 11f7720..da1a6ed 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -28,6 +28,7 @@ import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.KeyEvent;
@@ -46,6 +47,7 @@ public class MediaSessionLegacyHelper {
private static final Object sLock = new Object();
private static MediaSessionLegacyHelper sInstance;
+ private Context mContext;
private MediaSessionManager mSessionManager;
private Handler mHandler = new Handler(Looper.getMainLooper());
// The legacy APIs use PendingIntents to register/unregister media button
@@ -54,6 +56,7 @@ public class MediaSessionLegacyHelper {
= new ArrayMap<PendingIntent, SessionHolder>();
private MediaSessionLegacyHelper(Context context) {
+ mContext = context;
mSessionManager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
}
@@ -206,13 +209,13 @@ public class MediaSessionLegacyHelper {
}
}
- mSessionManager.dispatchAdjustVolumeBy(AudioManager.USE_DEFAULT_STREAM_TYPE,
+ mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
direction, flags);
}
}
public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
- mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+ mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
if (DEBUG) {
Log.d(TAG, "dispatched volume adjustment");
}
@@ -225,6 +228,9 @@ public class MediaSessionLegacyHelper {
return;
}
SessionHolder holder = getHolder(pi, true);
+ if (holder == null) {
+ return;
+ }
if (holder.mRccListener != null) {
if (holder.mRccListener == listener) {
if (DEBUG) {
@@ -270,6 +276,9 @@ public class MediaSessionLegacyHelper {
return;
}
SessionHolder holder = getHolder(pi, true);
+ if (holder == null) {
+ return;
+ }
if (holder.mMediaButtonListener != null) {
// Already have this listener registered, but update it anyway as
// the extras may have changed.
@@ -316,7 +325,8 @@ public class MediaSessionLegacyHelper {
private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
SessionHolder holder = mSessions.get(pi);
if (holder == null && createIfMissing) {
- MediaSession session = mSessionManager.createSession(TAG);
+ MediaSession session;
+ session = new MediaSession(mContext, TAG);
session.setActive(true);
holder = new SessionHolder(session, pi);
mSessions.put(pi, holder);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c477406..824b397 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.media.AudioManager;
import android.media.IRemoteVolumeController;
import android.media.session.ISessionManager;
import android.os.IBinder;
@@ -64,42 +65,15 @@ public final class MediaSessionManager {
}
/**
- * Creates a new session.
+ * Create a new session in the system and get the binder for it.
*
* @param tag A short name for debugging purposes.
- * @return A {@link MediaSession} for the new session.
- */
- public @NonNull MediaSession createSession(@NonNull String tag) {
- return createSessionAsUser(tag, UserHandle.myUserId());
- }
-
- /**
- * Creates a new session as the specified user. To create a session as a
- * user other than your own you must hold the
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
- * permission.
- *
- * @param tag A short name for debugging purposes.
- * @param userId The user id to create the session as.
- * @return A {@link MediaSession} for the new session.
+ * @return The binder object from the system
* @hide
*/
- public @NonNull MediaSession createSessionAsUser(@NonNull String tag, int userId) {
- if (TextUtils.isEmpty(tag)) {
- throw new IllegalArgumentException("tag must not be null or empty");
- }
-
- try {
- MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
- MediaSession session = new MediaSession(mService
- .createSession(mContext.getPackageName(), cbStub, tag, userId), cbStub);
- cbStub.setMediaSession(session);
-
- return session;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to create session: ", e);
- return null;
- }
+ public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
+ @NonNull String tag, int userId) throws RemoteException {
+ return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
}
/**
@@ -142,7 +116,7 @@ public final class MediaSessionManager {
List<IBinder> binders = mService.getSessions(notificationListener, userId);
int size = binders.size();
for (int i = 0; i < size; i++) {
- MediaController controller = MediaController.fromBinder(ISessionController.Stub
+ MediaController controller = new MediaController(ISessionController.Stub
.asInterface(binders.get(i)));
controllers.add(controller);
}
@@ -256,17 +230,19 @@ public final class MediaSessionManager {
/**
* Dispatch an adjust volume request to the system. It will be sent to the
- * most relevant audio stream or media session.
+ * most relevant audio stream or media session. The direction must be one of
+ * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+ * {@link AudioManager#ADJUST_SAME}.
*
* @param suggestedStream The stream to fall back to if there isn't a
* relevant stream
- * @param delta The amount to adjust the volume by.
+ * @param direction The direction to adjust volume in.
* @param flags Any flags to include with the volume change.
* @hide
*/
- public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+ public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
try {
- mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+ mService.dispatchAdjustVolume(suggestedStream, direction, flags);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send adjust volume.", e);
}
@@ -295,7 +271,7 @@ public final class MediaSessionManager {
ArrayList<MediaController> controllers = new ArrayList<MediaController>();
int size = tokens.size();
for (int i = 0; i < size; i++) {
- controllers.add(MediaController.fromToken(tokens.get(i)));
+ controllers.add(new MediaController(tokens.get(i)));
}
SessionListener.this.onActiveSessionsChanged(controllers);
}
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
new file mode 100644
index 0000000..905b0bd
--- /dev/null
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class representing a TV content rating.
+ */
+public class TvContentRating {
+ private static final String TAG = "TvContentRating";
+
+ private static final int RATING_PREFIX_LENGTH = 10;
+ private static final String PREFIX_RATING_US = "RATING_US_";
+ private static final String PREFIX_SUBRATING_US = "SUBRATING_US_";
+
+ /**
+ * Rating constant for TV-Y from the TV Parental Guidelines system in US. This program is
+ * designed to be appropriate for all children.
+ */
+ public static final String RATING_US_TV_Y = PREFIX_RATING_US + "TV_Y";
+ /**
+ * Rating constant for TV-Y7 from the TV Parental Guidelines system in US. This program is
+ * designed for children age 7 and above.
+ */
+ public static final String RATING_US_TV_Y7 = PREFIX_RATING_US + "TV_Y7";
+ /**
+ * Rating constant for TV-G from the TV Parental Guidelines system in US. Most parents would
+ * find this program suitable for all ages.
+ */
+ public static final String RATING_US_TV_G = PREFIX_RATING_US + "TV_G";
+ /**
+ * Rating constant for TV-PG from the TV Parental Guidelines system in US. This program contains
+ * material that parents may find unsuitable for younger children.
+ */
+ public static final String RATING_US_TV_PG = PREFIX_RATING_US + "TV_PG";
+ /**
+ * Rating constant for TV-14 from the TV Parental Guidelines system in US. This program contains
+ * some material that many parents would find unsuitable for children under 14 years of age.
+ */
+ public static final String RATING_US_TV_14 = PREFIX_RATING_US + "TV_14";
+ /**
+ * Rating constant for TV-MA from the TV Parental Guidelines system in US. This program is
+ * specifically designed to be viewed by adults and therefore may be unsuitable for children
+ * under 17.
+ */
+ public static final String RATING_US_TV_MA = PREFIX_RATING_US + "TV_MA";
+
+ /**
+ * Sub-rating constant for D (Suggestive dialogue) from the TV Parental Guidelines system in US.
+ */
+ public static final String SUBRATING_US_D = PREFIX_SUBRATING_US + "D";
+ /**
+ * Sub-rating constant for L (Coarse language) from the TV Parental Guidelines system in US.
+ */
+ public static final String SUBRATING_US_L = PREFIX_SUBRATING_US + "L";
+ /**
+ * Sub-rating constant for S (Sexual content) from the TV Parental Guidelines system in US.
+ */
+ public static final String SUBRATING_US_S = PREFIX_SUBRATING_US + "S";
+ /**
+ * Sub-rating constant for V (Violence) from the TV Parental Guidelines system in US.
+ */
+ public static final String SUBRATING_US_V = PREFIX_SUBRATING_US + "V";
+ /**
+ * Sub-rating constant for FV (Fantasy violence) from the TV Parental Guidelines system in US.
+ */
+ public static final String SUBRATING_US_FV = PREFIX_SUBRATING_US + "FV";
+
+ private static final String PREFIX_RATING_KR = "RATING_KR_";
+
+ /**
+ * Rating constant for 'ALL' from the South Korean television rating system. This rating is for
+ * programming that is appropriate for all ages.
+ */
+ public static final String RATING_KR_ALL = PREFIX_RATING_KR + "ALL";
+ /**
+ * Rating constant for '7' from the South Korean television rating system. This rating is for
+ * programming that may contain material inappropriate for children younger than 7, and parental
+ * discretion should be used.
+ */
+ public static final String RATING_KR_7 = PREFIX_RATING_KR + "7";
+ /**
+ * Rating constant for '12' from the South Korean television rating system. This rating is for
+ * programs that may deemed inappropriate for those younger than 12, and parental discretion
+ * should be used.
+ */
+ public static final String RATING_KR_12 = PREFIX_RATING_KR + "12";
+ /**
+ * Rating constant for '15' from the South Korean television rating system. This rating is for
+ * programs that contain material that may be inappropriate for children under 15, and that
+ * parental discretion should be used.
+ */
+ public static final String RATING_KR_15 = PREFIX_RATING_KR + "15";
+ /**
+ * Rating constant for '19' from the South Korean television rating system. This rating is for
+ * programs that are intended for adults only. 19-rated programming cannot air during the hours
+ * of 7:00AM to 9:00AM, and 1:00PM to 10:00PM.
+ */
+ public static final String RATING_KR_19 = PREFIX_RATING_KR + "19";
+
+ private static final String DELIMITER = "/";
+
+ // A mapping from two-letter country code (ISO 3166-1 alpha-2) to its rating-to-sub-ratings map.
+ // This is used for validating the builder parameters.
+ private static final Map<String, Map<String, String[]>> sRatings
+ = new HashMap<String, Map<String, String[]>>();
+
+ static {
+ Map<String, String[]> usRatings = new HashMap<String, String[]>();
+ usRatings.put(RATING_US_TV_Y, null);
+ usRatings.put(RATING_US_TV_Y7, new String[] { SUBRATING_US_FV });
+ usRatings.put(RATING_US_TV_G, null);
+ usRatings.put(RATING_US_TV_PG, new String[] {
+ SUBRATING_US_D, SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
+ usRatings.put(RATING_US_TV_14, new String[] {
+ SUBRATING_US_D, SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
+ usRatings.put(RATING_US_TV_MA, new String[] {
+ SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
+ sRatings.put(PREFIX_RATING_US, usRatings);
+
+ Map<String, String[]> krRatings = new HashMap<String, String[]>();
+ krRatings.put(RATING_KR_ALL, null);
+ krRatings.put(RATING_KR_7, null);
+ krRatings.put(RATING_KR_12, null);
+ krRatings.put(RATING_KR_15, null);
+ krRatings.put(RATING_KR_19, null);
+ sRatings.put(PREFIX_RATING_KR, krRatings);
+ }
+
+ private final String mRating;
+ private final String[] mSubRatings;
+
+ /**
+ * Constructs a TvContentRating object from a given rating constant.
+ *
+ * @param rating The rating constant defined in this class.
+ */
+ public TvContentRating(String rating) {
+ mRating = rating;
+ mSubRatings = null;
+ }
+
+ /**
+ * Constructs a TvContentRating object from a given rating and sub-rating constants.
+ *
+ * @param rating The rating constant defined in this class.
+ * @param subRatings The String array of sub-rating constants defined in this class.
+ */
+ public TvContentRating(String rating, String[] subRatings) {
+ mRating = rating;
+ mSubRatings = subRatings;
+ if (TextUtils.isEmpty(mRating)) {
+ throw new IllegalArgumentException("rating cannot be null");
+ }
+ String prefix = "";
+ if (mRating.length() > RATING_PREFIX_LENGTH) {
+ prefix = mRating.substring(0, RATING_PREFIX_LENGTH);
+ }
+ Map<String, String[]> ratings = sRatings.get(prefix);
+ if (ratings != null) {
+ if (!ratings.keySet().contains(mRating)) {
+ Log.w(TAG, "Unknown rating: " + mRating);
+ } else if (mSubRatings != null) {
+ String[] validSubRatings = ratings.get(mRating);
+ if (validSubRatings == null) {
+ Log.w(TAG, "Invalid subratings: " + mSubRatings);
+ } else {
+ List<String> validSubRatingList = Arrays.asList(subRatings);
+ for (String sr : mSubRatings) {
+ if (!validSubRatingList.contains(sr)) {
+ Log.w(TAG, "Invalid subrating: " + sr);
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ Log.w(TAG, "Rating undefined for " + mRating);
+ }
+ }
+
+ /**
+ * Recovers a TvContentRating from a String that was previously created with
+ * {@link #flattenToString}.
+ *
+ * @param ratingString The String that was returned by flattenToString().
+ * @return a new TvContentRating containing the rating and sub-ratings information was encoded
+ * in {@code ratingString}.
+ * @see #flattenToString
+ */
+ public static TvContentRating unflattenFromString(String ratingString) {
+ if (TextUtils.isEmpty(ratingString)) {
+ throw new IllegalArgumentException("Empty rating string");
+ }
+ String[] strs = ratingString.split(DELIMITER);
+ if (strs.length < 1) {
+ throw new IllegalArgumentException("Invalid rating string: " + ratingString);
+ }
+ if (strs.length > 1) {
+ String[] subRatings = new String[strs.length - 1];
+ System.arraycopy(strs, 1, subRatings, 0, subRatings.length);
+ return new TvContentRating(strs[0], subRatings);
+ }
+ return new TvContentRating(strs[0]);
+ }
+
+ /**
+ * @return a String that unambiguously describes both the rating and sub-rating information
+ * contained in the TvContentRating. You can later recover the TvContentRating from this
+ * string through {@link #unflattenFromString}.
+ * @see #unflattenFromString
+ */
+ public String flattenToString() {
+ StringBuffer ratingStr = new StringBuffer();
+ ratingStr.append(mRating);
+ if (mSubRatings != null) {
+ for (String subRating : mSubRatings) {
+ ratingStr.append(DELIMITER);
+ ratingStr.append(subRating);
+ }
+ }
+ return ratingStr.toString();
+ }
+}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 46aeb1d..8ecf808 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -16,9 +16,9 @@
package android.media.tv;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
+import android.media.tv.TvContract.Programs;
import android.net.Uri;
import android.provider.BaseColumns;
import android.util.ArraySet;
@@ -120,26 +120,23 @@ public final class TvContract {
/**
* Builds a URI that points to all browsable channels from a given TV input.
*
- * @param name {@link ComponentName} of the {@link android.media.tv.TvInputService} that
- * implements the given TV input.
+ * @param inputId The ID of the TV input to build a channels URI for.
*/
- public static final Uri buildChannelsUriForInput(ComponentName name) {
- return buildChannelsUriForInput(name, true);
+ public static final Uri buildChannelsUriForInput(String inputId) {
+ return buildChannelsUriForInput(inputId, true);
}
/**
* Builds a URI that points to all or browsable-only channels from a given TV input.
*
- * @param name {@link ComponentName} of the {@link android.media.tv.TvInputService} that
- * implements the given TV input.
+ * @param inputId The ID of the TV input to build a channels URI for.
* @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set
* to {@code false} the URI points to all channels regardless of whether they are
* browsable or not.
*/
- public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) {
+ public static final Uri buildChannelsUriForInput(String inputId, boolean browsableOnly) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
- .appendPath(PATH_INPUT).appendPath(name.getPackageName())
- .appendPath(name.getClassName()).appendPath(PATH_CHANNEL)
+ .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
.appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
}
@@ -147,26 +144,26 @@ public final class TvContract {
* Builds a URI that points to all or browsable-only channels which have programs with the given
* genre from the given TV input.
*
- * @param name {@link ComponentName} of the {@link android.media.tv.TvInputService} that
- * implements the given TV input. If null, builds a URI for all the TV inputs.
+ * @param inputId The ID of the TV input to build a channels URI for. If null, builds a URI for
+ * all the TV inputs.
* @param genre {@link Programs.Genres} to search.
* @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set
* to {@code false} the URI points to all channels regardless of whether they are
* browsable or not.
* @hide
*/
- public static final Uri buildChannelsUriForCanonicalGenre(ComponentName name, String genre,
+ public static final Uri buildChannelsUriForCanonicalGenre(String inputId, String genre,
boolean browsableOnly) {
if (!Programs.Genres.isCanonical(genre)) {
throw new IllegalArgumentException("Not a canonical genre: '" + genre + "'");
}
Uri uri;
- if (name == null) {
+ if (inputId == null) {
uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
.appendPath(PATH_CHANNEL).build();
} else {
- uri = buildChannelsUriForInput(name, browsableOnly);
+ uri = buildChannelsUriForInput(inputId, browsableOnly);
}
return uri.buildUpon().appendQueryParameter(PARAM_CANONICAL_GENRE, genre).build();
}
@@ -249,42 +246,25 @@ public final class TvContract {
}
/**
- * Extracts the {@link Channels#COLUMN_PACKAGE_NAME} from a given URI.
+ * Extracts the {@link Channels#COLUMN_INPUT_ID} from a given URI.
*
- * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
- * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
+ * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(String)},
+ * {@link #buildChannelsUriForInput(String, boolean)}, or
+ * {@link #buildChannelsUriForCanonicalGenre(String, String, boolean)}.
* @hide
*/
- public static final String getPackageName(Uri channelsUri) {
+ public static final String getInputId(Uri channelsUri) {
final List<String> paths = channelsUri.getPathSegments();
- if (paths.size() < 4) {
+ if (paths.size() < 3) {
throw new IllegalArgumentException("Not channels: " + channelsUri);
}
- if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
+ if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(2))) {
throw new IllegalArgumentException("Not channels: " + channelsUri);
}
return paths.get(1);
}
/**
- * Extracts the {@link Channels#COLUMN_SERVICE_NAME} from a given URI.
- *
- * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
- * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
- * @hide
- */
- public static final String getServiceName(Uri channelsUri) {
- final List<String> paths = channelsUri.getPathSegments();
- if (paths.size() < 4) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- return paths.get(2);
- }
-
- /**
* Extracts the {@link Channels#_ID} from a given URI.
*
* @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
@@ -495,15 +475,14 @@ public final class TvContract {
}
/**
- * The name of the {@link TvInputService} subclass that provides this TV channel. This
- * should be a fully qualified class name (such as, "com.example.project.TvInputService").
+ * The ID of the TV input that provides this TV channel.
* <p>
* This is a required field.
* </p><p>
* Type: TEXT
* </p>
*/
- public static final String COLUMN_SERVICE_NAME = "service_name";
+ public static final String COLUMN_INPUT_ID = "input_id";
/**
* The predefined type of this TV channel.
@@ -913,6 +892,24 @@ public final class TvContract {
public static final String COLUMN_AUDIO_LANGUAGE = "audio_language";
/**
+ * The comma-separated content ratings of this TV program.
+ * <p>
+ * This is used to describe the content rating(s) of this program. Each comma-separated
+ * content rating sub-string should be generated by calling
+ * {@link TvContentRating#flattenToString}. Note that in most cases the program content is
+ * rated by a single rating system, thus resulting in a corresponding single sub-string that
+ * does not require comma separation and multiple sub-strings appear only when the program
+ * content is rated by two or more content rating systems. If any of those ratings is
+ * specified as "blocked rating" in the user's parental control settings, the TV input
+ * service should block the current content and wait for the signal that it is okay to
+ * unblock.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_CONTENT_RATING = "content_rating";
+
+ /**
* The URI for the poster art of this TV program.
* <p>
* Can be empty.