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