/*
|
* Copyright (C) 2017 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License
|
*/
|
|
package com.android.server.wm;
|
|
import static android.app.ActivityManager.START_SUCCESS;
|
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
|
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
|
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
|
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
|
|
import android.app.ActivityOptions;
|
import android.app.IApplicationThread;
|
import android.content.ComponentName;
|
import android.content.ContentResolver;
|
import android.content.Intent;
|
import android.content.pm.ActivityInfo;
|
import android.content.pm.ApplicationInfo;
|
import android.content.pm.PackageManager;
|
import android.content.pm.ResolveInfo;
|
import android.os.Binder;
|
import android.os.Handler;
|
import android.os.IBinder;
|
import android.os.Looper;
|
import android.os.Message;
|
import android.os.UserHandle;
|
import android.provider.Settings;
|
import android.util.Slog;
|
import android.util.proto.ProtoOutputStream;
|
import android.view.RemoteAnimationAdapter;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.util.ArrayUtils;
|
import com.android.server.am.ActivityManagerService;
|
import com.android.server.am.PendingIntentRecord;
|
import com.android.server.wm.ActivityStackSupervisor.PendingActivityLaunch;
|
import com.android.server.wm.ActivityStarter.DefaultFactory;
|
import com.android.server.wm.ActivityStarter.Factory;
|
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.List;
|
|
/**
|
* Controller for delegating activity launches.
|
*
|
* This class' main objective is to take external activity start requests and prepare them into
|
* a series of discrete activity launches that can be handled by an {@link ActivityStarter}. It is
|
* also responsible for handling logic that happens around an activity launch, but doesn't
|
* necessarily influence the activity start. Examples include power hint management, processing
|
* through the pending activity list, and recording home activity launches.
|
*/
|
public class ActivityStartController {
|
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStartController" : TAG_ATM;
|
|
private static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 1;
|
|
private final ActivityTaskManagerService mService;
|
private final ActivityStackSupervisor mSupervisor;
|
|
/** Last home activity record we attempted to start. */
|
private ActivityRecord mLastHomeActivityStartRecord;
|
|
/** Temporary array to capture start activity results */
|
private ActivityRecord[] tmpOutRecord = new ActivityRecord[1];
|
|
/** The result of the last home activity we attempted to start. */
|
private int mLastHomeActivityStartResult;
|
|
/** A list of activities that are waiting to launch. */
|
private final ArrayList<ActivityStackSupervisor.PendingActivityLaunch>
|
mPendingActivityLaunches = new ArrayList<>();
|
|
private final Factory mFactory;
|
|
private final Handler mHandler;
|
|
private final PendingRemoteAnimationRegistry mPendingRemoteAnimationRegistry;
|
|
boolean mCheckedForSetup = false;
|
|
private final class StartHandler extends Handler {
|
public StartHandler(Looper looper) {
|
super(looper, null, true);
|
}
|
|
@Override
|
public void handleMessage(Message msg) {
|
switch(msg.what) {
|
case DO_PENDING_ACTIVITY_LAUNCHES_MSG:
|
synchronized (mService.mGlobalLock) {
|
doPendingActivityLaunches(true);
|
}
|
break;
|
}
|
}
|
}
|
|
/**
|
* TODO(b/64750076): Capture information necessary for dump and
|
* {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
|
* around
|
*/
|
private ActivityStarter mLastStarter;
|
|
ActivityStartController(ActivityTaskManagerService service) {
|
this(service, service.mStackSupervisor,
|
new DefaultFactory(service, service.mStackSupervisor,
|
new ActivityStartInterceptor(service, service.mStackSupervisor)));
|
}
|
|
@VisibleForTesting
|
ActivityStartController(ActivityTaskManagerService service, ActivityStackSupervisor supervisor,
|
Factory factory) {
|
mService = service;
|
mSupervisor = supervisor;
|
mHandler = new StartHandler(mService.mH.getLooper());
|
mFactory = factory;
|
mFactory.setController(this);
|
mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service,
|
service.mH);
|
}
|
|
/**
|
* @return A starter to configure and execute starting an activity. It is valid until after
|
* {@link ActivityStarter#execute} is invoked. At that point, the starter should be
|
* considered invalid and no longer modified or used.
|
*/
|
ActivityStarter obtainStarter(Intent intent, String reason) {
|
return mFactory.obtain().setIntent(intent).setReason(reason);
|
}
|
|
void onExecutionComplete(ActivityStarter starter) {
|
if (mLastStarter == null) {
|
mLastStarter = mFactory.obtain();
|
}
|
|
mLastStarter.set(starter);
|
mFactory.recycle(starter);
|
}
|
|
/**
|
* TODO(b/64750076): usage of this doesn't seem right. We're making decisions based off the
|
* last starter for an arbitrary task record. Re-evaluate whether we can remove.
|
*/
|
void postStartActivityProcessingForLastStarter(ActivityRecord r, int result,
|
ActivityStack targetStack) {
|
if (mLastStarter == null) {
|
return;
|
}
|
|
mLastStarter.postStartActivityProcessing(r, result, targetStack);
|
}
|
|
void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) {
|
final ActivityOptions options = ActivityOptions.makeBasic();
|
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
|
if (!ActivityRecord.isResolverActivity(aInfo.name)) {
|
// The resolver activity shouldn't be put in home stack because when the foreground is
|
// standard type activity, the resolver activity should be put on the top of current
|
// foreground instead of bring home stack to front.
|
options.setLaunchActivityType(ACTIVITY_TYPE_HOME);
|
}
|
options.setLaunchDisplayId(displayId);
|
mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
|
.setOutActivity(tmpOutRecord)
|
.setCallingUid(0)
|
.setActivityInfo(aInfo)
|
.setActivityOptions(options.toBundle())
|
.execute();
|
mLastHomeActivityStartRecord = tmpOutRecord[0];
|
final ActivityDisplay display =
|
mService.mRootActivityContainer.getActivityDisplay(displayId);
|
final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
|
if (homeStack != null && homeStack.mInResumeTopActivity) {
|
// If we are in resume section already, home activity will be initialized, but not
|
// resumed (to avoid recursive resume) and will stay that way until something pokes it
|
// again. We need to schedule another resume.
|
mSupervisor.scheduleResumeTopActivities();
|
}
|
}
|
|
/**
|
* Starts the "new version setup screen" if appropriate.
|
*/
|
void startSetupActivity() {
|
// Only do this once per boot.
|
if (mCheckedForSetup) {
|
return;
|
}
|
|
// We will show this screen if the current one is a different
|
// version than the last one shown, and we are not running in
|
// low-level factory test mode.
|
final ContentResolver resolver = mService.mContext.getContentResolver();
|
if (mService.mFactoryTest != FACTORY_TEST_LOW_LEVEL
|
&& Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
|
mCheckedForSetup = true;
|
|
// See if we should be showing the platform update setup UI.
|
final Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP);
|
final List<ResolveInfo> ris =
|
mService.mContext.getPackageManager().queryIntentActivities(intent,
|
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA
|
| ActivityManagerService.STOCK_PM_FLAGS);
|
if (!ris.isEmpty()) {
|
final ResolveInfo ri = ris.get(0);
|
String vers = ri.activityInfo.metaData != null
|
? ri.activityInfo.metaData.getString(Intent.METADATA_SETUP_VERSION)
|
: null;
|
if (vers == null && ri.activityInfo.applicationInfo.metaData != null) {
|
vers = ri.activityInfo.applicationInfo.metaData.getString(
|
Intent.METADATA_SETUP_VERSION);
|
}
|
String lastVers = Settings.Secure.getString(
|
resolver, Settings.Secure.LAST_SETUP_SHOWN);
|
if (vers != null && !vers.equals(lastVers)) {
|
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
intent.setComponent(new ComponentName(
|
ri.activityInfo.packageName, ri.activityInfo.name));
|
obtainStarter(intent, "startSetupActivity")
|
.setCallingUid(0)
|
.setActivityInfo(ri.activityInfo)
|
.execute();
|
}
|
}
|
}
|
}
|
|
/**
|
* If {@code validateIncomingUser} is true, check {@code targetUserId} against the real calling
|
* user ID inferred from {@code realCallingUid}, then return the resolved user-id, taking into
|
* account "current user", etc.
|
*
|
* If {@code validateIncomingUser} is false, it skips the above check, but instead
|
* ensures {@code targetUserId} is a real user ID and not a special user ID such as
|
* {@link android.os.UserHandle#USER_ALL}, etc.
|
*/
|
int checkTargetUser(int targetUserId, boolean validateIncomingUser,
|
int realCallingPid, int realCallingUid, String reason) {
|
if (validateIncomingUser) {
|
return mService.handleIncomingUser(
|
realCallingPid, realCallingUid, targetUserId, reason);
|
} else {
|
mService.mAmInternal.ensureNotSpecialUser(targetUserId);
|
return targetUserId;
|
}
|
}
|
|
final int startActivityInPackage(int uid, int realCallingPid, int realCallingUid,
|
String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
|
String resultWho, int requestCode, int startFlags, SafeActivityOptions options,
|
int userId, TaskRecord inTask, String reason, boolean validateIncomingUser,
|
PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
|
|
userId = checkTargetUser(userId, validateIncomingUser, realCallingPid, realCallingUid,
|
reason);
|
|
// TODO: Switch to user app stacks here.
|
return obtainStarter(intent, reason)
|
.setCallingUid(uid)
|
.setRealCallingPid(realCallingPid)
|
.setRealCallingUid(realCallingUid)
|
.setCallingPackage(callingPackage)
|
.setResolvedType(resolvedType)
|
.setResultTo(resultTo)
|
.setResultWho(resultWho)
|
.setRequestCode(requestCode)
|
.setStartFlags(startFlags)
|
.setActivityOptions(options)
|
.setMayWait(userId)
|
.setInTask(inTask)
|
.setOriginatingPendingIntent(originatingPendingIntent)
|
.setAllowBackgroundActivityStart(allowBackgroundActivityStart)
|
.execute();
|
}
|
|
/**
|
* Start intents as a package.
|
*
|
* @param uid Make a call as if this UID did.
|
* @param callingPackage Make a call as if this package did.
|
* @param intents Intents to start.
|
* @param userId Start the intents on this user.
|
* @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID.
|
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
|
* null if not originated by PendingIntent
|
*/
|
final int startActivitiesInPackage(int uid, String callingPackage, Intent[] intents,
|
String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId,
|
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
|
boolean allowBackgroundActivityStart) {
|
return startActivitiesInPackage(uid, 0 /* realCallingPid */, -1 /* realCallingUid */,
|
callingPackage, intents, resolvedTypes, resultTo, options, userId, validateIncomingUser,
|
originatingPendingIntent, allowBackgroundActivityStart);
|
}
|
|
/**
|
* Start intents as a package.
|
*
|
* @param uid Make a call as if this UID did.
|
* @param realCallingPid PID of the real caller.
|
* @param realCallingUid UID of the real caller.
|
* @param callingPackage Make a call as if this package did.
|
* @param intents Intents to start.
|
* @param userId Start the intents on this user.
|
* @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID.
|
* @param originatingPendingIntent PendingIntentRecord that originated this activity start or
|
* null if not originated by PendingIntent
|
*/
|
final int startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid,
|
String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo,
|
SafeActivityOptions options, int userId, boolean validateIncomingUser,
|
PendingIntentRecord originatingPendingIntent,
|
boolean allowBackgroundActivityStart) {
|
|
final String reason = "startActivityInPackage";
|
|
userId = checkTargetUser(userId, validateIncomingUser, Binder.getCallingPid(),
|
Binder.getCallingUid(), reason);
|
|
// TODO: Switch to user app stacks here.
|
return startActivities(null, uid, realCallingPid, realCallingUid, callingPackage, intents,
|
resolvedTypes, resultTo, options, userId, reason, originatingPendingIntent,
|
allowBackgroundActivityStart);
|
}
|
|
int startActivities(IApplicationThread caller, int callingUid, int incomingRealCallingPid,
|
int incomingRealCallingUid, String callingPackage, Intent[] intents,
|
String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options,
|
int userId, String reason, PendingIntentRecord originatingPendingIntent,
|
boolean allowBackgroundActivityStart) {
|
if (intents == null) {
|
throw new NullPointerException("intents is null");
|
}
|
if (resolvedTypes == null) {
|
throw new NullPointerException("resolvedTypes is null");
|
}
|
if (intents.length != resolvedTypes.length) {
|
throw new IllegalArgumentException("intents are length different than resolvedTypes");
|
}
|
|
final int realCallingPid = incomingRealCallingPid != 0
|
? incomingRealCallingPid
|
: Binder.getCallingPid();
|
final int realCallingUid = incomingRealCallingUid != -1
|
? incomingRealCallingUid
|
: Binder.getCallingUid();
|
|
int callingPid;
|
if (callingUid >= 0) {
|
callingPid = -1;
|
} else if (caller == null) {
|
callingPid = realCallingPid;
|
callingUid = realCallingUid;
|
} else {
|
callingPid = callingUid = -1;
|
}
|
final long origId = Binder.clearCallingIdentity();
|
try {
|
intents = ArrayUtils.filterNotNull(intents, Intent[]::new);
|
final ActivityStarter[] starters = new ActivityStarter[intents.length];
|
// Do not hold WM global lock on this loop because when resolving intent, it may
|
// potentially acquire activity manager lock that leads to deadlock.
|
for (int i = 0; i < intents.length; i++) {
|
Intent intent = intents[i];
|
|
// Refuse possible leaked file descriptors.
|
if (intent.hasFileDescriptors()) {
|
throw new IllegalArgumentException("File descriptors passed in Intent");
|
}
|
|
// Don't modify the client's object!
|
intent = new Intent(intent);
|
|
// Collect information about the target of the Intent.
|
ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i],
|
0 /* startFlags */, null /* profilerInfo */, userId,
|
ActivityStarter.computeResolveFilterUid(
|
callingUid, realCallingUid, UserHandle.USER_NULL));
|
aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
|
|
if (aInfo != null && (aInfo.applicationInfo.privateFlags
|
& ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
|
throw new IllegalArgumentException("FLAG_CANT_SAVE_STATE not supported here");
|
}
|
|
final boolean top = i == intents.length - 1;
|
final SafeActivityOptions checkedOptions = top
|
? options
|
: null;
|
starters[i] = obtainStarter(intent, reason)
|
.setCaller(caller)
|
.setResolvedType(resolvedTypes[i])
|
.setActivityInfo(aInfo)
|
.setResultTo(resultTo)
|
.setRequestCode(-1)
|
.setCallingPid(callingPid)
|
.setCallingUid(callingUid)
|
.setCallingPackage(callingPackage)
|
.setRealCallingPid(realCallingPid)
|
.setRealCallingUid(realCallingUid)
|
.setActivityOptions(checkedOptions)
|
.setComponentSpecified(intent.getComponent() != null)
|
|
// Top activity decides on animation being run, so we allow only for the
|
// top one as otherwise an activity below might consume it.
|
.setAllowPendingRemoteAnimationRegistryLookup(top /* allowLookup*/)
|
.setOriginatingPendingIntent(originatingPendingIntent)
|
.setAllowBackgroundActivityStart(allowBackgroundActivityStart);
|
}
|
|
final ActivityRecord[] outActivity = new ActivityRecord[1];
|
// Lock the loop to ensure the activities launched in a sequence.
|
synchronized (mService.mGlobalLock) {
|
for (int i = 0; i < starters.length; i++) {
|
final int startResult = starters[i].setOutActivity(outActivity).execute();
|
if (startResult < START_SUCCESS) {
|
// Abort by error result and recycle unused starters.
|
for (int j = i + 1; j < starters.length; j++) {
|
mFactory.recycle(starters[j]);
|
}
|
return startResult;
|
}
|
resultTo = outActivity[0] != null ? outActivity[0].appToken : null;
|
}
|
}
|
} finally {
|
Binder.restoreCallingIdentity(origId);
|
}
|
|
return START_SUCCESS;
|
}
|
|
void schedulePendingActivityLaunches(long delayMs) {
|
mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
|
Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
|
mHandler.sendMessageDelayed(msg, delayMs);
|
}
|
|
void doPendingActivityLaunches(boolean doResume) {
|
while (!mPendingActivityLaunches.isEmpty()) {
|
final PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
|
final boolean resume = doResume && mPendingActivityLaunches.isEmpty();
|
final ActivityStarter starter = obtainStarter(null /* intent */,
|
"pendingActivityLaunch");
|
try {
|
starter.startResolvedActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags,
|
resume, pal.r.pendingOptions, null);
|
} catch (Exception e) {
|
Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
|
pal.sendErrorResult(e.getMessage());
|
}
|
}
|
}
|
|
void addPendingActivityLaunch(PendingActivityLaunch launch) {
|
mPendingActivityLaunches.add(launch);
|
}
|
|
boolean clearPendingActivityLaunches(String packageName) {
|
final int pendingLaunches = mPendingActivityLaunches.size();
|
|
for (int palNdx = pendingLaunches - 1; palNdx >= 0; --palNdx) {
|
final PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
|
final ActivityRecord r = pal.r;
|
if (r != null && r.packageName.equals(packageName)) {
|
mPendingActivityLaunches.remove(palNdx);
|
}
|
}
|
return mPendingActivityLaunches.size() < pendingLaunches;
|
}
|
|
void registerRemoteAnimationForNextActivityStart(String packageName,
|
RemoteAnimationAdapter adapter) {
|
mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter);
|
}
|
|
PendingRemoteAnimationRegistry getPendingRemoteAnimationRegistry() {
|
return mPendingRemoteAnimationRegistry;
|
}
|
|
void dump(PrintWriter pw, String prefix, String dumpPackage) {
|
pw.print(prefix);
|
pw.print("mLastHomeActivityStartResult=");
|
pw.println(mLastHomeActivityStartResult);
|
|
if (mLastHomeActivityStartRecord != null) {
|
pw.print(prefix);
|
pw.println("mLastHomeActivityStartRecord:");
|
mLastHomeActivityStartRecord.dump(pw, prefix + " ");
|
}
|
|
final boolean dumpPackagePresent = dumpPackage != null;
|
|
if (mLastStarter != null) {
|
final boolean dump = !dumpPackagePresent
|
|| mLastStarter.relatedToPackage(dumpPackage)
|
|| (mLastHomeActivityStartRecord != null
|
&& dumpPackage.equals(mLastHomeActivityStartRecord.packageName));
|
|
if (dump) {
|
pw.print(prefix);
|
mLastStarter.dump(pw, prefix + " ");
|
|
if (dumpPackagePresent) {
|
return;
|
}
|
}
|
}
|
|
if (dumpPackagePresent) {
|
pw.print(prefix);
|
pw.println("(nothing)");
|
}
|
}
|
|
public void writeToProto(ProtoOutputStream proto, long fieldId) {
|
for (PendingActivityLaunch activity: mPendingActivityLaunches) {
|
activity.r.writeIdentifierToProto(proto, fieldId);
|
}
|
}
|
}
|