1 /*
2  * Copyright (C) 2014 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 com.android.systemui;
17 
18 import static org.mockito.Mockito.spy;
19 import static org.mockito.Mockito.when;
20 
21 import android.app.Instrumentation;
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.MessageQueue;
26 import android.support.test.InstrumentationRegistry;
27 import android.support.test.filters.SmallTest;
28 import android.testing.LeakCheck;
29 import android.util.Log;
30 
31 import org.junit.After;
32 import org.junit.Before;
33 import org.junit.Rule;
34 
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.Future;
37 
38 /**
39  * Base class that does System UI specific setup.
40  */
41 public abstract class SysuiTestCase {
42 
43     private static final String TAG = "SysuiTestCase";
44 
45     private Handler mHandler;
46     @Rule
47     public SysuiTestableContext mContext = new SysuiTestableContext(
48             InstrumentationRegistry.getContext(), getLeakCheck());
49     public TestableDependency mDependency = new TestableDependency(mContext);
50     private Instrumentation mRealInstrumentation;
51 
52     @Before
SysuiSetup()53     public void SysuiSetup() throws Exception {
54         System.setProperty("dexmaker.share_classloader", "true");
55         SystemUIFactory.createFromConfig(mContext);
56 
57         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
58         Instrumentation inst = spy(mRealInstrumentation);
59         when(inst.getContext()).thenThrow(new RuntimeException(
60                 "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
61         when(inst.getTargetContext()).thenThrow(new RuntimeException(
62                 "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
63         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
64     }
65 
66     @After
SysuiTeardown()67     public void SysuiTeardown() {
68         InstrumentationRegistry.registerInstance(mRealInstrumentation,
69                 InstrumentationRegistry.getArguments());
70     }
71 
getLeakCheck()72     protected LeakCheck getLeakCheck() {
73         return null;
74     }
75 
getContext()76     public Context getContext() {
77         return mContext;
78     }
79 
waitForIdleSync()80     protected void waitForIdleSync() {
81         if (mHandler == null) {
82             mHandler = new Handler(Looper.getMainLooper());
83         }
84         waitForIdleSync(mHandler);
85     }
86 
waitForUiOffloadThread()87     protected void waitForUiOffloadThread() {
88         Future<?> future = Dependency.get(UiOffloadThread.class).submit(() -> {});
89         try {
90             future.get();
91         } catch (InterruptedException | ExecutionException e) {
92             Log.e(TAG, "Failed to wait for ui offload thread.", e);
93         }
94     }
95 
waitForIdleSync(Handler h)96     public static void waitForIdleSync(Handler h) {
97         validateThread(h.getLooper());
98         Idler idler = new Idler(null);
99         h.getLooper().getQueue().addIdleHandler(idler);
100         // Ensure we are non-idle, so the idle handler can run.
101         h.post(new EmptyRunnable());
102         idler.waitForIdle();
103     }
104 
validateThread(Looper l)105     private static final void validateThread(Looper l) {
106         if (Looper.myLooper() == l) {
107             throw new RuntimeException(
108                 "This method can not be called from the looper being synced");
109         }
110     }
111 
112     public static final class EmptyRunnable implements Runnable {
run()113         public void run() {
114         }
115     }
116 
117     public static final class Idler implements MessageQueue.IdleHandler {
118         private final Runnable mCallback;
119         private boolean mIdle;
120 
Idler(Runnable callback)121         public Idler(Runnable callback) {
122             mCallback = callback;
123             mIdle = false;
124         }
125 
126         @Override
queueIdle()127         public boolean queueIdle() {
128             if (mCallback != null) {
129                 mCallback.run();
130             }
131             synchronized (this) {
132                 mIdle = true;
133                 notifyAll();
134             }
135             return false;
136         }
137 
waitForIdle()138         public void waitForIdle() {
139             synchronized (this) {
140                 while (!mIdle) {
141                     try {
142                         wait();
143                     } catch (InterruptedException e) {
144                     }
145                 }
146             }
147         }
148     }
149 }
150