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.os.Handler;
23 import android.os.Looper;
24 import android.os.MessageQueue;
25 import android.os.ParcelFileDescriptor;
26 import android.testing.DexmakerShareClassLoaderRule;
27 import android.testing.LeakCheck;
28 import android.util.Log;
29 
30 import androidx.test.InstrumentationRegistry;
31 
32 import com.android.keyguard.KeyguardUpdateMonitor;
33 import com.android.systemui.util.Assert;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Rule;
38 
39 import java.io.FileInputStream;
40 import java.io.IOException;
41 import java.util.concurrent.ExecutionException;
42 import java.util.concurrent.Future;
43 
44 /**
45  * Base class that does System UI specific setup.
46  */
47 public abstract class SysuiTestCase {
48 
49     private static final String TAG = "SysuiTestCase";
50 
51     private Handler mHandler;
52     @Rule
53     public SysuiTestableContext mContext = new SysuiTestableContext(
54             InstrumentationRegistry.getContext(), getLeakCheck());
55     @Rule
56     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
57             new DexmakerShareClassLoaderRule();
58     public TestableDependency mDependency = new TestableDependency(mContext);
59     private Instrumentation mRealInstrumentation;
60 
61     @Before
SysuiSetup()62     public void SysuiSetup() throws Exception {
63         SystemUIFactory.createFromConfig(mContext);
64 
65         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
66         Instrumentation inst = spy(mRealInstrumentation);
67         when(inst.getContext()).thenAnswer(invocation -> {
68             throw new RuntimeException(
69                     "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
70         });
71         when(inst.getTargetContext()).thenAnswer(invocation -> {
72             throw new RuntimeException(
73                     "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
74         });
75         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
76         KeyguardUpdateMonitor.disableHandlerCheckForTesting(inst);
77     }
78 
79     @After
SysuiTeardown()80     public void SysuiTeardown() {
81         InstrumentationRegistry.registerInstance(mRealInstrumentation,
82                 InstrumentationRegistry.getArguments());
83         // Reset the assert's main looper.
84         Assert.sMainLooper = Looper.getMainLooper();
85     }
86 
getLeakCheck()87     protected LeakCheck getLeakCheck() {
88         return null;
89     }
90 
getContext()91     public SysuiTestableContext getContext() {
92         return mContext;
93     }
94 
runShellCommand(String command)95     protected void runShellCommand(String command) throws IOException {
96         ParcelFileDescriptor pfd = mRealInstrumentation.getUiAutomation()
97                 .executeShellCommand(command);
98 
99         // Read the input stream fully.
100         FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
101         while (fis.read() != -1);
102         fis.close();
103     }
104 
waitForIdleSync()105     protected void waitForIdleSync() {
106         if (mHandler == null) {
107             mHandler = new Handler(Looper.getMainLooper());
108         }
109         waitForIdleSync(mHandler);
110     }
111 
waitForUiOffloadThread()112     protected void waitForUiOffloadThread() {
113         Future<?> future = Dependency.get(UiOffloadThread.class).submit(() -> {});
114         try {
115             future.get();
116         } catch (InterruptedException | ExecutionException e) {
117             Log.e(TAG, "Failed to wait for ui offload thread.", e);
118         }
119     }
120 
waitForIdleSync(Handler h)121     public static void waitForIdleSync(Handler h) {
122         validateThread(h.getLooper());
123         Idler idler = new Idler(null);
124         h.getLooper().getQueue().addIdleHandler(idler);
125         // Ensure we are non-idle, so the idle handler can run.
126         h.post(new EmptyRunnable());
127         idler.waitForIdle();
128     }
129 
validateThread(Looper l)130     private static final void validateThread(Looper l) {
131         if (Looper.myLooper() == l) {
132             throw new RuntimeException(
133                 "This method can not be called from the looper being synced");
134         }
135     }
136 
137     public static final class EmptyRunnable implements Runnable {
run()138         public void run() {
139         }
140     }
141 
142     public static final class Idler implements MessageQueue.IdleHandler {
143         private final Runnable mCallback;
144         private boolean mIdle;
145 
Idler(Runnable callback)146         public Idler(Runnable callback) {
147             mCallback = callback;
148             mIdle = false;
149         }
150 
151         @Override
queueIdle()152         public boolean queueIdle() {
153             if (mCallback != null) {
154                 mCallback.run();
155             }
156             synchronized (this) {
157                 mIdle = true;
158                 notifyAll();
159             }
160             return false;
161         }
162 
waitForIdle()163         public void waitForIdle() {
164             synchronized (this) {
165                 while (!mIdle) {
166                     try {
167                         wait();
168                     } catch (InterruptedException e) {
169                     }
170                 }
171             }
172         }
173     }
174 }
175