1 /*
2  * Copyright (C) 2018 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 package android.view.autofill;
17 
18 import android.view.View;
19 import android.view.autofill.AutofillManager.AutofillCallback;
20 
21 import androidx.annotation.NonNull;
22 
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.concurrent.BlockingQueue;
26 import java.util.concurrent.LinkedBlockingQueue;
27 import java.util.concurrent.Semaphore;
28 import java.util.concurrent.TimeUnit;
29 
30 import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_HIDDEN;
31 import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
32 import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_UNAVAILABLE;
33 
34 import android.os.CancellationSignal;
35 import android.service.autofill.FillCallback;
36 import android.service.autofill.FillRequest;
37 import android.util.Log;
38 
39 /**
40  * Custom {@link AutofillCallback} used to recover events during tests.
41  */
42 public final class MyAutofillCallback extends AutofillCallback {
43 
44     private static final String TAG = "MyAutofillCallback";
45     private static final int TIMEOUT_MS = 5000;
46 
47     private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>(2);
48     private final List<String> mAsyncErrors = new ArrayList<>();
49 
50     @Override
onAutofillEvent(View view, int event)51     public void onAutofillEvent(View view, int event) {
52         boolean offered = false;
53         try {
54             offered = mEvents.offer(new MyEvent(view, event), TIMEOUT_MS, TimeUnit.MILLISECONDS);
55         } catch (InterruptedException e) {
56             Thread.currentThread().interrupt();
57         }
58         if (!offered) {
59             String error = "could not offer " + toString(view, event) + " in " + TIMEOUT_MS + "ms";
60             Log.e(TAG, error);
61             mAsyncErrors.add(error);
62         }
63     }
64 
65     /**
66      * Asserts the callback is called for the given view and event, or fail if it times out.
67      */
expectEvent(@onNull View view, int event)68     public void expectEvent(@NonNull View view, int event) {
69         MyEvent myEvent;
70         try {
71             myEvent = mEvents.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
72             if (myEvent == null) {
73                 throw new IllegalStateException("no event received in " + TIMEOUT_MS
74                         + "ms while waiting for " + toString(view, event));
75             }
76         } catch (InterruptedException e) {
77             Thread.currentThread().interrupt();
78             throw new IllegalStateException("interrupted waiting for " + toString(view, event));
79         }
80         if (!myEvent.view.equals(view) || myEvent.event != event) {
81             throw new AssertionError("Invalid event: expected " + myEvent + ", got "
82                     + toString(view, event));
83         }
84     }
85 
86     /**
87      * Throws an exception if an error happened asynchronously while handing
88      * {@link #onAutofillEvent(View, int)}.
89      */
assertNoAsyncErrors()90     public void assertNoAsyncErrors() {
91        if (!mAsyncErrors.isEmpty()) {
92            throw new IllegalStateException(mAsyncErrors.size() + " errors: " + mAsyncErrors);
93        }
94     }
95 
eventToString(int event)96     private static String eventToString(int event) {
97         switch (event) {
98             case EVENT_INPUT_HIDDEN:
99                 return "HIDDEN";
100             case EVENT_INPUT_SHOWN:
101                 return "SHOWN";
102             case EVENT_INPUT_UNAVAILABLE:
103                 return "UNAVAILABLE";
104             default:
105                 throw new IllegalArgumentException("invalid event: " + event);
106         }
107     }
108 
toString(View view, int event)109     private static String toString(View view, int event) {
110         return eventToString(event) + ": " + view + ")";
111     }
112 
113     private static final class MyEvent {
114         public final View view;
115         public final int event;
116 
MyEvent(View view, int event)117         MyEvent(View view, int event) {
118             this.view = view;
119             this.event = event;
120         }
121 
122         @Override
toString()123         public String toString() {
124             return MyAutofillCallback.toString(view, event);
125         }
126     }
127 }
128