1 package com.mobileer.oboetester; 2 3 import android.content.Context; 4 import android.content.Intent; 5 import android.os.Build; 6 import android.util.AttributeSet; 7 import android.util.Log; 8 import android.view.LayoutInflater; 9 import android.view.View; 10 import android.view.WindowManager; 11 import android.widget.Button; 12 import android.widget.LinearLayout; 13 import android.widget.ScrollView; 14 import android.widget.TextView; 15 16 import androidx.annotation.NonNull; 17 18 import java.text.DateFormat; 19 import java.text.SimpleDateFormat; 20 import java.util.Calendar; 21 import java.util.Date; 22 23 /** 24 * Run an automated test from a UI, gather logs, 25 * and display a summary. 26 */ 27 public class AutomatedTestRunner extends LinearLayout implements Runnable { 28 29 private Button mStartButton; 30 private Button mStopButton; 31 private Button mShareButton; 32 private TextView mAutoTextView; 33 private ScrollView mAutoTextScroller; 34 private TextView mSingleTestIndex; 35 private StringBuffer mFailedSummary; 36 private StringBuffer mSummary; 37 private StringBuffer mFullSummary; 38 private int mTestCount; 39 private int mPassCount; 40 private int mFailCount; 41 private int mSkipCount; 42 private TestAudioActivity mActivity; 43 44 private Thread mAutoThread; 45 private volatile boolean mThreadEnabled; 46 private CachedTextViewLog mCachedTextView; 47 AutomatedTestRunner(Context context)48 public AutomatedTestRunner(Context context) { 49 super(context); 50 initializeViews(context); 51 } 52 AutomatedTestRunner(Context context, AttributeSet attrs)53 public AutomatedTestRunner(Context context, AttributeSet attrs) { 54 super(context, attrs); 55 initializeViews(context); 56 } 57 AutomatedTestRunner(Context context, AttributeSet attrs, int defStyle)58 public AutomatedTestRunner(Context context, 59 AttributeSet attrs, 60 int defStyle) { 61 super(context, attrs, defStyle); 62 initializeViews(context); 63 } 64 getActivity()65 public TestAudioActivity getActivity() { 66 return mActivity; 67 } 68 setActivity(TestAudioActivity activity)69 public void setActivity(TestAudioActivity activity) { 70 this.mActivity = activity; 71 mCachedTextView = new CachedTextViewLog(activity, mAutoTextView); 72 } 73 74 /** 75 * Inflates the views in the layout. 76 * 77 * @param context 78 * the current context for the view. 79 */ initializeViews(Context context)80 private void initializeViews(Context context) { 81 LayoutInflater inflater = (LayoutInflater) context 82 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 83 inflater.inflate(R.layout.auto_test_runner, this); 84 85 mStartButton = (Button) findViewById(R.id.button_start); 86 mStartButton.setOnClickListener( new OnClickListener() { 87 @Override 88 public void onClick(View v) { 89 startTest(); 90 } 91 }); 92 93 mStopButton = (Button) findViewById(R.id.button_stop); 94 mStopButton.setOnClickListener( new OnClickListener() { 95 @Override 96 public void onClick(View v) { 97 stopTest(); 98 } 99 }); 100 101 mShareButton = (Button) findViewById(R.id.button_share); 102 mShareButton.setOnClickListener( new OnClickListener() { 103 @Override 104 public void onClick(View v) { 105 shareResult(); 106 mShareButton.setEnabled(true); 107 } 108 }); 109 mShareButton.setEnabled(false); 110 111 mSingleTestIndex = (TextView) findViewById(R.id.single_test_index); 112 113 mAutoTextScroller = (ScrollView) findViewById(R.id.text_log_auto_scroller); 114 mAutoTextView = (TextView) findViewById(R.id.text_log_auto); 115 116 mFailedSummary = new StringBuffer(); 117 mSummary = new StringBuffer(); 118 mFullSummary = new StringBuffer(); 119 } 120 updateStartStopButtons(boolean running)121 private void updateStartStopButtons(boolean running) { 122 mStartButton.setEnabled(!running); 123 mStopButton.setEnabled(running); 124 } 125 getTestCount()126 public int getTestCount() { 127 return mTestCount; 128 } 129 isThreadEnabled()130 public boolean isThreadEnabled() { 131 return mThreadEnabled; 132 } 133 appendFailedSummary(String text)134 public void appendFailedSummary(String text) { 135 mFailedSummary.append(text); 136 } 137 appendSummary(String text)138 public void appendSummary(String text) { 139 mSummary.append(text); 140 } 141 incrementFailCount()142 public void incrementFailCount() { 143 mFailCount++; 144 } incrementPassCount()145 public void incrementPassCount() { 146 mPassCount++; 147 } incrementSkipCount()148 public void incrementSkipCount() { 149 mSkipCount++; 150 } incrementTestCount()151 public void incrementTestCount() { 152 mTestCount++; 153 } 154 155 // Write to scrollable TextView log(final String text)156 public void log(final String text) { 157 if (text == null) return; 158 Log.d(TestAudioActivity.TAG, "LOG - " + text); 159 mCachedTextView.append(text + "\n"); 160 mFullSummary.append(text + "\n"); 161 scrollToBottom(); 162 } 163 scrollToBottom()164 public void scrollToBottom() { 165 mAutoTextScroller.fullScroll(View.FOCUS_DOWN); 166 } 167 168 // Flush any logs that are stuck in the cache. flushLog()169 public void flushLog() { 170 mCachedTextView.flush(); 171 scrollToBottom(); 172 } 173 logClear()174 private void logClear() { 175 mCachedTextView.clear(); 176 mFullSummary.delete(0, mFullSummary.length()); 177 mSummary.delete(0, mSummary.length()); 178 mFailedSummary.delete(0, mFailedSummary.length()); 179 } 180 getFullLogs()181 protected String getFullLogs() { 182 return mFullSummary.toString(); 183 } 184 startAutoThread()185 private void startAutoThread() { 186 mThreadEnabled = true; 187 mAutoThread = new Thread(this); 188 mAutoThread.start(); 189 } 190 stopAutoThread()191 private void stopAutoThread() { 192 try { 193 if (mAutoThread != null) { 194 Log.d(TestAudioActivity.TAG, 195 "Who called stopAutoThread()?", 196 new RuntimeException("Just for debugging.")); 197 mThreadEnabled = false; 198 mAutoThread.interrupt(); 199 mAutoThread.join(100); 200 mAutoThread = null; 201 } 202 } catch (InterruptedException e) { 203 e.printStackTrace(); 204 } 205 } 206 setTestIndexText(int newTestIndex)207 protected void setTestIndexText(int newTestIndex) { 208 if (newTestIndex >= 0) { 209 mSingleTestIndex.setText(String.valueOf(newTestIndex)); 210 } else { 211 mSingleTestIndex.setText(""); 212 } 213 } 214 updateTestIndex()215 private void updateTestIndex() { 216 CharSequence chars = mSingleTestIndex.getText(); 217 String text = chars.toString(); 218 int testIndex = -1; 219 String trimmed = chars.toString().trim(); 220 if (trimmed.length() > 0) { 221 try { 222 testIndex = Integer.parseInt(text); 223 } catch (NumberFormatException e) { 224 mActivity.showErrorToast("Badly formated callback size: " + text); 225 mSingleTestIndex.setText(""); 226 } 227 } 228 mActivity.setSingleTestIndex(testIndex); 229 } 230 startTest()231 protected void startTest() { 232 updateTestIndex(); 233 updateStartStopButtons(true); 234 startAutoThread(); 235 } 236 stopTest()237 public void stopTest() { 238 stopAutoThread(); 239 } 240 241 // Only call from UI thread. onTestStarted()242 public void onTestStarted() { 243 mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 244 } 245 246 // Only call from UI thread. onTestFinished()247 public void onTestFinished() { 248 mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 249 updateStartStopButtons(false); 250 mShareButton.setEnabled(true); 251 } 252 getTimestampString()253 public static String getTimestampString() { 254 DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss"); 255 Date now = Calendar.getInstance().getTime(); 256 return df.format(now); 257 } 258 259 // Share text from log via GMail, Drive or other method. shareResult()260 public void shareResult() { 261 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); 262 sharingIntent.setType("text/plain"); 263 264 String subjectText = "OboeTester-" + mActivity.getTestName() 265 + "-" + Build.MANUFACTURER 266 + "-" + Build.MODEL 267 + "-" + getTimestampString(); 268 subjectText = subjectText.replace(' ', '-'); 269 sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subjectText); 270 271 String shareBody = mAutoTextView.getText().toString(); 272 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody); 273 274 mActivity.startActivity(Intent.createChooser(sharingIntent, "Share using:")); 275 } 276 277 @Override run()278 public void run() { 279 mActivity.runOnUiThread(new Runnable() { 280 @Override 281 public void run() { 282 onTestStarted(); 283 } 284 }); 285 logClear(); 286 log("=== STARTED at " + new Date()); 287 log(mActivity.getTestName()); 288 log(MainActivity.getVersionText()); 289 log(Build.MANUFACTURER + ", " + Build.MODEL + ", " + Build.PRODUCT); 290 log(Build.DISPLAY); 291 appendFailedSummary("Summary\n"); 292 mTestCount = 0; 293 mPassCount = 0; 294 mFailCount = 0; 295 mSkipCount = 0; 296 try { 297 mActivity.runTest(); 298 log("Tests finished."); 299 } catch(Exception e) { 300 log("EXCEPTION: " + e.getMessage()); 301 } finally { 302 mActivity.stopTest(); 303 if (!mThreadEnabled) { 304 log("== TEST STOPPED =="); 305 } 306 log("\n==== SUMMARY ========"); 307 log(mSummary.toString()); 308 if (mFailCount > 0) { 309 log("These tests FAILED:"); 310 log(mFailedSummary.toString()); 311 log("------------"); 312 } else if (mPassCount > 0) { 313 log("All " + mPassCount + " tests PASSED."); 314 } else { 315 log("No tests were run!"); 316 } 317 log(getPassFailReport()); 318 log("== FINISHED at " + new Date()); 319 320 flushLog(); 321 322 mActivity.saveIntentLog(); 323 324 mActivity.runOnUiThread(new Runnable() { 325 @Override 326 public void run() { 327 onTestFinished(); 328 flushLog(); 329 } 330 }); 331 } 332 } 333 334 @NonNull getPassFailReport()335 public String getPassFailReport() { 336 return mPassCount + " passed. " 337 + mFailCount + " failed. " 338 + mSkipCount + " skipped. "; 339 } 340 341 } 342