1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.N; 4 import static android.os.Build.VERSION_CODES.O_MR1; 5 import static android.os.Build.VERSION_CODES.P; 6 import static android.os.Build.VERSION_CODES.R; 7 import static android.os.Build.VERSION_CODES.S; 8 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 9 import static org.robolectric.util.reflector.Reflector.reflector; 10 11 import android.app.Activity; 12 import android.app.ActivityThread; 13 import android.app.ActivityThread.ActivityClientRecord; 14 import android.app.Application; 15 import android.app.Instrumentation; 16 import android.app.ResultInfo; 17 import android.content.ComponentName; 18 import android.content.Intent; 19 import android.content.pm.ActivityInfo; 20 import android.content.pm.ApplicationInfo; 21 import android.content.pm.PackageManager; 22 import android.content.pm.PackageManager.ComponentInfoFlags; 23 import android.content.res.Configuration; 24 import android.os.IBinder; 25 import com.android.internal.content.ReferrerIntent; 26 import java.lang.reflect.InvocationHandler; 27 import java.lang.reflect.Method; 28 import java.lang.reflect.Proxy; 29 import java.util.Collections; 30 import java.util.List; 31 import java.util.Map; 32 import javax.annotation.Nonnull; 33 import org.robolectric.RuntimeEnvironment; 34 import org.robolectric.annotation.Implementation; 35 import org.robolectric.annotation.Implements; 36 import org.robolectric.annotation.RealObject; 37 import org.robolectric.annotation.ReflectorObject; 38 import org.robolectric.annotation.Resetter; 39 import org.robolectric.util.Logger; 40 import org.robolectric.util.ReflectionHelpers; 41 import org.robolectric.util.reflector.Accessor; 42 import org.robolectric.util.reflector.ForType; 43 import org.robolectric.util.reflector.Reflector; 44 45 @Implements(value = ActivityThread.class, isInAndroidSdk = false, looseSignatures = true) 46 public class ShadowActivityThread { 47 private static ApplicationInfo applicationInfo; 48 @RealObject protected ActivityThread realActivityThread; 49 @ReflectorObject protected _ActivityThread_ activityThreadReflector; 50 51 @Implementation getPackageManager()52 public static Object getPackageManager() { 53 ClassLoader classLoader = ShadowActivityThread.class.getClassLoader(); 54 Class<?> iPackageManagerClass; 55 try { 56 iPackageManagerClass = classLoader.loadClass("android.content.pm.IPackageManager"); 57 } catch (ClassNotFoundException e) { 58 throw new RuntimeException(e); 59 } 60 return Proxy.newProxyInstance( 61 classLoader, 62 new Class[] {iPackageManagerClass}, 63 new InvocationHandler() { 64 @Override 65 public Object invoke(Object proxy, @Nonnull Method method, Object[] args) 66 throws Exception { 67 if (method.getName().equals("getApplicationInfo")) { 68 String packageName = (String) args[0]; 69 int flags = ((Number) args[1]).intValue(); 70 if (packageName.equals(ShadowActivityThread.applicationInfo.packageName)) { 71 return ShadowActivityThread.applicationInfo; 72 } 73 74 try { 75 return RuntimeEnvironment.getApplication() 76 .getPackageManager() 77 .getApplicationInfo(packageName, flags); 78 } catch (PackageManager.NameNotFoundException e) { 79 return null; 80 } 81 } else if (method.getName().equals("notifyPackageUse")) { 82 return null; 83 } else if (method.getName().equals("getPackageInstaller")) { 84 try { 85 Class<?> iPackageInstallerClass = 86 classLoader.loadClass("android.content.pm.IPackageInstaller"); 87 return ReflectionHelpers.createNullProxy(iPackageInstallerClass); 88 } catch (ClassNotFoundException e) { 89 throw new RuntimeException(e); 90 } 91 } else if (method.getName().equals("hasSystemFeature")) { 92 String featureName = (String) args[0]; 93 return RuntimeEnvironment.getApplication() 94 .getPackageManager() 95 .hasSystemFeature(featureName); 96 } else if (method.getName().equals("getServiceInfo")) { 97 ComponentName componentName = (ComponentName) args[0]; 98 if (args[1] instanceof ComponentInfoFlags) { 99 return RuntimeEnvironment.getApplication() 100 .getPackageManager() 101 .getServiceInfo(componentName, (ComponentInfoFlags) args[1]); 102 } else { 103 return RuntimeEnvironment.getApplication() 104 .getPackageManager() 105 .getServiceInfo(componentName, ((Number) args[1]).intValue()); 106 } 107 } 108 throw new UnsupportedOperationException("sorry, not supporting " + method + " yet!"); 109 } 110 }); 111 } 112 113 @Implementation 114 public static Object currentActivityThread() { 115 return RuntimeEnvironment.getActivityThread(); 116 } 117 118 @Implementation 119 protected static Application currentApplication() { 120 return ((ActivityThread) currentActivityThread()).getApplication(); 121 } 122 123 @Implementation 124 protected Application getApplication() { 125 // Prefer the stored application from the real Activity Thread. 126 Application currentApplication = 127 Reflector.reflector(_ActivityThread_.class, realActivityThread).getInitialApplication(); 128 if (currentApplication == null) { 129 return RuntimeEnvironment.getApplication(); 130 } else { 131 return currentApplication; 132 } 133 } 134 135 @Implementation(minSdk = R) 136 public static Object getPermissionManager() { 137 ClassLoader classLoader = ShadowActivityThread.class.getClassLoader(); 138 Class<?> iPermissionManagerClass; 139 try { 140 iPermissionManagerClass = classLoader.loadClass("android.permission.IPermissionManager"); 141 } catch (ClassNotFoundException e) { 142 throw new RuntimeException(e); 143 } 144 return Proxy.newProxyInstance( 145 classLoader, 146 new Class<?>[] {iPermissionManagerClass}, 147 new InvocationHandler() { 148 @Override 149 public Object invoke(Object proxy, @Nonnull Method method, Object[] args) 150 throws Exception { 151 if (method.getName().equals("getSplitPermissions")) { 152 return Collections.emptyList(); 153 } 154 return method.getDefaultValue(); 155 } 156 }); 157 } 158 159 // Override this method as it's used directly by reflection by androidx ActivityRecreator. 160 @Implementation(minSdk = N, maxSdk = O_MR1) 161 protected void requestRelaunchActivity( 162 IBinder token, 163 List<ResultInfo> pendingResults, 164 List<ReferrerIntent> pendingNewIntents, 165 int configChanges, 166 boolean notResumed, 167 Configuration config, 168 Configuration overrideConfig, 169 boolean fromServer, 170 boolean preserveWindow) { 171 ActivityClientRecord record = activityThreadReflector.getActivities().get(token); 172 if (record != null) { 173 reflector(ActivityClientRecordReflector.class, record).getActivity().recreate(); 174 } 175 } 176 177 /** Update's ActivityThread's list of active Activities */ 178 void registerActivityLaunch( 179 Intent intent, ActivityInfo activityInfo, Activity activity, IBinder token) { 180 ActivityClientRecord record; 181 if (RuntimeEnvironment.getApiLevel() >= P) { 182 record = new ActivityClientRecord(); 183 } else { 184 record = ReflectionHelpers.callConstructor(ActivityClientRecord.class); 185 } 186 ActivityClientRecordReflector recordReflector = 187 reflector(ActivityClientRecordReflector.class, record); 188 recordReflector.setToken(token); 189 recordReflector.setIntent(intent); 190 recordReflector.setActivityInfo(activityInfo); 191 recordReflector.setActivity(activity); 192 reflector(_ActivityThread_.class, realActivityThread).getActivities().put(token, record); 193 } 194 195 void removeActivity(IBinder token) { 196 reflector(_ActivityThread_.class, realActivityThread).getActivities().remove(token); 197 } 198 199 /** 200 * Internal use only. 201 * 202 * @deprecated do not use 203 */ 204 @Deprecated 205 public static void setApplicationInfo(ApplicationInfo applicationInfo) { 206 ShadowActivityThread.applicationInfo = applicationInfo; 207 } 208 209 static ApplicationInfo getApplicationInfo() { 210 return applicationInfo; 211 } 212 213 /** 214 * internal, do not use 215 * 216 * @param androidConfiguration 217 */ 218 public void setCompatConfiguration(Configuration androidConfiguration) { 219 if (RuntimeEnvironment.getApiLevel() >= S) { 220 // Setting compat configuration was refactored in android S 221 // use reflection to create package private classes 222 Class<?> activityThreadInternalClass = 223 ReflectionHelpers.loadClass( 224 getClass().getClassLoader(), "android.app.ActivityThreadInternal"); 225 Class<?> configurationControllerClass = 226 ReflectionHelpers.loadClass( 227 getClass().getClassLoader(), "android.app.ConfigurationController"); 228 Object configController = 229 ReflectionHelpers.callConstructor( 230 configurationControllerClass, from(activityThreadInternalClass, realActivityThread)); 231 ReflectionHelpers.callInstanceMethod( 232 configController, 233 "setCompatConfiguration", 234 from(Configuration.class, androidConfiguration)); 235 androidConfiguration = 236 ReflectionHelpers.callInstanceMethod(configController, "getCompatConfiguration"); 237 ReflectionHelpers.setField(realActivityThread, "mConfigurationController", configController); 238 } else { 239 reflector(_ActivityThread_.class, realActivityThread) 240 .setCompatConfiguration(androidConfiguration); 241 } 242 } 243 244 /** Accessor interface for {@link ActivityThread}'s internals. */ 245 @ForType(ActivityThread.class) 246 public interface _ActivityThread_ { 247 248 @Accessor("mBoundApplication") 249 void setBoundApplication(Object data); 250 251 @Accessor("mBoundApplication") 252 Object getBoundApplication(); 253 254 @Accessor("mCompatConfiguration") 255 void setCompatConfiguration(Configuration configuration); 256 257 @Accessor("mInitialApplication") 258 void setInitialApplication(Application application); 259 260 /** internal use only. Tests should use {@link ActivityThread.getApplication} */ 261 @Accessor("mInitialApplication") 262 Application getInitialApplication(); 263 264 @Accessor("mInstrumentation") 265 void setInstrumentation(Instrumentation instrumentation); 266 267 @Accessor("mActivities") 268 Map<IBinder, ActivityClientRecord> getActivities(); 269 } 270 271 /** Accessor interface for {@link ActivityThread.AppBindData}'s internals. */ 272 @ForType(className = "android.app.ActivityThread$AppBindData") 273 public interface _AppBindData_ { 274 275 @Accessor("appInfo") 276 void setAppInfo(ApplicationInfo applicationInfo); 277 278 @Accessor("processName") 279 void setProcessName(String name); 280 } 281 282 @ForType(ActivityClientRecord.class) 283 private interface ActivityClientRecordReflector { 284 @Accessor("activity") 285 void setActivity(Activity activity); 286 287 @Accessor("activity") 288 Activity getActivity(); 289 290 @Accessor("token") 291 void setToken(IBinder token); 292 293 @Accessor("intent") 294 void setIntent(Intent intent); 295 296 @Accessor("activityInfo") 297 void setActivityInfo(ActivityInfo activityInfo); 298 } 299 300 @Resetter 301 public static void reset() { 302 Object activityThread = RuntimeEnvironment.getActivityThread(); 303 if (activityThread == null) { 304 Logger.warn( 305 "RuntimeEnvironment.getActivityThread() is null, an error likely occurred during test" 306 + " initialization."); 307 } else { 308 reflector(_ActivityThread_.class, activityThread).getActivities().clear(); 309 } 310 } 311 } 312