summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AudioManager.java76
-rw-r--r--media/java/android/media/MediaCodec.java6
-rw-r--r--media/java/android/media/MediaCodecInfo.java80
-rw-r--r--media/java/android/media/MediaPlayer.java52
-rw-r--r--media/java/android/media/MediaSync.java22
-rw-r--r--media/java/android/media/tv/TvContentRating.java98
-rw-r--r--media/java/android/media/tv/TvInputManager.java2
-rw-r--r--media/java/android/media/tv/TvInputService.java14
8 files changed, 284 insertions, 66 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8cd2688..19900d0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -57,7 +57,8 @@ import java.util.Iterator;
*/
public class AudioManager {
- private final Context mApplicationContext;
+ private Context mOriginalContext;
+ private Context mApplicationContext;
private long mVolumeKeyUpTime;
private final boolean mUseVolumeKeySounds;
private final boolean mUseFixedVolume;
@@ -621,14 +622,33 @@ public class AudioManager {
* @hide
*/
public AudioManager(Context context) {
- mApplicationContext = context;
- mUseVolumeKeySounds = mApplicationContext.getResources().getBoolean(
+ setContext(context);
+ mUseVolumeKeySounds = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_useVolumeKeySounds);
- mUseFixedVolume = mApplicationContext.getResources().getBoolean(
+ mUseFixedVolume = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
sAudioPortEventHandler.init();
}
+ private Context getContext() {
+ if (mApplicationContext == null) {
+ setContext(mOriginalContext);
+ }
+ if (mApplicationContext != null) {
+ return mApplicationContext;
+ }
+ return mOriginalContext;
+ }
+
+ private void setContext(Context context) {
+ mApplicationContext = context.getApplicationContext();
+ if (mApplicationContext != null) {
+ mOriginalContext = null;
+ } else {
+ mOriginalContext = context;
+ }
+ }
+
private static IAudioService getService()
{
if (sService != null) {
@@ -663,7 +683,7 @@ public class AudioManager {
* or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}.
*/
public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
helper.sendMediaButtonEvent(keyEvent, false);
}
@@ -709,7 +729,7 @@ public class AudioManager {
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (event.getRepeatCount() == 0) {
- MediaSessionLegacyHelper.getHelper(mApplicationContext)
+ MediaSessionLegacyHelper.getHelper(getContext())
.sendVolumeKeyEvent(event, false);
}
break;
@@ -737,7 +757,7 @@ public class AudioManager {
mVolumeKeyUpTime = SystemClock.uptimeMillis();
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
- MediaSessionLegacyHelper.getHelper(mApplicationContext)
+ MediaSessionLegacyHelper.getHelper(getContext())
.sendVolumeKeyEvent(event, false);
break;
}
@@ -783,7 +803,7 @@ public class AudioManager {
IAudioService service = getService();
try {
service.adjustStreamVolume(streamType, direction, flags,
- mApplicationContext.getOpPackageName());
+ getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustStreamVolume", e);
}
@@ -813,7 +833,7 @@ public class AudioManager {
* @see #isVolumeFixed()
*/
public void adjustVolume(int direction, int flags) {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
}
@@ -842,7 +862,7 @@ public class AudioManager {
* @see #isVolumeFixed()
*/
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
}
@@ -850,7 +870,7 @@ public class AudioManager {
public void setMasterMute(boolean mute, int flags) {
IAudioService service = getService();
try {
- service.setMasterMute(mute, flags, mApplicationContext.getOpPackageName());
+ service.setMasterMute(mute, flags, getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setMasterMute", e);
}
@@ -997,7 +1017,7 @@ public class AudioManager {
}
IAudioService service = getService();
try {
- service.setRingerModeExternal(ringerMode, mApplicationContext.getOpPackageName());
+ service.setRingerModeExternal(ringerMode, getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setRingerMode", e);
}
@@ -1018,7 +1038,7 @@ public class AudioManager {
public void setStreamVolume(int streamType, int index, int flags) {
IAudioService service = getService();
try {
- service.setStreamVolume(streamType, index, flags, mApplicationContext.getOpPackageName());
+ service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setStreamVolume", e);
}
@@ -1331,7 +1351,7 @@ public class AudioManager {
* @see #startBluetoothSco()
*/
public boolean isBluetoothScoAvailableOffCall() {
- return mApplicationContext.getResources().getBoolean(
+ return getContext().getResources().getBoolean(
com.android.internal.R.bool.config_bluetooth_sco_off_call);
}
@@ -1384,7 +1404,7 @@ public class AudioManager {
IAudioService service = getService();
try {
service.startBluetoothSco(mICallBack,
- mApplicationContext.getApplicationInfo().targetSdkVersion);
+ getContext().getApplicationInfo().targetSdkVersion);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in startBluetoothSco", e);
}
@@ -1532,7 +1552,7 @@ public class AudioManager {
public void setMicrophoneMute(boolean on){
IAudioService service = getService();
try {
- service.setMicrophoneMute(on, mApplicationContext.getOpPackageName());
+ service.setMicrophoneMute(on, getContext().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setMicrophoneMute", e);
}
@@ -1963,7 +1983,7 @@ public class AudioManager {
* Settings has an in memory cache, so this is fast.
*/
private boolean querySoundEffectsEnabled(int user) {
- return Settings.System.getIntForUser(mApplicationContext.getContentResolver(),
+ return Settings.System.getIntForUser(getContext().getContentResolver(),
Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
}
@@ -2375,7 +2395,7 @@ public class AudioManager {
try {
status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
mAudioFocusDispatcher, getIdForAudioFocusListener(l),
- mApplicationContext.getOpPackageName() /* package name */, flags,
+ getContext().getOpPackageName() /* package name */, flags,
ap != null ? ap.cb() : null);
} catch (RemoteException e) {
Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e);
@@ -2400,7 +2420,7 @@ public class AudioManager {
.setInternalLegacyStreamType(streamType).build(),
durationHint, mICallBack, null,
AudioSystem.IN_VOICE_COMM_FOCUS_ID,
- mApplicationContext.getOpPackageName(),
+ getContext().getOpPackageName(),
AUDIOFOCUS_FLAG_LOCK,
null /* policy token */);
} catch (RemoteException e) {
@@ -2469,7 +2489,7 @@ public class AudioManager {
if (eventReceiver == null) {
return;
}
- if (!eventReceiver.getPackageName().equals(mApplicationContext.getPackageName())) {
+ if (!eventReceiver.getPackageName().equals(getContext().getPackageName())) {
Log.e(TAG, "registerMediaButtonEventReceiver() error: " +
"receiver and context package names don't match");
return;
@@ -2478,7 +2498,7 @@ public class AudioManager {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
// the associated intent will be handled by the component being registered
mediaButtonIntent.setComponent(eventReceiver);
- PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext,
+ PendingIntent pi = PendingIntent.getBroadcast(getContext(),
0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
registerMediaButtonIntent(pi, eventReceiver);
}
@@ -2512,8 +2532,8 @@ public class AudioManager {
Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter");
return;
}
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
- helper.addMediaButtonListener(pi, eventReceiver, mApplicationContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
+ helper.addMediaButtonListener(pi, eventReceiver, getContext());
}
/**
@@ -2531,7 +2551,7 @@ public class AudioManager {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
// the associated intent will be handled by the component being registered
mediaButtonIntent.setComponent(eventReceiver);
- PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext,
+ PendingIntent pi = PendingIntent.getBroadcast(getContext(),
0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/);
unregisterMediaButtonIntent(pi);
}
@@ -2554,7 +2574,7 @@ public class AudioManager {
* @hide
*/
public void unregisterMediaButtonIntent(PendingIntent pi) {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext);
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
helper.removeMediaButtonListener(pi);
}
@@ -2571,7 +2591,7 @@ public class AudioManager {
if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
- rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mApplicationContext));
+ rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(getContext()));
}
/**
@@ -2586,7 +2606,7 @@ public class AudioManager {
if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) {
return;
}
- rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(mApplicationContext));
+ rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(getContext()));
}
/**
@@ -3280,7 +3300,7 @@ public class AudioManager {
*/
public void setRingerModeInternal(int ringerMode) {
try {
- getService().setRingerModeInternal(ringerMode, mApplicationContext.getOpPackageName());
+ getService().setRingerModeInternal(ringerMode, getContext().getOpPackageName());
} catch (RemoteException e) {
Log.w(TAG, "Error calling setRingerModeInternal", e);
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8220a74..6f7b583 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -918,9 +918,9 @@ final public class MediaCodec {
}
/**
- * This indicates that no key has been set to perform the requested
- * decrypt operation. The operation can be retried after adding
- * a decryption key.
+ * This indicates that the requested key was not found when trying to
+ * perform a decrypt operation. The operation can be retried after adding
+ * the correct decryption key.
*/
public static final int ERROR_NO_KEY = 1;
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 974c9af..89d419a 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1733,6 +1733,72 @@ public final class MediaCodecInfo {
maxBlocks, maxBlocksPerSecond,
16 /* blockWidth */, 16 /* blockHeight */,
1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ boolean supported = true;
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.MPEG2ProfileSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG2LevelML:
+ FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 15000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG2ProfileMain:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG2LevelLL:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break;
+ case CodecProfileLevel.MPEG2LevelML:
+ FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 15000; break;
+ case CodecProfileLevel.MPEG2LevelH14:
+ FR = 60; W = 90; H = 68; MBPS = 367200; FS = 6120; BR = 60000; break;
+ case CodecProfileLevel.MPEG2LevelHL:
+ FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG2Profile422:
+ case CodecProfileLevel.MPEG2ProfileSNR:
+ case CodecProfileLevel.MPEG2ProfileSpatial:
+ case CodecProfileLevel.MPEG2ProfileHigh:
+ Log.i(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ }
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
} else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
int maxWidth = 11, maxHeight = 9, maxRate = 15;
maxBlocks = 99;
@@ -2343,6 +2409,20 @@ public final class MediaCodecInfo {
public static final int MPEG4Level4a = 0x40;
public static final int MPEG4Level5 = 0x80;
+ // from OMX_VIDEO_MPEG2PROFILETYPE
+ public static final int MPEG2ProfileSimple = 0x00;
+ public static final int MPEG2ProfileMain = 0x01;
+ public static final int MPEG2Profile422 = 0x02;
+ public static final int MPEG2ProfileSNR = 0x03;
+ public static final int MPEG2ProfileSpatial = 0x04;
+ public static final int MPEG2ProfileHigh = 0x05;
+
+ // from OMX_VIDEO_MPEG2LEVELTYPE
+ public static final int MPEG2LevelLL = 0x00;
+ public static final int MPEG2LevelML = 0x01;
+ public static final int MPEG2LevelH14 = 0x02;
+ public static final int MPEG2LevelHL = 0x03;
+
// from OMX_AUDIO_AACPROFILETYPE
public static final int AACObjectMain = 1;
public static final int AACObjectLC = 2;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 77adb39..6ec10c7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -42,6 +42,7 @@ import android.system.OsConstants;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
+import android.widget.VideoView;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaFormat;
@@ -2121,6 +2122,43 @@ public class MediaPlayer implements SubtitleController.Listener
mSubtitleController.setAnchor(anchor);
}
+ /**
+ * The private version of setSubtitleAnchor is used internally to set mSubtitleController if
+ * necessary when clients don't provide their own SubtitleControllers using the public version
+ * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one).
+ */
+ private synchronized void setSubtitleAnchor() {
+ if (mSubtitleController == null) {
+ final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread");
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Context context = ActivityThread.currentApplication();
+ mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer.this);
+ mSubtitleController.setAnchor(new Anchor() {
+ @Override
+ public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+ }
+
+ @Override
+ public Looper getSubtitleLooper() {
+ return Looper.getMainLooper();
+ }
+ });
+ thread.getLooper().quitSafely();
+ }
+ });
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.w(TAG, "failed to join SetSubtitleAnchorThread");
+ }
+ }
+ }
+
private final Object mInbandSubtitleLock = new Object();
private SubtitleTrack[] mInbandSubtitleTracks;
private int mSelectedSubtitleTrackIndex = -1;
@@ -2381,24 +2419,14 @@ public class MediaPlayer implements SubtitleController.Listener
fFormat.setString(MediaFormat.KEY_MIME, mime);
fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1);
- Context context = ActivityThread.currentApplication();
// A MediaPlayer created by a VideoView should already have its mSubtitleController set.
if (mSubtitleController == null) {
- mSubtitleController = new SubtitleController(context, mTimeProvider, this);
- mSubtitleController.setAnchor(new Anchor() {
- @Override
- public void setSubtitleWidget(RenderingWidget subtitleWidget) {
- }
-
- @Override
- public Looper getSubtitleLooper() {
- return Looper.getMainLooper();
- }
- });
+ setSubtitleAnchor();
}
if (!mSubtitleController.hasRendererFor(fFormat)) {
// test and add not atomic
+ Context context = ActivityThread.currentApplication();
mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler));
}
final SubtitleTrack track = mSubtitleController.addTrack(fFormat);
diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java
index ecc87e7..a5b0d39 100644
--- a/media/java/android/media/MediaSync.java
+++ b/media/java/android/media/MediaSync.java
@@ -312,13 +312,13 @@ final public class MediaSync {
* @throws IllegalArgumentException if the surface has been released, is invalid,
* or can not be connected.
* @throws IllegalStateException if setting the surface is not supported, e.g.
- * not in the Initialized state, or another surface has already been configured.
+ * not in the Initialized state, or another surface has already been set.
*/
public void setSurface(@Nullable Surface surface) {
- native_configureSurface(surface);
+ native_setSurface(surface);
}
- private native final void native_configureSurface(@Nullable Surface surface);
+ private native final void native_setSurface(@Nullable Surface surface);
/**
* Sets the audio track for MediaSync.
@@ -328,21 +328,17 @@ final public class MediaSync {
* @param audioTrack Specify an AudioTrack through which to render the audio data.
* @throws IllegalArgumentException if the audioTrack has been released, or is invalid.
* @throws IllegalStateException if setting the audio track is not supported, e.g.
- * not in the Initialized state, or another audio track has already been configured.
+ * not in the Initialized state, or another audio track has already been set.
*/
public void setAudioTrack(@Nullable AudioTrack audioTrack) {
- // AudioTrack has sanity check for configured sample rate.
- int nativeSampleRateInHz = (audioTrack == null ? 0 : audioTrack.getSampleRate());
-
- native_configureAudioTrack(audioTrack, nativeSampleRateInHz);
+ native_setAudioTrack(audioTrack);
mAudioTrack = audioTrack;
if (audioTrack != null && mAudioThread == null) {
createAudioThread();
}
}
- private native final void native_configureAudioTrack(
- @Nullable AudioTrack audioTrack, int nativeSampleRateInHz);
+ private native final void native_setAudioTrack(@Nullable AudioTrack audioTrack);
/**
* Requests a Surface to use as the input. This may only be called after
@@ -350,7 +346,7 @@ final public class MediaSync {
* <p>
* The application is responsible for calling release() on the Surface when
* done.
- * @throws IllegalStateException if not configured, or another input surface has
+ * @throws IllegalStateException if not set, or another input surface has
* already been created.
*/
@NonNull
@@ -574,7 +570,7 @@ final public class MediaSync {
* @param sizeInBytes number of bytes to queue.
* @param presentationTimeUs the presentation timestamp in microseconds for the first frame
* in the buffer.
- * @throws IllegalStateException if audio track is not configured or internal configureation
+ * @throws IllegalStateException if audio track is not set or internal configureation
* has not been done correctly.
*/
public void queueAudio(
@@ -582,7 +578,7 @@ final public class MediaSync {
long presentationTimeUs) {
if (mAudioTrack == null || mAudioThread == null) {
throw new IllegalStateException(
- "AudioTrack is NOT configured or audio thread is not created");
+ "AudioTrack is NOT set or audio thread is not created");
}
synchronized(mAudioLock) {
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 966e41a..043b80e 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -171,6 +171,14 @@ import java.util.Objects;
* <td>TV content rating system for Brazil</td>
* </tr>
* <tr>
+ * <td>CA_TV_EN</td>
+ * <td>TV content rating system for Canada (English)</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_FR</td>
+ * <td>TV content rating system for Canada (French)</td>
+ * </tr>
+ * <tr>
* <td>DVB</td>
* <td>DVB content rating system</td>
* </tr>
@@ -195,6 +203,10 @@ import java.util.Objects;
* <td>TV content rating system for Singapore</td>
* </tr>
* <tr>
+ * <td>US_MV</td>
+ * <td>Movie content rating system for the United States</td>
+ * </tr>
+ * <tr>
* <td>US_TV</td>
* <td>TV content rating system for the United States</td>
* </tr>
@@ -290,6 +302,60 @@ import java.util.Objects;
* <td>Content suitable for viewers over the age of 18</td>
* </tr>
* <tr>
+ * <td valign="top" rowspan="7">CA_TV_EN</td>
+ * <td>CA_TV_EN_EXEMPT</td>
+ * <td>Exempt from ratings</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_EN_C</td>
+ * <td>Suitable for children ages 2&#8211;7</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_EN_C8</td>
+ * <td>Suitable for children ages 8 and older</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_EN_G</td>
+ * <td>Suitable for the entire family</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_EN_PG</td>
+ * <td>May contain moderate violence, profanity, nudity, and sexual references</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_EN_14</td>
+ * <td>Intended for viewers ages 14 and older</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_EN_18</td>
+ * <td>Intended for viewers ages 18 and older</td>
+ * </tr>
+ * <tr>
+ * <td valign="top" rowspan="6">CA_TV_FR</td>
+ * <td>CA_TV_FR_E</td>
+ * <td>Exempt from ratings</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_FR_G</td>
+ * <td>Appropriate for all ages</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_FR_8</td>
+ * <td>Appropriate for children 8</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_FR_13</td>
+ * <td>Suitable for children 13</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_FR_16</td>
+ * <td>Recommended for children over the age of 16</td>
+ * </tr>
+ * <tr>
+ * <td>CA_TV_FR_18</td>
+ * <td>Only to be viewed by adults</td>
+ * </tr>
+ * <tr>
* <td valign="top" rowspan="15">DVB</td>
* <td>DVB_4</td>
* <td>Recommended for ages 4 and over</td>
@@ -608,6 +674,27 @@ import java.util.Objects;
* <td>Suitable for adults aged 21 and above</td>
* </tr>
* <tr>
+ * <td valign="top" rowspan="5">US_MV</td>
+ * <td>US_MV_G</td>
+ * <td>General audiences</td>
+ * </tr>
+ * <tr>
+ * <td>US_MV_PG</td>
+ * <td>Parental guidance suggested</td>
+ * </tr>
+ * <tr>
+ * <td>US_MV_PG13</td>
+ * <td>Parents strongly cautioned</td>
+ * </tr>
+ * <tr>
+ * <td>US_MV_R</td>
+ * <td>Restricted, under 17 requires accompanying parent or adult guardian</td>
+ * </tr>
+ * <tr>
+ * <td>US_MV_NC17</td>
+ * <td>No one 17 and under admitted</td>
+ * </tr>
+ * <tr>
* <td valign="top" rowspan="6">US_TV</td>
* <td>US_TV_Y</td>
* <td>This program is designed to be appropriate for all children</td>
@@ -696,10 +783,15 @@ public final class TvContentRating {
private final int mHashCode;
/**
- * Rating constant denoting unrated content.
+ * Rating constant denoting unrated content. Used to handle the case where the content rating
+ * information is missing.
+ *
+ * <p>TV input services can call {@link TvInputManager#isRatingBlocked} with this constant to
+ * determine whether they should block unrated content. The subsequent call to
+ * {@link TvInputService.Session#notifyContentBlocked} with the same constant notifies
+ * applications that the current program content is blocked by parental controls.
*/
- public static final TvContentRating UNRATED = new TvContentRating("com.android.tv", "",
- "UNRATED", null);
+ public static final TvContentRating UNRATED = new TvContentRating("null", "null", "null", null);
/**
* Creates a {@code TvContentRating} object with predefined content rating strings.
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 3272a23..dca0631 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1035,7 +1035,7 @@ public final class TvInputManager {
/**
* Checks whether a given TV content rating is blocked by the user.
*
- * @param rating The TV content rating to check.
+ * @param rating The TV content rating to check. Can be {@link TvContentRating#UNRATED}.
* @return {@code true} if the given TV content rating is blocked, {@code false} otherwise.
*/
public boolean isRatingBlocked(@NonNull TvContentRating rating) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 34c36c3..c1035b0 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -528,11 +528,12 @@ public abstract class TvInputService extends Service {
* TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
* service should block the content or not is determined by invoking
* {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
- * with the content rating for the current program. Then the {@link TvInputManager} makes a
- * judgment based on the user blocked ratings stored in the secure settings and returns the
- * result. If the rating in question turns out to be blocked, the TV input service must
- * immediately block the content and call this method with the content rating of the current
- * program to prompt the PIN verification screen.
+ * with the content rating for the current program or {@link TvContentRating#UNRATED} in
+ * case the rating information is missing. Then the {@link TvInputManager} makes a judgment
+ * based on the user blocked ratings stored in the secure settings and returns the result.
+ * If the rating in question turns out to be blocked, the TV input service must immediately
+ * block the content and call this method with the content rating of the current program to
+ * prompt the PIN verification screen.
*
* <p>Each TV input service also needs to continuously listen to any changes made to the
* parental controls settings by registering a broadcast receiver to receive
@@ -540,7 +541,8 @@ public abstract class TvInputService extends Service {
* {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
* reevaluate the current program with the new parental controls settings.
*
- * @param rating The content rating for the current TV program.
+ * @param rating The content rating for the current TV program. Can be
+ * {@link TvContentRating#UNRATED}.
* @see #notifyContentAllowed
* @see TvInputManager
*/