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