aboutsummaryrefslogtreecommitdiff
path: root/tests/input/src/com/android
diff options
context:
space:
mode:
authorNick Chalko <nchalko@google.com>2015-08-03 15:39:56 -0700
committerNick Chalko <nchalko@google.com>2015-08-03 15:53:37 -0700
commit816a4be1a0f34f6a48877c8afd3dbbca19eac435 (patch)
tree4f18dda269764494942f5313acc93db4a35d47db /tests/input/src/com/android
parent6edd2b09e5d16a29c703a5fcbd2e88c5cf5e55b7 (diff)
downloadTV-816a4be1a0f34f6a48877c8afd3dbbca19eac435.tar.gz
Migrate Live Channels App Src to AOSP branch
Bug: 21625152 Change-Id: I07e2830b27440556dc757e6340b4f77d1c0cbc66
Diffstat (limited to 'tests/input/src/com/android')
-rw-r--r--tests/input/src/com/android/tv/testinput/TestInputControl.java98
-rw-r--r--tests/input/src/com/android/tv/testinput/TestInputControlService.java32
-rw-r--r--tests/input/src/com/android/tv/testinput/TestTvInputService.java345
-rw-r--r--tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java119
-rw-r--r--tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java134
5 files changed, 728 insertions, 0 deletions
diff --git a/tests/input/src/com/android/tv/testinput/TestInputControl.java b/tests/input/src/com/android/tv/testinput/TestInputControl.java
new file mode 100644
index 00000000..cd85c86e
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestInputControl.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 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.tv.testinput;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import com.android.tv.testing.ChannelInfo;
+import com.android.tv.testing.ChannelUtils;
+import com.android.tv.testing.testinput.ChannelState;
+import com.android.tv.testing.testinput.ChannelStateData;
+import com.android.tv.testing.testinput.ITestInputControl;
+
+import java.util.Map;
+
+/**
+ * Maintains state for the {@link TestTvInputService}.
+ *
+ * <p>Maintains the current state for every channel. A default is sent if the state is not
+ * explicitly set. The state is versioned so TestTvInputService can tell if onNotifyXXX events need
+ * to be sent.
+ *
+ * <p> Test update the state using @{link ITestInputControl} via {@link TestInputControlService}.
+ */
+class TestInputControl extends ITestInputControl.Stub {
+
+ private final static String TAG = "TestInputControl";
+ private final static TestInputControl INSTANCE = new TestInputControl();
+
+ private final LongSparseArray<ChannelInfo> mId2ChannelInfoMap = new LongSparseArray<>();
+ private final LongSparseArray<ChannelState> mOrigId2StateMap = new LongSparseArray<>();
+
+ private java.lang.String mInputId;
+ private boolean initialized;
+
+ private TestInputControl() {
+ }
+
+ public static TestInputControl getInstance() {
+ return INSTANCE;
+ }
+
+ public synchronized void init(Context context, String inputId) {
+ if (!initialized) {
+ // TODO run initialization in a separate thread.
+ mInputId = inputId;
+ updateChannelMap(context);
+ initialized = true;
+ }
+ }
+
+ private void updateChannelMap(Context context) {
+ mId2ChannelInfoMap.clear();
+ Map<Long, ChannelInfo> channelIdToInfoMap =
+ ChannelUtils.queryChannelInfoMapForTvInput(context, mInputId);
+ for (Long channelId : channelIdToInfoMap.keySet()) {
+ mId2ChannelInfoMap.put(channelId, channelIdToInfoMap.get(channelId));
+ }
+ Log.i(TAG, "Initialized channel map for " + mInputId + " with " + mId2ChannelInfoMap.size()
+ + " channels");
+ }
+
+ public ChannelInfo getChannelInfo(Uri channelUri) {
+ return mId2ChannelInfoMap.get(ContentUris.parseId(channelUri));
+ }
+
+ public ChannelState getChannelState(int originalNetworkId) {
+ return mOrigId2StateMap.get(originalNetworkId, ChannelState.DEFAULT);
+ }
+
+ @Override
+ public synchronized void updateChannelState(int origId, ChannelStateData data)
+ throws RemoteException {
+ ChannelState state;
+ ChannelState orig = getChannelState(origId);
+ state = orig.next(data);
+ mOrigId2StateMap.put(origId, state);
+
+ Log.i(TAG, "Setting channel " + origId + " state to " + state);
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/TestInputControlService.java b/tests/input/src/com/android/tv/testinput/TestInputControlService.java
new file mode 100644
index 00000000..4a5668cc
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestInputControlService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 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.tv.testinput;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Testcases communicate to the {@link TestInputControl} via
+ * {@link com.android.tv.testing.testinput.ITestInputControl}.
+ */
+public class TestInputControlService extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return TestInputControl.getInstance();
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
new file mode 100644
index 00000000..98ac9438
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2015 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.tv.testinput;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.PlaybackParams;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Surface;
+
+import com.android.tv.common.TvCommonConstants;
+import com.android.tv.testing.ChannelInfo;
+import com.android.tv.testing.testinput.ChannelState;
+
+import java.util.Date;
+
+/**
+ * Simple TV input service which provides test channels.
+ */
+public class TestTvInputService extends TvInputService {
+ private static final String TAG = "TestTvInputServices";
+ private static final int REFRESH_DELAY_MS = 1000 / 5;
+ private static final boolean DEBUG = false;
+ private TestInputControl mBackend = TestInputControl.getInstance();
+
+ public static String buildInputId(Context context) {
+ return TvContract.buildInputId(new ComponentName(context, TestTvInputService.class));
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mBackend.init(this, buildInputId(this));
+ }
+
+ @Override
+ public Session onCreateSession(String inputId) {
+ Log.v(TAG, "Creating session for " + inputId);
+ return new SimpleSessionImpl(this);
+ }
+
+ /**
+ * Simple session implementation that just display some text.
+ */
+ private class SimpleSessionImpl extends Session {
+ private static final int MSG_SEEK = 1000;
+ private static final int SEEK_DELAY_MS = 300;
+
+ private final Paint mTextPaint = new Paint();
+ private final DrawRunnable mDrawRunnable = new DrawRunnable();
+ private Surface mSurface = null;
+ private ChannelInfo mChannel = null;
+ private ChannelState mCurrentState = null;
+ private String mCurrentVideoTrackId = null;
+ private String mCurrentAudioTrackId = null;
+
+ private long mRecordStartTimeMs;
+ private long mPausedTimeMs;
+ // The time in milliseconds when the current position is lastly updated.
+ private long mLastCurrentPositionUpdateTimeMs;
+ // The current playback position.
+ private long mCurrentPositionMs;
+ // The current playback speed rate.
+ private float mSpeed;
+
+ private final Handler mHandler = new Handler(Looper.myLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_SEEK) {
+ // Actually, this input doesn't play any videos, it just shows the image.
+ // So we should simulate the playback here by changing the current playback
+ // position periodically in order to test the time shift.
+ // If the playback is paused, the current playback position doesn't need to be
+ // changed.
+ if (mPausedTimeMs == 0) {
+ long currentTimeMs = System.currentTimeMillis();
+ mCurrentPositionMs += (long) ((currentTimeMs
+ - mLastCurrentPositionUpdateTimeMs) * mSpeed);
+ mCurrentPositionMs = Math.max(mRecordStartTimeMs,
+ Math.min(mCurrentPositionMs, currentTimeMs));
+ mLastCurrentPositionUpdateTimeMs = currentTimeMs;
+ }
+ sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS);
+ }
+ super.handleMessage(msg);
+ }
+ };
+
+ SimpleSessionImpl(Context context) {
+ super(context);
+ mTextPaint.setColor(Color.BLACK);
+ mTextPaint.setTextSize(150);
+ mHandler.post(mDrawRunnable);
+ if (DEBUG) {
+ Log.v(TAG, "Created session " + this);
+ }
+ }
+
+ private void setAudioTrack(String selectedAudioTrackId) {
+ Log.i(TAG, "Set audio track to " + selectedAudioTrackId);
+ mCurrentAudioTrackId = selectedAudioTrackId;
+ notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, mCurrentAudioTrackId);
+ }
+
+ private void setVideoTrack(String selectedVideoTrackId) {
+ Log.i(TAG, "Set video track to " + selectedVideoTrackId);
+ mCurrentVideoTrackId = selectedVideoTrackId;
+ notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, mCurrentVideoTrackId);
+ }
+
+ @Override
+ public void onRelease() {
+ if (DEBUG) {
+ Log.v(TAG, "Releasing session " + this);
+ }
+ mDrawRunnable.cancel();
+ mHandler.removeCallbacks(mDrawRunnable);
+ mSurface = null;
+ mChannel = null;
+ mCurrentState = null;
+ }
+
+ @Override
+ public boolean onSetSurface(Surface surface) {
+ synchronized (mDrawRunnable) {
+ mSurface = surface;
+ }
+ if (surface != null) {
+ if (DEBUG) {
+ Log.v(TAG, "Surface set");
+ }
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "Surface unset");
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onSurfaceChanged(int format, int width, int height) {
+ super.onSurfaceChanged(format, width, height);
+ Log.d(TAG, "format=" + format + " width=" + width + " height=" + height);
+ }
+
+ @Override
+ public void onSetStreamVolume(float volume) {
+ // No-op
+ }
+
+ @Override
+ public boolean onTune(Uri channelUri) {
+ Log.i(TAG, "Tune to " + channelUri);
+ ChannelInfo info = mBackend.getChannelInfo(channelUri);
+ synchronized (mDrawRunnable) {
+ if (info == null || mChannel == null
+ || mChannel.originalNetworkId != info.originalNetworkId) {
+ mCurrentState = null;
+ }
+ mChannel = info;
+ mCurrentVideoTrackId = null;
+ mCurrentAudioTrackId = null;
+ }
+ if (mChannel == null) {
+ Log.i(TAG, "Channel not found for " + channelUri);
+ notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
+ } else {
+ Log.i(TAG, "Tuning to " + mChannel);
+ }
+ if (TvCommonConstants.HAS_TIME_SHIFT_API) {
+ notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
+ mRecordStartTimeMs = mCurrentPositionMs = mLastCurrentPositionUpdateTimeMs
+ = System.currentTimeMillis();
+ mPausedTimeMs = 0;
+ mHandler.sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS);
+ mSpeed = 1;
+ }
+ return true;
+ }
+
+ @Override
+ public void onSetCaptionEnabled(boolean enabled) {
+ // No-op
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ Log.d(TAG, "onKeyDown (keyCode=" + keyCode + ", event=" + event + ")");
+ return true;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ Log.d(TAG, "onKeyUp (keyCode=" + keyCode + ", event=" + event + ")");
+ return true;
+ }
+
+ @Override
+ public long onTimeShiftGetCurrentPosition() {
+ Log.d(TAG, "currentPositionMs=" + mCurrentPositionMs);
+ return mCurrentPositionMs;
+ }
+
+ @Override
+ public long onTimeShiftGetStartPosition() {
+ return mRecordStartTimeMs;
+ }
+
+ @Override
+ public void onTimeShiftPause() {
+ mCurrentPositionMs = mPausedTimeMs = mLastCurrentPositionUpdateTimeMs
+ = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeShiftResume() {
+ mSpeed = 1;
+ mPausedTimeMs = 0;
+ mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onTimeShiftSeekTo(long timeMs) {
+ mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis();
+ mCurrentPositionMs = Math.max(mRecordStartTimeMs,
+ Math.min(timeMs, mLastCurrentPositionUpdateTimeMs));
+ }
+
+ @Override
+ public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
+ mSpeed = params.getSpeed();
+ }
+
+ private final class DrawRunnable implements Runnable {
+ private volatile boolean mIsCanceled = false;
+
+ @Override
+ public void run() {
+ if (mIsCanceled) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Draw task running");
+ }
+ boolean updatedState = false;
+ ChannelState oldState;
+ ChannelState newState = null;
+ Surface currentSurface;
+ ChannelInfo currentChannel;
+
+ synchronized (this) {
+ oldState = mCurrentState;
+ currentSurface = mSurface;
+ currentChannel = mChannel;
+ if (currentChannel != null) {
+ newState = mBackend.getChannelState(currentChannel.originalNetworkId);
+ if (oldState == null || newState.getVersion() > oldState.getVersion()) {
+ mCurrentState = newState;
+ updatedState = true;
+ }
+ } else {
+ mCurrentState = null;
+ }
+ }
+
+ draw(currentSurface, currentChannel);
+ if (updatedState) {
+ update(oldState, newState, currentChannel);
+ }
+
+ if (!mIsCanceled) {
+ mHandler.postDelayed(this, REFRESH_DELAY_MS);
+ }
+ }
+
+ private void update(ChannelState oldState, ChannelState newState,
+ ChannelInfo currentChannel) {
+ Log.i(TAG, "Updating channel " + currentChannel.number + " state to " + newState);
+ notifyTracksChanged(newState.getTrackInfoList());
+ if (oldState == null || oldState.getTuneStatus() != newState.getTuneStatus()) {
+ if (newState.getTuneStatus() == ChannelState.TUNE_STATUS_VIDEO_AVAILABLE) {
+ notifyVideoAvailable();
+ //TODO handle parental controls.
+ notifyContentAllowed();
+ setAudioTrack(newState.getSelectedAudioTrackId());
+ setVideoTrack(newState.getSelectedVideoTrackId());
+ } else {
+ notifyVideoUnavailable(newState.getTuneStatus());
+ }
+ }
+ }
+
+ private void draw(Surface surface, ChannelInfo currentChannel) {
+ if (surface != null) {
+ String now = TvCommonConstants.HAS_TIME_SHIFT_API
+ ? new Date(mCurrentPositionMs).toString() : new Date().toString();
+ String name = currentChannel == null ? "Null" : currentChannel.name;
+ Canvas c = surface.lockCanvas(null);
+ c.drawColor(0xFF888888);
+ c.drawText(name, 100f, 200f, mTextPaint);
+ c.drawText(now, 100f, 400f, mTextPaint);
+ surface.unlockCanvasAndPost(c);
+ if (DEBUG) {
+ Log.v(TAG, "Post to canvas");
+ }
+ } else {
+ if (DEBUG) {
+ Log.v(TAG, "No surface");
+ }
+ }
+ }
+
+ public void cancel() {
+ mIsCanceled = true;
+ }
+ }
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java
new file mode 100644
index 00000000..732972cc
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/TestTvInputSetupActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 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.tv.testinput;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.tv.testing.ChannelInfo;
+import com.android.tv.testing.ChannelUtils;
+import com.android.tv.testing.Constants;
+import com.android.tv.testing.ProgramInfo;
+import com.android.tv.testing.ProgramUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The setup activity for {@link TestTvInputService}.
+ */
+public class TestTvInputSetupActivity extends Activity {
+ private static final String TAG = "TestTvInputSetup";
+ private String mInputId;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mInputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+
+ DialogFragment newFragment = new MyAlertDialogFragment();
+ newFragment.show(getFragmentManager(), "dialog");
+ }
+
+ private void registerChannels(int channelCount) {
+ TestTvInputSetupActivity context = this;
+ registerChannels(context, mInputId, false, channelCount);
+ }
+
+ public static void registerChannels(Context context, String inputId, boolean updateBrowsable,
+ int channelCount) {
+ Log.i(TAG, "Registering " + channelCount + " channels");
+ List<ChannelInfo> channels = new ArrayList<>();
+ for (int i = 1; i <= channelCount; i++) {
+ channels.add(ChannelInfo.create(context, i));
+ }
+ ChannelUtils.updateChannels(context, inputId, channels);
+ if (updateBrowsable) {
+ updateChannelsBrowsable(context.getContentResolver(), inputId);
+ }
+
+ // Reload channels so we have the ids.
+ Map<Long, ChannelInfo> channelIdToInfoMap =
+ ChannelUtils.queryChannelInfoMapForTvInput(context, inputId);
+ for (Long channelId : channelIdToInfoMap.keySet()) {
+ // TODO: http://b/21705569 Create better program info for tests
+ ProgramInfo programInfo = ProgramInfo.create();
+ ProgramUtils.populatePrograms(context, TvContract.buildChannelUri(channelId),
+ programInfo);
+ }
+ }
+
+ private static void updateChannelsBrowsable(ContentResolver contentResolver, String inputId) {
+ Uri uri = TvContract.buildChannelsUriForInput(inputId);
+ ContentValues values = new ContentValues();
+ values.put(TvContract.Channels.COLUMN_BROWSABLE, 1);
+ contentResolver.update(uri, values, null, null);
+ }
+
+ public static class MyAlertDialogFragment extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity()).setTitle(R.string.simple_setup_title)
+ .setMessage(R.string.simple_setup_message)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ // TODO: add UI to ask how many channels
+ ((TestTvInputSetupActivity) getActivity())
+ .registerChannels(Constants.UNIT_TEST_CHANNEL_COUNT);
+ // Sets the results so that the application can process the
+ // registered channels properly.
+ getActivity().setResult(Activity.RESULT_OK);
+ getActivity().finish();
+ }
+ }).setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ getActivity().finish();
+ }
+ }).create();
+ }
+ }
+}
diff --git a/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java
new file mode 100644
index 00000000..379bce86
--- /dev/null
+++ b/tests/input/src/com/android/tv/testinput/instrument/TestSetupInstrumentation.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 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.tv.testinput.instrument;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.tv.testing.Constants;
+import com.android.tv.testinput.TestTvInputService;
+import com.android.tv.testinput.TestTvInputSetupActivity;
+
+/**
+ * An instrumentation utility to set up the needed inputs, channels, programs and other settings
+ * for automated unit tests.
+ *
+ * <p><pre>{@code
+ * adb shell am instrument \
+ * -e testSetupMode {func,jank,unit} \
+ * -w com.android.tv.testinput/.instrument.TestSetupInstrumentation
+ * }</pre>
+ *
+ * <p>Optional arguments are:
+ * <pre>
+ * -e channelCount number
+ * </pre>
+ */
+public class TestSetupInstrumentation extends Instrumentation {
+ private static final String TAG = "TestSetupInstrument";
+ private static final String TEST_SETUP_MODE_ARG = "testSetupMode";
+ private static final String CHANNEL_COUNT_ARG = "channelCount";
+ private Bundle mArguments;
+ private String mInputId;
+
+ /**
+ * Fails an instrumentation request.
+ *
+ * @param errMsg an error message
+ */
+ protected void fail(String errMsg) {
+ Log.e(TAG, errMsg);
+ Bundle result = new Bundle();
+ result.putString("error", errMsg);
+ finish(Activity.RESULT_CANCELED, result);
+ }
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mArguments = arguments;
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ try {
+ mInputId = TestTvInputService.buildInputId(getContext());
+ setup();
+ finish(Activity.RESULT_OK, new Bundle());
+ } catch (TestSetupException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ private void setup() throws TestSetupException {
+ final String testSetupMode = mArguments.getString(TEST_SETUP_MODE_ARG);
+ if (TextUtils.isEmpty(testSetupMode)) {
+ Log.i(TAG, "Performing no setup actions because " + TEST_SETUP_MODE_ARG
+ + " was not passed as an argument");
+ } else {
+ Log.i(TAG, "Running setup for " + testSetupMode + " tests.");
+ int channelCount;
+ switch (testSetupMode) {
+ case "func":
+ channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG,
+ Constants.FUNC_TEST_CHANNEL_COUNT);
+ break;
+ case "jank":
+ channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG,
+ Constants.JANK_TEST_CHANNEL_COUNT);
+ break;
+ case "unit":
+ channelCount = getArgumentAsInt(CHANNEL_COUNT_ARG,
+ Constants.UNIT_TEST_CHANNEL_COUNT);
+ break;
+ default:
+ throw new TestSetupException(
+ "Unknown " + TEST_SETUP_MODE_ARG + " of " + testSetupMode);
+ }
+ TestTvInputSetupActivity.registerChannels(getContext(), mInputId, true, channelCount);
+ }
+ }
+
+ private int getArgumentAsInt(String arg, int defaultValue) {
+ String stringValue = mArguments.getString(arg);
+ if (stringValue != null) {
+ try {
+ return Integer.parseInt(stringValue);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Unable to parse arg " + arg + " with value " + stringValue
+ + " to a integer.", e);
+ }
+ }
+ return defaultValue;
+ }
+
+ static class TestSetupException extends Exception {
+ public TestSetupException(String msg) {
+ super(msg);
+ }
+
+ public static TestSetupException fromMissingArg(String arg) {
+ return new TestSetupException(
+ String.format("Error: missing mandatory argument '%s'", arg));
+ }
+ }
+}