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