Fix BugreportReceiverTest

- Implements a local binder and #onBind in the service to return the
  service instance.
- Mocks BugreportManager in the service to avoid service interacting
  with the dumpstate.
- Fixes dialog name field did not disable after service is finished.
- Fixes screenshot did not remove if the name is empty.
- Extends screenshot delay timeout in tests.
- Fine tune the UiBot to fit new bugreport notification.
- Removes obsolete cases in the tests.

Bug: 143130523
Test: atest BugreportReceiverTest
Change-Id: Iae89206da1d08a10891503869bbbf1ce18d4e31f
This commit is contained in:
Rhed Jao
2020-06-16 12:31:32 +08:00
parent 6526999606
commit 45c7d94d7a
3 changed files with 362 additions and 409 deletions

View File

@@ -47,6 +47,7 @@ import android.content.res.Configuration;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Binder;
import android.os.BugreportManager; import android.os.BugreportManager;
import android.os.BugreportManager.BugreportCallback; import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportManager.BugreportCallback.BugreportErrorCode; import android.os.BugreportManager.BugreportCallback.BugreportErrorCode;
@@ -186,7 +187,7 @@ public class BugreportProgressService extends Service {
static final int SCREENSHOT_DELAY_SECONDS = 3; static final int SCREENSHOT_DELAY_SECONDS = 3;
/** System property where dumpstate stores last triggered bugreport id */ /** System property where dumpstate stores last triggered bugreport id */
private static final String PROPERTY_LAST_ID = "dumpstate.last_id"; static final String PROPERTY_LAST_ID = "dumpstate.last_id";
private static final String BUGREPORT_SERVICE = "bugreport"; private static final String BUGREPORT_SERVICE = "bugreport";
@@ -233,7 +234,7 @@ public class BugreportProgressService extends Service {
private File mBugreportsDir; private File mBugreportsDir;
private BugreportManager mBugreportManager; @VisibleForTesting BugreportManager mBugreportManager;
/** /**
* id of the notification used to set service on foreground. * id of the notification used to set service on foreground.
@@ -248,6 +249,11 @@ public class BugreportProgressService extends Service {
*/ */
private boolean mTakingScreenshot; private boolean mTakingScreenshot;
/**
* The delay timeout before taking a screenshot.
*/
@VisibleForTesting int mScreenshotDelaySec = SCREENSHOT_DELAY_SECONDS;
@GuardedBy("sNotificationBundle") @GuardedBy("sNotificationBundle")
private static final Bundle sNotificationBundle = new Bundle(); private static final Bundle sNotificationBundle = new Bundle();
@@ -282,6 +288,7 @@ public class BugreportProgressService extends Service {
mContext.getString(R.string.bugreport_notification_channel), mContext.getString(R.string.bugreport_notification_channel),
isTv(this) ? NotificationManager.IMPORTANCE_DEFAULT isTv(this) ? NotificationManager.IMPORTANCE_DEFAULT
: NotificationManager.IMPORTANCE_LOW)); : NotificationManager.IMPORTANCE_LOW));
mBugreportManager = mContext.getSystemService(BugreportManager.class);
} }
@Override @Override
@@ -305,7 +312,7 @@ public class BugreportProgressService extends Service {
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return null; return new LocalBinder();
} }
@Override @Override
@@ -375,6 +382,7 @@ public class BugreportProgressService extends Service {
mInfo.renameScreenshots(); mInfo.renameScreenshots();
synchronized (mLock) { synchronized (mLock) {
sendBugreportFinishedBroadcastLocked(); sendBugreportFinishedBroadcastLocked();
mMainThreadHandler.post(() -> mInfoDialog.onBugreportFinished(mInfo));
} }
} }
@@ -624,8 +632,6 @@ public class BugreportProgressService extends Service {
} }
} }
mBugreportManager = (BugreportManager) mContext.getSystemService(
Context.BUGREPORT_SERVICE);
final Executor executor = ActivityThread.currentActivityThread().getExecutor(); final Executor executor = ActivityThread.currentActivityThread().getExecutor();
Log.i(TAG, "bugreport type = " + bugreportType Log.i(TAG, "bugreport type = " + bugreportType
@@ -885,12 +891,12 @@ public class BugreportProgressService extends Service {
collapseNotificationBar(); collapseNotificationBar();
final String msg = mContext.getResources() final String msg = mContext.getResources()
.getQuantityString(com.android.internal.R.plurals.bugreport_countdown, .getQuantityString(com.android.internal.R.plurals.bugreport_countdown,
SCREENSHOT_DELAY_SECONDS, SCREENSHOT_DELAY_SECONDS); mScreenshotDelaySec, mScreenshotDelaySec);
Log.i(TAG, msg); Log.i(TAG, msg);
// Show a toast just once, otherwise it might be captured in the screenshot. // Show a toast just once, otherwise it might be captured in the screenshot.
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
takeScreenshot(id, SCREENSHOT_DELAY_SECONDS); takeScreenshot(id, mScreenshotDelaySec);
} }
/** /**
@@ -1619,6 +1625,16 @@ public class BugreportProgressService extends Service {
|| c == '_' || c == '-'; || c == '_' || c == '-';
} }
/**
* A local binder with interface to return an instance of BugreportProgressService for the
* purpose of testing.
*/
final class LocalBinder extends Binder {
@VisibleForTesting BugreportProgressService getService() {
return BugreportProgressService.this;
}
}
/** /**
* Helper class encapsulating the UI elements and logic used to display a dialog where user * Helper class encapsulating the UI elements and logic used to display a dialog where user
* can change the details of a bugreport. * can change the details of a bugreport.
@@ -1748,6 +1764,22 @@ public class BugreportProgressService extends Service {
} }
} }
/**
* Notifies the dialog that the bugreport has finished so it disables the {@code name}
* field.
* <p>Once the bugreport is finished dumpstate has already generated the final files, so
* changing the name would have no effect.
*/
void onBugreportFinished(BugreportInfo info) {
if (mId == info.id && mInfoName != null) {
mInfoName.setEnabled(false);
mInfoName.setText(null);
if (!TextUtils.isEmpty(info.getName())) {
mInfoName.setText(info.getName());
}
}
}
void cancel() { void cancel() {
if (mDialog != null) { if (mDialog != null) {
mDialog.cancel(); mDialog.cancel();
@@ -1992,12 +2024,21 @@ public class BugreportProgressService extends Service {
Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile); Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile);
bugreportFile.delete(); bugreportFile.delete();
} }
for (File file : screenshotFiles) { deleteEmptyScreenshots();
if (file.length() == 0) { }
/**
* Deletes empty screenshot files.
*/
private void deleteEmptyScreenshots() {
screenshotFiles.removeIf(file -> {
final long length = file.length();
if (length == 0) {
Log.i(TAG, "Deleting empty screenshot file: " + file); Log.i(TAG, "Deleting empty screenshot file: " + file);
file.delete(); file.delete();
} }
} return length == 0;
});
} }
/** /**
@@ -2005,7 +2046,8 @@ public class BugreportProgressService extends Service {
* {@code initialName} if user has changed it. * {@code initialName} if user has changed it.
*/ */
void renameScreenshots() { void renameScreenshots() {
if (TextUtils.isEmpty(name)) { deleteEmptyScreenshots();
if (TextUtils.isEmpty(name) || screenshotFiles.isEmpty()) {
return; return;
} }
final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size()); final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
@@ -2024,7 +2066,7 @@ public class BugreportProgressService extends Service {
if (newFile.length() > 0) { if (newFile.length() > 0) {
renamedFiles.add(newFile); renamedFiles.add(newFile);
} else if (newFile.delete()) { } else if (newFile.delete()) {
Log.d(TAG, "screenshot file: " + newFile + "deleted successfully."); Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
} }
} }
screenshotFiles = renamedFiles; screenshotFiles = renamedFiles;

