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.ScrollView; 36 import android.widget.TextView; 37 import android.widget.Toast; 38 39 import androidx.annotation.Nullable; 40 41 /** 42 * Test list activity that supports showing dialogs with pass/fail buttons instead of 43 * starting new activities. 44 * 45 * <p>In addition to that dialogs have a 'go' button that can be configured to launch an intent. 46 * Instructions are shown on top of the screen and a test preparation button is provided. 47 */ 48 public abstract class DialogTestListActivity extends PassFailButtons.TestListActivity { 49 private final String TAG = "DialogTestListActivity"; 50 private final int mLayoutId; 51 private final int mTitleStringId; 52 private final int mInfoStringId; 53 private final int mInstructionsStringId; 54 55 /** 56 * The button to prepare the test, populated if the layout ID used to construct the object 57 * contains a {@code prepare_test_button} {@link Button}. 58 */ 59 @Nullable protected Button mPrepareTestButton; 60 61 protected ListView mTestFeaturesList; 62 63 protected int mCurrentTestPosition; 64 65 /** 66 * Constructs the activity with the given resources. 67 * 68 * @param layoutId The layout to inflate, which must contain a {@link TextView} with ID {@code 69 * test_instructions} for the test instructions within a {@link ScrollView}, a {@link ListView} 70 * with ID {@code android:list} for the tests, and must include the {@code pass_fail_buttons} 71 * layout. If a custom layout is not required, {@code dialog_test_list_activity} can be used. 72 * @param titleStringId The string resource to use for the title at the top of the screen. 73 * @param infoStringId The string resource to use for the info button between the pass and fail 74 * buttons. 75 * @param instructionsStringId The string resource to use for the explanation that populates the 76 * {@link TextView} with ID {@code test_instructions} in the given layout. 77 */ DialogTestListActivity(int layoutId, int titleStringId, int infoStringId, int instructionsStringId)78 protected DialogTestListActivity(int layoutId, int titleStringId, int infoStringId, 79 int instructionsStringId) { 80 mLayoutId = layoutId; 81 mTitleStringId = titleStringId; 82 mInfoStringId = infoStringId; 83 mInstructionsStringId = instructionsStringId; 84 } 85 86 @Override onCreate(Bundle savedInstanceState)87 protected void onCreate(Bundle savedInstanceState) { 88 super.onCreate(savedInstanceState); 89 90 setContentView(mLayoutId); 91 setInfoResources(mTitleStringId, mInfoStringId, -1); 92 setPassFailButtonClickListeners(); 93 getPassButton().setEnabled(false); 94 setResult(RESULT_CANCELED); 95 96 ArrayTestListAdapter adapter = new ArrayTestListAdapter(this); 97 98 setupTests(adapter); 99 100 adapter.registerDataSetObserver(new DataSetObserver() { 101 @Override 102 public void onChanged() { 103 updatePassButton(); 104 } 105 }); 106 107 setTestListAdapter(adapter); 108 109 mCurrentTestPosition = 0; 110 111 TextView instructionTextView = (TextView) findViewById(R.id.test_instructions); 112 instructionTextView.setText(mInstructionsStringId); 113 mPrepareTestButton = (Button) findViewById(R.id.prepare_test_button); 114 mTestFeaturesList = (ListView) findViewById(android.R.id.list); 115 if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 116 mTestFeaturesList.setOnTouchListener((View v, MotionEvent e) -> { 117 switch (e.getAction()) { 118 case MotionEvent.ACTION_DOWN: 119 v.getParent().requestDisallowInterceptTouchEvent(true); 120 break; 121 case MotionEvent.ACTION_UP: 122 v.getParent().requestDisallowInterceptTouchEvent(false); 123 break; 124 default: 125 } 126 return false; 127 }); 128 } 129 } 130 131 /** 132 * Subclasses must add their tests items to the provided adapter(usually instances of 133 * {@link DialogTestListItem} or {@link DialogTestListItemWithIcon} but any class deriving from 134 * {@link TestListAdapter.TestListItem} will do). 135 * 136 * @param adapter The adapter to add test items to. 137 */ setupTests(ArrayTestListAdapter adapter)138 protected abstract void setupTests(ArrayTestListAdapter adapter); 139 140 public class DefaultTestCallback implements DialogTestListItem.TestCallback { 141 final private DialogTestListItem mTest; 142 DefaultTestCallback(DialogTestListItem test)143 public DefaultTestCallback(DialogTestListItem test) { 144 mTest = test; 145 } 146 147 @Override onPass()148 public void onPass() { 149 clearRemainingState(mTest); 150 setTestResult(mTest, TestResult.TEST_RESULT_PASSED); 151 } 152 153 @Override onFail()154 public void onFail() { 155 clearRemainingState(mTest); 156 setTestResult(mTest, TestResult.TEST_RESULT_FAILED); 157 } 158 } 159 showManualTestDialog(final DialogTestListItem test)160 public void showManualTestDialog(final DialogTestListItem test) { 161 showManualTestDialog(test, new DefaultTestCallback(test)); 162 } 163 showManualTestDialog(final DialogTestListItem test, final DialogTestListItem.TestCallback callback)164 public void showManualTestDialog(final DialogTestListItem test, 165 final DialogTestListItem.TestCallback callback) { 166 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this) 167 .setIcon(android.R.drawable.ic_dialog_info) 168 .setTitle(mTitleStringId) 169 .setPositiveButton(R.string.pass_button_text, new AlertDialog.OnClickListener() { 170 @Override 171 public void onClick(DialogInterface dialog, int which) { 172 callback.onPass(); 173 } 174 }) 175 .setNegativeButton(R.string.fail_button_text, new AlertDialog.OnClickListener() { 176 @Override 177 public void onClick(DialogInterface dialog, int which) { 178 callback.onFail(); 179 } 180 }); 181 if (test.intent != null) { 182 dialogBuilder.setNeutralButton(R.string.go_button_text, null); 183 } 184 View customView = test.getCustomView(); 185 if (customView != null) { 186 dialogBuilder.setView(customView); 187 } else { 188 dialogBuilder.setMessage(test.getManualTestInstruction()); 189 } 190 final AlertDialog dialog = dialogBuilder.show(); 191 // Note: Setting the OnClickListener on the Dialog rather than the Builder, prevents the 192 // dialog being dismissed on onClick. 193 if (test.intent != null) { 194 dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() { 195 @Override 196 public void onClick(View v) { 197 if (!startTestIntent(test)) { 198 dialog.dismiss(); 199 } 200 } 201 }); 202 } 203 } 204 205 @Override handleItemClick(ListView l, View v, int position, long id)206 protected void handleItemClick(ListView l, View v, int position, long id) { 207 TestListAdapter.TestListItem test = (TestListAdapter.TestListItem) getListAdapter() 208 .getItem(position); 209 if (test instanceof DialogTestListItem) { 210 mCurrentTestPosition = position; 211 ((DialogTestListItem) test).performTest(this); 212 } else { 213 try { 214 super.handleItemClick(l, v, position, id); 215 } catch (ActivityNotFoundException e) { 216 Log.d(TAG, "handleItemClick() threw exception: ", e); 217 setTestResult(test, TestResult.TEST_RESULT_FAILED); 218 showToast(R.string.test_failed_cannot_start_intent); 219 } 220 } 221 } 222 223 224 /** 225 * Start a test's manual intent 226 * 227 * @param test The test the manual intent of which is to be started. 228 * @return true if activity could be started successfully, false otherwise. 229 */ startTestIntent(final DialogTestListItem test)230 boolean startTestIntent(final DialogTestListItem test) { 231 final Intent intent = test.intent; 232 try { 233 startActivity(intent); 234 } catch (ActivityNotFoundException e) { 235 Log.w(TAG, "Cannot start activity.", e); 236 Toast.makeText(this, "Cannot start " + intent, Toast.LENGTH_LONG).show(); 237 setTestResult(test, TestResult.TEST_RESULT_FAILED); 238 return false; 239 } 240 return true; 241 } 242 clearRemainingState(final DialogTestListItem test)243 protected void clearRemainingState(final DialogTestListItem test) { 244 // do nothing, override in subclass if needed 245 } 246 setTestResult(TestListAdapter.TestListItem test, int result)247 protected void setTestResult(TestListAdapter.TestListItem test, int result) { 248 // Bundle result in an intent to feed into handleLaunchTestResult 249 Intent resultIntent = new Intent(); 250 TestResult.addResultData(resultIntent, result, test.testName, /* testDetails */ null, 251 /* reportLog */ null, null); 252 handleLaunchTestResult(RESULT_OK, resultIntent); 253 getListView().smoothScrollToPosition(mCurrentTestPosition + 1); 254 } 255 setTestResult(String testName, int result)256 protected void setTestResult(String testName, int result) { 257 // Bundle result in an intent to feed into handleLaunchTestResult 258 Intent resultIntent = new Intent(); 259 TestResult.addResultData(resultIntent, result, testName, /* testDetails */ null, 260 /* reportLog */ null, null); 261 handleLaunchTestResult(RESULT_OK, resultIntent); 262 getListView().smoothScrollToPosition(mCurrentTestPosition + 1); 263 } 264 showToast(int messageId)265 protected void showToast(int messageId) { 266 String message = getString(messageId); 267 Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); 268 } 269 270 protected static class DialogTestListItem extends TestListAdapter.TestListItem { 271 272 public interface TestCallback { onPass()273 void onPass(); 274 onFail()275 void onFail(); 276 } 277 278 private String mManualInstruction; 279 DialogTestListItem(Context context, String nameId, String testId)280 public DialogTestListItem(Context context, String nameId, String testId) { 281 super(nameId, testId, null, null, null, null); 282 } 283 DialogTestListItem(Context context, int nameResId, String testId)284 public DialogTestListItem(Context context, int nameResId, String testId) { 285 super(context.getString(nameResId), testId, null, null, null, null); 286 } 287 DialogTestListItem(Context context, int nameResId, String testId, int testInstructionResId)288 public DialogTestListItem(Context context, int nameResId, String testId, 289 int testInstructionResId) { 290 this(context, nameResId, testId, testInstructionResId, null); 291 } 292 DialogTestListItem(Context context, int nameResId, String testId, int testInstructionResId, Intent testIntent)293 public DialogTestListItem(Context context, int nameResId, String testId, 294 int testInstructionResId, Intent testIntent) { 295 super(context.getString(nameResId), testId, testIntent, null, null, null); 296 mManualInstruction = context.getString(testInstructionResId); 297 } 298 performTest(DialogTestListActivity activity)299 public void performTest(DialogTestListActivity activity) { 300 activity.showManualTestDialog(this); 301 } 302 getManualTestInstruction()303 public String getManualTestInstruction() { 304 return mManualInstruction; 305 } 306 getManualTestIntent()307 public Intent getManualTestIntent() { 308 return intent; 309 } 310 getCustomView()311 public View getCustomView() { 312 return null; 313 } 314 315 @Override isTest()316 boolean isTest() { 317 return true; 318 } 319 } 320 321 protected static class DialogTestListItemWithIcon extends DialogTestListItem { 322 323 private final int mImageResId; 324 private final Context mContext; 325 DialogTestListItemWithIcon(Context context, int nameResId, String testId, int testInstructionResId, Intent testIntent, int imageResId)326 public DialogTestListItemWithIcon(Context context, int nameResId, String testId, 327 int testInstructionResId, Intent testIntent, int imageResId) { 328 super(context, nameResId, testId, testInstructionResId, testIntent); 329 mContext = context; 330 mImageResId = imageResId; 331 } 332 333 @Override getCustomView()334 public View getCustomView() { 335 LayoutInflater layoutInflater = LayoutInflater.from(mContext); 336 View view = layoutInflater.inflate(R.layout.dialog_custom_view, 337 null /* root */); 338 ((ImageView) view.findViewById(R.id.sample_icon)).setImageResource(mImageResId); 339 ((TextView) view.findViewById(R.id.message)).setText(getManualTestInstruction()); 340 return view; 341 } 342 } 343 } 344