Search code examples
javaandroidandroid-notificationsandroid-uiautomatorandroid-media3

How to access media-service notification controls with UiAutomator?


I'm basically starting a foreground media-service from an activity and then move the activity to Lifecycle.State.DESTROYED. ServiceTestRule is not really sufficient for testing bound services, which may require testing the bound service notification.

@RunWith(AndroidJUnit4.class)
public class MainActivityTest extends BaseActivityTestCase {

    protected ActivityScenario<MainActivity> mScenario;

    @Before
    public void setupTest() {
        this.setScenario(this.activityScenarioRule.getScenario());
        this.getScenario().onActivity(activity -> { /* ... */ });
    }

    @Test
    public void testActivityLifecycle() {

        /* Destroy the Activity. */
        this.getScenario().moveToState(Lifecycle.State.DESTROYED);

        /* TODO: Use UiAutomator to click media-controls. */
    }

    @NonNull
    protected ActivityScenario<MainActivity> getScenario() {
        return this.mScenario;
    }

    protected void setScenario(@NonNull ActivityScenario<MainActivity> scenario) {
        this.mScenario = scenario;
    }

    @Override
    public void tearDown() throws Exception {
        this.getScenario().close();
        super.tearDown();
    }
}

How to access media-service notification controls with UiAutomator?


Solution

  • I've wrote some methods, in order to get a handle to the media-service notification.
    a) This open the notification panel - and returns the notification panel ScrollView:

    static final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
    
    UiObject openNotificationPanel() {
        synchronized (mDevice) {
            if (mDevice.openNotification()) {
                mDevice.wait(Until.hasObject(By.pkg("com.android.systemui")), 10000);
                UiObject item = mDevice.findObject(new UiSelector().resourceId("com.android.systemui:id/notification_stack_scroller"));
                assertTrue(item.exists());
                return item;
            } else {
                return null;
            }
        }
    }
    

    b) This returns child-node android:id/notification_media_content from the ScrollView:

    UiObject getMediaServiceNotification(@NonNull UiObject scrollView) {
        try {
            UiObject item = scrollView
                .getChild(new UiSelector().resourceId("android:id/notification_media_content"));
            assertTrue(item.exists());
            return item;
        } catch (UiObjectNotFoundException e) {
            Log.e(LOG_TAG,"UiObjectNotFoundException: " + e.getMessage());
            return null;
        }
    }
    

    c) And this ultimately clicks the media-action button inside the notification.
    The relevant child-nodes are: mediaActions, mainColumn, mediaProgress.

    Boolean clickMediaActionAtIndex(@NonNull UiObject notification, int index) {
        try {
    
            UiObject mediaActions = notification.getChild(new UiSelector().resourceId("android:id/media_actions"));
            assertTrue(mediaActions.exists());
    
            UiObject mainColumn = notification.getChild(new UiSelector().resourceId("android:id/notification_main_column"));
            assertTrue(mainColumn.exists());
    
            UiObject mediaProgress = notification.getChild(new UiSelector().resourceId("android:id/notification_media_progress"));
            assertTrue(mediaProgress.exists());
    
            if (index < mediaActions.getChildCount()) {
                UiObject mediaAction = mediaActions.getChild(new UiSelector().className("android.widget.ImageButton").index(index));
                assertTrue(mediaAction.exists());
                mediaAction.click();
                return true;
            }
        } catch (UiObjectNotFoundException e) {
            Log.e(LOG_TAG,"UiObjectNotFoundException: " + e.getMessage());
        }
        return false;
    }
    

    And the test-case looks about alike this:

    @RunWith(AndroidJUnit4.class)
    public class MainActivityTest extends TestCase {
    
        @Test
        public void testActivityLifecycle() {
    
            /* Destroy the Activity. */
            this.getScenario().moveToState(Lifecycle.State.DESTROYED);
    
            /* UiAutomator: Media Service Notification. */
            UiObject scrollView = this.openNotificationPanel();
            UiObject notification = this.getMediaServiceNotification(scrollView);
    
            /* Click Media Action: Play/Pause. */
            assertTrue(this.clickMediaActionAtIndex(notification, 2));
        }
    }