In this example, we will guide you through the steps needed to introduce a custom permission and learn how to make use of it to protect a method we add to the Activity Manager service.
We will edit the following files:
Filename |
Location |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
We begin by adding a new transaction and method prototype to
IActivityManager.java
.The following code is appended to
IActivityManager.java
://Packt - Add this towards the end of the file int PACKT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 118; public int packtSensitiveMethod(int data) throws RemoteException;
The number 118 is just 1 plus the last transaction number I have on my version of the source code. The ones you may see may differ. Use whatever number you see in your version and add one to it.
The preceding lines basically set a transaction number for our method. Since the underlying binder driver uses these numbers to match which methods need to be performed when an IPC call is made, we set our method's transaction number to be one plus the last transaction known. Depending on the source code version you are working with, the number written above may have to be increased. The logic is simply adding 1 to the last transaction number you see. The IPC method needs to throw a
RemoteException
since this is mandated by the Binder IPC protocol. We then create the public interface for this method. To do this, add the following lines toActivityManager.java
, towards the end of the file:/* Packt sensitive method demo */ public int packtSensitiveMethod(int data) { try { return ActivityManagerNative.getDefault().packtSensitiveMethod(data); } catch (RemoteException e) { } return -1; }
This is a very simple method that does nothing useful. The aim of this recipe is to help you understand the mechanics of adding a new method to the Activity Manager service.
Next, we will implement the marshalling code for this method. The concept of marshalling is no different than other interprocess frameworks. The basic aim is to serialize complex structures.
Inside
ActivityManagerNative.java
, we make two changes. The first is inside theActivityManagerProxy
class that is inside the file. The code is organized in a way such that theActivityManagerProxy
class is towards the end of the file. Therefore, we write our proxy implementation ofpacktSensitiveMethod
towards the end of theActivityManagerNative.java
file inside the scope of theActivityManagerProxy
class.The following code is added to
ActivityManagerNative.java
towards the end. It is thepacktSensitiveMethod
proxy implementation://Packt public int packtSensitiveMethod(int data) throws RemoteException { Parcel out = Parcel.obtain(); Parcel reply = Parcel.obtain(); out.writeInterfaceToken(IActivityManager.descriptor); out.writeInt(data); mRemote.transact(PACKT_TRANSACTION, out, reply, 0); int result = reply.readInt(); out.recycle(); reply.recycle(); return result; }
The second change is made in the
onTransact
method of the file. At the lastcase
statement, we add ourcase
code://Packt case PACKT_TRANSACTION: data.enforceInterface(IActivityManager.descriptor); int param = data.readInt(); int result = packtSensitiveMethod(param); reply.writeInt(result); reply.writeNoException(); return true;
Finally, we have to implement this method. The following implementation is written inside
ActivityManagerService.java
://=========================================================== // PACKT //=========================================================== public int packtSensitiveMethod(int data) throws RemoteException { if(checkCallingPermission("packt.PACKT_PERMISSION") != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires permission packt.PACKT_PERMISSION"); } else { Log.i("PACKTinAMS", "sensitive method called with parameter: " + data); return data * 2; } }
In Android, the ActivityManagerService
class executes within the context of the system server process. Hence, calls into it will be remote calls. Therefore, we have to make use of the Binder IPC mechanism. The IActivityManager.java
file contains the set of remote calls exposed by the Activity Manager service. Hence, we add a new interface method packtSensitiveMethod()
and a transaction constant PACKT_TRANSACTION
that will correspond to this method.
ActivityManager.java
represents a class library that user applications use to access the functionality of the Activity Manager service. This is similar to the custom class library we added in the earlier recipe on creating custom class libraries. As I've stated before, our implementations follow the design guidelines used in the Android code. Therefore, you will see several existing parallels in the Android source code. This is just one of the many examples that exist. Hence, we add a shim method to that file that invokes the real method through a remote procedure call.
As stated earlier, the Activity Manager service was written before the AIDL compiler existed, therefore the proxies and stubs for all remote calls are implemented manually. Hence, we add marshalling code to ActivityManagerNative.java
.
You may wonder why we still add the marshalling code manually. The answer is that even though the AIDL compiler is now available, the build process was never updated for the ActivityManagerService
class to make use of the AIDL compiler.