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.autofillservice.cts.testcore;
17 
18 import android.autofillservice.cts.activities.AbstractAutoFillActivity;
19 import android.util.ArraySet;
20 import android.util.Log;
21 
22 import androidx.annotation.GuardedBy;
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 import androidx.test.platform.app.InstrumentationRegistry;
26 
27 import com.android.compatibility.common.util.TestNameUtils;
28 
29 import org.junit.rules.TestWatcher;
30 import org.junit.runner.Description;
31 
32 import java.util.Set;
33 
34 /**
35  * Custom {@link TestWatcher} that's the outer rule of all {@link AutoFillServiceTestCase} tests.
36  *
37  * <p>This class is not thread safe, but should be fine...
38  */
39 public final class AutofillTestWatcher extends TestWatcher {
40 
41     /**
42      * Cleans up all launched activities between the tests and retries.
43      */
cleanAllActivities()44     public void cleanAllActivities() {
45         try {
46             finishActivities();
47             waitUntilAllDestroyed();
48         } finally {
49             resetStaticState();
50         }
51     }
52 
53     private static final String TAG = "AutofillTestWatcher";
54 
55     @GuardedBy("sUnfinishedBusiness")
56     private static final Set<AbstractAutoFillActivity> sUnfinishedBusiness = new ArraySet<>();
57 
58     @GuardedBy("sAllActivities")
59     private static final Set<AbstractAutoFillActivity> sAllActivities = new ArraySet<>();
60 
61     @Override
starting(Description description)62     protected void starting(Description description) {
63         resetStaticState();
64         final String testName = description.getDisplayName();
65         Log.i(TAG, "Starting " + testName);
66         TestNameUtils.setCurrentTestName(testName);
67     }
68 
69     @Override
finished(Description description)70     protected void finished(Description description) {
71         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
72         final String testName = description.getDisplayName();
73         cleanAllActivities();
74         Log.i(TAG, "Finished " + testName);
75         TestNameUtils.setCurrentTestName(null);
76     }
77 
resetStaticState()78     private void resetStaticState() {
79         synchronized (sUnfinishedBusiness) {
80             sUnfinishedBusiness.clear();
81         }
82         synchronized (sAllActivities) {
83             sAllActivities.clear();
84         }
85     }
86 
87     /**
88      * Registers an activity so it's automatically finished (if necessary) after the test.
89      */
registerActivity(@onNull String where, @NonNull AbstractAutoFillActivity activity)90     public static void registerActivity(@NonNull String where,
91             @NonNull AbstractAutoFillActivity activity) {
92         synchronized (sUnfinishedBusiness) {
93             if (sUnfinishedBusiness.contains(activity)) {
94                 throw new IllegalStateException("Already registered " + activity);
95             }
96             Log.v(TAG, "registering activity on " + where + ": " + activity);
97             sUnfinishedBusiness.add(activity);
98             sAllActivities.add(activity);
99         }
100         synchronized (sAllActivities) {
101             sAllActivities.add(activity);
102 
103         }
104     }
105 
106     /**
107      * Unregisters an activity so it's not automatically finished after the test.
108      */
unregisterActivity(@onNull String where, @NonNull AbstractAutoFillActivity activity)109     public static void unregisterActivity(@NonNull String where,
110             @NonNull AbstractAutoFillActivity activity) {
111         synchronized (sUnfinishedBusiness) {
112             final boolean unregistered = sUnfinishedBusiness.remove(activity);
113             if (unregistered) {
114                 Log.d(TAG, "unregistered activity on " + where + ": " + activity);
115             } else {
116                 Log.v(TAG, "ignoring already unregistered activity on " + where + ": " + activity);
117             }
118         }
119     }
120 
121     /**
122      * Gets the instance of a previously registered activity.
123      */
124     @Nullable
getActivity(@onNull Class<A> clazz)125     public static <A extends AbstractAutoFillActivity> A getActivity(@NonNull Class<A> clazz) {
126         @SuppressWarnings("unchecked")
127         final A activity = (A) sAllActivities.stream().filter(a -> a.getClass().equals(clazz))
128                 .findFirst()
129                 .get();
130         return activity;
131     }
132 
finishActivities()133     private void finishActivities() {
134         synchronized (sUnfinishedBusiness) {
135             if (sUnfinishedBusiness.isEmpty()) {
136                 return;
137             }
138             Log.d(TAG, "Manually finishing " + sUnfinishedBusiness.size() + " activities");
139             for (AbstractAutoFillActivity activity : sUnfinishedBusiness) {
140                 if (activity.isFinishing()) {
141                     Log.v(TAG, "Ignoring activity that isFinishing(): " + activity);
142                 } else {
143                     Log.d(TAG, "Finishing activity: " + activity);
144                     activity.finishOnly();
145                 }
146             }
147         }
148     }
149 
waitUntilAllDestroyed()150     private void waitUntilAllDestroyed() {
151         synchronized (sAllActivities) {
152             if (sAllActivities.isEmpty()) return;
153 
154             Log.d(TAG, "Waiting until " + sAllActivities.size() + " activities are destroyed");
155             for (AbstractAutoFillActivity activity : sAllActivities) {
156                 Log.d(TAG, "Waiting for " + activity);
157                 try {
158                     activity.waitUntilDestroyed(Timeouts.ACTIVITY_RESURRECTION);
159                 } catch (InterruptedException e) {
160                     Log.e(TAG, "interrupted waiting for " + activity + " to be destroyed");
161                     Thread.currentThread().interrupt();
162                 }
163             }
164         }
165     }
166 }
167