TV: Add parental control tests to CtsVerifier
- Test if parental control settings really go through framework.
Bug: 18069561
Change-Id: I87bfc9f6f5400e241200e63fbda2d8979c85d3d1
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 92a2367..d5cd98b 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1377,7 +1377,19 @@
<meta-data android:name="test_category" android:value="@string/test_category_other" />
</activity>
- <activity android:name=".tv.TvInputDiscoveryTestActivity" android:label="@string/tv">
+ <activity android:name=".tv.TvInputDiscoveryTestActivity"
+ android:label="@string/tv_input_discover_test">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ </intent-filter>
+ <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+ <meta-data android:name="test_required_features"
+ android:value="android.software.live_tv" />
+ </activity>
+
+ <activity android:name=".tv.ParentalControlTestActivity"
+ android:label="@string/tv_parental_control_test">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.cts.intent.category.MANUAL_TEST" />
@@ -1408,6 +1420,14 @@
android:resource="@xml/mock_tv_input_service" />
</service>
+ <receiver android:name=".tv.TvInputReceiver">
+ <intent-filter>
+ <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
+ </intent-filter>
+ <meta-data android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
+ android:resource="@xml/mock_content_rating_systems" />
+ </receiver>
+
</application>
</manifest>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 757951c..4c6d3df 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1402,22 +1402,23 @@
<string name="js_any_connectivity_test">Device with no connectivity will not execute a job with an unmetered connectivity constraint.</string>
<string name="js_no_connectivity_test">Device with no connectivity will still execute a job with no connectivity constraints.</string>
- <!-- String for TV app Tests -->
- <string name="tv">TV App Behavior Verifier</string>
- <string name="tv_info">
- This test verifies that the default TV app is invoked via intents and issues appropriate
- calls to framework APIs, so that TV input apps work properly with the default TV app.
- </string>
+ <!-- String for Live Channels app Tests -->
- <string name="tv_input_discover_test">TV app input discoverability test</string>
+ <string name="tv_input_discover_test">3rd-party TV input app discoverability test</string>
+ <string name="tv_input_discover_test_info">
+ This test verifies that the pre-loaded Live Channels app is invoked via intents and issues
+ appropriate calls to framework APIs, so that TV input apps work properly with the pre-loaded
+ Live Channels app.
+ </string>
<string name="tv_input_discover_test_go_to_setup">
- Press the \"Launch TV app\" button, and set up the newly installed TV input: \"CTS Verifier\".
+ Press the \"Launch Live Channels\" button, and set up the newly installed TV input:
+ \"CTS Verifier\".
</string>
<string name="tv_input_discover_test_verify_setup">
Setup activity must have been started.
</string>
<string name="tv_input_discover_test_tune_to_channel">
- Press the \"Launch TV app\" button, and tune to the channel named \"Dummy\" from
+ Press the \"Launch Live Channels\" button, and tune to the channel named \"Dummy\" from
\"CTS Verifier\" input. If necessary, configure the channel to be visible.
</string>
<string name="tv_input_discover_test_verify_tune">
@@ -1428,8 +1429,38 @@
when you tune to the \"Dummy\" channel.
</string>
- <string name="tv_launch_tv_app">Launch TV app</string>
+ <string name="tv_parental_control_test">Live Channels app parental control test</string>
+ <string name="tv_parental_control_test_info">
+ This test verifies that the default Live Channels app invokes proper parental control APIs in
+ the framework.
+ </string>
+ <string name="tv_parental_control_test_turn_on_parental_control">
+ Press the \"Launch Live Channels\" button, and turn on the parental control. If it\'s on
+ already, turn it off and on again.
+ </string>
+ <string name="tv_parental_control_test_verify_receive_broadcast1">
+ TV input service must have received ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED broadcast.
+ </string>
+ <string name="tv_parental_control_test_block_tv_ma">
+ Press the \"Launch Live Channels\" button, and block \"Fake\" rating for \"CtsVerifier\" rating
+ system in the parental control settings. You may have to enable the rating system if it is
+ disabled by default. If it\'s already blocked, unblock, save, and then block again.
+ </string>
+ <string name="tv_parental_control_test_verify_receive_broadcast2">
+ TV input service must have received ACTION_BLOCKED_RATINGS_CHANGED broadcast.
+ </string>
+ <string name="tv_parental_control_test_block_unblock">
+ Press the \"Launch Live Channels\" button; verify that the channel is blocked visually.
+ Try unblock the screen by entering PIN; verify that it\'s unblocked visually.
+ </string>
+
+ <string name="tv_launch_tv_app">Launch Live Channels</string>
+ <string name="tv_channel_not_found">
+ CtsVerifier channel is not set up. Please set up before proceeding.
+ </string>
+
<string name="overlay_view_text">Overlay View Dummy Text</string>
+ <string name="fake_rating">Fake</string>
<!-- A list of fully-qualified test classes that should not be run. -->
<string-array name="disabled_tests" />
diff --git a/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml b/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml
new file mode 100644
index 0000000..245d7f5
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 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.
+-->
+
+<rating-system-definitions xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="1">
+ <rating-system-definition android:name="CTS_VERIFIER"
+ android:title="CtsVerifier"
+ android:description="@string/app_name">
+ <rating-definition android:name="MOCK_FAKE"
+ android:title="Fake"
+ android:contentAgeHint="0"
+ android:description="@string/fake_rating" />
+ </rating-system-definition>
+</rating-system-definitions>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
index ae0a699..a6bb01a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
@@ -18,35 +18,101 @@
import com.android.cts.verifier.R;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
import android.media.tv.TvInputService;
import android.net.Uri;
-import android.util.Pair;
+import android.os.Handler;
+import android.os.Looper;
import android.view.Surface;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
+import com.android.cts.verifier.R;
+
public class MockTvInputService extends TvInputService {
private static final String TAG = "MockTvInputService";
private static Object sLock = new Object();
- private static Pair<View, Runnable> sTuneCallback = null;
- private static Pair<View, Runnable> sOverlayViewCallback = null;
+ private static Callback sTuneCallback = null;
+ private static Callback sOverlayViewCallback = null;
+ private static Callback sBroadcastCallback = null;
+ private static String sExpectedBroadcastAction = null;
+ private static Callback sUnblockContentCallback = null;
+ private static TvContentRating sRating = null;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (sLock) {
+ if (sBroadcastCallback != null) {
+ if (intent.getAction().equals(sExpectedBroadcastAction)) {
+ sBroadcastCallback.post();
+ sBroadcastCallback = null;
+ sExpectedBroadcastAction = null;
+ }
+ }
+ }
+ }
+ };
static void expectTune(View postTarget, Runnable successCallback) {
synchronized (sLock) {
- sTuneCallback = Pair.create(postTarget, successCallback);
+ sTuneCallback = new Callback(postTarget, successCallback);
+ }
+ }
+
+ static void expectBroadcast(View postTarget, String action, Runnable successCallback) {
+ synchronized (sLock) {
+ sBroadcastCallback = new Callback(postTarget, successCallback);
+ sExpectedBroadcastAction = action;
+ }
+ }
+
+ static void expectUnblockContent(View postTarget, Runnable successCallback) {
+ synchronized (sLock) {
+ sUnblockContentCallback = new Callback(postTarget, successCallback);
+ }
+ }
+
+ static void setBlockRating(TvContentRating rating) {
+ synchronized (sLock) {
+ sRating = rating;
}
}
static void expectOverlayView(View postTarget, Runnable successCallback) {
synchronized (sLock) {
- sOverlayViewCallback = Pair.create(postTarget, successCallback);
+ sOverlayViewCallback = new Callback(postTarget, successCallback);
}
}
@Override
+ public void onCreate() {
+ super.onCreate();
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED);
+ intentFilter.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
+ registerReceiver(mBroadcastReceiver, intentFilter);
+ }
+
+ @Override
+ public void onDestroy() {
+ unregisterReceiver(mBroadcastReceiver);
+ super.onDestroy();
+ }
+
+ @Override
public Session onCreateSession(String inputId) {
Session session = new MockSessionImpl(this);
session.setOverlayViewEnabled(true);
@@ -54,7 +120,8 @@
}
private static class MockSessionImpl extends Session {
- private Context mContext;
+ private final Context mContext;
+ private Surface mSurface = null;
private MockSessionImpl(Context context) {
super(context);
@@ -65,6 +132,28 @@
public void onRelease() {
}
+ private void draw() {
+ Surface surface = mSurface;
+ if (surface == null) return;
+ if (!surface.isValid()) return;
+
+ Canvas c = surface.lockCanvas(null);
+ if (c == null) return;
+ try {
+ Bitmap b = BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.icon);
+ int srcWidth = b.getWidth();
+ int srcHeight = b.getHeight();
+ int dstWidth = c.getWidth();
+ int dstHeight = c.getHeight();
+ c.drawColor(Color.BLACK);
+ c.drawBitmap(b, new Rect(0, 0, srcWidth, srcHeight),
+ new Rect(10, 10, dstWidth - 10, dstHeight - 10), null);
+ } finally {
+ surface.unlockCanvasAndPost(c);
+ }
+ }
+
@Override
public View onCreateOverlayView() {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
@@ -75,13 +164,13 @@
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
- Pair<View, Runnable> overlayViewCallback = null;
+ Callback overlayViewCallback = null;
synchronized (sLock) {
overlayViewCallback = sOverlayViewCallback;
sOverlayViewCallback = null;
}
if (overlayViewCallback != null) {
- overlayViewCallback.first.post(overlayViewCallback.second);
+ overlayViewCallback.post();
}
}
});
@@ -90,6 +179,8 @@
@Override
public boolean onSetSurface(Surface surface) {
+ mSurface = surface;
+ draw();
return true;
}
@@ -99,16 +190,19 @@
@Override
public boolean onTune(Uri channelUri) {
- Pair<View, Runnable> tuneCallback = null;
synchronized (sLock) {
- tuneCallback = sTuneCallback;
- sTuneCallback = null;
- }
- if (tuneCallback != null) {
- tuneCallback.first.post(tuneCallback.second);
+ if (sRating != null) {
+ notifyContentBlocked(sRating);
+ }
+ if (sTuneCallback != null) {
+ sTuneCallback.post();
+ sTuneCallback = null;
+ }
+ if (sRating == null) {
+ notifyContentAllowed();
+ }
}
notifyVideoAvailable();
- notifyContentAllowed();
return true;
}
@@ -120,5 +214,30 @@
@Override
public void onSetCaptionEnabled(boolean enabled) {
}
+
+ @Override
+ public void onUnblockContent(TvContentRating unblockedRating) {
+ synchronized (sLock) {
+ if (sRating != null && sRating.equals(unblockedRating)) {
+ sUnblockContentCallback.post();
+ sRating = null;
+ notifyContentAllowed();
+ }
+ }
+ }
+ }
+
+ private static class Callback {
+ private final View mPostTarget;
+ private final Runnable mAction;
+
+ Callback(View postTarget, Runnable action) {
+ mPostTarget = postTarget;
+ mAction = action;
+ }
+
+ public void post() {
+ mPostTarget.post(mAction);
+ }
}
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
index 00f0091..81a8edc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
@@ -30,7 +30,7 @@
public class MockTvInputSetupActivity extends Activity {
private static final String TAG = "MockTvInputSetupActivity";
- private static final String CHANNEL_NUMBER = "999-999";
+ private static final String CHANNEL_NUMBER = "999-0";
private static final String CHANNEL_NAME = "Dummy";
private static Object sLock = new Object();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java
new file mode 100644
index 0000000..284b485
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Tests for verifying TV app behavior on parental control.
+ */
+public class ParentalControlTestActivity extends TvAppVerifierActivity
+ implements View.OnClickListener {
+ private static final String TAG = "ParentalControlTestActivity";
+
+ private static final long TIMEOUT_MS = 5l * 60l * 1000l; // 5 mins.
+
+ private View mTurnOnParentalControlItem;
+ private View mVerifyReceiveBroadcast1Item;
+ private View mBlockTvMaItem;
+ private View mVerifyReceiveBroadcast2Item;
+ private View mBlockUnblockItem;
+
+ private Intent mTvAppIntent = null;
+
+ @Override
+ public void onClick(View v) {
+ final View postTarget = getPostTarget();
+
+ if (containsButton(mTurnOnParentalControlItem, v)) {
+ final Runnable failCallback = new Runnable() {
+ @Override
+ public void run() {
+ setPassState(mVerifyReceiveBroadcast1Item, false);
+ }
+ };
+ postTarget.postDelayed(failCallback, TIMEOUT_MS);
+ MockTvInputService.expectBroadcast(postTarget,
+ TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED, new Runnable() {
+ @Override
+ public void run() {
+ postTarget.removeCallbacks(failCallback);
+ setPassState(mTurnOnParentalControlItem, true);
+ setPassState(mVerifyReceiveBroadcast1Item, true);
+ setButtonEnabled(mBlockTvMaItem, true);
+ }
+ });
+ } else if (containsButton(mBlockTvMaItem, v)) {
+ final Runnable failCallback = new Runnable() {
+ @Override
+ public void run() {
+ setPassState(mVerifyReceiveBroadcast2Item, false);
+ }
+ };
+ postTarget.postDelayed(failCallback, TIMEOUT_MS);
+ MockTvInputService.expectBroadcast(postTarget,
+ TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED, new Runnable() {
+ @Override
+ public void run() {
+ postTarget.removeCallbacks(failCallback);
+ setPassState(mBlockTvMaItem, true);
+ setPassState(mVerifyReceiveBroadcast2Item, true);
+ setButtonEnabled(mBlockUnblockItem, true);
+ }
+ });
+ } else if (containsButton(mBlockUnblockItem, v)) {
+ final Runnable failCallback = new Runnable() {
+ @Override
+ public void run() {
+ setPassState(mBlockUnblockItem, false);
+ }
+ };
+ postTarget.postDelayed(failCallback, TIMEOUT_MS);
+ MockTvInputService.setBlockRating(TvContentRating.createRating(
+ "com.android.cts.verifier", "CTS_VERIFIER", "FAKE"));
+ MockTvInputService.expectUnblockContent(postTarget, new Runnable() {
+ @Override
+ public void run() {
+ postTarget.removeCallbacks(failCallback);
+ setPassState(mBlockUnblockItem, true);
+ getPassButton().setEnabled(true);
+ }
+ });
+ }
+ if (mTvAppIntent == null) {
+ String[] projection = { TvContract.Channels._ID };
+ try (Cursor cursor = getContentResolver().query(TvContract.Channels.CONTENT_URI,
+ projection, null, null, null)) {
+ if (cursor != null && cursor.moveToNext()) {
+ mTvAppIntent = new Intent(Intent.ACTION_VIEW,
+ TvContract.buildChannelUri(cursor.getLong(0)));
+ }
+ }
+ if (mTvAppIntent == null) {
+ Toast.makeText(this, R.string.tv_channel_not_found, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ }
+ startActivity(mTvAppIntent);
+ }
+
+ @Override
+ protected void createTestItems() {
+ mTurnOnParentalControlItem = createUserItem(
+ R.string.tv_parental_control_test_turn_on_parental_control,
+ R.string.tv_launch_tv_app, this);
+ setButtonEnabled(mTurnOnParentalControlItem, true);
+ mVerifyReceiveBroadcast1Item = createAutoItem(
+ R.string.tv_parental_control_test_verify_receive_broadcast1);
+ mBlockTvMaItem = createUserItem(R.string.tv_parental_control_test_block_tv_ma,
+ R.string.tv_launch_tv_app, this);
+ mVerifyReceiveBroadcast2Item = createAutoItem(
+ R.string.tv_parental_control_test_verify_receive_broadcast2);
+ mBlockUnblockItem = createUserItem(R.string.tv_parental_control_test_block_unblock,
+ R.string.tv_launch_tv_app, this);
+ }
+
+ @Override
+ protected void setInfoResources() {
+ setInfoResources(R.string.tv_parental_control_test,
+ R.string.tv_parental_control_test_info, -1);
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
index cb6df8a..3529237 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
@@ -30,14 +30,11 @@
import android.widget.TextView;
/**
- * Tests for verifying TV app behavior.
+ * Base class for TV app tests.
*/
public abstract class TvAppVerifierActivity extends PassFailButtons.Activity {
private static final String TAG = "TvAppVerifierActivity";
- protected static final Intent TV_APP_INTENT = new Intent(Intent.ACTION_VIEW,
- TvContract.buildChannelUri(0));
-
private static final long TIMEOUT_MS = 5l * 60l * 1000l; // 5 mins.
private LayoutInflater mInflater;
@@ -59,7 +56,7 @@
createTestItems();
setContentView(view);
setPassFailButtonClickListeners();
- setInfoResources(R.string.tv, R.string.tv_info, -1);
+ setInfoResources();
getPassButton().setEnabled(false);
}
@@ -81,6 +78,8 @@
protected abstract void createTestItems();
+ protected abstract void setInfoResources();
+
/**
* Call this to create a test step where the user must perform some action.
*/
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
index a627a55..3d17a1a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
@@ -16,17 +16,22 @@
package com.android.cts.verifier.tv;
-import com.android.cts.verifier.R;
-
+import android.content.Intent;
+import android.media.tv.TvContract;
import android.view.View;
+import com.android.cts.verifier.R;
+
/**
- * Tests for verifying TV app behavior.
+ * Tests for verifying TV app behavior for third-party TV input apps.
*/
public class TvInputDiscoveryTestActivity extends TvAppVerifierActivity
implements View.OnClickListener {
private static final String TAG = "TvInputDiscoveryTestActivity";
+ private static final Intent TV_APP_INTENT = new Intent(Intent.ACTION_VIEW,
+ TvContract.buildChannelUri(0));
+
private static final long TIMEOUT_MS = 5l * 60l * 1000l; // 5 mins.
private View mGoToSetupItem;
@@ -109,4 +114,10 @@
getPassButton().setEnabled(true);
}
}
+
+ @Override
+ protected void setInfoResources() {
+ setInfoResources(R.string.tv_input_discover_test,
+ R.string.tv_input_discover_test_info, -1);
+ }
}