From 69c0292affe8be51e10afb2dbf58f0133918a2c3 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Tue, 24 Nov 2015 17:48:05 -0800 Subject: [PATCH] Created a new bug report workflow so user can keep track of its progress. The old workflow was: 1. dumpstate starts. 2. When dumpstate finishes, it sends a BUGREPORT_FINISHED intent. 3. Shell's BugreportReceiver receives the BUGREPORT_FINISHED and issues a system notification so user can share the bug report. The new workflow is: 1. When dumpstate starts, it sends a BUGREPORT_STARTED with its pid and the estimated total effort. 2. When Shell's BugreportReceiver receives the BUGREPORT_STARTED, it: 2.1 Issues a system notification so user can watch the progresss (which is 0% initially). 2.2 Starts a service (BugreportProgressService) responsible for polling the dumpstate progress (using system properties and the pid) and updating the system notification. 3. As dumpstate progress, it updates the proper system property. 4. When dumpstate finishes, it sends a BUGREPORT_FINISHED event. 5. When Shell's BugreportReceiver receives the BUGREPORT_FINISHED, it: 5.1 Finishes the service if necessary. 5.2 Issues a system notification so user can share the bug report. This CL handles the Shell changes only, the dumpstate changes will be changed in a separate CL. BUG: 25794470 Change-Id: Icbd0b42dd48e8db376b60544348b6818c6374338 --- packages/Shell/AndroidManifest.xml | 1 + packages/Shell/res/values/strings.xml | 2 + .../shell/BugreportProgressService.java | 410 ++++++++++++++++-- .../com/android/shell/BugreportReceiver.java | 9 +- .../android/shell/BugreportReceiverTest.java | 53 ++- .../tests/src/com/android/shell/UiBot.java | 33 +- 6 files changed, 470 insertions(+), 38 deletions(-) diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index bf3982d9a71d4..25346acd16004 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -144,6 +144,7 @@ android:name=".BugreportReceiver" android:permission="android.permission.DUMP"> + diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml index 4469d387853c5..cff36f73f74d5 100644 --- a/packages/Shell/res/values/strings.xml +++ b/packages/Shell/res/values/strings.xml @@ -17,6 +17,8 @@ Shell + + Bug report in progress Bug report captured diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index a2030ef68817b..d0e91d22f87af 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -21,11 +21,16 @@ import static com.android.shell.BugreportPrefs.getWarningState; import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -45,26 +50,102 @@ import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.AsyncTask; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Parcelable; +import android.os.Process; import android.os.SystemProperties; import android.support.v4.content.FileProvider; +import android.text.format.DateUtils; import android.util.Log; import android.util.Patterns; +import android.util.SparseArray; import android.widget.Toast; +/** + * Service used to keep progress of bug reports processes ({@code dumpstate}). + *

+ * The workflow is: + *

    + *
  1. When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with its pid and the + * estimated total effort. + *
  2. {@link BugreportReceiver} receives the intent and delegates it to this service. + *
  3. Upon start, this service: + *
      + *
    1. Issues a system notification so user can watch the progresss (which is 0% initially). + *
    2. Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress. + *
    3. If the progress changed, it updates the system notification. + *
    + *
  4. As {@code dumpstate} progresses, it updates the system property. + *
  5. When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent. + *
  6. {@link BugreportReceiver} receives the intent and delegates it to this service, which in + * turn: + *
      + *
    1. Updates the system notification so user can share the bug report. + *
    2. Stops monitoring that {@code dumpstate} process. + *
    3. Stops itself if it doesn't have any process left to monitor. + *
    + *
