1 // Copyright 2013 Google Inc. All Rights Reserved.
2 
3 package com.android.cts.verifier;
4 
5 import android.app.AlertDialog;
6 import android.content.ActivityNotFoundException;
7 import android.content.Context;
8 import android.content.Intent;
9 import android.os.Bundle;
10 import android.os.Parcel;
11 import android.os.Parcelable;
12 import android.text.TextUtils;
13 import android.util.Log;
14 import android.view.View;
15 import android.view.View.OnClickListener;
16 import android.view.ViewGroup;
17 import android.widget.Button;
18 import android.widget.TextView;
19 
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.List;
23 import java.util.Objects;
24 
25 /**
26  *  A generic activity for intent based tests.
27  *
28  *  This activity can be reused for various tests that are intent based. The activity consists of a
29  *  text view containing instructions and one or more buttons that trigger intents.
30  *  Each button can send one or more intenrs as startActivity() calls.
31  *
32  *  The intents can either be generated statically and passed as an extra to the intent that started
33  *  this activity or in some cases where the intent needs to be based on dynamic data (for example
34  *  time of day), an {@link IntentFactory} class name can be passed instead. This intent factory
35  *  will dynamically create the intent when the button is clicked based on the test id and the
36  *  button that was clicked.
37  */
38 public final class IntentDrivenTestActivity extends PassFailButtons.Activity
39         implements OnClickListener {
40 
41     private static final String TAG = IntentDrivenTestActivity.class.getSimpleName();
42 
43     public static final String EXTRA_ID = "id";
44     public static final String EXTRA_TITLE = "title";
45     public static final String EXTRA_INFO = "info";
46     public static final String EXTRA_BUTTONS = "buttons";
47 
48     private static final String[] REQUIRED_EXTRAS = {
49             EXTRA_ID, EXTRA_TITLE, EXTRA_INFO, EXTRA_BUTTONS
50     };
51 
52     private String mTestId;
53     private ButtonInfo[] mButtonInfos;
54 
55     @Override
onCreate(Bundle savedInstanceState)56     protected void onCreate(Bundle savedInstanceState) {
57         super.onCreate(savedInstanceState);
58         setContentView(R.layout.intent_driven_test);
59         setPassFailButtonClickListeners();
60 
61         final Intent intent = checkAndGetIntent();
62 
63         mTestId = intent.getStringExtra(EXTRA_ID);
64         setTitle(intent.getIntExtra(EXTRA_TITLE, -1));
65 
66         final TextView info = (TextView) findViewById(R.id.info);
67         info.setText(intent.getIntExtra(EXTRA_INFO, -1));
68 
69         final ViewGroup buttons = (ViewGroup) findViewById(R.id.buttons);
70         final Parcelable[] parcelables = intent.getParcelableArrayExtra(EXTRA_BUTTONS);
71         mButtonInfos = new ButtonInfo[parcelables.length];
72         for (int i = 0; i < parcelables.length; i++) {
73             final ButtonInfo buttonInfo = (ButtonInfo) parcelables[i];
74             mButtonInfos[i] = buttonInfo;
75             final Button button = new Button(this);
76             buttons.addView(button);
77             button.setText(buttonInfo.mButtonText);
78             button.setTag(i);
79             button.setOnClickListener(this);
80         }
81     }
82 
83     @Override
onResume()84     protected void onResume() {
85         super.onResume();
86         setResult(RESULT_CANCELED);
87     }
88 
89     @Override
onClick(View v)90     public void onClick(View v) {
91         final ButtonInfo buttonInfo = mButtonInfos[(Integer) v.getTag()];
92         final Intent[] intents = buttonInfo.getIntents();
93         try {
94             if (intents != null) {
95                 for (Parcelable intent : intents) {
96                     startActivity((Intent) intent);
97                 }
98             } else {
99                 final Class<?> factoryClass;
100                 final String className = buttonInfo.getIntentFactoryClassName();
101                 try {
102                     factoryClass = Thread.currentThread().getContextClassLoader().loadClass(className);
103                 } catch (ClassNotFoundException e) {
104                     throw new IllegalArgumentException("Factory not found: " + className, e);
105                 }
106                 final IntentFactory factory;
107                 try {
108                     factory = (IntentFactory) factoryClass.newInstance();
109                 } catch (InstantiationException e) {
110                     throw new IllegalArgumentException("Can't create factory: " + className, e);
111                 } catch (IllegalAccessException e) {
112                     throw new IllegalArgumentException("Can't create factory: " + className, e);
113                 }
114 
115                 for (Intent intent : factory.createIntents(mTestId, buttonInfo.getButtonText())) {
116                     startActivity(intent);
117                 }
118             }
119         } catch (ActivityNotFoundException e) {
120             // Instead of crashing, log and alert the user of that the Intent couldn't be resolved.
121             Log.w(TAG, "Could not resolve Intent.", e);
122             showFailedIntentResolution(e.getMessage());
123         }
124     }
125 
showFailedIntentResolution(String message)126     private void showFailedIntentResolution(String message) {
127         new AlertDialog.Builder(this)
128                 .setTitle(R.string.intent_not_resolved)
129                 .setMessage(getString(R.string.intent_not_resolved_info, message))
130                 .show();
131     }
132 
133     @Override
getTestId()134     public String getTestId() {
135         return mTestId;
136     }
137 
138     public static final class TestInfo {
139         private final String mTestId;
140         private final int mTitle;
141         private final int mInfoText;
142         private final ButtonInfo[] mButtons;
143 
TestInfo(String testId, int title, int infoText, ButtonInfo... buttons)144         public TestInfo(String testId,  int title, int infoText, ButtonInfo... buttons) {
145             /** The Intent Driven Test layout {@link R.layout.intent_driven_test} is designed for
146              *  up to 2 buttons and won't render well with more buttons on small screen devices.
147              *  If you need more than 2 buttons, please change the layout so it can properly render
148              *  them even on the smallest devices.
149              */
150             if (buttons.length > 2) {
151                 throw new RuntimeException("Too many buttons");
152             }
153             mTestId = nonEmpty("testId", testId);
154             mTitle = title;
155             mInfoText = infoText;
156             mButtons = buttons;
157         }
158 
getTestId()159         public String getTestId() {
160             return mTestId;
161         }
162 
getTitle()163         public int getTitle() {
164             return mTitle;
165         }
166 
getInfoText()167         public int getInfoText() {
168             return mInfoText;
169         }
170 
getButtons()171         public ButtonInfo[] getButtons() {
172             return mButtons;
173         }
174     }
175 
176     public static final class ButtonInfo implements Parcelable {
177         private final int mButtonText;
178         private final Intent[] mIntents;
179         private final String mIntentFactoryClassName;
180 
ButtonInfo(int buttonText, Intent... intents)181         public ButtonInfo(int buttonText, Intent... intents) {
182             mButtonText = buttonText;
183             mIntents = intents;
184             mIntentFactoryClassName = null;
185         }
186 
ButtonInfo(int buttonText, String intentFactoryClassName)187         public ButtonInfo(int buttonText, String intentFactoryClassName) {
188             mButtonText = buttonText;
189             mIntents = null;
190             mIntentFactoryClassName = intentFactoryClassName;
191         }
192 
ButtonInfo(Parcel source)193         public ButtonInfo(Parcel source) {
194             mButtonText = source.readInt();
195             final Parcelable[] parcelables = source.readParcelableArray(null);
196             if (parcelables != null) {
197                 mIntents = new Intent[parcelables.length];
198                 for (int i = 0, parcelablesLength = parcelables.length; i < parcelablesLength; i++) {
199                     mIntents[i] = (Intent) parcelables[i];
200                 }
201             } else {
202                 mIntents = null;
203             }
204             mIntentFactoryClassName = source.readString();
205         }
206 
getButtonText()207         public int getButtonText() {
208             return mButtonText;
209         }
210 
getIntents()211         public Intent[] getIntents() {
212             return mIntents;
213         }
214 
getIntentFactoryClassName()215         public String getIntentFactoryClassName() {
216             return mIntentFactoryClassName;
217         }
218 
219         @Override
describeContents()220         public int describeContents() {
221             return 0;
222         }
223 
224         @Override
writeToParcel(Parcel dest, int flags)225         public void writeToParcel(Parcel dest, int flags) {
226             dest.writeInt(mButtonText);
227             dest.writeParcelableArray(mIntents, 0);
228             dest.writeString(mIntentFactoryClassName);
229         }
230 
231         public static final Creator<ButtonInfo> CREATOR = new Creator<ButtonInfo>() {
232             public ButtonInfo createFromParcel(Parcel source) {
233                 return new ButtonInfo(source);
234             }
235 
236             public ButtonInfo[] newArray(int size) {
237                 return new ButtonInfo[size];
238             }
239         };
240 
241     }
242 
243     public interface IntentFactory {
createIntents(String testId, int buttonText)244         Intent[] createIntents(String testId, int buttonText);
245     }
246 
newIntent(Context context, String testId, int titleResId, int infoResId, ButtonInfo[] buttons)247     public static Intent newIntent(Context context, String testId, int titleResId,
248             int infoResId, ButtonInfo[] buttons) {
249         Intent intent = new Intent(context, IntentDrivenTestActivity.class)
250                 .putExtra(IntentDrivenTestActivity.EXTRA_ID, nonEmpty("testId", testId))
251                 .putExtra(IntentDrivenTestActivity.EXTRA_TITLE, titleResId)
252                 .putExtra(IntentDrivenTestActivity.EXTRA_INFO, infoResId)
253                 .putExtra(IntentDrivenTestActivity.EXTRA_BUTTONS,
254                         Objects.requireNonNull(buttons, "buttons cannot be null"));
255         Log.d(TAG, "Added 4 required extras to  " + toString(context, intent));
256         return intent;
257     }
258 
nonEmpty(String name, String value)259     private static String nonEmpty(String name, String value) {
260         if (TextUtils.isEmpty(value)) {
261             throw new IllegalArgumentException(name + " cannot be null or empty: '" + value + "'");
262         }
263         return value;
264     }
265 
checkAndGetIntent()266     private Intent checkAndGetIntent() {
267         Intent intent = getIntent();
268         List<String> missingExtras = new ArrayList<>();
269         for (String extra : REQUIRED_EXTRAS) {
270             if (!intent.hasExtra(extra)) {
271                 missingExtras.add(extra);
272             }
273         }
274         if (!missingExtras.isEmpty()) {
275             throw new IllegalArgumentException(toString(this, intent) + ") is missing "
276                     + missingExtras.size() + " required extras: "
277                     + Arrays.toString(REQUIRED_EXTRAS));
278         }
279         return intent;
280     }
281 
toString(Context context, Intent intent)282     public static String toString(Context context, Intent intent) {
283         return new StringBuilder("Intent["
284                 + "address=").append(System.identityHashCode(intent))
285                 .append(", numberExtras=").append(intent.getExtras().size())
286                 .append(", extraKeys=").append(new ArrayList<>(intent.getExtras().keySet()))
287                 .append(", testId=")
288                 .append(intent.hasExtra(EXTRA_ID) ? intent.getStringExtra(EXTRA_ID) : "N/A")
289                 .append(", title=").append(intent.hasExtra(EXTRA_TITLE)
290                                 ? context.getString(intent.getIntExtra(EXTRA_TITLE, -1))
291                                 : "N/A")
292                 .append(intent.hasExtra(EXTRA_INFO) ? ", has_info" : ", no_info")
293                 .append(", numberButtons=").append(intent.hasExtra(EXTRA_BUTTONS)
294                                 ? intent.getParcelableArrayExtra(EXTRA_BUTTONS).length
295                                 : 0)
296                 .append(", rawIntent=").append(intent)
297                 .append(']').toString();
298     }
299 }
300