1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier; 18 19 import android.app.AlertDialog; 20 import android.content.ActivityNotFoundException; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.database.DataSetObserver; 26 import android.os.Bundle; 27 import android.util.Log; 28 import android.view.LayoutInflater; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.View.OnClickListener; 32 import android.widget.Button; 33 import android.widget.ImageView; 34 import android.widget.ListView; 35 import android.widget.TextView; 36 import android.widget.Toast; 37 38 import com.android.cts.verifier.R; 39 40 /** 41 * Test list activity that supports showing dialogs with pass/fail buttons instead of 42 * starting new activities. 43 * In addition to that dialogs have a 'go' button that can be configured to launch an intent. 44 * Instructions are shown on top of the screen and a test preparation button is provided. 45 */ 46 public abstract class DialogTestListActivity extends PassFailButtons.TestListActivity { 47 private final String TAG = "DialogTestListActivity"; 48 private final int mLayoutId; 49 private final int mTitleStringId; 50 private final int mInfoStringId; 51 private final int mInstructionsStringId; 52 53 protected Button mPrepareTestButton; 54 protected ListView mTestFeaturesList; 55 56 protected int mCurrentTestPosition; 57 DialogTestListActivity(int layoutId, int titleStringId, int infoStringId, int instructionsStringId)58 protected DialogTestListActivity(int layoutId, int titleStringId, int infoStringId, 59 int instructionsStringId) { 60 mLayoutId = layoutId; 61 mTitleStringId = titleStringId; 62 mInfoStringId = infoStringId; 63 mInstructionsStringId = instructionsStringId; 64 } 65 66 @Override onCreate(Bundle savedInstanceState)67 protected void onCreate(Bundle savedInstanceState) { 68 super.onCreate(savedInstanceState); 69 70 setContentView(mLayoutId); 71 setInfoResources(mTitleStringId, mInfoStringId, -1); 72 setPassFailButtonClickListeners(); 73 getPassButton().setEnabled(false); 74 setResult(RESULT_CANCELED); 75 76 ArrayTestListAdapter adapter = new ArrayTestListAdapter(this); 77 78 setupTests(adapter); 79 80 adapter.registerDataSetObserver(new DataSetObserver() { 81 @Override 82 public void onChanged() { 83 updatePassButton(); 84 } 85 }); 86 87 setTestListAdapter(adapter); 88 89 mCurrentTestPosition = 0; 90 91 TextView instructionTextView = (TextView) findViewById(R.id.test_instructions); 92 instructionTextView.setText(mInstructionsStringId); 93 mPrepareTestButton = (Button) findViewById(R.id.prepare_test_button); 94 mTestFeaturesList = (ListView) findViewById(android.R.id.list); 95 if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 96 mTestFeaturesList.setOnTouchListener((View v, MotionEvent e) -> { 97 switch (e.getAction()) { 98 case MotionEvent.ACTION_DOWN: 99 v.getParent().requestDisallowInterceptTouchEvent(true); 100 break; 101 case MotionEvent.ACTION_UP: 102 v.getParent().requestDisallowInterceptTouchEvent(false); 103 break; 104 default: 105 } 106 return false; 107 }); 108 } 109 } 110 111 /** 112 * Subclasses must add their tests items to the provided adapter(usually instances of 113 * {@link DialogTestListItem} or {@link DialogTestListItemWithIcon} but any class deriving from 114 * {@link TestListAdapter.TestListItem} will do). 115 * 116 * @param adapter The adapter to add test items to. 117 */ setupTests(ArrayTestListAdapter adapter)118 protected abstract void setupTests(ArrayTestListAdapter adapter); 119 120 public class DefaultTestCallback implements DialogTestListItem.TestCallback { 121 final private DialogTestListItem mTest; 122 DefaultTestCallback(DialogTestListItem test)123 public DefaultTestCallback(DialogTestListItem test) { 124 mTest = test; 125 } 126 127 @Override onPass()128 public void onPass() { 129 clearRemainingState(mTest); 130 setTestResult(mTest, TestResult.TEST_RESULT_PASSED); 131 } 132 133 @Override onFail()134 public void onFail() { 135 clearRemainingState(mTest); 136 setTestResult(mTest, TestResult.TEST_RESULT_FAILED); 137 } 138 } 139 showManualTestDialog(final DialogTestListItem test)140 public void showManualTestDialog(final DialogTestListItem test) { 141 showManualTestDialog(test, new DefaultTestCallback(test)); 142 } 143 showManualTestDialog(final DialogTestListItem test, final DialogTestListItem.TestCallback callback)144 public void showManualTestDialog(final DialogTestListItem test, 145 final DialogTestListItem.TestCallback callback) { 146 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this) 147 .setIcon(android.R.drawable.ic_dialog_info) 148 .setTitle(mTitleStringId) 149 .setNeutralButton(R.string.go_button_text, null) 150 .setPositiveButton(R.string.pass_button_text, new AlertDialog.OnClickListener() { 151 @Override 152 public void onClick(DialogInterface dialog, int which) { 153 callback.onPass(); 154 } 155 }) 156 .setNegativeButton(R.string.fail_button_text, new AlertDialog.OnClickListener() { 157 @Override 158 public void onClick(DialogInterface dialog, int which) { 159 callback.onFail(); 160 } 161 }); 162 View customView = test.getCustomView(); 163 if (customView != null) { 164 dialogBuilder.setView(customView); 165 } else { 166 dialogBuilder.setMessage(test.getManualTestInstruction()); 167 } 168 final AlertDialog dialog = dialogBuilder.show(); 169 // Note: Setting the OnClickListener on the Dialog rather than the Builder, prevents the 170 // dialog being dismissed on onClick. 171 dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() { 172 @Override 173 public void onClick(View v) { 174 if (!startTestIntent(test)) { 175 dialog.dismiss(); 176 } 177 } 178 }); 179 } 180 181 @Override handleItemClick(ListView l, View v, int position, long id)182 protected void handleItemClick(ListView l, View v, int position, long id) { 183 TestListAdapter.TestListItem test = (TestListAdapter.TestListItem) getListAdapter() 184 .getItem(position); 185 if (test instanceof DialogTestListItem) { 186 mCurrentTestPosition = position; 187 ((DialogTestListItem) test).performTest(this); 188 } else { 189 try { 190 super.handleItemClick(l, v, position, id); 191 } catch (ActivityNotFoundException e) { 192 Log.d(TAG, "handleItemClick() threw exception: ", e); 193 setTestResult(test, TestResult.TEST_RESULT_FAILED); 194 showToast(R.string.test_failed_cannot_start_intent); 195 } 196 } 197 } 198 199 200 /** 201 * Start a test's manual intent 202 * 203 * @param test The test the manual intent of which is to be started. 204 * @return true if activity could be started successfully, false otherwise. 205 */ startTestIntent(final DialogTestListItem test)206 boolean startTestIntent(final DialogTestListItem test) { 207 final Intent intent = test.intent; 208 try { 209 startActivity(intent); 210 } catch (ActivityNotFoundException e) { 211 Log.w(TAG, "Cannot start activity.", e); 212 Toast.makeText(this, "Cannot start " + intent, Toast.LENGTH_LONG).show(); 213 setTestResult(test, TestResult.TEST_RESULT_FAILED); 214 return false; 215 } 216 return true; 217 } 218 clearRemainingState(final DialogTestListItem test)219 protected void clearRemainingState(final DialogTestListItem test) { 220 // do nothing, override in subclass if needed 221 } 222 setTestResult(TestListAdapter.TestListItem test, int result)223 protected void setTestResult(TestListAdapter.TestListItem test, int result) { 224 // Bundle result in an intent to feed into handleLaunchTestResult 225 Intent resultIntent = new Intent(); 226 TestResult.addResultData(resultIntent, result, test.testName, /* testDetails */ null, 227 /* reportLog */ null); 228 handleLaunchTestResult(RESULT_OK, resultIntent); 229 getListView().smoothScrollToPosition(mCurrentTestPosition + 1); 230 } 231 setTestResult(String testName, int result)232 protected void setTestResult(String testName, int result) { 233 // Bundle result in an intent to feed into handleLaunchTestResult 234 Intent resultIntent = new Intent(); 235 TestResult.addResultData(resultIntent, result, testName, /* testDetails */ null, 236 /* reportLog */ null); 237 handleLaunchTestResult(RESULT_OK, resultIntent); 238 getListView().smoothScrollToPosition(mCurrentTestPosition + 1); 239 } 240 showToast(int messageId)241 protected void showToast(int messageId) { 242 String message = getString(messageId); 243 Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); 244 } 245 246 protected static class DialogTestListItem extends TestListAdapter.TestListItem { 247 248 public interface TestCallback { onPass()249 void onPass(); 250 onFail()251 void onFail(); 252 } 253 254 private String mManualInstruction; 255 DialogTestListItem(Context context, String nameId, String testId)256 public DialogTestListItem(Context context, String nameId, String testId) { 257 super(nameId, testId, null, null, null, null); 258 } 259 DialogTestListItem(Context context, int nameResId, String testId)260 public DialogTestListItem(Context context, int nameResId, String testId) { 261 super(context.getString(nameResId), testId, null, null, null, null); 262 } 263 DialogTestListItem(Context context, int nameResId, String testId, int testInstructionResId, Intent testIntent)264 public DialogTestListItem(Context context, int nameResId, String testId, 265 int testInstructionResId, Intent testIntent) { 266 super(context.getString(nameResId), testId, testIntent, null, null, null); 267 mManualInstruction = context.getString(testInstructionResId); 268 } 269 performTest(DialogTestListActivity activity)270 public void performTest(DialogTestListActivity activity) { 271 activity.showManualTestDialog(this); 272 } 273 getManualTestInstruction()274 public String getManualTestInstruction() { 275 return mManualInstruction; 276 } 277 getManualTestIntent()278 public Intent getManualTestIntent() { 279 return intent; 280 } 281 getCustomView()282 public View getCustomView() { 283 return null; 284 } 285 286 @Override isTest()287 boolean isTest() { 288 return true; 289 } 290 } 291 292 protected static class DialogTestListItemWithIcon extends DialogTestListItem { 293 294 private final int mImageResId; 295 private final Context mContext; 296 DialogTestListItemWithIcon(Context context, int nameResId, String testId, int testInstructionResId, Intent testIntent, int imageResId)297 public DialogTestListItemWithIcon(Context context, int nameResId, String testId, 298 int testInstructionResId, Intent testIntent, int imageResId) { 299 super(context, nameResId, testId, testInstructionResId, testIntent); 300 mContext = context; 301 mImageResId = imageResId; 302 } 303 304 @Override getCustomView()305 public View getCustomView() { 306 LayoutInflater layoutInflater = LayoutInflater.from(mContext); 307 View view = layoutInflater.inflate(R.layout.dialog_custom_view, 308 null /* root */); 309 ((ImageView) view.findViewById(R.id.sample_icon)).setImageResource(mImageResId); 310 ((TextView) view.findViewById(R.id.message)).setText(getManualTestInstruction()); 311 return view; 312 } 313 } 314 } 315