It has been a while since the last time I shared something. Well, this time i will write about how to communicate between user profiles in Android because as we already know in latest releases we have the option to create multiple user profiles in one device. And because i have some trouble trying to figure out how to do it :).
First i want to share the link with the document where you can find more info related to building applications with multi users profiles.
But lets start with some requirements to create the test application:
- Android device with user profiles functionality
- Rooted device
- System application permissions.
Register for the switch user event
Add the following code to register as a listener of the user profile change event
@Override
protected void onCreate(Bundle savedInstanceState) {
...
SwitchingUserReceiver receiver = new SwitchingUserReceiver();
IntentFilter switchingUserProfileEvent = new IntentFilter();
switchingUserProfileEvent.addAction(Intent.ACTION_USER_BACKGROUND);
switchingUserProfileEvent.addAction(Intent.ACTION_USER_FOREGROUND);
registerReceiver(receiver, switchingUserProfileEvent);
}
Create broadcast receiver for the switch user event
Create the following broadcast receiver to listen for the switching user profile event.
public class SwitchingUserReceiver extends BroadcastReceiver {
private static final String TAG = SwitchingUserReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
}
}
Because we register the broadcast receiver in runtime, is not necessary to add this event in the Manifest file.
Launch broadcast receiver to interact with an application in the starting user profile
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
final int userId = UserHelper.getCurrentUser();
int previousUser = intent.getExtras().getInt("android.intent.extra.user_handle");
UserHandle userHandle = userManager.getUserForSerialNumber(userId);
Log.d(TAG, "userHandle " + userHandle);
for (Method method : context.getClass().getMethods()) {
if ("sendBroadcastAsUser".equalsIgnoreCase(method.getName())) {
if (method.getParameterTypes().length == 2) {
method.setAccessible(true);
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.example.clerks.myapplication.myaction");
int processId = android.os.Process.myPid();
broadcastIntent.putExtra("user", previousUser);
broadcastIntent.putExtra("processId", processId);
method.invoke(context, broadcastIntent, userHandle);
}
}
}
...AndroidManifest...
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"
android:protectionLevel="signatureOrSystem"/>
Now we are going to use the method sendBroadcastAsUser. This method is not available to execute directly, but we are going to invoke it by reflection.
sendBroadcastAsUser receives as firs parameter the intent with the action to send the broadcast and the user where we want to send the brodcast. Note that when this onReceive method executes, the current user has already change, so the user in the foreground is the user profile that we select in the user profile interface. Thats why we are sending the broadcast to this current user (userHandle).
And the action("com.example.clerks.myapplication.myaction"), is the one that we are going to use to select our broadcast receiver.
To execute this code we require the permissions to MANAGE_USERS and INTERACT_ACROSS_USERS, so we are going to create system application.
Broadcast receiver for the custom action
public class OnUserSwitchedReceiver extends BroadcastReceiver {
private static final String TAG = OnUserSwitchedReceiver.class.getSimpleName();
@Override
public void onReceive(final Context context, Intent intent) {
final int previousUser = intent.getIntExtra("user", -1);
final int previousProcessId = intent.getIntExtra("processId", -1);
final int processId = android.os.Process.myPid();
final int userId = UserHelper.getCurrentUser();
Log.d(TAG, "Active user = " + userId);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, String.format("Message received. Process Ids: %d vs %d, Users: %d vs %d",
processId, previousProcessId, userId,
previousUser),
Toast.LENGTH_LONG).show();
}
});
}
}
<receiver android:name=".OnUserSwitchedReceiver">
<intent-filter>
<action android:name="com.example.clerks.myapplication.myaction" />
</intent-filter>
</receiver>
In this case we have to register this broadcast receiver in the manifest file for the application that can handle this action
Helper class to get the current user
public class UserHelper {
public static int getCurrentUser() {
Method getCurrentUser = null;
try {
getCurrentUser = ActivityManager.class.getDeclaredMethod("getCurrentUser");
getCurrentUser.setAccessible(true);
return (int) getCurrentUser.invoke(null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return -1;
}
}
Deploy application
echo "setting as root"
adb root
echo "remounting device"
adb remount
echo "creating package for application"
gradle clean assembleDebug
mv build/outputs/apk/app-debug.apk build/outputs/apk/MultiprofileApp.apk
echo "removing current apk in priv-app"
adb shell rm system/priv-app/MultiprofileApp/MultiprofileApp.apk
echo "moving current apk to priv-app"
adb push build/outputs/apk/MultiprofileApp.apk system/priv-app/MultiprofileApp/
echo "Changing permissions to directory MultiprofileApp"
adb shell chmod -R 755 system/priv-app/MultiprofileApp
echo "changing permissions to apk (644)"
adb shell chmod 644 system/priv-app/MultiprofileApp/MultiprofileApp.apk
echo "restarting device"
adb reboot
As you can see I have created a directory call MultiprofileApp in the priv-app directory. priv-app is used to install sytem applications. After create the directory and push the apk file, we need to reboot the device so the operating system can scan again the applications.
Test application
Start application, change the user and check that the toast appears in the open user.
Some things to keep in mind
- The context has another methods to interact between users, for example, start a service and an activity, I just test the startServiceAsUser and sendBroadcastAsUser but with startServiceAsUser I was getting all the time an exception related to INTERACT_ACROSS_USERS permission, even if i have include that permission and install my application as preinstalled, i was getting that exception. But you can try by yourself.
- I´m having some problems to get the method sendBroadcastAsUser to execute in the current Context, that's why you can see that for approach.
And that's all, now you can interact between system applications.