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