diff options
Diffstat (limited to 'media/java')
-rw-r--r-- | media/java/android/media/AudioManager.java | 76 | ||||
-rw-r--r-- | media/java/android/media/MediaCodec.java | 6 | ||||
-rw-r--r-- | media/java/android/media/MediaCodecInfo.java | 80 | ||||
-rw-r--r-- | media/java/android/media/MediaPlayer.java | 52 | ||||
-rw-r--r-- | media/java/android/media/MediaSync.java | 22 | ||||
-rw-r--r-- | media/java/android/media/tv/TvContentRating.java | 98 | ||||
-rw-r--r-- | media/java/android/media/tv/TvInputManager.java | 2 | ||||
-rw-r--r-- | media/java/android/media/tv/TvInputService.java | 14 |
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–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 */ |