+ */ public class BugreportProgressService extends Service { private static final String TAG = "Shell"; + private static final boolean DEBUG = false; private static final String AUTHORITY = "com.android.shell"; + static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED"; + static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED"; + static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT"; static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT"; + static final String EXTRA_PID = "android.intent.extra.PID"; + static final String EXTRA_MAX = "android.intent.extra.MAX"; + static final String EXTRA_NAME = "android.intent.extra.NAME"; + static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT"; + + private static final int MSG_SERVICE_COMMAND = 1; + private static final int MSG_POLL = 2; + + /** Polling frequency, in milliseconds. */ + private static final long POLLING_FREQUENCY = 500; + + /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */ + private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS; + + /** System property used for monitoring progress. */ + private static final String PROGRESS_PROPERTY_TEMPLATE = "dumpstate.%d.progress"; + + /** Managed dumpstate processes (keyed by pid) */ + private final SparseArray mProcesses = new SparseArray<>(); + + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + + @Override + public void onCreate() { + HandlerThread thread = new HandlerThread("BugreportProgressServiceThread", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { - onBugreportFinished(intent); + // Handle it in a separate thread. + Message msg = mServiceHandler.obtainMessage(); + msg.what = MSG_SERVICE_COMMAND; + msg.obj = intent; + mServiceHandler.sendMessage(msg); } + + // If service is killed it cannot be recreated because it would not know which + // dumpstate PIDs it would have to watch. return START_NOT_STICKY; } @@ -73,26 +154,249 @@ public class BugreportProgressService extends Service { return null; } - private void onBugreportFinished(Intent intent) { - final Context context = getApplicationContext(); - final Configuration conf = context.getResources().getConfiguration(); - final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); - final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT); + @Override + public void onDestroy() { + mServiceLooper.quit(); + super.onDestroy(); + } - if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) { - triggerLocalNotification(context, bugreportFile, screenshotFile); + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.printf("Monitored dumpstate processes: \n"); + synchronized (mProcesses) { + for (int i = 0; i < mProcesses.size(); i++) { + writer.printf("\t%s\n", mProcesses.valueAt(i)); + } + } + } + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + pollProgress(); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_POLL) { + pollProgress(); + return; + } + + if (msg.what != MSG_SERVICE_COMMAND) { + // Sanity check. + Log.e(TAG, "Invalid message type: " + msg.what); + return; + } + + // At this point it's handling onStartCommand(), whose intent contains the extras + // originally received by BugreportReceiver. + if (!(msg.obj instanceof Intent)) { + // Sanity check. + Log.e(TAG, "Internal error: invalid msg.obj: " + msg.obj); + return; + } + final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT); + if (!(parcel instanceof Intent)) { + // Sanity check. + Log.e(TAG, "Internal error: msg.obj is missing extra " + EXTRA_ORIGINAL_INTENT); + return; + } + + final Intent intent = (Intent) parcel; + final String action = intent.getAction(); + int pid = intent.getIntExtra(EXTRA_PID, 0); + int max = intent.getIntExtra(EXTRA_MAX, -1); + String name = intent.getStringExtra(EXTRA_NAME); + + if (DEBUG) Log.v(TAG, "action: " + action + ", name: " + name + ", pid: " + pid + + ", max: "+ max); + switch (action) { + case INTENT_BUGREPORT_STARTED: + if (!startProgress(name, pid, max)) { + stopSelfWhenDone(); + return; + } + break; + case INTENT_BUGREPORT_FINISHED: + if (pid == -1) { + // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy, + // out-of-sync dumpstate process. + Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent); + } + stopProgress(pid, intent); + break; + default: + Log.w(TAG, "Unsupported intent: " + action); + } + return; + + } + + /** + * Creates the {@link BugreportInfo} for a process and issue a system notification to + * indicate its progress. + * + * @return whether it succeeded or not. + */ + private boolean startProgress(String name, int pid, int max) { + if (name == null) { + Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent"); + name = "N/A"; + } + if (pid == -1) { + Log.e(TAG, "Missing " + EXTRA_PID + " on start intent"); + return false; + } + if (max <= 0) { + Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max); + return false; + } + + final BugreportInfo info = new BugreportInfo(pid, name, max); + synchronized (mProcesses) { + if (mProcesses.indexOfKey(pid) >= 0) { + Log.w(TAG, "PID " + pid + " already watched"); + } else { + mProcesses.put(info.pid, info); + } + } + updateProgress(info); + return true; + } + + /** + * Updates the system notification for a given bug report. + */ + private void updateProgress(BugreportInfo info) { + if (info.max <= 0 || info.progress < 0 || info.name == null) { + Log.e(TAG, "Invalid progress values for " + info); + return; + } + + final Context context = getApplicationContext(); + final NumberFormat nf = NumberFormat.getPercentInstance(); + nf.setMinimumFractionDigits(2); + nf.setMaximumFractionDigits(2); + final String percentText = nf.format((double) info.progress / info.max); + + final String title = context.getString(R.string.bugreport_in_progress_title); + final Notification notification = new Notification.Builder(context) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) + .setContentTitle(title) + .setTicker(title) + .setContentText(info.name) + .setContentInfo(percentText) + .setProgress(info.max, info.progress, false) + // TODO: .setOngoing(true) once it has a CANCEL action + .setLocalOnly(true) + .setColor(context.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .build(); + + NotificationManager.from(context).notify(TAG, info.pid, notification); + } + + /** + * Finalizes the progress on a given process and sends the finished intent. + */ + private void stopProgress(int pid, Intent intent) { + synchronized (mProcesses) { + if (mProcesses.indexOfKey(pid) < 0) { + Log.w(TAG, "PID not watched: " + pid); + } else { + mProcesses.remove(pid); + } + stopSelfWhenDone(); + } + if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification"); + NotificationManager.from(getApplicationContext()).cancel(TAG, pid); + if (intent != null) { + // Bug report finished fine: send a new, different notification. + if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): finish bug report"); + onBugreportFinished(pid, intent); + } + } + + /** + * Poll {@link SystemProperties} to get the progress on each monitored process. + */ + private void pollProgress() { + synchronized (mProcesses) { + if (mProcesses.size() == 0) { + Log.d(TAG, "No process to poll progress."); + } + for (int i = 0; i < mProcesses.size(); i++) { + int pid = mProcesses.keyAt(i); + String property = String.format(PROGRESS_PROPERTY_TEMPLATE, pid); + int progress; + try { + progress = SystemProperties.getInt(property, 0); + } catch (IllegalArgumentException e) { + Log.v(TAG, "Could not read system property " + property, e); + continue; + } + if (progress == 0) { + Log.v(TAG, "System property " + property + " is not set yet"); + continue; + } + + BugreportInfo info = mProcesses.valueAt(i); + + if (progress != info.progress) { + if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + " from " + + info.progress + " to " + progress); + info.progress = progress; + info.lastUpdate = System.currentTimeMillis(); + updateProgress(info); + } else { + long inactiveTime = System.currentTimeMillis() - info.lastUpdate; + if (inactiveTime >= INACTIVITY_TIMEOUT) { + Log.w(TAG, "No progress update for process " + pid + " since " + + info.getFormattedLastUpdate()); + stopProgress(info.pid, null); + } + } + } + // Keep polling... + sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY); + } + } + + /** + * Finishes the service when it's not monitoring any more processes. + */ + private void stopSelfWhenDone() { + synchronized (mProcesses) { + if (mProcesses.size() > 0) { + if (DEBUG) Log.v(TAG, "Staying alive, waiting for pids " + mProcesses); + return; + } + Log.v(TAG, "No more pids to handle, shutting down"); + stopSelf(); + } + } + + private void onBugreportFinished(int pid, Intent intent) { + final Context context = getApplicationContext(); + final Configuration conf = context.getResources().getConfiguration(); + final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); + final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT); + + if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) { + triggerLocalNotification(context, pid, bugreportFile, screenshotFile); + } } - stopSelf(); } /** - * Responsible for triggering a notification that allows the user to start a - * "share" intent with the bug report. On watches we have other methods to allow the user to - * start this intent (usually by triggering it on another connected device); we don't need to - * display the notification in this case. + * Responsible for triggering a notification that allows the user to start a "share" intent with + * the bug report. On watches we have other methods to allow the user to start this intent + * (usually by triggering it on another connected device); we don't need to display the + * notification in this case. */ - private static void triggerLocalNotification(final Context context, final File bugreportFile, - final File screenshotFile) { + private static void triggerLocalNotification(final Context context, final int pid, + final File bugreportFile, final File screenshotFile) { if (!bugreportFile.exists() || !bugreportFile.canRead()) { Log.e(TAG, "Could not read bugreport file " + bugreportFile); Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text), @@ -103,10 +407,10 @@ public class BugreportProgressService extends Service { boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt"); if (!isPlainText) { // Already zipped, send it right away. - sendBugreportNotification(context, bugreportFile, screenshotFile); + sendBugreportNotification(context, pid, bugreportFile, screenshotFile); } else { // Asynchronously zip the file first, then send it. - sendZippedBugreportNotification(context, bugreportFile, screenshotFile); + sendZippedBugreportNotification(context, pid, bugreportFile, screenshotFile); } } @@ -155,7 +459,7 @@ public class BugreportProgressService extends Service { /** * Sends a bugreport notitication. */ - private static void sendBugreportNotification(Context context, File bugreportFile, + private static void sendBugreportNotification(Context context, int pid, File bugreportFile, File screenshotFile) { // Files are kept on private storage, so turn into Uris that we can // grant temporary permissions for. @@ -173,10 +477,11 @@ public class BugreportProgressService extends Service { } notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + final String title = context.getString(R.string.bugreport_finished_title); final Notification.Builder builder = new Notification.Builder(context) .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) - .setContentTitle(context.getString(R.string.bugreport_finished_title)) - .setTicker(context.getString(R.string.bugreport_finished_title)) + .setContentTitle(title) + .setTicker(title) .setContentText(context.getString(R.string.bugreport_finished_text)) .setContentIntent(PendingIntent.getActivity( context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT)) @@ -185,19 +490,19 @@ public class BugreportProgressService extends Service { .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)); - NotificationManager.from(context).notify(TAG, 0, builder.build()); + NotificationManager.from(context).notify(TAG, pid, builder.build()); } /** * Sends a zipped bugreport notification. */ private static void sendZippedBugreportNotification(final Context context, - final File bugreportFile, final File screenshotFile) { + final int pid, final File bugreportFile, final File screenshotFile) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { File zippedFile = zipBugreport(bugreportFile); - sendBugreportNotification(context, zippedFile, screenshotFile); + sendBugreportNotification(context, pid, zippedFile, screenshotFile); return null; } }.execute(); @@ -213,8 +518,8 @@ public class BugreportProgressService extends Service { Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath); File bugreportZippedFile = new File(zippedPath); try (InputStream is = new FileInputStream(bugreportFile); - ZipOutputStream zos = new ZipOutputStream( - new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) { + ZipOutputStream zos = new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) { ZipEntry entry = new ZipEntry(bugreportFile.getName()); entry.setTime(bugreportFile.lastModified()); zos.putNextEntry(entry); @@ -230,8 +535,8 @@ public class BugreportProgressService extends Service { } return bugreportZippedFile; } catch (IOException e) { - Log.e(TAG, "exception zipping file " + zippedPath, e); - return bugreportFile; // Return original. + Log.e(TAG, "exception zipping file " + zippedPath, e); + return bugreportFile; // Return original. } } @@ -281,4 +586,55 @@ public class BugreportProgressService extends Service { return null; } } + + /** + * Information about a bug report process while its in progress. + */ + private static final class BugreportInfo { + /** + * {@code pid} of the {@code dumpstate} process generating the bug report. + */ + final int pid; + + /** + * Name of the bug report, will be used to rename the final files. + *

