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