1 /* 2 * Copyright (C) 2017 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.autofillservice.cts; 18 19 import static android.autofillservice.cts.Helper.callbackEventAsString; 20 import static android.autofillservice.cts.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS; 21 import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT; 22 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.autofill.AutofillManager.AutofillCallback; 30 31 import com.android.compatibility.common.util.RetryableException; 32 import com.android.compatibility.common.util.Timeout; 33 34 import java.util.concurrent.BlockingQueue; 35 import java.util.concurrent.LinkedBlockingQueue; 36 import java.util.concurrent.TimeUnit; 37 38 /** 39 * Custom {@link AutofillCallback} used to recover events during tests. 40 */ 41 public final class MyAutofillCallback extends AutofillCallback { 42 43 private static final String TAG = "MyAutofillCallback"; 44 private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>(); 45 46 public static final Timeout MY_TIMEOUT = CONNECTION_TIMEOUT; 47 48 // We must handle all requests in a separate thread as the service's main thread is the also 49 // the UI thread of the test process and we don't want to hose it in case of failures here 50 private static final HandlerThread sMyThread = new HandlerThread("MyCallbackThread"); 51 private final Handler mHandler; 52 53 static { Log.i(TAG, "Starting thread " + sMyThread)54 Log.i(TAG, "Starting thread " + sMyThread); sMyThread.start()55 sMyThread.start(); 56 } 57 MyAutofillCallback()58 MyAutofillCallback() { 59 mHandler = Handler.createAsync(sMyThread.getLooper()); 60 } 61 62 @Override onAutofillEvent(View view, int event)63 public void onAutofillEvent(View view, int event) { 64 mHandler.post(() -> offer(new MyEvent(view, event))); 65 } 66 67 @Override onAutofillEvent(View view, int childId, int event)68 public void onAutofillEvent(View view, int childId, int event) { 69 mHandler.post(() -> offer(new MyEvent(view, childId, event))); 70 } 71 offer(MyEvent event)72 private void offer(MyEvent event) { 73 Log.v(TAG, "offer: " + event); 74 Helper.offer(mEvents, event, MY_TIMEOUT.ms()); 75 } 76 77 /** 78 * Gets the next available event or fail if it times out. 79 */ getEvent()80 public MyEvent getEvent() throws InterruptedException { 81 final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS); 82 if (event == null) { 83 throw new RetryableException(CONNECTION_TIMEOUT, "no event"); 84 } 85 return event; 86 } 87 88 /** 89 * Assert no more events were received. 90 */ assertNotCalled()91 public void assertNotCalled() throws InterruptedException { 92 final MyEvent event = mEvents.poll(CALLBACK_NOT_CALLED_TIMEOUT_MS, TimeUnit.MILLISECONDS); 93 if (event != null) { 94 // Not retryable. 95 throw new IllegalStateException("should not have received " + event); 96 } 97 } 98 99 /** 100 * Used to assert there is no event left behind. 101 */ assertNumberUnhandledEvents(int expected)102 public void assertNumberUnhandledEvents(int expected) { 103 assertWithMessage("Invalid number of events left: %s", mEvents).that(mEvents.size()) 104 .isEqualTo(expected); 105 } 106 107 /** 108 * Convenience method to assert an UI shown event for the given view was received. 109 */ assertUiShownEvent(View expectedView)110 public MyEvent assertUiShownEvent(View expectedView) throws InterruptedException { 111 final MyEvent event = getEvent(); 112 assertWithMessage("Invalid type on event %s", event).that(event.event) 113 .isEqualTo(EVENT_INPUT_SHOWN); 114 assertWithMessage("Invalid view on event %s", event).that(event.view) 115 .isSameAs(expectedView); 116 return event; 117 } 118 119 /** 120 * Convenience method to assert an UI shown event for the given virtual view was received. 121 */ assertUiShownEvent(View expectedView, int expectedChildId)122 public void assertUiShownEvent(View expectedView, int expectedChildId) 123 throws InterruptedException { 124 final MyEvent event = assertUiShownEvent(expectedView); 125 assertWithMessage("Invalid child on event %s", event).that(event.childId) 126 .isEqualTo(expectedChildId); 127 } 128 129 /** 130 * Convenience method to assert an UI shown event a virtual view was received. 131 * 132 * @return virtual child id 133 */ assertUiShownEventForVirtualChild(View expectedView)134 public int assertUiShownEventForVirtualChild(View expectedView) throws InterruptedException { 135 final MyEvent event = assertUiShownEvent(expectedView); 136 return event.childId; 137 } 138 139 /** 140 * Convenience method to assert an UI hidden event for the given view was received. 141 */ assertUiHiddenEvent(View expectedView)142 public MyEvent assertUiHiddenEvent(View expectedView) throws InterruptedException { 143 final MyEvent event = getEvent(); 144 assertWithMessage("Invalid type on event %s", event).that(event.event) 145 .isEqualTo(EVENT_INPUT_HIDDEN); 146 assertWithMessage("Invalid view on event %s", event).that(event.view) 147 .isSameAs(expectedView); 148 return event; 149 } 150 151 /** 152 * Convenience method to assert an UI hidden event for the given view was received. 153 */ assertUiHiddenEvent(View expectedView, int expectedChildId)154 public void assertUiHiddenEvent(View expectedView, int expectedChildId) 155 throws InterruptedException { 156 final MyEvent event = assertUiHiddenEvent(expectedView); 157 assertWithMessage("Invalid child on event %s", event).that(event.childId) 158 .isEqualTo(expectedChildId); 159 } 160 161 /** 162 * Convenience method to assert an UI unavailable event for the given view was received. 163 */ assertUiUnavailableEvent(View expectedView)164 public MyEvent assertUiUnavailableEvent(View expectedView) throws InterruptedException { 165 final MyEvent event = getEvent(); 166 assertWithMessage("Invalid type on event %s", event).that(event.event) 167 .isEqualTo(EVENT_INPUT_UNAVAILABLE); 168 assertWithMessage("Invalid view on event %s", event).that(event.view) 169 .isSameAs(expectedView); 170 return event; 171 } 172 173 /** 174 * Convenience method to assert an UI unavailable event for the given view was received. 175 */ assertUiUnavailableEvent(View expectedView, int expectedChildId)176 public void assertUiUnavailableEvent(View expectedView, int expectedChildId) 177 throws InterruptedException { 178 final MyEvent event = assertUiUnavailableEvent(expectedView); 179 assertWithMessage("Invalid child on event %s", event).that(event.childId) 180 .isEqualTo(expectedChildId); 181 } 182 183 private static final class MyEvent { 184 public final View view; 185 public final int childId; 186 public final int event; 187 MyEvent(View view, int event)188 MyEvent(View view, int event) { 189 this(view, View.NO_ID, event); 190 } 191 MyEvent(View view, int childId, int event)192 MyEvent(View view, int childId, int event) { 193 this.view = view; 194 this.childId = childId; 195 this.event = event; 196 } 197 198 @Override toString()199 public String toString() { 200 return callbackEventAsString(event) + ": " + view + " (childId: " + childId + ")"; 201 } 202 } 203 } 204