• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getFakeBroadcastDispatcher()143     protected FakeBroadcastDispatcher getFakeBroadcastDispatcher() {
144         return mFakeBroadcastDispatcher;
145     }
146 
getContext()147     public SysuiTestableContext getContext() {
148         return mContext;
149     }
150 
runShellCommand(String command)151     protected void runShellCommand(String command) throws IOException {
152         ParcelFileDescriptor pfd = mRealInstrumentation.getUiAutomation()
153                 .executeShellCommand(command);
154 
155         // Read the input stream fully.
156         FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
157         while (fis.read() != -1);
158         fis.close();
159     }
160 
waitForIdleSync()161     protected void waitForIdleSync() {
162         if (mHandler == null) {
163             mHandler = new Handler(Looper.getMainLooper());
164         }
165         waitForIdleSync(mHandler);
166     }
167 
waitForUiOffloadThread()168     protected void waitForUiOffloadThread() {
169         Future<?> future = Dependency.get(UiOffloadThread.class).execute(() -> { });
170         try {
171             future.get();
172         } catch (InterruptedException | ExecutionException e) {
173             Log.e(TAG, "Failed to wait for ui offload thread.", e);
174         }
175     }
176 
waitForIdleSync(Handler h)177     public static void waitForIdleSync(Handler h) {
178         validateThread(h.getLooper());
179         Idler idler = new Idler(null);
180         h.getLooper().getQueue().addIdleHandler(idler);
181         // Ensure we are non-idle, so the idle handler can run.
182         h.post(new EmptyRunnable());
183         idler.waitForIdle();
184     }
185 
validateThread(Looper l)186     private static final void validateThread(Looper l) {
187         if (Looper.myLooper() == l) {
188             throw new RuntimeException(
189                 "This method can not be called from the looper being synced");
190         }
191     }
192 
193     public static final class EmptyRunnable implements Runnable {
run()194         public void run() {
195         }
196     }
197 
198     public static final class Idler implements MessageQueue.IdleHandler {
199         private final Runnable mCallback;
200         private boolean mIdle;
201 
Idler(Runnable callback)202         public Idler(Runnable callback) {
203             mCallback = callback;
204             mIdle = false;
205         }
206 
207         @Override
queueIdle()208         public boolean queueIdle() {
209             if (mCallback != null) {
210                 mCallback.run();
211             }
212             synchronized (this) {
213                 mIdle = true;
214                 notifyAll();
215             }
216             return false;
217         }
218 
waitForIdle()219         public void waitForIdle() {
220             synchronized (this) {
221                 while (!mIdle) {
222                     try {
223                         wait();
224                     } catch (InterruptedException e) {
225                     }
226                 }
227             }
228         }
229     }
230 }
231