Reflection is one of the many aces up the sleeve when it comes to taking apart Android applications and getting them running in a state that
suits you. To put it simply reflection is an API that can be used to access, examine, and modify objects at runtime – this includes fields, methods,
classes, and interfaces. 

Some examples of components reflection can be used on when working from Java (or Kotlin) include:

  • Classes – A class is a blueprint/template where when used individual objects can be created from them.
  • Methods – A method is a segment of code, with a specific purpose, which is run when called and can be part of a class or standalone.
  • Constructors – A constructor is a special type of method that is used as part of the initialization of an object (e.g., a class) to set variables and call methods.
  • Interfaces – An interface is an abstract class that contains a collection of methods with empty bodies.

Class Loading

Java Class Loaders are a component of the Java Runtime Environment (JRE) which load Java classes into a Java Virtual Machine (JVM)/Dalvik Virtual Machine (DVM)/Android Runtime (ART). Not all classes are loaded simultaneously, nor with the same ClassLoader. The context method
getClassLoader() can be used to get the current class loader. There are several types of class loading in Android, these being:

  • PathClassLoader – This is used by the Android system for its system and application class loader(s).
  • DexClassLoader – This loads file types containing a .dex file (e.g., .jar and .apk or .dex file directly). These .dex files (Dalvik executable) contain Dalvik bytecode.
  • URLClassLoader – This is used to retrieve classes or resources via URL paths. Paths ending with / are assumed to be directories, while otherwise they are assumed to be .jar files.

Below shows how to retrieve the current application context class loader:

ClassLoader loader = getApplicationContext().getClassLoader();

Below shows how a new class loaded can be created:

dexLoader = new DexClassLoader(filePath, dexCacheDirectory.getAbsolutePath(), null, loader);
//After creating a dex class loader, choose the class to load, as a string:
loadedClass = dexLoader.loadClass("me.jamesstevenson.dexloadable.MainActivity"); //alter path for your use case
//At this stage the uninitialized class can be used as normal. The following shows how to safely
initialize this class:
initialisedClass = loadedClass != null ? loadedClass.newInstance() : null;
method = loadedClass != null ? loadedClass.getMethod("loadMeAndIllTakeContext", Context.class) : null;
Object methodResponse = method != null ? method.
invoke(initialisedClass, getApplicationContext()) : null;

Reflection Examples

The two supporting classes for these reflection examples can be found on GitHub under DeviceData.Java and Loadable.Java.

Initializing a class:

try {
    Object initialisedDeviceData= DeviceData.class.
    newInstance();
    initialisedDeviceData.getClass().getDeclaredMethod("setDeviceInfo").invoke(initialisedDeviceData);
    String model = (String) initialisedDeviceData.getClass().
    getDeclaredField("model").get(initialisedDeviceData);
    Log.v(TAG, model);
} catch (...) {
    // Todo catch all exceptions
}

Retrieving class methods:

Static method example:
try {
    Method getDeviceName = Loadable.class.getDeclaredMethod("getDeviceName");
    getDeviceName.setAccessible(true);
    Log.v(TAG,(String) getDeviceName.invoke(Loadable.class));
} catch (...) {
    // Todo catch all exceptions
}

Retrieve all methods for a class:

getMethods() example:
    for (Method method : Loadable.class.getMethods()){
    Log.v(TAG, method.getName());
}

The following is an example of constructing a class with a private constructor:

try {
    Constructor<?> constructor = Loadable.class.getDeclaredConstructor(Context.class, long.class);
    constructor.setAccessible(true);
    Object instance = constructor.newInstance(getApplicationContext(), (Object) 12); // constructor takes a context and an id.
    Field uniqueIdField = instance.getClass().getDeclaredField("uniqueId");
    uniqueIdField.setAccessible(true);
    long uniqueId = (long) uniqueIdField.get(instance);
    Log.v(TAG, ""+uniqueId);
} catch (...) {
    // Todo catch all exceptions
}

Instance class example:

