diff options
Diffstat (limited to 'media')
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.<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.<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. |