am b719bf57: am 3245f3d7: Merge "TIF: Add TvParentalControlManager" into lmp-dev
* commit 'b719bf5720ea501fa6324d866e49e65b59b1a182': TIF: Add TvParentalControlManager
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -33,4 +33,5 @@ oneway interface ITvInputSessionCallback {
|
||||
void onTrackInfoChanged(in List<TvTrackInfo> tracks);
|
||||
void onVideoAvailable();
|
||||
void onVideoUnavailable(int reason);
|
||||
void onContentBlocked(in String rating);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
268
media/java/android/media/tv/TvParentalControlManager.java
Normal file
268
media/java/android/media/tv/TvParentalControlManager.java
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user