-
Notifications
You must be signed in to change notification settings - Fork 0
Injectors
Chimeras allow combining student and provided code in one class. There is a rarely used analogous feature for Android UI: injectors. They are difficult to write and their involvement probably indicates that the checkpoint structure needs to be reconsidered; I document them here only for completeness.
Conceptually, injectors are simple. They are Android libraries that provide a class (outside any package) containing a static method that will be called at the end of the student's activity setup function. The injector function must take an Object parameter for the instance of the injected activity. It can manipulate that instance e.g. to add views.
Injectors usually have several helper functions, so it can be convenient to make an instance of the class so that you don't have to pass the activity object around constantly:
@SuppressWarnings("unused")
public class Injector {
private Activity activity;
public static void inject(Object self) throws Exception {
new Injector((Activity) self).afterOnCreate();
}
private Injector(Activity setActivity) {
activity = setActivity;
}
private void afterOnCreate() throws IllegalAccessException {
// perform injection on activity
}
}The injector function can use the activity's layout inflater to inflate its own layout resources. Keep in mind that setContentView has already been called and the rest of the student onCreate may have manipulated the UI, so you probably do not want to just clobber the entire activity's UI with your layout. To transplant a view from an inflated layout resource into the activity's view, you must first call removeView to remove the provided view from your layout. You should be careful to avoid adding duplicate views if the student already added the necessary UI to their activity. If replacing an existing but potentially incomplete view hierarchy, you should use reflection to update the activity object's instance variables to refer to your provided views instead of their equivalents you replaced so that other student methods operate on the view actually in the UI. This function updates any instance variables holding a specific view:
private void remapFields(@Nullable View original, @NonNull View replaced) throws IllegalAccessException {
if (original == null) return;
for (Field f : activity.getClass().getDeclaredFields()) {
f.setAccessible(true);
if (f.get(activity) == original) {
f.set(activity, replaced);
}
}
}Just like normal library classes, the injector class is added to the app and does its work at runtime. eMPire modifies the target student class at compile time to add calls to the injector method before the injected student method returns. For example:
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_example);
if (!getIntent().hasExtra("name")) {
Injector.inject(this); // injected
return;
}
((TextView) findViewById(R.id.name)).setText(getIntent().getStringExtra("name"));
Injector.inject(this); // injected
}Segments with injectors will have both an addAars directive to add the injector class and resources to the app, and an injector directive to inject the call. The parameters to injector are the target student class name relative to excludedSrcPath, the name of the student method to inject the calls into (usually onCreate), the name of your injector class, and the name of your static injector method (default inject). Since injectors don't replace any student classes, no removeClasses directive is needed.