try {
    // The loadable class has a static method that can be used to construct it in this example
    Object instance = Loadable.class.getDeclaredMethod("construct", Context.class).invoke(Loadable.class, getApplicationContext());
    // Retrieve the field device data which is the class we're looking to get the data of.
    Field devicdDataField = instance.getClass().getDeclaredField("deviceData");
    devicdDataField.setAccessible(true);
    Object initialisedDeviceData = devicdDataField.get(instance);
    // After accessing the value from the field we're looking to access the fields of, we can use the same type of reflection again after getting it's class
    Field modelField = initialisedDeviceData.getClass().getDeclaredField("device");
    modelField.setAccessible(true);
    String model = (String) modelField.get(initialisedDeviceData);
    Log.v(TAG,model);
}  catch (...) {
    // Todo catch all exceptions
}

 

 

Read More

This article was derived from a chapter in my book Android Software Internals Quick Reference. If you found it interesting please consider picking the book up for yourself! 

 

 

 

Adverts





 

Android is saturated with Inter Process Communication (IPC) mechanisms used for both cross system and process communication (i.e. Binder, Sockets, and IOCTL calls) and for inter application communication (i.e. Intents and Broadcasts). This article will focus on Android Intents, one of the simplest and most used IPC techniques for inter app communication in Android. 

WHat Is an Intent?

Intents allow for applications to communicate (e.g., send data or initiate an action) with other Android components on the device, even if the recipient is not currently running. This means that an application can send intents to a wide variety of Android components – including application activities (using startActivity() or startActivityForResult()), background services (using startService()), and broadcast receivers (usingsendBroadcast() or sendOrderedBroadcast()). An application can send an intent that will fall into one of two (or three, depending on how you look at it) categories, these being:

  • Explicit – Explicit intents are intents that specify the application or both the application and component that will action the request.
  • Implicit – Implicit intents are more vague and specify a type of action that is desired (e.g., opening a camera or location application).
  • Broadcasts – These broadcast messages, which can be sent by the Android system or applications, are simultaneously received by all applications on the device that have previously registered for the specific broadcast action type.

An Intent class object is created in Java or Kotlin and has several core and additional attributes. These can be seen below:

  • Action (Core) –  Settable using an initialized Intent object’s setAction method. The action defines the high-level action to be performed.
  • Category (Core) –  Settable using an initialized Intent object’s setData method. The data field includes the data this intent is operating on (e.g., a file to open in an image editing app).
  • Type (Additional) – Settable using an initialized Intent object’s addCategory method. Categories provide additional context about the action to be performed by the intent. Only activities that can facilitate all specified categories can be chosen to receive the intent.
  • Component (Additional) – Settable using an initialized Intent object’s setComponent method. This attribute identifies the name of a component class to use for the intent. This is an optional attribute as it is normally identified based on the content of the intent.

intent filters

All components should have an android:exported tag. This is false by default, and if it is false, then the component can only receive messages from inside of its application. By adding any intent filters, this is automatically true and means that external applications can interact with the component. These intent filters are also used to advertise the component when the system receives an implicit intent.

The following is an example of a typical entry point (as defined in the Android Manifest) to an application with the MAIN action and LAUNCHER category set. The main action defines that this component should be started as the entry point to the application and that it does not receive any data. The launcher category defines that the activity should be displayed in the top-level launcher.

<intent-filter> 
     <action android:name="android.intent.action.MAIN" /> 
    <category android:name="android.intent.category.LAUNCHER" /> 
</intent-filter>

Receiving and Sending

Below are several examples of how intents can be used inside of an application to communicate with internal or external components:

Starting an internal activity with data:

public void sendIntentToActivityInApp(){
    Intent intent= new Intent(this, IntentReceiver.class);
    // Update class to be internal class (to the application) to receive the intent
    Bundle bundle= new Bundle();
    bundle.putString("key", "value");
    intent.putExtras(bundle);
    startActivity(intent);
}

Receiving an intent bundle in an activity:

protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getIntent().hasExtra("key")) {
        String value = getIntent().getExtras().getString("key");
        Log.v(this.getClass().getSimpleName(), value);
    }
}

Starting an external application:

public void startActivityViaIntent(){
    Intent launchIntent = getPackageManager().
    getLaunchIntentForPackage("com.android.chrome");
    startActivity(launchIntent);
}

Starting a specific activity of an external application:

public void sendIntentToAnotherActivity(){
    Intent intent = new Intent();
    intent.setClassName("com.android.chrome", "com.google.android.apps.chrome.Main");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ;
    startActivity(intent);
}

 

Read More

This article was derived from a chapter in my book Android Software Internals Quick Reference. If you found it interesting please think about picking the book up for yourself! 

 

 

 

Adverts