1 /*
2  * Copyright (C) 2019 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 com.android.server.wm;
18 
19 import android.os.AsyncTask;
20 import android.os.Handler;
21 
22 import org.junit.runner.Description;
23 import org.junit.runners.model.FrameworkMethod;
24 import org.junit.runners.model.Statement;
25 
26 import java.lang.annotation.ElementType;
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.lang.annotation.Target;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.concurrent.Callable;
33 import java.util.concurrent.atomic.AtomicBoolean;
34 
35 /**
36  * Holds {@link WindowManagerGlobalLock} for test methods (including before and after) to prevent
37  * the race condition with other threads (e.g. {@link com.android.server.DisplayThread},
38  * {@link com.android.server.AnimationThread}, {@link com.android.server.UiThread}).
39  */
40 class WindowManagerGlobalLockRule implements WindowTestRunner.MethodWrapper {
41 
42     private final SystemServicesTestRule mSystemServicesTestRule;
43     private volatile boolean mIsLocked;
44 
WindowManagerGlobalLockRule(SystemServicesTestRule systemServiceTestRule)45     WindowManagerGlobalLockRule(SystemServicesTestRule systemServiceTestRule) {
46         mSystemServicesTestRule = systemServiceTestRule;
47     }
48 
49     @Override
apply(Statement base, Description description)50     public Statement apply(Statement base, Description description) {
51         if (description.getAnnotation(NoGlobalLock.class) == null) {
52             return new StatementWrapper(base);
53         }
54         return base;
55     }
56 
57     @Override
apply(FrameworkMethod base)58     public FrameworkMethod apply(FrameworkMethod base) {
59         if (base.getAnnotation(NoGlobalLock.class) == null) {
60             return new FrameworkMethodWrapper(base);
61         }
62         return base;
63     }
64 
runWithScissors(Handler handler, Runnable r, long timeout)65     boolean runWithScissors(Handler handler, Runnable r, long timeout) {
66         return waitForLocked(() -> handler.runWithScissors(r, timeout));
67     }
68 
waitForLocked(Runnable r)69     void waitForLocked(Runnable r) {
70         waitForLocked(() -> {
71             r.run();
72             return null;
73         });
74     }
75 
76     /**
77      * If the test holds the lock, we need to invoke {@link Object#wait} to release it so other
78      * threads won't be blocked when we are waiting.
79      */
waitForLocked(Callable<T> callable)80     <T> T waitForLocked(Callable<T> callable) {
81         if (!mIsLocked) {
82             try {
83                 return callable.call();
84             } catch (Exception e) {
85                 throw new RuntimeException(e);
86             }
87         }
88 
89         final Object lock = mSystemServicesTestRule.getWindowManagerService().mGlobalLock;
90         final AtomicBoolean done = new AtomicBoolean(false);
91         final List<T> result = Arrays.asList((T) null);
92         final Exception[] exception = { null };
93 
94         AsyncTask.SERIAL_EXECUTOR.execute(() -> {
95             try {
96                 result.set(0, callable.call());
97             } catch (Exception e) {
98                 exception[0] = e;
99             }
100             synchronized (lock) {
101                 lock.notifyAll();
102                 done.set(true);
103             }
104         });
105 
106         synchronized (lock) {
107             if (!done.get()) {
108                 try {
109                     lock.wait();
110                 } catch (InterruptedException impossible) {
111                 }
112             }
113         }
114         if (exception[0] != null) {
115             throw new RuntimeException(exception[0]);
116         }
117 
118         return result.get(0);
119     }
120 
121     /** Wraps methods annotated with {@link org.junit.Test}. */
122     private class StatementWrapper extends Statement {
123         final Statement mBase;
124 
StatementWrapper(Statement base)125         StatementWrapper(Statement base) {
126             mBase = base;
127         }
128 
129         @Override
evaluate()130         public void evaluate() throws Throwable {
131             try {
132                 synchronized (mSystemServicesTestRule.getWindowManagerService().mGlobalLock) {
133                     mIsLocked = true;
134                     mBase.evaluate();
135                 }
136             } finally {
137                 mIsLocked = false;
138             }
139         }
140     }
141 
142     /** Wraps methods annotated with {@link org.junit.Before} or {@link org.junit.After}. */
143     private class FrameworkMethodWrapper extends FrameworkMethod {
144 
FrameworkMethodWrapper(FrameworkMethod base)145         FrameworkMethodWrapper(FrameworkMethod base) {
146             super(base.getMethod());
147         }
148 
149         @Override
invokeExplosively(Object target, Object... params)150         public Object invokeExplosively(Object target, Object... params) throws Throwable {
151             try {
152                 synchronized (mSystemServicesTestRule.getWindowManagerService().mGlobalLock) {
153                     mIsLocked = true;
154                     return super.invokeExplosively(target, params);
155                 }
156             } finally {
157                 mIsLocked = false;
158             }
159         }
160     }
161 
162     /**
163      * If the test method is annotated with {@link NoGlobalLock}, the rule
164      * {@link WindowManagerGlobalLockRule} won't apply to the method.
165      */
166     @Target(ElementType.METHOD)
167     @Retention(RetentionPolicy.RUNTIME)
168     @interface NoGlobalLock {
reason()169         String reason() default "";
170     }
171 }
172