1 /* 2 * Copyright (C) 2016 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 17 package com.android; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.mockito.Mockito.spy; 21 import static org.mockito.Mockito.doCallRealMethod; 22 import static org.mockito.Mockito.doReturn; 23 24 import android.content.ContextWrapper; 25 import android.content.res.Resources; 26 import android.os.Handler; 27 import android.os.HandlerThread; 28 import android.os.Looper; 29 import android.os.TestLooperManager; 30 import android.util.Log; 31 32 import androidx.test.InstrumentationRegistry; 33 34 import com.android.internal.telephony.GsmCdmaPhone; 35 import com.android.internal.telephony.Phone; 36 import com.android.internal.telephony.PhoneConfigurationManager; 37 import com.android.internal.telephony.PhoneFactory; 38 import com.android.internal.telephony.data.DataConfigManager; 39 import com.android.internal.telephony.data.DataNetworkController; 40 import com.android.internal.telephony.metrics.MetricsCollector; 41 import com.android.internal.telephony.metrics.PersistAtomsStorage; 42 import com.android.internal.telephony.satellite.SatelliteController; 43 import com.android.phone.PhoneGlobals; 44 import com.android.phone.PhoneInterfaceManager; 45 46 import org.junit.After; 47 import org.junit.Before; 48 import org.junit.Rule; 49 import org.mockito.Mock; 50 import org.mockito.Mockito; 51 import org.mockito.junit.MockitoJUnit; 52 import org.mockito.junit.MockitoRule; 53 54 import java.lang.reflect.Field; 55 import java.util.Collections; 56 import java.util.HashMap; 57 import java.util.Iterator; 58 import java.util.LinkedList; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.Executor; 61 import java.util.concurrent.TimeUnit; 62 63 /** 64 * Helper class to load Mockito Resources into Telephony unit tests. 65 */ 66 public class TelephonyTestBase { 67 @Rule public final MockitoRule mocks = MockitoJUnit.rule(); 68 69 protected TestContext mContext; 70 @Mock protected PhoneGlobals mPhoneGlobals; 71 @Mock protected GsmCdmaPhone mPhone; 72 @Mock protected DataNetworkController mDataNetworkController; 73 @Mock private MetricsCollector mMetricsCollector; 74 75 private HandlerThread mTestHandlerThread; 76 protected Looper mTestLooper; 77 protected TestLooperManager mLooperManager; 78 79 private final HashMap<InstanceKey, Object> mOldInstances = new HashMap<>(); 80 private final LinkedList<InstanceKey> mInstanceKeys = new LinkedList<>(); 81 82 @Before setUp()83 public void setUp() throws Exception { 84 if (Looper.myLooper() == null) { 85 Looper.prepare(); 86 } 87 88 doCallRealMethod().when(mPhoneGlobals).getBaseContext(); 89 doCallRealMethod().when(mPhoneGlobals).getResources(); 90 doCallRealMethod().when(mPhoneGlobals).getSystemService(Mockito.anyString()); 91 doCallRealMethod().when(mPhoneGlobals).getSystemService(Mockito.any(Class.class)); 92 doCallRealMethod().when(mPhoneGlobals).getSystemServiceName(Mockito.any(Class.class)); 93 doCallRealMethod().when(mPhone).getServiceState(); 94 95 mContext = spy(new TestContext()); 96 doReturn(mContext).when(mPhone).getContext(); 97 replaceInstance(ContextWrapper.class, "mBase", mPhoneGlobals, mContext); 98 99 Resources resources = InstrumentationRegistry.getTargetContext().getResources(); 100 assertNotNull(resources); 101 doReturn(resources).when(mContext).getResources(); 102 103 replaceInstance(Handler.class, "mLooper", mPhone, Looper.myLooper()); 104 replaceInstance(PhoneFactory.class, "sMadeDefaults", null, true); 105 replaceInstance(PhoneFactory.class, "sPhone", null, mPhone); 106 replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone}); 107 replaceInstance(PhoneGlobals.class, "sMe", null, mPhoneGlobals); 108 replaceInstance(PhoneFactory.class, "sMetricsCollector", null, mMetricsCollector); 109 replaceInstance(SatelliteController.class, "sInstance", null, 110 Mockito.mock(SatelliteController.class)); 111 112 doReturn(Mockito.mock(PersistAtomsStorage.class)).when(mMetricsCollector).getAtomsStorage(); 113 114 doReturn(mDataNetworkController).when(mPhone).getDataNetworkController(); 115 doReturn(Collections.emptyList()).when(mDataNetworkController) 116 .getInternetDataDisallowedReasons(); 117 doReturn(Mockito.mock(DataConfigManager.class)).when(mDataNetworkController) 118 .getDataConfigManager(); 119 120 mPhoneGlobals.phoneMgr = Mockito.mock(PhoneInterfaceManager.class); 121 } 122 123 @After tearDown()124 public void tearDown() throws Exception { 125 // Ensure there are no static references to handlers after test completes. 126 PhoneConfigurationManager.unregisterAllMultiSimConfigChangeRegistrants(); 127 cleanupTestLooper(); 128 restoreInstances(); 129 } 130 setupTestLooper()131 protected void setupTestLooper() { 132 mTestHandlerThread = new HandlerThread("TestHandlerThread"); 133 mTestHandlerThread.start(); 134 mTestLooper = mTestHandlerThread.getLooper(); 135 mLooperManager = new TestLooperManager(mTestLooper); 136 } 137 cleanupTestLooper()138 private void cleanupTestLooper() { 139 mTestLooper = null; 140 if (mLooperManager != null) { 141 mLooperManager.release(); 142 mLooperManager = null; 143 } 144 if (mTestHandlerThread != null) { 145 mTestHandlerThread.quit(); 146 try { 147 mTestHandlerThread.join(); 148 } catch (InterruptedException ex) { 149 Log.w("TelephonyTestBase", "HandlerThread join interrupted", ex); 150 } 151 mTestHandlerThread = null; 152 } 153 } 154 processOneMessage()155 protected void processOneMessage() { 156 var msg = mLooperManager.next(); 157 mLooperManager.execute(msg); 158 mLooperManager.recycle(msg); 159 } 160 processAllMessages()161 protected void processAllMessages() { 162 for (var msg = mLooperManager.poll(); msg != null && msg.getTarget() != null;) { 163 mLooperManager.execute(msg); 164 mLooperManager.recycle(msg); 165 } 166 } 167 waitForExecutorAction(Executor executor, long timeoutMillis)168 protected final boolean waitForExecutorAction(Executor executor, long timeoutMillis) { 169 final CountDownLatch lock = new CountDownLatch(1); 170 executor.execute(() -> { 171 lock.countDown(); 172 }); 173 while (lock.getCount() > 0) { 174 try { 175 return lock.await(timeoutMillis, TimeUnit.MILLISECONDS); 176 } catch (InterruptedException e) { 177 // do nothing 178 } 179 } 180 return true; 181 } 182 waitForHandlerAction(Handler h, long timeoutMillis)183 protected final void waitForHandlerAction(Handler h, long timeoutMillis) { 184 final CountDownLatch lock = new CountDownLatch(1); 185 h.post(lock::countDown); 186 while (lock.getCount() > 0) { 187 try { 188 lock.await(timeoutMillis, TimeUnit.MILLISECONDS); 189 } catch (InterruptedException e) { 190 // do nothing 191 } 192 } 193 } 194 waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs)195 protected final void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) { 196 final CountDownLatch lock = new CountDownLatch(1); 197 h.postDelayed(lock::countDown, delayMs); 198 while (lock.getCount() > 0) { 199 try { 200 lock.await(timeoutMillis, TimeUnit.MILLISECONDS); 201 } catch (InterruptedException e) { 202 // do nothing 203 } 204 } 205 } 206 waitForMs(long ms)207 protected void waitForMs(long ms) { 208 try { 209 Thread.sleep(ms); 210 } catch (InterruptedException e) { 211 Log.e("TelephonyTestBase", "InterruptedException while waiting: " + e); 212 } 213 } 214 replaceInstance(final Class c, final String instanceName, final Object obj, final Object newValue)215 protected synchronized void replaceInstance(final Class c, final String instanceName, 216 final Object obj, final Object newValue) 217 throws Exception { 218 Field field = c.getDeclaredField(instanceName); 219 field.setAccessible(true); 220 221 InstanceKey key = new InstanceKey(c, instanceName, obj); 222 if (!mOldInstances.containsKey(key)) { 223 mOldInstances.put(key, field.get(obj)); 224 mInstanceKeys.add(key); 225 } 226 field.set(obj, newValue); 227 } 228 restoreInstances()229 private synchronized void restoreInstances() throws Exception { 230 Iterator<InstanceKey> it = mInstanceKeys.descendingIterator(); 231 232 while (it.hasNext()) { 233 InstanceKey key = it.next(); 234 Field field = key.mClass.getDeclaredField(key.mInstName); 235 field.setAccessible(true); 236 field.set(key.mObj, mOldInstances.get(key)); 237 } 238 239 mInstanceKeys.clear(); 240 mOldInstances.clear(); 241 } 242 243 private static class InstanceKey { 244 public final Class mClass; 245 public final String mInstName; 246 public final Object mObj; InstanceKey(final Class c, final String instName, final Object obj)247 InstanceKey(final Class c, final String instName, final Object obj) { 248 mClass = c; 249 mInstName = instName; 250 mObj = obj; 251 } 252 253 @Override hashCode()254 public int hashCode() { 255 return (mClass.getName().hashCode() * 31 + mInstName.hashCode()) * 31; 256 } 257 258 @Override equals(Object obj)259 public boolean equals(Object obj) { 260 if (obj == null || obj.getClass() != getClass()) { 261 return false; 262 } 263 264 InstanceKey other = (InstanceKey) obj; 265 return (other.mClass == mClass && other.mInstName.equals(mInstName) 266 && other.mObj == mObj); 267 } 268 } 269 getTestContext()270 protected TestContext getTestContext() { 271 return mContext; 272 } 273 } 274