Add TV app tests to CtsVerifier DO NOT MERGE
- Test whether TV app shows third-party TV input properly.
Bug: 18069561
Change-Id: I82fb83e622f4f090c7fa12c27b1c0181dac30f08
(cherry picked from commit 18aa5750346e3f38b0372fb57dad2c28e9b2dab7)
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index abf0e8e..0255d30 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -55,6 +55,8 @@
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+ <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -1459,6 +1461,37 @@
<!-- Used by the SensorTestScreenManipulator to reset the screen timeout after turn off. -->
<activity android:name=".os.TimeoutResetActivity"/>
+ <activity android:name=".tv.TvInputDiscoveryTestActivity" android:label="@string/tv">
+ <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.MockTvInputSettingsActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".tv.MockTvInputSetupActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".tv.MockTvInputService"
+ android:permission="android.permission.BIND_TV_INPUT">
+ <intent-filter>
+ <action android:name="android.media.tv.TvInputService" />
+ </intent-filter>
+ <meta-data android:name="android.media.tv.input"
+ android:resource="@xml/mock_tv_input_service" />
+ </service>
+
</application>
</manifest>
diff --git a/apps/CtsVerifier/res/layout/tv_item.xml b/apps/CtsVerifier/res/layout/tv_item.xml
new file mode 100644
index 0000000..e7311f9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/tv_item.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <ImageView
+ android:id="@+id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="10dip"
+ android:contentDescription="@string/pass_button_text"
+ android:padding="10dip"
+ android:src="@drawable/fs_indeterminate" />
+
+ <TextView
+ android:id="@+id/instructions"
+ style="@style/InstructionsSmallFont"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:layout_toRightOf="@id/status" />
+
+ <Button
+ android:id="@+id/user_action_button"
+ android:enabled="false"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_below="@id/instructions"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:layout_toRightOf="@id/status"
+ android:visibility="gone" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 429d379..eb2d1c9 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -36,6 +36,7 @@
<string name="test_category_features">Features</string>
<string name="test_category_deskclock">Clock</string>
<string name="test_category_jobscheduler">Job Scheduler</string>
+ <string name="test_category_tv">TV</string>
<string name="test_category_other">Other</string>
<string name="clear">Clear</string>
<string name="test_results_cleared">Test results cleared.</string>
@@ -1329,7 +1330,30 @@
<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 name="tv_input_discover_test">TV app input discoverability test</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\".
+ </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
+ \"CTS Verifier\" input. If necessary, configure the channel to be visible.
+ </string>
+ <string name="tv_input_discover_test_verify_tune">
+ Tune command must be called.
+ </string>
+
+ <string name="tv_launch_tv_app">Launch TV app</string>
+
<!-- A list of fully-qualified test classes that should not be run. -->
<string-array name="disabled_tests" />
-
</resources>
diff --git a/apps/CtsVerifier/res/xml/mock_tv_input_service.xml b/apps/CtsVerifier/res/xml/mock_tv_input_service.xml
new file mode 100644
index 0000000..1a2cf86
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/mock_tv_input_service.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+ android:setupActivity="com.android.cts.verifier.tv.MockTvInputSetupActivity"
+ android:settingsActivity="com.android.cts.verifier.tv.MockTvInputSettingsActivity" />
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
new file mode 100644
index 0000000..8c5b18c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.content.Context;
+import android.media.tv.TvInputService;
+import android.net.Uri;
+import android.util.Pair;
+import android.view.Surface;
+import android.view.View;
+
+public class MockTvInputService extends TvInputService {
+ private static final String TAG = "MockTvInputService";
+
+ private static Object sLock = new Object();
+ private static Pair<View, Runnable> sTuneCallback = null;
+
+ static void expectTune(View postTarget, Runnable successCallback) {
+ synchronized (sLock) {
+ sTuneCallback = Pair.create(postTarget, successCallback);
+ }
+ }
+
+ @Override
+ public Session onCreateSession(String inputId) {
+ return new MockSessionImpl(this);
+ }
+
+ private static class MockSessionImpl extends Session {
+ private MockSessionImpl(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onRelease() {
+ }
+
+ @Override
+ public boolean onSetSurface(Surface surface) {
+ return true;
+ }
+
+ @Override
+ public void onSetStreamVolume(float volume) {
+ }
+
+ @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);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onSelectTrack(int type, String trackId) {
+ return true;
+ }
+
+ @Override
+ public void onSetCaptionEnabled(boolean enabled) {
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
new file mode 100644
index 0000000..4231db7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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 android.preference.PreferenceActivity;
+
+public class MockTvInputSettingsActivity extends PreferenceActivity {
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
new file mode 100644
index 0000000..00f0091
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.app.Activity;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.View;
+
+public class MockTvInputSetupActivity extends Activity {
+ private static final String TAG = "MockTvInputSetupActivity";
+
+ private static final String CHANNEL_NUMBER = "999-999";
+ private static final String CHANNEL_NAME = "Dummy";
+
+ private static Object sLock = new Object();
+ private static Pair<View, Runnable> sLaunchCallback = null;
+
+ static void expectLaunch(View postTarget, Runnable successCallback) {
+ synchronized (sLock) {
+ sLaunchCallback = Pair.create(postTarget, successCallback);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ try {
+ super.onCreate(savedInstanceState);
+ final String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+ final Uri uri = TvContract.buildChannelsUriForInput(inputId);
+ final String[] projection = { TvContract.Channels._ID };
+ try (Cursor cursor = getContentResolver().query(uri, projection, null, null, null)) {
+ // If we already have channels, just finish without doing anything.
+ if (cursor != null && cursor.getCount() > 0) {
+ return;
+ }
+ }
+ ContentValues values = new ContentValues();
+ values.put(TvContract.Channels.COLUMN_INPUT_ID, inputId);
+ values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, CHANNEL_NUMBER);
+ values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, CHANNEL_NAME);
+ Uri channelUri = getContentResolver().insert(uri, values);
+ // If the channel's ID happens to be zero, we add another and delete the one.
+ if (ContentUris.parseId(channelUri) == 0) {
+ getContentResolver().insert(uri, values);
+ getContentResolver().delete(channelUri, null, null);
+ }
+ } finally {
+ Pair<View, Runnable> launchCallback = null;
+ synchronized (sLock) {
+ launchCallback = sLaunchCallback;
+ sLaunchCallback = null;
+ }
+ if (launchCallback != null) {
+ launchCallback.first.post(launchCallback.second);
+ }
+
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
new file mode 100644
index 0000000..cb6df8a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
@@ -0,0 +1,114 @@
+/*
+ * 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.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.media.tv.TvContract;
+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;
+
+/**
+ * Tests for verifying TV app behavior.
+ */
+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;
+ private ViewGroup mItemList;
+ private View mPostTarget;
+
+ protected View getPostTarget() {
+ return mPostTarget;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mInflater = getLayoutInflater();
+ // Reusing location_mode_main.
+ View view = mInflater.inflate(R.layout.location_mode_main, null);
+ mPostTarget = mItemList = (ViewGroup) view.findViewById(R.id.test_items);
+ createTestItems();
+ setContentView(view);
+ setPassFailButtonClickListeners();
+ setInfoResources(R.string.tv, R.string.tv_info, -1);
+
+ getPassButton().setEnabled(false);
+ }
+
+ protected void setButtonEnabled(View item, boolean enabled) {
+ View button = item.findViewById(R.id.user_action_button);
+ button.setClickable(enabled);
+ button.setEnabled(enabled);
+ }
+
+ protected void setPassState(View item, boolean passed) {
+ ImageView status = (ImageView) item.findViewById(R.id.status);
+ status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
+ View button = item.findViewById(R.id.user_action_button);
+ button.setClickable(false);
+ button.setEnabled(false);
+ status.invalidate();
+ }
+
+ protected abstract void createTestItems();
+
+ /**
+ * Call this to create a test step where the user must perform some action.
+ */
+ protected View createUserItem(int instructionTextId, int buttonTextId, View.OnClickListener l) {
+ View item = mInflater.inflate(R.layout.tv_item, mItemList, false);
+ TextView instructions = (TextView) item.findViewById(R.id.instructions);
+ instructions.setText(instructionTextId);
+ Button button = (Button) item.findViewById(R.id.user_action_button);
+ button.setVisibility(View.VISIBLE);
+ button.setText(buttonTextId);
+ button.setOnClickListener(l);
+ mItemList.addView(item);
+ return item;
+ }
+
+ /**
+ * Call this to create a test step where the test automatically evaluates whether
+ * an expected condition is satisfied.
+ */
+ protected View createAutoItem(int stringId) {
+ View item = mInflater.inflate(R.layout.tv_item, mItemList, false);
+ TextView instructions = (TextView) item.findViewById(R.id.instructions);
+ instructions.setText(stringId);
+ mItemList.addView(item);
+ return item;
+ }
+
+ static boolean containsButton(View item, View button) {
+ return item.findViewById(R.id.user_action_button) == button;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
new file mode 100644
index 0000000..cee05e2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
@@ -0,0 +1,89 @@
+/*
+ * 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.view.View;
+
+/**
+ * Tests for verifying TV app behavior.
+ */
+public class TvInputDiscoveryTestActivity extends TvAppVerifierActivity
+ implements View.OnClickListener {
+ private static final String TAG = "TvInputDiscoveryTestActivity";
+
+ private static final long TIMEOUT_MS = 5l * 60l * 1000l; // 5 mins.
+
+ private View mGoToSetupItem;
+ private View mVerifySetupItem;
+ private View mTuneToChannelItem;
+ private View mVerifyTuneItem;
+
+ @Override
+ public void onClick(View v) {
+ final View postTarget = getPostTarget();
+
+ if (containsButton(mGoToSetupItem, v)) {
+ final Runnable failCallback = new Runnable() {
+ @Override
+ public void run() {
+ setPassState(mVerifySetupItem, false);
+ }
+ };
+ postTarget.postDelayed(failCallback, TIMEOUT_MS);
+ MockTvInputSetupActivity.expectLaunch(postTarget, new Runnable() {
+ @Override
+ public void run() {
+ postTarget.removeCallbacks(failCallback);
+ setPassState(mGoToSetupItem, true);
+ setPassState(mVerifySetupItem, true);
+ setButtonEnabled(mTuneToChannelItem, true);
+ }
+ });
+ } else if (containsButton(mTuneToChannelItem, v)) {
+ final Runnable failCallback = new Runnable() {
+ @Override
+ public void run() {
+ setPassState(mVerifyTuneItem, false);
+ }
+ };
+ postTarget.postDelayed(failCallback, TIMEOUT_MS);
+ MockTvInputService.expectTune(postTarget, new Runnable() {
+ @Override
+ public void run() {
+ postTarget.removeCallbacks(failCallback);
+ setPassState(mTuneToChannelItem, true);
+ setPassState(mVerifyTuneItem, true);
+ getPassButton().setEnabled(true);
+ }
+ });
+ }
+ startActivity(TV_APP_INTENT);
+ }
+
+ @Override
+ protected void createTestItems() {
+ mGoToSetupItem = createUserItem(R.string.tv_input_discover_test_go_to_setup,
+ R.string.tv_launch_tv_app, this);
+ setButtonEnabled(mGoToSetupItem, true);
+ mVerifySetupItem = createAutoItem(R.string.tv_input_discover_test_verify_setup);
+ mTuneToChannelItem = createUserItem(R.string.tv_input_discover_test_tune_to_channel,
+ R.string.tv_launch_tv_app, this);
+ mVerifyTuneItem = createAutoItem(R.string.tv_input_discover_test_verify_tune);
+ }
+}