blob: f5f2b102596b6aee82b088e29177bb15ffd1c1e5 [file] [log] [blame]
/*
* Copyright (C) 2020 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.am;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.WindowProcessController;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* The state info of all services in the process.
*/
final class ProcessServiceRecord {
/**
* Are there any client services with activities?
*/
private boolean mHasClientActivities;
/**
* Running any services that are foreground?
*/
private boolean mHasForegroundServices;
/**
* Last reported state of whether it's running any services that are foreground.
*/
private boolean mRepHasForegroundServices;
/**
* Running any services that are almost perceptible (started with
* {@link Context#BIND_ALMOST_PERCEPTIBLE} while the app was on TOP)?
*/
private boolean mHasTopStartedAlmostPerceptibleServices;
/**
* The latest value of {@link ServiceRecord#lastTopAlmostPerceptibleBindRequestUptimeMs} among
* the currently running services.
*/
private long mLastTopStartedAlmostPerceptibleBindRequestUptimeMs;
/**
* Service that applied current connectionGroup/Importance.
*/
private ServiceRecord mConnectionService;
/**
* Last group set by a connection.
*/
private int mConnectionGroup;
/**
* Last importance set by a connection.
*/
private int mConnectionImportance;
/**
* The OR'ed foreground service types that are running on this process.
* Note, because TYPE_NONE (==0) is also a valid type for pre-U apps, this field doesn't tell
* if the process has any TYPE_NONE FGS or not, but {@link #mHasTypeNoneFgs} will be set
* in that case.
*/
private int mFgServiceTypes;
/**
* Whether the process has any foreground services of TYPE_NONE running.
* @see #mFgServiceTypes
*/
private boolean mHasTypeNoneFgs;
/**
* Last reported foreground service types.
*/
private int mRepFgServiceTypes;
/**
* Bound using BIND_ABOVE_CLIENT, so want to be lower.
*/
private boolean mHasAboveClient;
/**
* Bound using BIND_TREAT_LIKE_ACTIVITY.
*/
private boolean mTreatLikeActivity;
/**
* Do we need to be executing services in the foreground?
*/
private boolean mExecServicesFg;
/**
* App is allowed to manage allowlists such as temporary Power Save mode allowlist.
*/
boolean mAllowlistManager;
/**
* All ServiceRecord running in this process.
*/
final ArraySet<ServiceRecord> mServices = new ArraySet<>();
/**
* Services that are currently executing code (need to remain foreground).
*/
private final ArraySet<ServiceRecord> mExecutingServices = new ArraySet<>();
/**
* All ConnectionRecord this process holds.
*/
private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>();
/**
* All ConnectionRecord this process holds indirectly to SDK sandbox processes.
*/
private @Nullable ArraySet<ConnectionRecord> mSdkSandboxConnections;
/**
* A set of UIDs of all bound clients.
*/
private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
final ProcessRecord mApp;
private final ActivityManagerService mService;
ProcessServiceRecord(ProcessRecord app) {
mApp = app;
mService = app.mService;
}
void setHasClientActivities(boolean hasClientActivities) {
mHasClientActivities = hasClientActivities;
mApp.getWindowProcessController().setHasClientActivities(hasClientActivities);
}
boolean hasClientActivities() {
return mHasClientActivities;
}
void setHasForegroundServices(boolean hasForegroundServices, int fgServiceTypes,
boolean hasTypeNoneFgs) {
// hasForegroundServices should be the same as "either it has any FGS types, or none types".
// We still take this as a parameter because it's used in the callsite...
if (ActivityManagerDebugConfig.DEBUG_SERVICE
&& hasForegroundServices != ((fgServiceTypes != 0) || hasTypeNoneFgs)) {
throw new IllegalStateException("hasForegroundServices mismatch");
}
mHasForegroundServices = hasForegroundServices;
mFgServiceTypes = fgServiceTypes;
mHasTypeNoneFgs = hasTypeNoneFgs;
mApp.getWindowProcessController().setHasForegroundServices(hasForegroundServices);
if (hasForegroundServices) {
mApp.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE);
} else {
mApp.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE);
}
}
/**
* @return true if this process has any foreground services (even timed-out short-FGS)
*/
boolean hasForegroundServices() {
return mHasForegroundServices;
}
void setHasReportedForegroundServices(boolean hasForegroundServices) {
mRepHasForegroundServices = hasForegroundServices;
}
boolean hasReportedForegroundServices() {
return mRepHasForegroundServices;
}
/**
* Returns the FGS typps, but it doesn't tell if the types include "NONE" or not, so
* do not use it outside of this class.
*/
private int getForegroundServiceTypes() {
return mHasForegroundServices ? mFgServiceTypes : 0;
}
boolean areForegroundServiceTypesSame(@ServiceInfo.ForegroundServiceType int types,
boolean hasTypeNoneFgs) {
return ((getForegroundServiceTypes() & types) == types)
&& (mHasTypeNoneFgs == hasTypeNoneFgs);
}
/**
* @return true if the fgs types includes any of the given types.
* (wouldn't work for TYPE_NONE, which is 0)
*/
boolean containsAnyForegroundServiceTypes(@ServiceInfo.ForegroundServiceType int types) {
return (getForegroundServiceTypes() & types) != 0;
}
/**
* @return true if the process has any FGS that are _not_ a "short" FGS.
*/
boolean hasNonShortForegroundServices() {
if (!mHasForegroundServices) {
return false; // Process has no FGS running.
}
// Does the process has any FGS of TYPE_NONE?
if (mHasTypeNoneFgs) {
return true;
}
// If not, we can just check mFgServiceTypes.
return mFgServiceTypes != ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
/**
* @return if this process:
* - has at least one short-FGS
* - has no other types of FGS
* - and all the short-FGSes are procstate-timed out.
*/
boolean areAllShortForegroundServicesProcstateTimedOut(long nowUptime) {
if (!mHasForegroundServices) { // Process has no FGS?
return false;
}
if (hasNonShortForegroundServices()) { // Any non-short FGS running?
return false;
}
// Now we need to look at all short-FGS within the process and see if all of them are
// procstate-timed-out or not.
for (int i = mServices.size() - 1; i >= 0; i--) {
final ServiceRecord sr = mServices.valueAt(i);
if (!sr.isShortFgs() || !sr.hasShortFgsInfo()) {
continue;
}
if (sr.getShortFgsInfo().getProcStateDemoteTime() >= nowUptime) {
return false;
}
}
return true;
}
int getReportedForegroundServiceTypes() {
return mRepFgServiceTypes;
}
void setReportedForegroundServiceTypes(int foregroundServiceTypes) {
mRepFgServiceTypes = foregroundServiceTypes;
}
int getNumForegroundServices() {
int count = 0;
for (int i = 0, serviceCount = mServices.size(); i < serviceCount; i++) {
if (mServices.valueAt(i).isForeground) {
count++;
}
}
return count;
}
void updateHasTopStartedAlmostPerceptibleServices() {
mHasTopStartedAlmostPerceptibleServices = false;
mLastTopStartedAlmostPerceptibleBindRequestUptimeMs = 0;
for (int s = mServices.size() - 1; s >= 0; --s) {
final ServiceRecord sr = mServices.valueAt(s);
mLastTopStartedAlmostPerceptibleBindRequestUptimeMs = Math.max(
mLastTopStartedAlmostPerceptibleBindRequestUptimeMs,
sr.lastTopAlmostPerceptibleBindRequestUptimeMs);
if (!mHasTopStartedAlmostPerceptibleServices && isAlmostPerceptible(sr)) {
mHasTopStartedAlmostPerceptibleServices = true;
}
}
}
private boolean isAlmostPerceptible(ServiceRecord record) {
if (record.lastTopAlmostPerceptibleBindRequestUptimeMs <= 0) {
return false;
}
final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
record.getConnections();
for (int m = serviceConnections.size() - 1; m >= 0; --m) {
final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(m);
for (int c = clist.size() - 1; c >= 0; --c) {
final ConnectionRecord cr = clist.get(c);
if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)) {
return true;
}
}
}
return false;
}
boolean hasTopStartedAlmostPerceptibleServices() {
return mHasTopStartedAlmostPerceptibleServices
|| (mLastTopStartedAlmostPerceptibleBindRequestUptimeMs > 0
&& SystemClock.uptimeMillis() - mLastTopStartedAlmostPerceptibleBindRequestUptimeMs
< mService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs);
}
ServiceRecord getConnectionService() {
return mConnectionService;
}
void setConnectionService(ServiceRecord connectionService) {
mConnectionService = connectionService;
}
int getConnectionGroup() {
return mConnectionGroup;
}
void setConnectionGroup(int connectionGroup) {
mConnectionGroup = connectionGroup;
}
int getConnectionImportance() {
return mConnectionImportance;
}
void setConnectionImportance(int connectionImportance) {
mConnectionImportance = connectionImportance;
}
void updateHasAboveClientLocked() {
mHasAboveClient = false;
for (int i = mConnections.size() - 1; i >= 0; i--) {
ConnectionRecord cr = mConnections.valueAt(i);
final boolean isSameProcess = cr.binding.service.app != null
&& cr.binding.service.app.mServices == this;
if (!isSameProcess && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
mHasAboveClient = true;
break;
}
}
}
void setHasAboveClient(boolean hasAboveClient) {
mHasAboveClient = hasAboveClient;
}
boolean hasAboveClient() {
return mHasAboveClient;
}
int modifyRawOomAdj(int adj) {
if (mHasAboveClient) {
// If this process has bound to any services with BIND_ABOVE_CLIENT,
// then we need to drop its adjustment to be lower than the service's
// in order to honor the request. We want to drop it by one adjustment
// level... but there is special meaning applied to various levels so
// we will skip some of them.
if (adj < ProcessList.FOREGROUND_APP_ADJ) {
// System process will not get dropped, ever
} else if (adj < ProcessList.VISIBLE_APP_ADJ) {
adj = ProcessList.VISIBLE_APP_ADJ;
} else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
} else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
adj = ProcessList.CACHED_APP_MIN_ADJ;
} else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
adj++;
}
}
return adj;
}
boolean isTreatedLikeActivity() {
return mTreatLikeActivity;
}
void setTreatLikeActivity(boolean treatLikeActivity) {
mTreatLikeActivity = treatLikeActivity;
}
boolean shouldExecServicesFg() {
return mExecServicesFg;
}
void setExecServicesFg(boolean execServicesFg) {
mExecServicesFg = execServicesFg;
}
/**
* Records a service as running in the process. Note that this method does not actually start
* the service, but records the service as started for bookkeeping.
*
* @return true if the service was added, false otherwise.
*/
boolean startService(ServiceRecord record) {
if (record == null) {
return false;
}
boolean added = mServices.add(record);
if (added && record.serviceInfo != null) {
mApp.getWindowProcessController().onServiceStarted(record.serviceInfo);
updateHostingComonentTypeForBindingsLocked();
}
if (record.lastTopAlmostPerceptibleBindRequestUptimeMs > 0) {
mLastTopStartedAlmostPerceptibleBindRequestUptimeMs = Math.max(
mLastTopStartedAlmostPerceptibleBindRequestUptimeMs,
record.lastTopAlmostPerceptibleBindRequestUptimeMs);
if (!mHasTopStartedAlmostPerceptibleServices) {
mHasTopStartedAlmostPerceptibleServices = isAlmostPerceptible(record);
}
}
return added;
}
/**
* Records a service as stopped. Note that like {@link #startService(ServiceRecord)} this method
* does not actually stop the service, but records the service as stopped for bookkeeping.
*
* @return true if the service was removed, false otherwise.
*/
boolean stopService(ServiceRecord record) {
final boolean removed = mServices.remove(record);
if (record.lastTopAlmostPerceptibleBindRequestUptimeMs > 0) {
updateHasTopStartedAlmostPerceptibleServices();
}
if (removed) {
updateHostingComonentTypeForBindingsLocked();
}
return removed;
}
/**
* The same as calling {@link #stopService(ServiceRecord)} on all current running services.
*/
void stopAllServices() {
mServices.clear();
updateHasTopStartedAlmostPerceptibleServices();
}
/**
* Returns the number of services added with {@link #startService(ServiceRecord)} and not yet
* removed by a call to {@link #stopService(ServiceRecord)} or {@link #stopAllServices()}.
*
* @see #startService(ServiceRecord)
* @see #stopService(ServiceRecord)
*/
int numberOfRunningServices() {
return mServices.size();
}
/**
* Returns the service at the specified {@code index}.
*
* @see #numberOfRunningServices()
*/
ServiceRecord getRunningServiceAt(int index) {
return mServices.valueAt(index);
}
void startExecutingService(ServiceRecord service) {
mExecutingServices.add(service);
}
void stopExecutingService(ServiceRecord service) {
mExecutingServices.remove(service);
}
void stopAllExecutingServices() {
mExecutingServices.clear();
}
ServiceRecord getExecutingServiceAt(int index) {
return mExecutingServices.valueAt(index);
}
int numberOfExecutingServices() {
return mExecutingServices.size();
}
void addConnection(ConnectionRecord connection) {
mConnections.add(connection);
addSdkSandboxConnectionIfNecessary(connection);
}
void removeConnection(ConnectionRecord connection) {
mConnections.remove(connection);
removeSdkSandboxConnectionIfNecessary(connection);
}
void removeAllConnections() {
for (int i = 0, size = mConnections.size(); i < size; i++) {
removeSdkSandboxConnectionIfNecessary(mConnections.valueAt(i));
}
mConnections.clear();
}
ConnectionRecord getConnectionAt(int index) {
return mConnections.valueAt(index);
}
int numberOfConnections() {
return mConnections.size();
}
private void addSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
final ProcessRecord attributedClient = connection.binding.attributedClient;
if (attributedClient != null && connection.binding.service.isSdkSandbox) {
if (attributedClient.mServices.mSdkSandboxConnections == null) {
attributedClient.mServices.mSdkSandboxConnections = new ArraySet<>();
}
attributedClient.mServices.mSdkSandboxConnections.add(connection);
}
}
private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
final ProcessRecord attributedClient = connection.binding.attributedClient;
if (attributedClient != null && connection.binding.service.isSdkSandbox) {
if (attributedClient.mServices.mSdkSandboxConnections == null) {
attributedClient.mServices.mSdkSandboxConnections.remove(connection);
}
}
}
void removeAllSdkSandboxConnections() {
if (mSdkSandboxConnections != null) {
mSdkSandboxConnections.clear();
}
}
ConnectionRecord getSdkSandboxConnectionAt(int index) {
return mSdkSandboxConnections != null ? mSdkSandboxConnections.valueAt(index) : null;
}
int numberOfSdkSandboxConnections() {
return mSdkSandboxConnections != null ? mSdkSandboxConnections.size() : 0;
}
void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) {
mBoundClientUids.add(clientUid);
mApp.getWindowProcessController()
.addBoundClientUid(clientUid, clientPackageName, bindFlags);
}
void updateBoundClientUids() {
clearBoundClientUids();
if (mServices.isEmpty()) {
return;
}
// grab a set of clientUids of all mConnections of all services
final ArraySet<Integer> boundClientUids = new ArraySet<>();
final int serviceCount = mServices.size();
WindowProcessController controller = mApp.getWindowProcessController();
for (int j = 0; j < serviceCount; j++) {
final ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns =
mServices.valueAt(j).getConnections();
final int size = conns.size();
for (int conni = 0; conni < size; conni++) {
ArrayList<ConnectionRecord> c = conns.valueAt(conni);
for (int i = 0; i < c.size(); i++) {
ConnectionRecord cr = c.get(i);
boundClientUids.add(cr.clientUid);
controller.addBoundClientUid(cr.clientUid, cr.clientPackageName, cr.getFlags());
}
}
}
mBoundClientUids = boundClientUids;
}
void addBoundClientUidsOfNewService(ServiceRecord sr) {
if (sr == null) {
return;
}
ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = sr.getConnections();
for (int conni = conns.size() - 1; conni >= 0; conni--) {
ArrayList<ConnectionRecord> c = conns.valueAt(conni);
for (int i = 0; i < c.size(); i++) {
ConnectionRecord cr = c.get(i);
mBoundClientUids.add(cr.clientUid);
mApp.getWindowProcessController()
.addBoundClientUid(cr.clientUid, cr.clientPackageName, cr.getFlags());
}
}
}
void clearBoundClientUids() {
mBoundClientUids.clear();
mApp.getWindowProcessController().clearBoundClientUids();
}
@GuardedBy("mService")
void updateHostingComonentTypeForBindingsLocked() {
boolean hasBoundClient = false;
for (int i = numberOfRunningServices() - 1; i >= 0; i--) {
final ServiceRecord sr = getRunningServiceAt(i);
if (sr != null && !sr.getConnections().isEmpty()) {
hasBoundClient = true;
break;
}
}
if (hasBoundClient) {
mApp.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BOUND_SERVICE);
} else {
mApp.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BOUND_SERVICE);
}
}
@GuardedBy("mService")
boolean incServiceCrashCountLocked(long now) {
final boolean procIsBoundForeground = mApp.mState.getCurProcState()
== ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
boolean tryAgain = false;
// Bump up the crash count of any services currently running in the proc.
for (int i = numberOfRunningServices() - 1; i >= 0; i--) {
// Any services running in the application need to be placed
// back in the pending list.
ServiceRecord sr = getRunningServiceAt(i);
// If the service was restarted a while ago, then reset crash count, else increment it.
if (now > sr.restartTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
sr.crashCount = 1;
} else {
sr.crashCount++;
}
// Allow restarting for started or bound foreground services that are crashing.
// This includes wallpapers.
if (sr.crashCount < mService.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
&& (sr.isForeground || procIsBoundForeground)) {
tryAgain = true;
}
}
return tryAgain;
}
@GuardedBy("mService")
void onCleanupApplicationRecordLocked() {
mTreatLikeActivity = false;
mHasAboveClient = false;
setHasClientActivities(false);
}
void dump(PrintWriter pw, String prefix, long nowUptime) {
if (mHasForegroundServices || mApp.mState.getForcingToImportant() != null) {
pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices);
pw.print(" forcingToImportant="); pw.println(mApp.mState.getForcingToImportant());
}
if (mHasTopStartedAlmostPerceptibleServices
|| mLastTopStartedAlmostPerceptibleBindRequestUptimeMs > 0) {
pw.print(prefix); pw.print("mHasTopStartedAlmostPerceptibleServices=");
pw.print(mHasTopStartedAlmostPerceptibleServices);
pw.print(" mLastTopStartedAlmostPerceptibleBindRequestUptimeMs=");
pw.println(mLastTopStartedAlmostPerceptibleBindRequestUptimeMs);
}
if (mHasClientActivities || mHasAboveClient || mTreatLikeActivity) {
pw.print(prefix); pw.print("hasClientActivities="); pw.print(mHasClientActivities);
pw.print(" hasAboveClient="); pw.print(mHasAboveClient);
pw.print(" treatLikeActivity="); pw.println(mTreatLikeActivity);
}
if (mConnectionService != null || mConnectionGroup != 0) {
pw.print(prefix); pw.print("connectionGroup="); pw.print(mConnectionGroup);
pw.print(" Importance="); pw.print(mConnectionImportance);
pw.print(" Service="); pw.println(mConnectionService);
}
if (mAllowlistManager) {
pw.print(prefix); pw.print("allowlistManager="); pw.println(mAllowlistManager);
}
if (mServices.size() > 0) {
pw.print(prefix); pw.println("Services:");
for (int i = 0, size = mServices.size(); i < size; i++) {
pw.print(prefix); pw.print(" - "); pw.println(mServices.valueAt(i));
}
}
if (mExecutingServices.size() > 0) {
pw.print(prefix); pw.print("Executing Services (fg=");
pw.print(mExecServicesFg); pw.println(")");
for (int i = 0, size = mExecutingServices.size(); i < size; i++) {
pw.print(prefix); pw.print(" - "); pw.println(mExecutingServices.valueAt(i));
}
}
if (mConnections.size() > 0) {
pw.print(prefix); pw.println("mConnections:");
for (int i = 0, size = mConnections.size(); i < size; i++) {
pw.print(prefix); pw.print(" - "); pw.println(mConnections.valueAt(i));
}
}
}
}