+ * Initial value is the bug report filename reported by {@code dumpstate}, but user can + * change it later to a more meaningful name. + */ + final String name; + + /** + * Maximum progress of the bug report generation. + */ + final int max; + + /** + * Current progress of the bug report generation. + */ + int progress; + + /** + * Time of the last progress update. + */ + long lastUpdate = System.currentTimeMillis(); + + BugreportInfo(int pid, String name, int max) { + this.pid = pid; + this.name = name; + this.max = max; + } + + String getFormattedLastUpdate() { + return SimpleDateFormat.getDateTimeInstance().format(new Date(lastUpdate)); + } + + @Override + public String toString() { + final float percent = ((float) progress * 100 / max); + return String.format("Progress for %s (pid=%d): %d/%d (%2.2f%%) Last update: %s", name, + pid, progress, max, percent, + getFormattedLastUpdate()); + } + } } diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java index f1da14d57c24f..5133162a1ec9a 100644 --- a/packages/Shell/src/com/android/shell/BugreportReceiver.java +++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java @@ -17,6 +17,8 @@ package com.android.shell; import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT; +import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT; +import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED; import static com.android.shell.BugreportProgressService.getFileExtra; import java.io.File; @@ -50,13 +52,16 @@ public class BugreportReceiver extends BroadcastReceiver { // Clean up older bugreports in background cleanupOldFiles(intent); - // Delegate to service. + // Delegate intent handling to service. Intent serviceIntent = new Intent(context, BugreportProgressService.class); - serviceIntent.putExtras(intent.getExtras()); + serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent); context.startService(serviceIntent); } private void cleanupOldFiles(Intent intent) { + if (!INTENT_BUGREPORT_FINISHED.equals(intent.getAction())) { + return; + } final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); final PendingResult result = goAsync(); new AsyncTask() { diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index 1bdd9ddc874d6..33c4ef1ffbd54 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -19,7 +19,12 @@ package com.android.shell; import static android.test.MoreAsserts.assertContainsRegex; import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME; import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT; +import static com.android.shell.BugreportProgressService.EXTRA_MAX; +import static com.android.shell.BugreportProgressService.EXTRA_NAME; +import static com.android.shell.BugreportProgressService.EXTRA_PID; import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT; +import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED; +import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED; import java.io.BufferedOutputStream; import java.io.BufferedWriter; @@ -96,6 +101,33 @@ public class BugreportReceiverTest extends InstrumentationTestCase { cancelExistingNotifications(); } + public void testFullWorkflow() throws Exception { + final String name = "BUG, Y U NO REPORT?"; + // TODO: call method to remove property instead + SystemProperties.set("dumpstate.42.progress", "-1"); + + Intent intent = new Intent(INTENT_BUGREPORT_STARTED); + intent.putExtra(EXTRA_PID, 42); + intent.putExtra(EXTRA_NAME, name); + intent.putExtra(EXTRA_MAX, 1000); + mContext.sendBroadcast(intent); + + assertProgressNotification(name, "0.00%"); + + SystemProperties.set("dumpstate.42.progress", "108"); + assertProgressNotification(name, "10.80%"); + + SystemProperties.set("dumpstate.42.progress", "500"); + assertProgressNotification(name, "50.00%"); + + createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT); + createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT); + Bundle extras = sendBugreportFinishedIntent(42, PLAIN_TEXT_PATH, SCREENSHOT_PATH); + assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT); + + // TODO: assert service is down + } + public void testBugreportFinished_plainBugreportAndScreenshot() throws Exception { createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT); createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT); @@ -131,13 +163,32 @@ public class BugreportReceiverTest extends InstrumentationTestCase { } } + private void assertProgressNotification(String name, String percent) { + // TODO: it current looks for 3 distinct objects, without taking advantage of their + // relationship. + String title = mContext.getString(R.string.bugreport_in_progress_title); + Log.v(TAG, "Looking for progress notification title: '" + title+ "'"); + mUiBot.getNotification(title); + Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'"); + mUiBot.getObject(name); + mUiBot.getObject(percent); + } + /** * Sends a "bugreport finished" intent and waits for the result. * * @return extras sent to the bugreport finished consumer. */ private Bundle sendBugreportFinishedIntent(String bugreportPath, String screenshotPath) { - Intent intent = new Intent("android.intent.action.BUGREPORT_FINISHED"); + return sendBugreportFinishedIntent(null, bugreportPath, screenshotPath); + } + + private Bundle sendBugreportFinishedIntent(Integer pid, String bugreportPath, + String screenshotPath) { + Intent intent = new Intent(INTENT_BUGREPORT_FINISHED); + if (pid != null) { + intent.putExtra(EXTRA_PID, pid); + } if (bugreportPath != null) { intent.putExtra(EXTRA_BUGREPORT, bugreportPath); } diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java index f5dd31c6fdc02..fa1714efcd41d 100644 --- a/packages/Shell/tests/src/com/android/shell/UiBot.java +++ b/packages/Shell/tests/src/com/android/shell/UiBot.java @@ -42,32 +42,49 @@ final class UiBot { } /** - * Opens the system notification and clicks a given notification. + * Opens the system notification and gets a given notification. * * @param text Notificaton's text as displayed by the UI. + * @return notification object. */ - public void clickOnNotification(String text) { + public UiObject getNotification(String text) { boolean opened = mDevice.openNotification(); Log.v(TAG, "openNotification(): " + opened); boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGED)), mTimeout); assertTrue("could not get system ui (" + SYSTEMUI_PACKAGED + ")", gotIt); - gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout); - assertTrue("object with text '(" + text + "') not visible yet", gotIt); - - UiObject notification = getVisibleObject(text); + return getObject(text); + } + /** + * Opens the system notification and clicks a given notification. + * + * @param text Notificaton's text as displayed by the UI. + */ + public void clickOnNotification(String text) { + UiObject notification = getNotification(text); click(notification, "bug report notification"); } /** - * Gets an object which is guaranteed to be present in the current UI.\ + * Gets an object that might not yet be available in current UI. + * + * @param text Object's text as displayed by the UI. + */ + public UiObject getObject(String text) { + boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout); + assertTrue("object with text '(" + text + "') not visible yet", gotIt); + return getVisibleObject(text); + } + + /** + * Gets an object which is guaranteed to be present in the current UI. * * @param text Object's text as displayed by the UI. */ public UiObject getVisibleObject(String text) { UiObject uiObject = mDevice.findObject(new UiSelector().text(text)); - assertTrue("could not find object with text '(" + text + "')", uiObject.exists()); + assertTrue("could not find object with text '" + text + "'", uiObject.exists()); return uiObject; }