View File

@@ -18,17 +18,23 @@ package com.android.shell;
import android.app.Instrumentation; import android.app.Instrumentation;
import android.app.StatusBarManager; import android.app.StatusBarManager;
import android.os.SystemClock;
import android.support.test.uiautomator.By; import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until; import android.support.test.uiautomator.Until;
import android.text.format.DateUtils;
import android.util.Log; import android.util.Log;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import java.util.List;
/** /**
* A helper class for UI-related testing tasks. * A helper class for UI-related testing tasks.
*/ */
@@ -36,6 +42,9 @@ final class UiBot {
private static final String TAG = "UiBot"; private static final String TAG = "UiBot";
private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static final String ANDROID_PACKAGE = "android";
private static final long SHORT_UI_TIMEOUT_MS = (3 * DateUtils.SECOND_IN_MILLIS);
private final Instrumentation mInstrumentation; private final Instrumentation mInstrumentation;
private final UiDevice mDevice; private final UiDevice mDevice;
@@ -48,9 +57,9 @@ final class UiBot {
} }
/** /**
* Opens the system notification and gets a given notification. * Opens the system notification and gets a UiObject with the text.
* *
* @param text Notificaton's text as displayed by the UI. * @param text Notification's text as displayed by the UI.
* @return notification object. * @return notification object.
*/ */
public UiObject getNotification(String text) { public UiObject getNotification(String text) {
@@ -62,6 +71,43 @@ final class UiBot {
return getObject(text); return getObject(text);
} }
/**
* Opens the system notification and gets a notification containing the text.
*
* @param text Notification's text as displayed by the UI.
* @return notification object.
*/
public UiObject2 getNotification2(String text) {
boolean opened = mDevice.openNotification();
Log.v(TAG, "openNotification(): " + opened);
final UiObject2 notificationScroller = mDevice.wait(Until.findObject(
By.res(SYSTEMUI_PACKAGE, "notification_stack_scroller")), mTimeout);
assertNotNull("could not get notification stack scroller", notificationScroller);
final List<UiObject2> notificationList = notificationScroller.getChildren();
for (UiObject2 notification: notificationList) {
final UiObject2 notificationText = notification.findObject(By.textContains(text));
if (notificationText != null) {
return notification;
}
}
return null;
}
/**
* Expands the notification.
*
* @param notification The notification object returned by {@link #getNotification2(String)}.
*/
public void expandNotification(UiObject2 notification) {
final UiObject2 expandBtn = notification.findObject(
By.res(ANDROID_PACKAGE, "expand_button"));
if (expandBtn.getContentDescription().equals("Collapse")) {
return;
}
expandBtn.click();
mDevice.waitForIdle();
}
public void collapseStatusBar() throws Exception { public void collapseStatusBar() throws Exception {
// TODO: mDevice should provide such method.. // TODO: mDevice should provide such method..
StatusBarManager sbm = StatusBarManager sbm =
@@ -162,6 +208,12 @@ final class UiBot {
*/ */
public void chooseActivity(String name) { public void chooseActivity(String name) {
// It uses an intent chooser now, so just getting the activity by text is enough... // It uses an intent chooser now, so just getting the activity by text is enough...
final String share = mInstrumentation.getContext().getString(
com.android.internal.R.string.share);
boolean gotIt = mDevice.wait(Until.hasObject(By.text(share)), mTimeout);
assertTrue("could not get share activity (" + share + ")", gotIt);
swipeUp();
SystemClock.sleep(SHORT_UI_TIMEOUT_MS);
UiObject activity = getObject(name); UiObject activity = getObject(name);
click(activity, name); click(activity, name);
} }
@@ -173,6 +225,11 @@ final class UiBot {
public void turnScreenOn() throws Exception { public void turnScreenOn() throws Exception {
mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP"); mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
mDevice.executeShellCommand("wm dismiss-keyguard"); mDevice.executeShellCommand("wm dismiss-keyguard");
mDevice.waitForIdle();
} }
public void swipeUp() {
mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() * 3 / 4,
mDevice.getDisplayWidth() / 2, 0, 30);
}
} }