package com.lody.virtual.server.am;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
import com.lody.virtual.client.core.VirtualCore;
import com.lody.virtual.client.env.VirtualRuntime;
import com.lody.virtual.client.stub.StubManifest;
import com.lody.virtual.helper.utils.ArrayUtils;
import com.lody.virtual.helper.utils.ClassUtils;
import com.lody.virtual.helper.utils.ComponentUtils;
import com.lody.virtual.remote.AppTaskInfo;
import com.lody.virtual.remote.StubActivityRecord;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import mirror.android.app.ActivityManagerNative;
import mirror.android.app.ActivityThread;
import mirror.android.app.IActivityManager;
import mirror.android.app.IApplicationThread;
import mirror.com.android.internal.R_Hide;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
/**
* @author Lody
*/
/* package */ class ActivityStack {
private final ActivityManager mAM;
private final VActivityManagerService mService;
/**
* [Key] = TaskId [Value] = TaskRecord
*/
private final SparseArray<TaskRecord> mHistory = new SparseArray<>();
ActivityStack(VActivityManagerService mService) {
this.mService = mService;
mAM = (ActivityManager) VirtualCore.get().getContext().getSystemService(Context.ACTIVITY_SERVICE);
}
private static void removeFlags(Intent intent, int flags) {
intent.setFlags(intent.getFlags() & ~flags);
}
private static boolean containFlags(Intent intent, int flags) {
return (intent.getFlags() & flags) != 0;
}
private static ActivityRecord topActivityInTask(TaskRecord task) {
synchronized (task.activities) {
for (int size = task.activities.size() - 1; size >= 0; size--) {
ActivityRecord r = task.activities.get(size);
if (!r.marked) {
return r;
}
}
return null;
}
}
private void deliverNewIntentLocked(ActivityRecord sourceRecord, ActivityRecord targetRecord, Intent intent) {
if (targetRecord == null) {
return;
}
String creator = sourceRecord != null ? sourceRecord.component.getPackageName() : "android";
try {
targetRecord.process.client.scheduleNewIntent(creator, targetRecord.token, intent);
} catch (RemoteException e) {
e.printStackTrace();
} catch (NullPointerException npe) {
npe.printStackTrace();
}
}
private TaskRecord findTaskByAffinityLocked(int userId, String affinity) {
for (int i = 0; i < this.mHistory.size(); i++) {
TaskRecord r = this.mHistory.valueAt(i);
if (userId == r.userId && affinity.equals(r.affinity)) {
return r;
}
}
return null;
}
private TaskRecord findTaskByIntentLocked(int userId, Intent intent) {
for (int i = 0; i < this.mHistory.size(); i++) {
TaskRecord r = this.mHistory.valueAt(i);
if (userId == r.userId && r.taskRoot != null && intent.getComponent().equals(r.taskRoot.getComponent())) {
return r;
}
}
return null;
}
private ActivityRecord findActivityByToken(int userId, IBinder token) {
ActivityRecord target = null;
if (token != null) {
for (int i = 0; i < this.mHistory.size(); i++) {
TaskRecord task = this.mHistory.valueAt(i);
if (task.userId != userId) {
continue;
}
synchronized (task.activities) {
for (ActivityRecord r : task.activities) {
if (r.token == token) {
target = r;
}
}
}
}
}
return target;
}
private boolean markTaskByClearTarget(TaskRecord task, ClearTarget clearTarget, ComponentName component) {
boolean marked = false;
switch (clearTarget) {
case TASK: {
synchronized (task.activities) {
for (ActivityRecord r : task.activities) {
r.marked = true;
marked = true;
}
}
}
break;
case SPEC_ACTIVITY: {
synchronized (task.activities) {
for (ActivityRecord r : task.activities) {
if (r.component.equals(component)) {
r.marked = true;
marked = true;
}
}
}
}
break;
case TOP: {
synchronized (task.activities) {
int N = task.activities.size();
while (N-- > 0) {
ActivityRecord r = task.activities.get(N);
if (r.component.equals(component)) {
marked = true;
break;
}
}
if (marked) {
while (N++ < task.activities.size() - 1) {
task.activities.get(N).marked = true;
}
}
}
}
break;
}
return marked;
}
/**
* App started in VA may be removed in OverView screen, then AMS.removeTask
* will be invoked, all data struct about the task in AMS are released,
* while the client's process is still alive. So remove related data in VA
* as well. A new TaskRecord will be recreated in `onActivityCreated`
*/
private void optimizeTasksLocked() {
// noinspection deprecation
ArrayList<ActivityManager.RecentTaskInfo> recentTask = new ArrayList<>(mAM.getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_WITH_EXCLUDED | ActivityManager.RECENT_IGNORE_UNAVAILABLE));
int N = mHistory.size();
while (N-- > 0) {
TaskRecord task = mHistory.valueAt(N);
ListIterator<ActivityManager.RecentTaskInfo> iterator = recentTask.listIterator();
boolean taskAlive = false;
while (iterator.hasNext()) {
ActivityManager.RecentTaskInfo info = iterator.next();
if (info.id == task.taskId) {
taskAlive = true;
iterator.remove();
break;
}
}
if (!taskAlive) {
mHistory.removeAt(N);
}
}
}
int startActivitiesLocked(int userId, Intent[] intents, ActivityInfo[] infos, String[] resolvedTypes, IBinder token, Bundle options) {
optimizeTasksLocked();
ReuseTarget reuseTarget = ReuseTarget.CURRENT;
Intent intent = intents[0];
ActivityInfo info = infos[0];
ActivityRecord resultTo = findActivityByToken(userId, token);
if (resultTo != null && resultTo.launchMode == LAUNCH_SINGLE_INSTANCE) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TOP)) {
removeFlags(intent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
}
if (containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK) && !containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
removeFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (info.documentLaunchMode) {
case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
reuseTarget = ReuseTarget.DOCUMENT;
break;
case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
reuseTarget = ReuseTarget.MULTIPLE;
break;
}
}
if (containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK) ? ReuseTarget.MULTIPLE : ReuseTarget.AFFINITY;
} else if (info.launchMode == LAUNCH_SINGLE_TASK) {
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK) ? ReuseTarget.MULTIPLE : ReuseTarget.AFFINITY;
}
if (resultTo == null && reuseTarget == ReuseTarget.CURRENT) {
reuseTarget = ReuseTarget.AFFINITY;
}
String affinity = ComponentUtils.getTaskAffinity(info);
TaskRecord reuseTask = null;
if (reuseTarget == ReuseTarget.AFFINITY) {
reuseTask = findTaskByAffinityLocked(userId, affinity);
} else if (reuseTarget == ReuseTarget.CURRENT) {
reuseTask = resultTo.task;
} else if (reuseTarget == ReuseTarget.DOCUMENT) {
reuseTask = findTaskByIntentLocked(userId, intent);
}
Intent[] destIntents = startActivitiesProcess(userId, intents, infos, resultTo);
if (reuseTask == null) {
realStartActivitiesLocked(null, destIntents, resolvedTypes, options);
} else {
ActivityRecord top = topActivityInTask(reuseTask);
if (top != null) {
realStartActivitiesLocked(top.token, destIntents, resolvedTypes, options);
}
}
return 0;
}
private Intent[] startActivitiesProcess(int userId, Intent[] intents, ActivityInfo[] infos, ActivityRecord resultTo) {
Intent[] destIntents = new Intent[intents.length];
for (int i = 0; i < intents.length; i++) {
destIntents[i] = startActivityProcess(userId, resultTo, intents[i], infos[i]);
}
return destIntents;
}
int startActivityLocked(int userId, Intent intent, ActivityInfo info, IBinder resultTo, Bundle options,
String resultWho, int requestCode) {
optimizeTasksLocked();
Intent destIntent;
ActivityRecord sourceRecord = findActivityByToken(userId, resultTo);
TaskRecord sourceTask = sourceRecord != null ? sourceRecord.task : null;
ReuseTarget reuseTarget = ReuseTarget.CURRENT;
ClearTarget clearTarget = ClearTarget.NOTHING;
boolean clearTop = containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (intent.getComponent() == null) {
intent.setComponent(new ComponentName(info.packageName, info.name));
}
if (sourceRecord != null && sourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (clearTop) {
removeFlags(intent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
clearTarget = ClearTarget.TOP;
}
if (containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
if (containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
clearTarget = ClearTarget.TASK;
} else {
removeFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (info.documentLaunchMode) {
case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
clearTarget = ClearTarget.TASK;
reuseTarget = ReuseTarget.DOCUMENT;
break;
case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
reuseTarget = ReuseTarget.MULTIPLE;
break;
}
}
boolean singleTop = false;
switch (info.launchMode) {
case LAUNCH_SINGLE_TOP: {
singleTop = true;
if (containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
? ReuseTarget.MULTIPLE
: ReuseTarget.AFFINITY;
}
}
break;
case LAUNCH_SINGLE_TASK: {
clearTop = false;
clearTarget = ClearTarget.TOP;
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
? ReuseTarget.MULTIPLE
: ReuseTarget.AFFINITY;
}
break;
case LAUNCH_SINGLE_INSTANCE: {
clearTop = false;
clearTarget = ClearTarget.TOP;
reuseTarget = ReuseTarget.AFFINITY;
}
break;
default: {
if (containFlags(intent, Intent.FLAG_ACTIVITY_SINGLE_TOP)) {
singleTop = true;
}
}
break;
}
if (clearTarget == ClearTarget.NOTHING) {
if (containFlags(intent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)) {
clearTarget = ClearTarget.SPEC_ACTIVITY;
}
}
if (sourceTask == null && reuseTarget == ReuseTarget.CURRENT) {
reuseTarget = ReuseTarget.AFFINITY;
}
String affinity = ComponentUtils.getTaskAffinity(info);
TaskRecord reuseTask = null;
switch (reuseTarget) {
case AFFINITY:
reuseTask = findTaskByAffinityLocked(userId, affinity);
break;
case DOCUMENT:
reuseTask = findTaskByIntentLocked(userId, intent);
break;
case CURRENT:
reuseTask = sourceTask;
break;
default:
break;
}
boolean taskMarked = false;
if (reuseTask == null) {
startActivityInNewTaskLocked(userId, intent, info, options);
} else {
boolean delivered = false;
mAM.moveTaskToFront(reuseTask.taskId, 0);
boolean startTaskToFront = !clearTop && ComponentUtils.isSameIntent(intent, reuseTask.taskRoot);
if (clearTarget.deliverIntent || singleTop) {
taskMarked = markTaskByClearTarget(reuseTask, clearTarget, intent.getComponent());
ActivityRecord topRecord = topActivityInTask(reuseTask);
if (clearTop && !singleTop && topRecord != null && taskMarked) {
topRecord.marked = true;
}
// Target activity is on top
if (topRecord != null && !topRecord.marked && topRecord.component.equals(intent.getComponent())) {
deliverNewIntentLocked(sourceRecord, topRecord, intent);
delivered = true;
}
}
if (taskMarked) {
synchronized (mHistory) {
scheduleFinishMarkedActivityLocked();
}
}
if (!startTaskToFront) {
if (!delivered) {
destIntent = startActivityProcess(userId, sourceRecord, intent, info);
if (destIntent != null) {
startActivityFromSourceTask(reuseTask, destIntent, info, resultWho, requestCode, options);
}
}
}
}
return 0;
}
private Intent startActivityInNewTaskLocked(int userId, Intent intent, ActivityInfo info, Bundle options) {
Intent destIntent = startActivityProcess(userId, null, intent, info);
if (destIntent != null) {
destIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
destIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
destIntent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// noinspection deprecation
destIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
} else {
destIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
VirtualCore.get().getContext().startActivity(destIntent, options);
} else {
VirtualCore.get().getContext().startActivity(destIntent);
}
}
return destIntent;
}
private void scheduleFinishMarkedActivityLocked() {
int N = mHistory.size();
while (N-- > 0) {
final TaskRecord task = mHistory.valueAt(N);
for (final ActivityRecord r : task.activities) {
if (!r.marked) {
continue;
}
VirtualRuntime.getUIHandler().post(new Runnable() {
@Override
public void run() {
try {
r.process.client.finishActivity(r.token);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
}
private boolean startActivityFromSourceTask(TaskRecord task, Intent intent, ActivityInfo info, String resultWho,
int requestCode, Bundle options) {
ActivityRecord top = topActivityInTask(task);
if (top != null) {
if (startActivityProcess(task.userId, top, intent, info) != null) {
realStartActivityLocked(top.token, intent, resultWho, requestCode, options);
}
}
return false;
}
private void realStartActivitiesLocked(IBinder resultTo, Intent[] intents, String[] resolvedTypes, Bundle options) {
Class<?>[] types = IActivityManager.startActivities.paramList();
Object[] args = new Object[types.length];
if (types[0] == IApplicationThread.TYPE) {
args[0] = ActivityThread.getApplicationThread.call(VirtualCore.mainThread());
}
int pkgIndex = ArrayUtils.protoIndexOf(types, String.class);
int intentsIndex = ArrayUtils.protoIndexOf(types, Intent[].class);
int resultToIndex = ArrayUtils.protoIndexOf(types, IBinder.class, 2);
int optionsIndex = ArrayUtils.protoIndexOf(types, Bundle.class);
int resolvedTypesIndex = intentsIndex + 1;
if (pkgIndex != -1) {
args[pkgIndex] = VirtualCore.get().getHostPkg();
}
args[intentsIndex] = intents;
args[resultToIndex] = resultTo;
args[resolvedTypesIndex] = resolvedTypes;
args[optionsIndex] = options;
ClassUtils.fixArgs(types, args);
IActivityManager.startActivities.call(ActivityManagerNative.getDefault.call(),
(Object[]) args);
}
private void realStartActivityLocked(IBinder resultTo, Intent intent, String resultWho, int requestCode,
Bundle options) {
Class<?>[] types = mirror.android.app.IActivityManager.startActivity.paramList();
Object[] args = new Object[types.length];
if (types[0] == IApplicationThread.TYPE) {
args[0] = ActivityThread.getApplicationThread.call(VirtualCore.mainThread());
}
int intentIndex = ArrayUtils.protoIndexOf(types, Intent.class);
int resultToIndex = ArrayUtils.protoIndexOf(types, IBinder.class, 2);
int optionsIndex = ArrayUtils.protoIndexOf(types, Bundle.class);
int resolvedTypeIndex = intentIndex + 1;
int resultWhoIndex = resultToIndex + 1;
int requestCodeIndex = resultToIndex + 2;
args[intentIndex] = intent;
args[resultToIndex] = resultTo;
args[resultWhoIndex] = resultWho;
args[requestCodeIndex] = requestCode;
if (optionsIndex != -1) {
args[optionsIndex] = options;
}
args[resolvedTypeIndex] = intent.getType();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
args[intentIndex - 1] = VirtualCore.get().getHostPkg();
}
ClassUtils.fixArgs(types, args);
mirror.android.app.IActivityManager.startActivity.call(ActivityManagerNative.getDefault.call(),
(Object[]) args);
}
private String fetchStubActivity(int vpid, ActivityInfo targetInfo) {
boolean isFloating = false;
boolean isTranslucent = false;
boolean showWallpaper = false;
try {
int[] R_Styleable_Window = R_Hide.styleable.Window.get();
int R_Styleable_Window_windowIsTranslucent = R_Hide.styleable.Window_windowIsTranslucent.get();
int R_Styleable_Window_windowIsFloating = R_Hide.styleable.Window_windowIsFloating.get();
int R_Styleable_Window_windowShowWallpaper = R_Hide.styleable.Window_windowShowWallpaper.get();
AttributeCache.Entry ent = AttributeCache.instance().get(targetInfo.packageName, targetInfo.theme,
R_Styleable_Window);
if (ent != null && ent.array != null) {
showWallpaper = ent.array.getBoolean(R_Styleable_Window_windowShowWallpaper, false);
isTranslucent = ent.array.getBoolean(R_Styleable_Window_windowIsTranslucent, false);
isFloating = ent.array.getBoolean(R_Styleable_Window_windowIsFloating, false);
}
} catch (Throwable e) {
e.printStackTrace();
}
boolean isDialogStyle = isFloating || isTranslucent || showWallpaper;
if (isDialogStyle) {
return StubManifest.getStubDialogName(vpid);
} else {
return StubManifest.getStubActivityName(vpid);
}
}
private Intent startActivityProcess(int userId, ActivityRecord sourceRecord, Intent intent, ActivityInfo info) {
intent = new Intent(intent);
ProcessRecord targetApp = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
if (targetApp == null) {
return null;
}
Intent targetIntent = new Intent();
targetIntent.setClassName(VirtualCore.get().getHostPkg(), fetchStubActivity(targetApp.vpid, info));
ComponentName component = intent.getComponent();
if (component == null) {
component = ComponentUtils.toComponentName(info);
}
targetIntent.setType(component.flattenToString());
StubActivityRecord saveInstance = new StubActivityRecord(intent, info,
sourceRecord != null ? sourceRecord.component : null, userId);
saveInstance.saveToIntent(targetIntent);
return targetIntent;
}
void onActivityCreated(ProcessRecord targetApp, ComponentName component, ComponentName caller, IBinder token,
Intent taskRoot, String affinity, int taskId, int launchMode, int flags) {
synchronized (mHistory) {
optimizeTasksLocked();
TaskRecord task = mHistory.get(taskId);
if (task == null) {
task = new TaskRecord(taskId, targetApp.userId, affinity, taskRoot);
mHistory.put(taskId, task);
}
ActivityRecord record = new ActivityRecord(task, component, caller, token, targetApp.userId, targetApp,
launchMode, flags, affinity);
synchronized (task.activities) {
task.activities.add(record);
}
}
}
void onActivityResumed(int userId, IBinder token) {
synchronized (mHistory) {
optimizeTasksLocked();
ActivityRecord r = findActivityByToken(userId, token);
if (r != null) {
synchronized (r.task.activities) {
r.task.activities.remove(r);
r.task.activities.add(r);
}
}
}
}
ActivityRecord onActivityDestroyed(int userId, IBinder token) {
synchronized (mHistory) {
optimizeTasksLocked();
ActivityRecord r = findActivityByToken(userId, token);
if (r != null) {
synchronized (r.task.activities) {
r.task.activities.remove(r);
// We shouldn't remove task at this point,
// it will be removed by optimizeTasksLocked().
}
}
return r;
}
}
void processDied(ProcessRecord record) {
synchronized (mHistory) {
optimizeTasksLocked();
int N = mHistory.size();
while (N-- > 0) {
TaskRecord task = mHistory.valueAt(N);
synchronized (task.activities) {
Iterator<ActivityRecord> iterator = task.activities.iterator();
while (iterator.hasNext()) {
ActivityRecord r = iterator.next();
if (r.process.pid == record.pid) {
iterator.remove();
if (task.activities.isEmpty()) {
mHistory.remove(task.taskId);
}
}
}
}
}
}
}
String getPackageForToken(int userId, IBinder token) {
synchronized (mHistory) {
ActivityRecord r = findActivityByToken(userId, token);
if (r != null) {
return r.component.getPackageName();
}
return null;
}
}
ComponentName getCallingActivity(int userId, IBinder token) {
synchronized (mHistory) {
ActivityRecord r = findActivityByToken(userId, token);
if (r != null) {
return r.caller;
}
return null;
}
}
public String getCallingPackage(int userId, IBinder token) {
synchronized (mHistory) {
ActivityRecord r = findActivityByToken(userId, token);
if (r != null) {
return r.caller != null ? r.caller.getPackageName() : null;
}
return null;
}
}
AppTaskInfo getTaskInfo(int taskId) {
synchronized (mHistory) {
TaskRecord task = mHistory.get(taskId);
if (task != null) {
return task.getAppTaskInfo();
}
return null;
}
}
ComponentName getActivityClassForToken(int userId, IBinder token) {
synchronized (mHistory) {
ActivityRecord r = findActivityByToken(userId, token);
if (r != null) {
return r.component;
}
return null;
}
}
private enum ClearTarget {
NOTHING,
SPEC_ACTIVITY,
TASK(true),
TOP(true);
boolean deliverIntent;
ClearTarget() {
this(false);
}
ClearTarget(boolean deliverIntent) {
this.deliverIntent = deliverIntent;
}
}
private enum ReuseTarget {
CURRENT, AFFINITY, DOCUMENT, MULTIPLE
}
}