In the previous chapter: Android Dual Open Sandbox VirtualApp Source Code Analysis (IV) Start Plug-in Service
Scheme speculation
Like Activity, Client App registered in Minefiest Static Broadcasting External AMS is unknown, after the analysis of previous chapters, I believe that you are old drivers, we can try to put forward our own views first.
1. Use Stub components like Activity to occupy holes? Think about it carefully, because you can't pre-determine the Intent Filter broadcast in Client App.
2. It's a good idea to replace static registration with dynamic registration, context. registerBroadcast Receiver, but the emphasis is on the timing of registration. We need to register the static Receiver of Client App installed in VAService in advance when VAService starts. In fact, external AMS does the same. Otherwise, you can't receive the broadcast without opening the App.
VA Static Broadcasting Registration
As we know before, the starting time of VAService is really BinderProvider.onCreate():
@Override
public boolean onCreate() {
.....................
VAppManagerService.get().scanApps();
.....................
return true;
}
See VAppManagerService.get().scanApps() -> Persistence Layer. read () -> Package Persistence Layer. readPersistence Data () --> VAppManagerService. loadPackage () -> VAppManagerService. loadePackageInnerLocked () -> Broadcast System. tUp ();
// Registration of static Receiver
public void startApp(VPackage p) {
PackageSetting setting = (PackageSetting) p.mExtras;
// Receiver traversing Client App
for (VPackage.ActivityComponent receiver : p.receivers) {
ActivityInfo info = receiver.info;
// Get a list of records for Client App in VAService
List<BroadcastReceiver> receivers = mReceivers.get(p.packageName);
if (receivers == null) {
receivers = new ArrayList<>();
mReceivers.put(p.packageName, receivers);
}
// Explicit intent of registration
String componentAction = String.format("_VA_%s_%s", info.packageName, info.name);
IntentFilter componentFilter = new IntentFilter(componentAction);
BroadcastReceiver r = new StaticBroadcastReceiver(setting.appId, info, componentFilter);
mContext.registerReceiver(r, componentFilter, null, mScheduler);
// Push record
receivers.add(r);
// Traversing the Implicit Intention of Registration
for (VPackage.ActivityIntentInfo ci : receiver.intents) {
IntentFilter cloneFilter = new IntentFilter(ci.filter);
SpecialComponentList.protectIntentFilter(cloneFilter);
r = new StaticBroadcastReceiver(setting.appId, info, cloneFilter);
mContext.registerReceiver(r, cloneFilter, null, mScheduler);
// Push record
receivers.add(r);
}
}
}
A unified proxy Static Broadcast Receiver registration is used for each Client App static Receiver information.
1. Register Receiver's explicit intentions first. Each explicit intent is redirected to a component action in the form of "_VA_PKGNAME_CLASSNAME". The reason for this is that the real registration is the Static Broadcast Receiver proxy Receiver in the VAService process space, not the VA Client App process space, so it is not intentional to register the real class name in the VA Client App directly. Meaning, this way, the real Receiver can be found in the VA Client through the VAService proxy and then the'_VA_PKGNAME_CLASSNAME'extracted from Intent. This logic is somewhat similar to the processing of Activation.
2. Then it traverses the Intent-Filter, and each Intent-Filter registers a Static Broadcast Receiver agent.
So our agent Receiver is registered.
When the agent Receiver receives the broadcast:
@Override
public void onReceive(Context context, Intent intent) {
if (mApp.isBooting()) {
return;
}
if ((intent.getFlags() & FLAG_RECEIVER_REGISTERED_ONLY) != 0 || isInitialStickyBroadcast()) {
return;
}
String privilegePkg = intent.getStringExtra("_VA_|_privilege_pkg_");
if (privilegePkg != null && !info.packageName.equals(privilegePkg)) {
return;
}
PendingResult result = goAsync();
if (!mAMS.handleStaticBroadcast(appId, info, intent, new PendingResultData(result))) {
result.finish();
}
}
Then you see handleStatic Broadcast
boolean handleStaticBroadcast(int appId, ActivityInfo info, Intent intent,
PendingResultData result) {
// Here's the real target Intent
Intent realIntent = intent.getParcelableExtra("_VA_|_intent_");
// Take out the real target component
ComponentName component = intent.getParcelableExtra("_VA_|_component_");
// User id
int userId = intent.getIntExtra("_VA_|_user_id_", VUserHandle.USER_NULL);
if (realIntent == null) {
return false;
}
if (userId < 0) {
VLog.w(TAG, "Sent a broadcast without userId " + realIntent);
return false;
}
int vuid = VUserHandle.getUid(userId, appId);
return handleUserBroadcast(vuid, info, component, realIntent, result);
}
Note that the real Intent is taken out here, similar to Active, but unlike Active processing, the logic is still in VAService:
Then handleUserBroadcast() - - > handleStatic Broadcast AsUser () - > performance Schedule Receiver ():
private void performScheduleReceiver(IVClient client, int vuid, ActivityInfo info, Intent intent,
PendingResultData result) {
ComponentName componentName = ComponentUtils.toComponentName(info);
BroadcastSystem.get().broadcastSent(vuid, info, result);
try {
// scheduleReceiver for remotely calling client app
client.scheduleReceiver(info.processName, componentName, intent, result);
} catch (Throwable e) {
if (result != null) {
result.finish();
}
}
}
client.scheduleReceiver() calls scheduleReceiver of Client App remotely at this time. So we go back to the Client App process space:
@Override
public void scheduleReceiver(String processName, ComponentName component, Intent intent, PendingResultData resultData) {
ReceiverData receiverData = new ReceiverData();
receiverData.resultData = resultData;
receiverData.intent = intent;
receiverData.component = component;
receiverData.processName = processName;
sendMessage(RECEIVER, receiverData);
}
Follow the message queue:
case RECEIVER: {
handleReceiver((ReceiverData) msg.obj);
}
private void handleReceiver(ReceiverData data) {
BroadcastReceiver.PendingResult result = data.resultData.build();
try {
// It still checks whether the Application is initialized or not, and if it is not, it is initialized.
if (!isBound()) {
bindApplication(data.component.getPackageName(), data.processName);
}
// Get the Content of Receiver, which is an instance of Receiver Restricted context with two main functions disabled: registerReceiver() and bindService(). These two functions are in Broadcast Receiver.onReceive()Calls are not allowed. every time Receiver Processing a broadcast, passing it in context They are all new examples.
Context context = mInitialApplication.getBaseContext();
Context receiverContext = ContextImpl.getReceiverRestrictedContext.call(context);
String className = data.component.getClassName();
// Instance target Receiver
BroadcastReceiver receiver = (BroadcastReceiver) context.getClassLoader().loadClass(className).newInstance();
mirror.android.content.BroadcastReceiver.setPendingResult.call(receiver, result);
data.intent.setExtrasClassLoader(context.getClassLoader());
// Manual call to onCreate
receiver.onReceive(receiverContext, data.intent);
// Notify Pending to End
if (mirror.android.content.BroadcastReceiver.getPendingResult.call(receiver) != null) {
result.finish();
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(
"Unable to start receiver " + data.component
+ ": " + e.toString(), e);
}
// Here you need to notify VAService that the broadcast has arrived remotely
VActivityManager.get().broadcastFinish(data.resultData);
}
Here's the key point. Simple summary is that new real Receiver then calls onCreate. Receiver life cycle is really very simple.
Note that broadCast has a timeout mechanism:
void broadcastFinish(PendingResultData res) {
synchronized (mBroadcastRecords) {
BroadcastRecord record = mBroadcastRecords.remove(res.mToken);
if (record == null) {
VLog.e(TAG, "Unable to find the BroadcastRecord by token: " + res.mToken);
}
}
mTimeoutHandler.removeMessages(0, res.mToken);
res.finish();
}
private final class TimeoutHandler extends Handler {
@Override
public void handleMessage(Message msg) {
IBinder token = (IBinder) msg.obj;
BroadcastRecord r = mBroadcastRecords.remove(token);
if (r != null) {
VLog.w(TAG, "Broadcast timeout, cancel to dispatch it.");
r.pendingResult.finish();
}
}
}
Here, if the broadcast timeouts, Pending Result will be notified to end, telling the sender that the broadcast is over.
Processing of transmitting broadcasting
In fact, the last part has talked a lot about the processing of sending broadcasts.
Here Hook uses the broacastIntent method:
static class BroadcastIntent extends MethodProxy {
@Override
public String getMethodName() {
return "broadcastIntent";
}
@Override
public Object call(Object who, Method method, Object... args) throws Throwable {
Intent intent = (Intent) args[1];
String type = (String) args[2];
intent.setDataAndType(intent.getData(), type);
if (VirtualCore.get().getComponentDelegate() != null) {
VirtualCore.get().getComponentDelegate().onSendBroadcast(intent);
}
Intent newIntent = handleIntent(intent);
if (newIntent != null) {
args[1] = newIntent;
} else {
return 0;
}
if (args[7] instanceof String || args[7] instanceof String[]) {
// clear the permission
args[7] = null;
}
return method.invoke(who, args);
}
private Intent handleIntent(final Intent intent) {
final String action = intent.getAction();
if ("android.intent.action.CREATE_SHORTCUT".equals(action)
|| "com.android.launcher.action.INSTALL_SHORTCUT".equals(action)) {
return VASettings.ENABLE_INNER_SHORTCUT ? handleInstallShortcutIntent(intent) : null;
} else if ("com.android.launcher.action.UNINSTALL_SHORTCUT".equals(action)) {
handleUninstallShortcutIntent(intent);
} else {
return ComponentUtils.redirectBroadcastIntent(intent, VUserHandle.myUserId());
}
return intent;
}
private Intent handleInstallShortcutIntent(Intent intent) {
Intent shortcut = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
if (shortcut != null) {
ComponentName component = shortcut.resolveActivity(VirtualCore.getPM());
if (component != null) {
String pkg = component.getPackageName();
Intent newShortcutIntent = new Intent();
newShortcutIntent.setClassName(getHostPkg(), Constants.SHORTCUT_PROXY_ACTIVITY_NAME);
newShortcutIntent.addCategory(Intent.CATEGORY_DEFAULT);
newShortcutIntent.putExtra("_VA_|_intent_", shortcut);
newShortcutIntent.putExtra("_VA_|_uri_", shortcut.toUri(0));
newShortcutIntent.putExtra("_VA_|_user_id_", VUserHandle.myUserId());
intent.removeExtra(Intent.EXTRA_SHORTCUT_INTENT);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, newShortcutIntent);
Intent.ShortcutIconResource icon = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
if (icon != null && !TextUtils.equals(icon.packageName, getHostPkg())) {
try {
Resources resources = VirtualCore.get().getResources(pkg);
int resId = resources.getIdentifier(icon.resourceName, "drawable", pkg);
if (resId > 0) {
//noinspection deprecation
Drawable iconDrawable = resources.getDrawable(resId);
Bitmap newIcon = BitmapUtils.drawableToBitmap(iconDrawable);
if (newIcon != null) {
intent.removeExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, newIcon);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
return intent;
}
- Intent, which creates shortcut icons, is intercepted here. This is an implicit broadcast sent to Launcher. VA intercepts this request because if this shortcut is not intercepted, it will point to the external App, and if the external App is not installed, the broadcast will not work. VA changed this broadcast into its own logic.
- Notice ComponentUtils. redirectBroadcast Intent (), similar to Activity wrapping real Intent with proxy Intent:
public static Intent redirectBroadcastIntent(Intent intent, int userId) {
Intent newIntent = intent.cloneFilter();
newIntent.setComponent(null);
newIntent.setPackage(null);
ComponentName component = intent.getComponent();
String pkg = intent.getPackage();
if (component != null) {
newIntent.putExtra("_VA_|_user_id_", userId);
// Here the explicit intent is relocated to the format of _VA_PKGNAME_CLASSNAME, corresponding to the previous registration.
newIntent.setAction(String.format("_VA_%s_%s", component.getPackageName(), component.getClassName()));
newIntent.putExtra("_VA_|_component_", component);
newIntent.putExtra("_VA_|_intent_", new Intent(intent));
} else if (pkg != null) {
newIntent.putExtra("_VA_|_user_id_", userId);
newIntent.putExtra("_VA_|_creator_", pkg);
newIntent.putExtra("_VA_|_intent_", new Intent(intent));
String protectedAction = SpecialComponentList.protectAction(intent.getAction());
if (protectedAction != null) {
newIntent.setAction(protectedAction);
}
} else {
newIntent.putExtra("_VA_|_user_id_", userId);
newIntent.putExtra("_VA_|_intent_", new Intent(intent));
String protectedAction = SpecialComponentList.protectAction(intent.getAction());
if (protectedAction != null) {
newIntent.setAction(protectedAction);
}
}
return newIntent;
}
Ok, Broadcast Receiver is done. The next one is to analyze the last component, ContentProvider.