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