am b719bf57: am 3245f3d7: Merge "TIF: Add TvParentalControlManager" into lmp-dev

* commit 'b719bf5720ea501fa6324d866e49e65b59b1a182':
  TIF: Add TvParentalControlManager
This commit is contained in:
Jae Seo
2014-07-18 03:30:41 +00:00
committed by Android Git Automerger
12 changed files with 519 additions and 32 deletions

View File

@@ -7214,6 +7214,7 @@ package android.content {
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
field public static final java.lang.String TV_INPUT_SERVICE = "tv_input";
field public static final java.lang.String TV_PARENTAL_CONTROL_SERVICE = "tv_parental_control";
field public static final java.lang.String UI_MODE_SERVICE = "uimode";
field public static final java.lang.String USB_SERVICE = "usb";
field public static final java.lang.String USER_SERVICE = "user";
@@ -16434,10 +16435,12 @@ package android.media.session {
package android.media.tv {
public class TvContentRating {
public final class TvContentRating {
ctor public TvContentRating(java.lang.String);
ctor public TvContentRating(java.lang.String, java.lang.String[]);
method public java.lang.String flattenToString();
method public java.lang.String getRating();
method public java.util.List<java.lang.String> getSubRatings();
method public static android.media.tv.TvContentRating unflattenFromString(java.lang.String);
field public static final java.lang.String RATING_KR_12 = "RATING_KR_12";
field public static final java.lang.String RATING_KR_15 = "RATING_KR_15";
@@ -16632,6 +16635,7 @@ package android.media.tv {
public abstract class TvInputService.Session implements android.view.KeyEvent.Callback {
ctor public TvInputService.Session();
method public void dispatchChannelRetuned(android.net.Uri);
method public void dispatchContentBlocked(android.media.tv.TvContentRating);
method public void dispatchTrackInfoChanged(java.util.List<android.media.tv.TvTrackInfo>);
method public void dispatchVideoAvailable();
method public void dispatchVideoUnavailable(int);
@@ -16653,6 +16657,19 @@ package android.media.tv {
method public void setOverlayViewEnabled(boolean);
}
public final class TvParentalControlManager {
method public void addParentalControlCallback(android.media.tv.TvParentalControlManager.ParentalControlCallback, android.os.Handler);
method public final boolean isEnabled();
method public final boolean isRatingBlocked(android.media.tv.TvContentRating);
method public void removeParentalControlCallback(android.media.tv.TvParentalControlManager.ParentalControlCallback);
}
public static abstract class TvParentalControlManager.ParentalControlCallback {
ctor public TvParentalControlManager.ParentalControlCallback();
method public void onBlockedRatingsChanged();
method public void onEnabledChanged(boolean);
}
public final class TvTrackInfo implements android.os.Parcelable {
method public boolean containsKey(java.lang.String);
method public int describeContents();
@@ -16710,6 +16727,7 @@ package android.media.tv {
public static abstract class TvView.TvInputListener {
ctor public TvView.TvInputListener();
method public void onChannelRetuned(java.lang.String, android.net.Uri);
method public void onContentBlocked(java.lang.String, android.media.tv.TvContentRating);
method public void onError(java.lang.String, int);
method public void onTrackInfoChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>);
method public void onVideoAvailable(java.lang.String);

View File

@@ -75,6 +75,7 @@ import android.media.MediaRouter;
import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
import android.media.tv.ITvInputManager;
import android.media.tv.TvParentalControlManager;
import android.media.tv.TvInputManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
@@ -730,6 +731,11 @@ class ContextImpl extends Context {
return new TvInputManager(service, UserHandle.myUserId());
}});
registerService(TV_PARENTAL_CONTROL_SERVICE, new ServiceFetcher() {
public Object getService(ContextImpl ctx) {
return new TvParentalControlManager(ctx);
}});
registerService(NETWORK_SCORE_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new NetworkScoreManager(ctx);

View File

@@ -2821,6 +2821,16 @@ public abstract class Context {
*/
public static final String TV_INPUT_SERVICE = "tv_input";
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.media.tv.TvParentalControlManager} for obtaining parental
* control settings and listening to their changes.
*
* @see #getSystemService
* @see android.media.tv.TvParentalControlManager
*/
public static final String TV_PARENTAL_CONTROL_SERVICE = "tv_parental_control";
/**
* {@link android.net.NetworkScoreManager} for managing network scoring.
* @see #getSystemService

View File

@@ -3627,6 +3627,20 @@ public final class Settings {
*/
public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
/**
* Whether the TV parental control is enabled.
* @hide
*/
public static final String TV_PARENTAL_CONTROL_ENABLED = "tv_parental_control_enabled";
/**
* List of TV content ratings blocked by the user. (comma-delimited)
* @hide
*/
public static final String TV_PARENTAL_CONTROL_BLOCKED_RATINGS =
"tv_parental_control_blocked_ratings";
/**
* Settings classname to launch when Settings is clicked from All
* Applications. Needed because of user testing between the old

View File

@@ -36,4 +36,5 @@ oneway interface ITvInputClient {
void onTrackInfoChanged(in List<TvTrackInfo> tracks, int seq);
void onVideoAvailable(int seq);
void onVideoUnavailable(int reason, int seq);
void onContentBlocked(in String rating, int seq);
}

View File

@@ -33,4 +33,5 @@ oneway interface ITvInputSessionCallback {
void onTrackInfoChanged(in List<TvTrackInfo> tracks);
void onVideoAvailable();
void onVideoUnavailable(int reason);
void onContentBlocked(in String rating);
}

View File

@@ -16,18 +16,20 @@
package android.media.tv;
import android.annotation.SystemApi;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A class representing a TV content rating.
*/
public class TvContentRating {
public final class TvContentRating {
private static final String TAG = "TvContentRating";
private static final int RATING_PREFIX_LENGTH = 10;
@@ -124,10 +126,10 @@ public class TvContentRating {
// 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[]>>();
= new ArrayMap<String, Map<String, String[]>>();
static {
Map<String, String[]> usRatings = new HashMap<String, String[]>();
Map<String, String[]> usRatings = new ArrayMap<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);
@@ -139,7 +141,7 @@ public class TvContentRating {
SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
sRatings.put(PREFIX_RATING_US, usRatings);
Map<String, String[]> krRatings = new HashMap<String, String[]>();
Map<String, String[]> krRatings = new ArrayMap<String, String[]>();
krRatings.put(RATING_KR_ALL, null);
krRatings.put(RATING_KR_7, null);
krRatings.put(RATING_KR_12, null);
@@ -157,8 +159,7 @@ public class TvContentRating {
* @param rating The rating constant defined in this class.
*/
public TvContentRating(String rating) {
mRating = rating;
mSubRatings = null;
this(rating, null);
}
/**
@@ -168,11 +169,11 @@ public class TvContentRating {
* @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)) {
if (TextUtils.isEmpty(rating)) {
throw new IllegalArgumentException("rating cannot be null");
}
mRating = rating;
mSubRatings = subRatings;
String prefix = "";
if (mRating.length() > RATING_PREFIX_LENGTH) {
prefix = mRating.substring(0, RATING_PREFIX_LENGTH);
@@ -188,6 +189,10 @@ public class TvContentRating {
} else {
List<String> validSubRatingList = Arrays.asList(subRatings);
for (String sr : mSubRatings) {
if (TextUtils.isEmpty(sr)) {
throw new IllegalArgumentException(
"subRatings cannot contain empty elements");
}
if (!validSubRatingList.contains(sr)) {
Log.w(TAG, "Invalid subrating: " + sr);
break;
@@ -200,6 +205,52 @@ public class TvContentRating {
}
}
/**
* Returns the main rating constant.
*
* @return the rating string that starts with "RATING_" prefix as defined in this class.
*/
public String getMainRating() {
return mRating;
}
/**
* Returns the list of sub-rating constants.
*
* @return the unmodifiable {@code List} of sub-rating strings that start with "SUBRATING_"
* prefix as defined in this class.
*/
public List<String> getSubRatings() {
if (mSubRatings == null) {
return null;
}
return Collections.unmodifiableList(Arrays.asList(mSubRatings));
}
/**
* Returns 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}.
*
* @return a new String holding rating/sub-rating information, which can later be stored in the
* database and settings.
* @see #unflattenFromString
*/
public String flattenToString() {
// TODO: Consider removing all obvious/redundant sub-strings including "RATING" and
// "SUBRATING" and find out a storage-efficient string format such as:
// <country>-<primary>/<sub1>/<sub2>/<sub3>
StringBuilder builder = new StringBuilder(mRating);
if (mSubRatings != null) {
for (String subRating : mSubRatings) {
builder.append(DELIMITER);
builder.append(subRating);
}
}
return builder.toString();
}
/**
* Recovers a TvContentRating from a String that was previously created with
* {@link #flattenToString}.
@@ -226,20 +277,35 @@ public class TvContentRating {
}
/**
* @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
* Returns true if this rating has the same main rating as the specified rating and when this
* rating's sub-ratings contain the other's.
* <p>
* For example, a TvContentRating object that represents TV-PG with S(Sexual content) and
* V(Violence) contains TV-PG, TV-PG/S, TV-PG/V and itself.
* </p>
*
* @param rating The {@link TvContentRating} to check.
* @return {@code true} if this object contains {@code rating}, {@code false} otherwise.
* @hide
*/
public String flattenToString() {
StringBuffer ratingStr = new StringBuffer();
ratingStr.append(mRating);
if (mSubRatings != null) {
for (String subRating : mSubRatings) {
ratingStr.append(DELIMITER);
ratingStr.append(subRating);
}
@SystemApi
public final boolean contains(TvContentRating rating) {
if (rating == null) {
throw new IllegalArgumentException("rating cannot be null");
}
if (!rating.getMainRating().equals(mRating)) {
return false;
}
List<String> subRatings = getSubRatings();
List<String> subRatingsOther = rating.getSubRatings();
if (subRatings == null && subRatingsOther == null) {
return true;
} else if (subRatings == null && subRatingsOther != null) {
return false;
} else if (subRatings != null && subRatingsOther == null) {
return true;
} else {
return subRatings.containsAll(subRatingsOther);
}
return ratingStr.toString();
}
}

View File

@@ -36,7 +36,6 @@ import android.view.Surface;
import android.view.View;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -49,10 +48,13 @@ import java.util.Map;
public final class TvInputManager {
private static final String TAG = "TvInputManager";
static final int VIDEO_UNAVAILABLE_REASON_START = 0;
static final int VIDEO_UNAVAILABLE_REASON_END = 3;
/**
* A generic reason. Video is not available due to an unspecified error.
*/
public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0;
public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = VIDEO_UNAVAILABLE_REASON_START;
/**
* Video is not available because the TV input is tuning to another channel.
*/
@@ -65,7 +67,7 @@ public final class TvInputManager {
* Video is not available because the TV input stopped the playback temporarily to buffer more
* data.
*/
public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END;
/**
* The TV input is connected.
@@ -184,6 +186,15 @@ public final class TvInputManager {
public void onVideoUnavailable(Session session, int reason) {
}
/**
* This is called when the current program content is blocked by parental controls.
*
* @param session A {@link TvInputManager.Session} associated with this callback
* @param rating The content ration of the blocked program.
*/
public void onContentBlocked(Session session, TvContentRating rating) {
}
/**
* This is called when a custom event has been sent from this session.
*
@@ -263,6 +274,15 @@ public final class TvInputManager {
});
}
public void postContentBlocked(final TvContentRating rating) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onContentBlocked(mSession, rating);
}
});
}
public void postSessionEvent(final String eventType, final Bundle eventArgs) {
mHandler.post(new Runnable() {
@Override
@@ -402,6 +422,18 @@ public final class TvInputManager {
}
}
@Override
public void onContentBlocked(String rating, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
record.postContentBlocked(TvContentRating.unflattenFromString(rating));
}
}
@Override
public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
synchronized (mSessionCallbackRecordMap) {

View File

@@ -19,7 +19,6 @@ package android.media.tv;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
@@ -319,6 +318,42 @@ public abstract class TvInputService extends Service {
});
}
/**
* Informs the application that the current program content is blocked by parent controls.
* <p>
* Each TV input service is required to query the system whether the user is allowed to
* watch the current program before showing it to the user if the parental control is turned
* on, which can be checked by calling {@link TvParentalControlManager#isEnabled}. Whether
* the TV input service should block the content or not is determined by invoking
* {@link TvParentalControlManager#isRatingBlocked} with the content rating for the current
* program. Then the TvParentalControlManager 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><p>
* Each TV input service also needs to continuously listen to any changes made to the
* parental control settings by registering a
* {@link TvParentalControlManager.ParentalControlCallback} to the manager and immediately
* reevaluate the current program with the new parental control settings.
* </p>
*
* @param rating The content rating for the current TV program.
*/
public void dispatchContentBlocked(final TvContentRating rating) {
mHandler.post(new Runnable() {
@Override
public void run() {
try {
if (DEBUG) Log.d(TAG, "dispatchContentBlocked");
mSessionCallback.onContentBlocked(rating.flattenToString());
} catch (RemoteException e) {
Log.w(TAG, "error in dispatchContentBlocked");
}
}
});
}
/**
* Informs the application that video is not available, so the TV input cannot continue
* playing the TV stream.
@@ -332,10 +367,8 @@ public abstract class TvInputService extends Service {
* </ul>
*/
public void dispatchVideoUnavailable(final int reason) {
if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN
&& reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNE
&& reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL
&& reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) {
if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
|| reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
throw new IllegalArgumentException("Unknown reason: " + reason);
}
mHandler.post(new Runnable() {

View File

@@ -0,0 +1,268 @@
/*
* 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.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Contains methods for accessing and monitoring the user's parental control settings.
* <p>
* To obtain a handle to the TV parental control manager, do the following:
* <p>
* <code>
* <pre>TvParentalControlManager tvParentalControlManager =
* (TvParentalControlManager) context.getSystemService(Context.TV_PARENTAL_CONTROL_SERVICE);
* </pre>
* </code>
*/
public final class TvParentalControlManager {
/** Default parental control enabled value. */
private static final int DEFAULT_ENABLED = 0;
private final Handler mHandler = new Handler();
private final ContentResolver mContentResolver;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final List<ParentalControlCallbackRecord> mParentalControlCallbackRecordList =
new LinkedList<ParentalControlCallbackRecord>();
@GuardedBy("mLock")
private final List<TvContentRating> mBlockedRatings = new ArrayList<TvContentRating>();
@GuardedBy("mLock")
private String mBlockedRatingsString;
/**
* Creates a new parental control manager for the specified context.
*
* @hide
*/
public TvParentalControlManager(Context context) {
mContentResolver = context.getContentResolver();
}
/**
* Returns the user's parental control enabled state.
*
* @return {@code true} if the user enabled the parental control, {@code false} otherwise.
*/
public final boolean isEnabled() {
return Settings.Secure.getInt(mContentResolver, Settings.Secure.TV_PARENTAL_CONTROL_ENABLED,
DEFAULT_ENABLED) == 1;
}
/**
* Checks whether a given TV content rating is blocked by the user.
*
* @param rating The TV content rating to check.
* @return {@code true} if blocked, {@code false} if not blocked or parental control is
* disabled.
*/
public final boolean isRatingBlocked(TvContentRating rating) {
if (!isEnabled()) {
// Parental control is disabled. Enjoy watching good stuff.
return false;
}
// Update the blocked ratings only when they change.
final String blockedRatingsString = Settings.Secure.getString(mContentResolver,
Settings.Secure.TV_PARENTAL_CONTROL_BLOCKED_RATINGS);
synchronized (mLock) {
if (!TextUtils.equals(blockedRatingsString, mBlockedRatingsString)) {
mBlockedRatingsString = blockedRatingsString;
updateBlockedRatingsLocked();
}
for (TvContentRating blockedRating : mBlockedRatings) {
if (rating.contains(blockedRating)) {
return true;
}
}
}
return false;
}
private void updateBlockedRatingsLocked() {
mBlockedRatings.clear();
if (TextUtils.isEmpty(mBlockedRatingsString)) {
return;
}
for (String blockedRatingString : mBlockedRatingsString.split("\\s*,\\s*")) {
mBlockedRatings.add(TvContentRating.unflattenFromString(blockedRatingString));
}
}
/**
* Adds a callback for monitoring the changes in the user's parental control settings.
*
* @param callback The callback to add.
* @param handler a {@link Handler} that the settings change will be delivered to.
*/
public void addParentalControlCallback(ParentalControlCallback callback,
Handler handler) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
if (handler == null) {
throw new IllegalArgumentException("handler cannot be null");
}
synchronized (mLock) {
if (mParentalControlCallbackRecordList.isEmpty()) {
registerObserver(Settings.Secure.TV_PARENTAL_CONTROL_ENABLED);
registerObserver(Settings.Secure.TV_PARENTAL_CONTROL_BLOCKED_RATINGS);
}
mParentalControlCallbackRecordList.add(
new ParentalControlCallbackRecord(callback, handler));
}
}
private void registerObserver(String key) {
mContentResolver.registerContentObserver(Settings.Secure.getUriFor(key), false,
mContentObserver);
}
/**
* Removes a callback previously added using {@link #addParentalControlCallback}.
*
* @param callback The callback to remove.
*/
public void removeParentalControlCallback(ParentalControlCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
synchronized (mLock) {
for (Iterator<ParentalControlCallbackRecord> it =
mParentalControlCallbackRecordList.iterator(); it.hasNext();) {
ParentalControlCallbackRecord record = it.next();
if (record.getCallback() == callback) {
it.remove();
break;
}
}
}
}
private void notifyEnabledChanged() {
final boolean enabled = isEnabled();
synchronized (mLock) {
for (ParentalControlCallbackRecord record : mParentalControlCallbackRecordList) {
record.postEnabledChanged(enabled);
}
}
}
private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
final String uriPath = uri.getPath();
final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
if (Settings.Secure.TV_PARENTAL_CONTROL_ENABLED.equals(name)) {
notifyEnabledChanged();
} else if (Settings.Secure.TV_PARENTAL_CONTROL_BLOCKED_RATINGS.equals(name)) {
// We only need a single callback when multiple ratings change in rapid
// succession.
mHandler.removeCallbacks(mBlockedRatingsChangedRunnable);
mHandler.post(mBlockedRatingsChangedRunnable);
}
}
};
/**
* Runnable posted when user blocked ratings change. This is used to prevent unnecessary change
* notifications when multiple ratings change in rapid succession.
*/
private final Runnable mBlockedRatingsChangedRunnable = new Runnable() {
@Override
public void run() {
synchronized (mLock) {
for (ParentalControlCallbackRecord record : mParentalControlCallbackRecordList) {
record.postBlockedRatingsChanged();
}
}
}
};
/**
* Callback for changes in parental control settings, including enabled state.
*/
public static abstract class ParentalControlCallback {
/**
* Called when the parental control enabled state changes.
*
* @param enabled the user's parental control enabled state
*/
public void onEnabledChanged(boolean enabled) {}
/**
* Called when the user blocked ratings change.
* <p>
* When this is invoked, one should immediately call
* {@link TvParentalControlManager#isRatingBlocked} to reevaluate the current content since
* the user might have changed her mind and blocked the rating for the content.
*
* @see TvParentalControlManager#isRatingBlocked
*/
public void onBlockedRatingsChanged() {}
}
private static final class ParentalControlCallbackRecord {
private final ParentalControlCallback mCallback;
private final Handler mHandler;
public ParentalControlCallbackRecord(ParentalControlCallback callback, Handler handler) {
mCallback = callback;
mHandler = handler;
}
public ParentalControlCallback getCallback() {
return mCallback;
}
public void postEnabledChanged(final boolean enabled) {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onEnabledChanged(enabled);
}
});
}
public void postBlockedRatingsChanged() {
mHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onBlockedRatingsChanged();
}
});
}
}
}

View File

@@ -547,6 +547,15 @@ public class TvView extends ViewGroup {
public void onVideoUnavailable(String inputId, int reason) {
}
/**
* This is called when the current program content is blocked by parental controls.
*
* @param inputId The ID of the TV input bound to this view.
* @param rating The content rating of the blocked program.
*/
public void onContentBlocked(String inputId, TvContentRating rating) {
}
/**
* This is invoked when a custom event from the bound TV input is sent to this view.
*
@@ -648,6 +657,7 @@ public class TvView extends ViewGroup {
}
}
@Override
public void onVideoAvailable(Session session) {
if (DEBUG) {
Log.d(TAG, "onVideoAvailable()");
@@ -657,6 +667,7 @@ public class TvView extends ViewGroup {
}
}
@Override
public void onVideoUnavailable(Session session, int reason) {
if (DEBUG) {
Log.d(TAG, "onVideoUnavailable(" + reason + ")");
@@ -666,6 +677,16 @@ public class TvView extends ViewGroup {
}
}
@Override
public void onContentBlocked(Session session, TvContentRating rating) {
if (DEBUG) {
Log.d(TAG, "onContentBlocked()");
}
if (mListener != null) {
mListener.onContentBlocked(mInputId, rating);
}
}
@Override
public void onSessionEvent(TvInputManager.Session session, String eventType,
Bundle eventArgs) {

View File

@@ -552,6 +552,23 @@ public final class TvInputManagerService extends SystemService {
}
}
@Override
public void onContentBlocked(String rating) {
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "onContentBlocked()");
}
if (sessionState.mSession == null || sessionState.mClient == null) {
return;
}
try {
sessionState.mClient.onContentBlocked(rating, sessionState.mSeq);
} catch (RemoteException e) {
Slog.e(TAG, "error in onContentBlocked");
}
}
}
@Override
public void onSessionEvent(String eventType, Bundle eventArgs) {
synchronized (mLock) {