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