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