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