Track deletion is handled by a service that shows a progress bar on the notification bar. Fixes #735.

This commit is contained in:
Román Martínez
2021-05-10 18:23:00 +02:00
parent 6b0ec72b9b
commit 93f203bcf1
11 changed files with 601 additions and 142 deletions
@@ -0,0 +1,285 @@
package de.dennisguse.opentracks;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ListView;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.espresso.assertion.ViewAssertions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.services.TrackDeleteService;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoDeleteTrackTest {
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant("android.permission.ACCESS_FINE_LOCATION");
private MyIdlingResource idlingResource;
@Before
public void registerIntentServiceIdlingResource() {
Instrumentation instrumentation
= InstrumentationRegistry.getInstrumentation();
idlingResource = new MyIdlingResource(instrumentation.getTargetContext());
Espresso.registerIdlingResources(idlingResource);
}
@After
public void unregisterIntentServiceIdlingResource() {
Espresso.unregisterIdlingResources(idlingResource);
}
@Test
public void espressoDeleteTrackTest() {
int countBefore;
{
// TrackListActivity: start recording
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
trackControllerRecordButton.perform(click());
}
{
// TrackRecordingActivity
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
// wait; stay recording
trackControllerRecordButton.perform(waitFor(5000));
// stop;
trackControllerStopButton.perform(veryLongTouch(1600));
}
// back
pressBack();
// get number of items before deleting a track
countBefore = numberOfItemsListView();
// select track
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(5000));
// open menu and delete selected track
ViewInteraction overflowMenuButton = onView(
allOf(withContentDescription("More options"),
childAtPosition(
childAtPosition(
withId(R.id.toolbar),
2),
2),
isDisplayed()));
overflowMenuButton.perform(click());
ViewInteraction appCompatTextView = onView(
allOf(withId(R.id.title), withText("Delete"),
childAtPosition(
childAtPosition(
withId(R.id.content),
0),
0),
isDisplayed()));
appCompatTextView.perform(click());
ViewInteraction appCompatButton = onView(
allOf(withId(android.R.id.button1), withText("Yes"),
childAtPosition(
childAtPosition(
withId(R.id.buttonPanel),
0),
3)));
appCompatButton.perform(scrollTo(), click());
// check number of items after delete
onView(withId(R.id.track_list)).check(ViewAssertions.matches(withListSize(countBefore - 1)));
}
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
public static Matcher<View> withListSize (final int size) {
return new TypeSafeMatcher<View> () {
@Override public boolean matchesSafely (final View view) {
return ((ListView) view).getCount () == size;
}
@Override public void describeTo (final Description description) {
description.appendText ("ListView should have " + size + " items");
}
};
}
private static ViewAction waitFor(final long duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Wait for milliseconds.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
uiController.loopMainThreadForAtLeast(duration_ms);
}
};
}
private static ViewAction veryLongTouch(final int duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Perform long touch.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
// Get view absolute position
int[] location = new int[2];
view.getLocationOnScreen(location);
// Offset coordinates by view position
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
// Send down event, pause, and send up
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
uiController.loopMainThreadForAtLeast(duration_ms);
MotionEvents.sendUp(uiController, down, coordinates);
}
};
}
private int numberOfItemsListView() {
final int[] counts = new int[1];
onView(withId(R.id.track_list)).check(matches(new TypeSafeMatcher<View>() {
@Override
public boolean matchesSafely(View view) {
ListView listView = (ListView) view;
counts[0] = listView.getCount();
return true;
}
@Override
public void describeTo(Description description) {
}
}));
return counts[0];
}
private class MyIdlingResource implements IdlingResource {
private Context context;
private ResourceCallback resourceCallback;
public MyIdlingResource(Context context) {
this.context = context;
}
@Override
public String getName() {
return MyIdlingResource.class.getName();
}
@Override
public void registerIdleTransitionCallback(IdlingResource.ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
@Override
public boolean isIdleNow() {
boolean idle = !isDeleteServiceRunning();
if (idle && resourceCallback != null) {
resourceCallback.onTransitionToIdle();
}
return idle;
}
private boolean isDeleteServiceRunning() {
ActivityManager manager =
(ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo info :
manager.getRunningServices(Integer.MAX_VALUE)) {
if (TrackDeleteService.class.getName().equals(
info.service.getClassName())) {
return true;
}
}
return false;
}
}
}
@@ -19,10 +19,13 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.anything;
@LargeTest
@RunWith(AndroidJUnit4.class)
@@ -97,6 +100,13 @@ public class EspressoUITest {
}
}
@LargeTest
@Test
public void selectAndDeleteTrack() {
onView(withId(R.id.track_list)).check(matches(isDisplayed()));
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(2000));
}
private static ViewAction veryLongTouch(final int duration_ms) {
return new ViewAction() {
@Override