1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.accessibilityservice.cts; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.fail; 22 23 import android.accessibility.cts.common.InstrumentedAccessibilityService; 24 import android.accessibilityservice.AccessibilityServiceInfo; 25 import android.app.UiAutomation; 26 import android.os.SystemClock; 27 import android.view.MotionEvent; 28 29 import androidx.annotation.NonNull; 30 31 import com.android.compatibility.common.util.TestUtils; 32 33 import java.util.concurrent.atomic.AtomicBoolean; 34 import java.util.function.Consumer; 35 36 public class StubMotionInterceptingAccessibilityService extends InstrumentedAccessibilityService { 37 private Consumer<MotionEvent> mMotionEventListener; 38 setMotionEventSources(int sources)39 public void setMotionEventSources(int sources) { 40 AccessibilityServiceInfo info = getServiceInfo(); 41 info.setMotionEventSources(sources); 42 setServiceInfo(info); 43 } 44 45 /** Sets the motion event sources to intercept but not consume events from. */ setObservedMotionEventSources(int sources)46 public void setObservedMotionEventSources(int sources) { 47 AccessibilityServiceInfo info = getServiceInfo(); 48 info.setObservedMotionEventSources(sources); 49 setServiceInfo(info); 50 } 51 52 /** 53 * Calls {@link AccessibilityServiceInfo#setMotionEventSources} and awaits confirmation 54 * that the input filter has been updated. 55 * 56 * <p> 57 * The AccessibilityInputFilter is updated asynchronously after the A11yService 58 * requests its list of interested motion event sources. For normal use this brief 59 * delay is inconsequential, but for testing we need a way to know when the filter 60 * is actually updated so that we can then inject a test event. 61 * 62 * <p> 63 * There are no public APIs to inspect the current InputFilter flags, so instead 64 * we send canary event(s) of type {@code canarySource} until we observe at least 65 * one. After a canary is observed, we know that the filter is installed, so tests 66 * can then safely send and await an event from {@code interestedSource}. 67 * 68 * @param canarySource The source that is only used as a canary. 69 * @param interestedSource The source (different from canary) that is expected by the test. 70 */ setAndAwaitMotionEventSources(UiAutomation uiAutomation, int canarySource, int interestedSource, long timeoutMs)71 public void setAndAwaitMotionEventSources(UiAutomation uiAutomation, int canarySource, 72 int interestedSource, long timeoutMs) { 73 assertThat(canarySource).isNotEqualTo(interestedSource); 74 final int requestedSources = canarySource | interestedSource; 75 AccessibilityServiceInfo info = getServiceInfo(); 76 info.setMotionEventSources(requestedSources); 77 setServiceInfo(info); 78 assertThat(getServiceInfo().getMotionEventSources()).isEqualTo(requestedSources); 79 final Object waitObject = new Object(); 80 final AtomicBoolean foundCanaryEvent = new AtomicBoolean(false); 81 mMotionEventListener = motionEvent -> { 82 synchronized (waitObject) { 83 if (motionEvent.getSource() == canarySource) { 84 foundCanaryEvent.set(true); 85 } 86 waitObject.notifyAll(); 87 } 88 }; 89 90 // Wait for the canary to signal that the filter has been updated. 91 final int maxAttempts = 3; 92 final String errorMessage = "Expected canary event from source " + canarySource; 93 for (int attempt = 0; attempt < maxAttempts; attempt++) { 94 uiAutomation.injectInputEventToInputFilter(createMotionEvent(canarySource)); 95 try { 96 TestUtils.waitOn(waitObject, foundCanaryEvent::get, 97 timeoutMs, errorMessage); 98 return; 99 } catch (AssertionError ignored) { 100 // retry 101 } 102 } 103 fail(errorMessage); 104 } 105 106 /** 107 * Injects an event to the AccessibilityInputFilter then awaits that the event 108 * is seen by {@link #onMotionEvent}. 109 */ injectAndAwaitMotionEvent(UiAutomation uiAutomation, int source, long timeoutMs)110 public void injectAndAwaitMotionEvent(UiAutomation uiAutomation, int source, long timeoutMs) { 111 final Object waitObject = new Object(); 112 AtomicBoolean gotEvent = new AtomicBoolean(false); 113 mMotionEventListener = motionEvent -> { 114 synchronized (waitObject) { 115 if (motionEvent.getSource() == source) { 116 gotEvent.set(true); 117 } 118 waitObject.notifyAll(); 119 } 120 }; 121 uiAutomation.injectInputEventToInputFilter(createMotionEvent(source)); 122 TestUtils.waitOn(waitObject, gotEvent::get, timeoutMs, 123 "Expected single event from source " + source); 124 } 125 setOnMotionEventListener(Consumer<MotionEvent> listener)126 public void setOnMotionEventListener(Consumer<MotionEvent> listener) { 127 mMotionEventListener = listener; 128 } 129 130 @Override onMotionEvent(@onNull MotionEvent event)131 public void onMotionEvent(@NonNull MotionEvent event) { 132 super.onMotionEvent(event); 133 mMotionEventListener.accept(event); 134 } 135 createMotionEvent(int source)136 private MotionEvent createMotionEvent(int source) { 137 // Only source is used by these tests, so set other properties to valid defaults. 138 final long eventTime = SystemClock.uptimeMillis(); 139 final MotionEvent.PointerProperties props = new MotionEvent.PointerProperties(); 140 props.id = 0; 141 return MotionEvent.obtain(eventTime, 142 eventTime, 143 MotionEvent.ACTION_MOVE, 144 1 /* pointerCount */, 145 new MotionEvent.PointerProperties[]{props}, 146 new MotionEvent.PointerCoords[]{new MotionEvent.PointerCoords()}, 147 0 /* metaState */, 148 0 /* buttonState */, 149 0 /* xPrecision */, 150 0 /* yPrecision */, 151 1 /* deviceId */, 152 0 /* edgeFlags */, 153 source, 154 0 /* flags */); 155 } 156 } 157