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.mock;
19 import static org.mockito.Mockito.spy;
20 import static org.mockito.Mockito.when;
21 
22 import android.app.Instrumentation;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.MessageQueue;
26 import android.os.ParcelFileDescriptor;
27 import android.testing.DexmakerShareClassLoaderRule;
28 import android.testing.LeakCheck;
29 import android.testing.TestableLooper;
30 import android.util.Log;
31 
32 import androidx.test.InstrumentationRegistry;
33 
34 import com.android.keyguard.KeyguardUpdateMonitor;
35 import com.android.settingslib.bluetooth.LocalBluetoothManager;
36 import com.android.systemui.broadcast.BroadcastDispatcher;
37 import com.android.systemui.broadcast.FakeBroadcastDispatcher;
38 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
39 import com.android.systemui.classifier.FalsingManagerFake;
40 import com.android.systemui.dump.DumpManager;
41 import com.android.systemui.plugins.FalsingManager;
42 import com.android.systemui.statusbar.SmartReplyController;
43 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
44 
45 import org.junit.After;
46 import org.junit.Before;
47 import org.junit.Rule;
48 
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.util.concurrent.ExecutionException;
52 import java.util.concurrent.Executor;
53 import java.util.concurrent.Future;
54 
55 /**
56  * Base class that does System UI specific setup.
57  */
58 public abstract class SysuiTestCase {
59 
60     private static final String TAG = "SysuiTestCase";
61 
62     private Handler mHandler;
63     @Rule
64     public SysuiTestableContext mContext = new SysuiTestableContext(
65             InstrumentationRegistry.getContext(), getLeakCheck());
66     @Rule
67     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
68             new DexmakerShareClassLoaderRule();
69     public TestableDependency mDependency;
70     private Instrumentation mRealInstrumentation;
71     private FakeBroadcastDispatcher mFakeBroadcastDispatcher;
72 
73     @Before
SysuiSetup()74     public void SysuiSetup() throws Exception {
75         SystemUIFactory.createFromConfig(mContext);
76         mDependency = new TestableDependency(mContext);
77         mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
78                 mock(Executor.class), mock(DumpManager.class),
79                 mock(BroadcastDispatcherLogger.class));
80 
81         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
82         Instrumentation inst = spy(mRealInstrumentation);
83         when(inst.getContext()).thenAnswer(invocation -> {
84             throw new RuntimeException(
85                     "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
86         });
87         when(inst.getTargetContext()).thenAnswer(invocation -> {
88             throw new RuntimeException(
89                     "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
90         });
91         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
92         // Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will
93         // record receivers registered. They are not actually leaked as they are kept just as a weak
94         // reference and are never sent to the Context. This will also prevent a real
95         // BroadcastDispatcher from actually registering receivers.
96         mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher);
97         // A lot of tests get the FalsingManager, often via several layers of indirection.
98         // None of them actually need it.
99         mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake());
100         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
101 
102         // A lot of tests get the LocalBluetoothManager, often via several layers of indirection.
103         // None of them actually need it.
104         mDependency.injectMockDependency(LocalBluetoothManager.class);
105 
106         // Notifications tests are injecting one of these, causing many classes (including
107         // KeyguardUpdateMonitor to be created (injected).
108         // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
109         mDependency.injectMockDependency(SmartReplyController.class);
110         mDependency.injectMockDependency(NotificationBlockingHelperManager.class);
111     }
112 
113     @After
SysuiTeardown()114     public void SysuiTeardown() {
115         InstrumentationRegistry.registerInstance(mRealInstrumentation,
116                 InstrumentationRegistry.getArguments());
117         if (TestableLooper.get(this) != null) {
118             TestableLooper.get(this).processAllMessages();
119         }
120         disallowTestableLooperAsMainThread();
121         SystemUIFactory.cleanup();
122         mContext.cleanUpReceivers(this.getClass().getSimpleName());
123         mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName());
124     }
125 
126     /**
127      * Tests are run on the TestableLooper; however, there are parts of SystemUI that assert that
128      * the code is run from the main looper. Therefore, we allow the TestableLooper to pass these
129      * assertions since in a test, the TestableLooper is essentially the MainLooper.
130      */
allowTestableLooperAsMainThread()131     protected void allowTestableLooperAsMainThread() {
132         com.android.systemui.util.Assert.setTestableLooper(TestableLooper.get(this).getLooper());
133     }
134 
disallowTestableLooperAsMainThread()135     protected void disallowTestableLooperAsMainThread() {
136         com.android.systemui.util.Assert.setTestableLooper(null);
137     }
138 
getLeakCheck()139     protected LeakCheck getLeakCheck() {
140         return null;
141     }
142 
getContext()143     public SysuiTestableContext getContext() {
144         return mContext;
145     }
146 
runShellCommand(String command)147     protected void runShellCommand(String command) throws IOException {
148         ParcelFileDescriptor pfd = mRealInstrumentation.getUiAutomation()
149                 .executeShellCommand(command);
150 
151         // Read the input stream fully.
152         FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
153         while (fis.read() != -1);
154         fis.close();
155     }
156 
waitForIdleSync()157     protected void waitForIdleSync() {
158         if (mHandler == null) {
159             mHandler = new Handler(Looper.getMainLooper());
160         }
161         waitForIdleSync(mHandler);
162     }
163 
waitForUiOffloadThread()164     protected void waitForUiOffloadThread() {
165         Future<?> future = Dependency.get(UiOffloadThread.class).execute(() -> { });
166         try {
167             future.get();
168         } catch (InterruptedException | ExecutionException e) {
169             Log.e(TAG, "Failed to wait for ui offload thread.", e);
170         }
171     }
172 
waitForIdleSync(Handler h)173     public static void waitForIdleSync(Handler h) {
174         validateThread(h.getLooper());
175         Idler idler = new Idler(null);
176         h.getLooper().getQueue().addIdleHandler(idler);
177         // Ensure we are non-idle, so the idle handler can run.
178         h.post(new EmptyRunnable());
179         idler.waitForIdle();
180     }
181 
validateThread(Looper l)182     private static final void validateThread(Looper l) {
183         if (Looper.myLooper() == l) {
184             throw new RuntimeException(
185                 "This method can not be called from the looper being synced");
186         }
187     }
188 
189     public static final class EmptyRunnable implements Runnable {
run()190         public void run() {
191         }
192     }
193 
194     public static final class Idler implements MessageQueue.IdleHandler {
195         private final Runnable mCallback;
196         private boolean mIdle;
197 
Idler(Runnable callback)198         public Idler(Runnable callback) {
199             mCallback = callback;
200             mIdle = false;
201         }
202 
203         @Override
queueIdle()204         public boolean queueIdle() {
205             if (mCallback != null) {
206                 mCallback.run();
207             }
208             synchronized (this) {
209                 mIdle = true;
210                 notifyAll();
211             }
212             return false;
213         }
214 
waitForIdle()215         public void waitForIdle() {
216             synchronized (this) {
217                 while (!mIdle) {
218                     try {
219                         wait();
220                     } catch (InterruptedException e) {
221                     }
222                 }
223             }
224         }
225     }
226 }
227