summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java261
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerLocal.java25
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java77
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java43
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceDelegation.java40
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java262
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java11
7 files changed, 704 insertions, 15 deletions
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 86bb699f07d2..2c9da4cc8198 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -137,6 +137,7 @@ import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -309,6 +310,12 @@ public final class ActiveServices {
final ArrayList<ServiceRecord> mPendingFgsNotifications = new ArrayList<>();
/**
+ * Map of ForegroundServiceDelegation to the delegation ServiceRecord. The delegation
+ * ServiceRecord has flag isFgsDelegate set to true.
+ */
+ final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
+
+ /**
* Whether there is a rate limit that suppresses immediate re-deferral of new FGS
* notifications from each app. On by default, disabled only by shell command for
* test-suite purposes. To disable the behavior more generally, use the usual
@@ -3043,7 +3050,7 @@ public final class ActiveServices {
ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
- isBindExternal, allowInstant);
+ isBindExternal, allowInstant, null /* fgsDelegateOptions */);
if (res == null) {
return 0;
}
@@ -3501,7 +3508,7 @@ public final class ActiveServices {
boolean allowInstant) {
return retrieveServiceLocked(service, instanceName, false, 0, null, resolvedType,
callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
- isBindExternal, allowInstant);
+ isBindExternal, allowInstant, null /* fgsDelegateOptions */);
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -3509,7 +3516,7 @@ public final class ActiveServices {
String sdkSandboxClientAppPackage, String resolvedType,
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
- boolean allowInstant) {
+ boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions) {
if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
@@ -3572,6 +3579,53 @@ public final class ActiveServices {
}
}
}
+
+ if (r == null && fgsDelegateOptions != null) {
+ // Create a ServiceRecord for FGS delegate.
+ final ServiceInfo sInfo = new ServiceInfo();
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+ fgsDelegateOptions.mClientPackageName,
+ ActivityManagerService.STOCK_PM_FLAGS,
+ userId);
+ } catch (RemoteException ex) {
+ // pm is in same process, this will never happen.
+ }
+ if (aInfo == null) {
+ throw new SecurityException("startForegroundServiceDelegate failed, "
+ + "could not resolve client package " + callingPackage);
+ }
+ if (aInfo.uid != fgsDelegateOptions.mClientUid) {
+ throw new SecurityException("startForegroundServiceDelegate failed, "
+ + "uid:" + aInfo.uid
+ + " does not match clientUid:" + fgsDelegateOptions.mClientUid);
+ }
+ sInfo.applicationInfo = aInfo;
+ sInfo.packageName = aInfo.packageName;
+ sInfo.mForegroundServiceType = fgsDelegateOptions.mForegroundServiceTypes;
+ sInfo.processName = aInfo.processName;
+ final ComponentName cn = service.getComponent();
+ sInfo.name = cn.getClassName();
+ if (createIfNeeded) {
+ final Intent.FilterComparison filter =
+ new Intent.FilterComparison(service.cloneFilter());
+ final ServiceRestarter res = new ServiceRestarter();
+ r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
+ sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
+ callingFromFg, res, null /* sdkSandboxProcessName */,
+ INVALID_UID /* sdkSandboxClientAppUid */,
+ null /* sdkSandboxClientAppPackage */);
+ res.setService(r);
+ smap.mServicesByInstanceName.put(cn, r);
+ smap.mServicesByIntent.put(filter, r);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Retrieve created new service: " + r);
+ r.mRecentCallingPackage = callingPackage;
+ r.mRecentCallingUid = callingUid;
+ }
+ return new ServiceLookupResult(r, resolution.getAlias());
+ }
+
if (r == null) {
try {
int flags = ActivityManagerService.STOCK_PM_FLAGS
@@ -4983,16 +5037,31 @@ public final class ActiveServices {
// Bump the process to the top of LRU list
mAm.updateLruProcessLocked(r.app, false, null);
updateServiceForegroundLocked(r.app.mServices, false);
- try {
- oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
- mDestroyingServices.add(r);
- r.destroying = true;
- r.app.getThread().scheduleStopService(r);
- } catch (Exception e) {
- Slog.w(TAG, "Exception when destroying service "
- + r.shortInstanceName, e);
- serviceProcessGoneLocked(r, enqueueOomAdj);
+ if (r.mIsFgsDelegate) {
+ if (r.mFgsDelegation.mConnection != null) {
+ mAm.mHandler.post(() -> {
+ r.mFgsDelegation.mConnection.onServiceDisconnected(
+ r.mFgsDelegation.mOptions.getComponentName());
+ });
+ }
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ if (mFgsDelegations.valueAt(i) == r) {
+ mFgsDelegations.removeAt(i);
+ break;
+ }
+ }
+ } else {
+ try {
+ oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
+ oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mDestroyingServices.add(r);
+ r.destroying = true;
+ r.app.getThread().scheduleStopService(r);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception when destroying service "
+ + r.shortInstanceName, e);
+ serviceProcessGoneLocked(r, enqueueOomAdj);
+ }
}
} else {
if (DEBUG_SERVICE) Slog.v(
@@ -7234,7 +7303,12 @@ public final class ActiveServices {
ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
- fgsTypeCheckCode);
+ fgsTypeCheckCode,
+ r.mIsFgsDelegate,
+ r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID,
+ r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
+ : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT
+ );
int event = 0;
if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
@@ -7298,4 +7372,163 @@ public final class ActiveServices {
return "UNKNOWN";
}
}
+
+ /**
+ * Start a foreground service delegate. The delegate is not an actual service component, it is
+ * merely a delegate that promotes the client process into foreground service process state.
+ *
+ * @param options an ForegroundServiceDelegationOptions object.
+ * @param connection callback if the delegate is started successfully.
+ * @return true if delegate is started, false otherwise.
+ * @throw SecurityException if PackageManaager can not resolve
+ * {@link ForegroundServiceDelegationOptions#mClientPackageName} or the resolved
+ * package's UID is not same as {@link ForegroundServiceDelegationOptions#mClientUid}
+ */
+ boolean startForegroundServiceDelegateLocked(
+ @NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ Slog.v(TAG, "startForegroundServiceDelegateLocked " + options.getDescription());
+ final ComponentName cn = options.getComponentName();
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ ForegroundServiceDelegation delegation = mFgsDelegations.keyAt(i);
+ if (delegation.mOptions.isSameDelegate(options)) {
+ Slog.e(TAG, "startForegroundServiceDelegate " + options.getDescription()
+ + " already exists, multiple connections are not allowed");
+ return false;
+ }
+ }
+ final int callingPid = options.mClientPid;
+ final int callingUid = options.mClientUid;
+ final int userId = UserHandle.getUserId(callingUid);
+ final String callingPackage = options.mClientPackageName;
+
+ if (!canStartForegroundServiceLocked(callingPid, callingUid, callingPackage)) {
+ Slog.d(TAG, "startForegroundServiceDelegateLocked aborted,"
+ + " app is in the background");
+ return false;
+ }
+
+ IApplicationThread caller = options.mClientAppThread;
+ ProcessRecord callerApp;
+ if (caller != null) {
+ callerApp = mAm.getRecordForAppLOSP(caller);
+ } else {
+ synchronized (mAm.mPidsSelfLocked) {
+ callerApp = mAm.mPidsSelfLocked.get(callingPid);
+ caller = callerApp.getThread();
+ }
+ }
+ if (callerApp == null) {
+ throw new SecurityException(
+ "Unable to find app for caller " + caller
+ + " (pid=" + callingPid
+ + ") when startForegroundServiceDelegateLocked " + cn);
+ }
+
+ Intent intent = new Intent();
+ intent.setComponent(cn);
+ ServiceLookupResult res = retrieveServiceLocked(intent, null /*instanceName */,
+ false /* isSdkSandboxService */, INVALID_UID /* sdkSandboxClientAppUid */,
+ null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
+ callingPid, callingUid, userId, true /* createIfNeeded */,
+ false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
+ options);
+ if (res == null || res.record == null) {
+ Slog.d(TAG,
+ "startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
+ return false;
+ }
+
+ final ServiceRecord r = res.record;
+ r.setProcess(callerApp, caller, callingPid, null);
+ r.mIsFgsDelegate = true;
+ final ForegroundServiceDelegation delegation =
+ new ForegroundServiceDelegation(options, connection);
+ r.mFgsDelegation = delegation;
+ mFgsDelegations.put(delegation, r);
+ r.isForeground = true;
+ r.mFgsEnterTime = SystemClock.uptimeMillis();
+ r.foregroundServiceType = options.mForegroundServiceTypes;
+ setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
+ false, false);
+ final ProcessServiceRecord psr = callerApp.mServices;
+ final boolean newService = psr.startService(r);
+ // updateOomAdj.
+ updateServiceForegroundLocked(psr, /* oomAdj= */ true);
+
+ synchronized (mAm.mProcessStats.mLock) {
+ final ServiceState stracker = r.getTracker();
+ if (stracker != null) {
+ stracker.setForeground(true,
+ mAm.mProcessStats.getMemFactorLocked(),
+ SystemClock.uptimeMillis());
+ }
+ }
+
+ mAm.mBatteryStatsService.noteServiceStartRunning(callingUid, callingPackage,
+ cn.getClassName());
+ mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+ AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
+ true, false, null, false,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ registerAppOpCallbackLocked(r);
+ logFGSStateChangeLocked(r,
+ FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
+ // Notify the caller.
+ if (connection != null) {
+ mAm.mHandler.post(() -> {
+ connection.onServiceConnected(cn, delegation.mBinder);
+ });
+ }
+ signalForegroundServiceObserversLocked(r);
+ return true;
+ }
+
+ /**
+ * Stop the foreground service delegate. This removes the process out of foreground service
+ * process state.
+ *
+ * @param options an ForegroundServiceDelegationOptions object.
+ */
+ void stopForegroundServiceDelegateLocked(@NonNull ForegroundServiceDelegationOptions options) {
+ ServiceRecord r = null;
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ if (mFgsDelegations.keyAt(i).mOptions.isSameDelegate(options)) {
+ Slog.d(TAG, "stopForegroundServiceDelegateLocked " + options.getDescription());
+ r = mFgsDelegations.valueAt(i);
+ break;
+ }
+ }
+ if (r != null) {
+ bringDownServiceLocked(r, false);
+ } else {
+ Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist "
+ + options.getDescription());
+ }
+ }
+
+ /**
+ * Stop the foreground service delegate by its ServiceConnection.
+ * This removes the process out of foreground service process state.
+ *
+ * @param connection an ServiceConnection object.
+ */
+ void stopForegroundServiceDelegateLocked(@NonNull ServiceConnection connection) {
+ ServiceRecord r = null;
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ final ForegroundServiceDelegation d = mFgsDelegations.keyAt(i);
+ if (d.mConnection == connection) {
+ Slog.d(TAG, "stopForegroundServiceDelegateLocked "
+ + d.mOptions.getDescription());
+ r = mFgsDelegations.valueAt(i);
+ break;
+ }
+ }
+ if (r != null) {
+ bringDownServiceLocked(r, false);
+ } else {
+ Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 1d2c36b63bda..9f2cc7f9cb44 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
@@ -92,4 +93,28 @@ public interface ActivityManagerLocal {
int clientAppUid, @NonNull String clientAppPackage, @NonNull String processName,
@Context.BindServiceFlags int flags)
throws RemoteException;
+
+ /**
+ * Start a foreground service delegate.
+ * @param options foreground service delegate options.
+ * @param connection a service connection served as callback to caller.
+ * @return true if delegate is started successfully, false otherwise.
+ * @hide
+ */
+ boolean startForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection);
+
+ /**
+ * Stop a foreground service delegate.
+ * @param options the foreground service delegate options.
+ * @hide
+ */
+ void stopForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options);
+
+ /**
+ * Stop a foreground service delegate by service connection.
+ * @param connection service connection used to start delegate previously.
+ * @hide
+ */
+ void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 28f5bf3f718d..9ea9e34f58a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18125,6 +18125,30 @@ public class ActivityManagerService extends IActivityManager.Stub
mUidObserverController.register(observer, which, cutpoint, callingPackage,
Binder.getCallingUid());
}
+
+ @Override
+ public boolean startForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ synchronized (ActivityManagerService.this) {
+ return mServices.startForegroundServiceDelegateLocked(options, connection);
+ }
+ }
+
+ @Override
+ public void stopForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options) {
+ synchronized (ActivityManagerService.this) {
+ mServices.stopForegroundServiceDelegateLocked(options);
+ }
+ }
+
+ @Override
+ public void stopForegroundServiceDelegate(@NonNull ServiceConnection connection) {
+ synchronized (ActivityManagerService.this) {
+ mServices.stopForegroundServiceDelegateLocked(connection);
+ }
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18314,6 +18338,59 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Start/stop foreground service delegate on a app's process.
+ * This interface is intended for the shell command to use.
+ */
+ void setForegroundServiceDelegate(String packageName, int uid, boolean isStart,
+ @ForegroundServiceDelegationOptions.DelegationService int delegateService,
+ String clientInstanceName) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != SYSTEM_UID && callingUid != ROOT_UID && callingUid != SHELL_UID) {
+ throw new SecurityException(
+ "No permission to start/stop foreground service delegate");
+ }
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ boolean foundPid = false;
+ synchronized (this) {
+ ArrayList<ForegroundServiceDelegationOptions> delegates = new ArrayList<>();
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+ final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+ final IApplicationThread thread = p.getThread();
+ if (p.uid == uid && thread != null) {
+ foundPid = true;
+ int pid = mPidsSelfLocked.keyAt(i);
+ ForegroundServiceDelegationOptions options =
+ new ForegroundServiceDelegationOptions(pid, uid, packageName,
+ null /* clientAppThread */,
+ false /* isSticky */,
+ clientInstanceName, 0 /* foregroundServiceType */,
+ delegateService);
+ delegates.add(options);
+ }
+ }
+ }
+ for (int i = delegates.size() - 1; i >= 0; i--) {
+ final ForegroundServiceDelegationOptions options = delegates.get(i);
+ if (isStart) {
+ ((ActivityManagerLocal) mInternal).startForegroundServiceDelegate(options,
+ null /* connection */);
+ } else {
+ ((ActivityManagerLocal) mInternal).stopForegroundServiceDelegate(options);
+ }
+ }
+ }
+ if (!foundPid) {
+ Slog.e(TAG, "setForegroundServiceDelegate can not find process for packageName:"
+ + packageName + " uid:" + uid);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
* Force the settings cache to be loaded
*/
void refreshSettingsCache() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e4f947da868e..10f5a369824f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -369,6 +369,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runResetDropboxRateLimiter();
case "list-secondary-displays-for-starting-users":
return runListSecondaryDisplaysForStartingUsers(pw);
+ case "set-foreground-service-delegate":
+ return runSetForegroundServiceDelegate(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -3592,6 +3594,45 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
+ int runSetForegroundServiceDelegate(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ final String packageName = getNextArgRequired();
+ final String action = getNextArgRequired();
+ boolean isStart = true;
+ if ("start".equals(action)) {
+ isStart = true;
+ } else if ("stop".equals(action)) {
+ isStart = false;
+ } else {
+ pw.println("Error: action is either start or stop");
+ return -1;
+ }
+
+ int uid = INVALID_UID;
+ try {
+ final PackageManager pm = mInternal.mContext.getPackageManager();
+ uid = pm.getPackageUidAsUser(packageName,
+ PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Error: userId:" + userId + " package:" + packageName + " is not found");
+ return -1;
+ }
+ mInternal.setForegroundServiceDelegate(packageName, uid, isStart,
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SPECIAL_USE,
+ "FgsDelegate");
+ return 0;
+ }
+
int runResetDropboxRateLimiter() throws RemoteException {
mInternal.resetDropboxRateLimiter();
return 0;
@@ -3968,6 +4009,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" list-secondary-displays-for-starting-users");
pw.println(" Lists the id of displays that can be used to start users on "
+ "background.");
+ pw.println(" set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
+ pw.println(" Start/stop an app's foreground service delegate.");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegation.java b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
new file mode 100644
index 000000000000..a051d174e1a5
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * A foreground service delegate which has client options and connection callback.
+ */
+public class ForegroundServiceDelegation {
+ public final IBinder mBinder = new Binder();
+ @NonNull
+ public final ForegroundServiceDelegationOptions mOptions;
+ @Nullable
+ public final ServiceConnection mConnection;
+
+ public ForegroundServiceDelegation(@NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ mOptions = options;
+ mConnection = connection;
+ }
+}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
new file mode 100644
index 000000000000..5eb5a55d2c77
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 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 android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.IApplicationThread;
+import android.content.ComponentName;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A service module such as MediaSessionService, VOIP, Camera, Microphone, Location can ask
+ * ActivityManagerService to start a foreground service delegate on behalf of the actual app,
+ * by which the client app's process state can be promoted to FOREGROUND_SERVICE process state which
+ * is higher than the app's actual process state if the app is in the background. This can help to
+ * keep the app in the memory and extra run-time.
+ * The app does not need to define an actual service component nor add it into manifest file.
+ */
+public class ForegroundServiceDelegationOptions {
+
+ public static final int DELEGATION_SERVICE_DEFAULT = 0;
+ public static final int DELEGATION_SERVICE_DATA_SYNC = 1;
+ public static final int DELEGATION_SERVICE_MEDIA_PLAYBACK = 2;
+ public static final int DELEGATION_SERVICE_PHONE_CALL = 3;
+ public static final int DELEGATION_SERVICE_LOCATION = 4;
+ public static final int DELEGATION_SERVICE_CONNECTED_DEVICE = 5;
+ public static final int DELEGATION_SERVICE_MEDIA_PROJECTION = 6;
+ public static final int DELEGATION_SERVICE_CAMERA = 7;
+ public static final int DELEGATION_SERVICE_MICROPHONE = 8;
+ public static final int DELEGATION_SERVICE_HEALTH = 9;
+ public static final int DELEGATION_SERVICE_REMOTE_MESSAGING = 10;
+ public static final int DELEGATION_SERVICE_SYSTEM_EXEMPTED = 11;
+ public static final int DELEGATION_SERVICE_SPECIAL_USE = 12;
+
+ @IntDef(flag = false, prefix = { "DELEGATION_SERVICE_" }, value = {
+ DELEGATION_SERVICE_DEFAULT,
+ DELEGATION_SERVICE_DATA_SYNC,
+ DELEGATION_SERVICE_MEDIA_PLAYBACK,
+ DELEGATION_SERVICE_PHONE_CALL,
+ DELEGATION_SERVICE_LOCATION,
+ DELEGATION_SERVICE_CONNECTED_DEVICE,
+ DELEGATION_SERVICE_MEDIA_PROJECTION,
+ DELEGATION_SERVICE_CAMERA,
+ DELEGATION_SERVICE_MICROPHONE,
+ DELEGATION_SERVICE_HEALTH,
+ DELEGATION_SERVICE_REMOTE_MESSAGING,
+ DELEGATION_SERVICE_SYSTEM_EXEMPTED,
+ DELEGATION_SERVICE_SPECIAL_USE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DelegationService {}
+
+ // The actual app's PID
+ public final int mClientPid;
+ // The actual app's UID
+ public final int mClientUid;
+ // The actual app's package name
+ @NonNull
+ public final String mClientPackageName;
+ // The actual app's app thread
+ @Nullable
+ public final IApplicationThread mClientAppThread;
+ public final boolean mSticky; // Is it a sticky service
+
+ // The delegation service's instance name which is to identify the delegate.
+ @NonNull
+ public String mClientInstanceName;
+ // The foreground service types it consists of.
+ public final int mForegroundServiceTypes;
+ /**
+ * The service's name such as MediaSessionService, VOIP, Camera, Microphone, Location. This is
+ * the internal module's name which actually starts the FGS delegate on behalf of the client
+ * app.
+ */
+ public final @DelegationService int mDelegationService;
+
+ public ForegroundServiceDelegationOptions(int clientPid,
+ int clientUid,
+ @NonNull String clientPackageName,
+ @NonNull IApplicationThread clientAppThread,
+ boolean isSticky,
+ @NonNull String clientInstanceName,
+ int foregroundServiceTypes,
+ @DelegationService int delegationService) {
+ mClientPid = clientPid;
+ mClientUid = clientUid;
+ mClientPackageName = clientPackageName;
+ mClientAppThread = clientAppThread;
+ mSticky = isSticky;
+ mClientInstanceName = clientInstanceName;
+ mForegroundServiceTypes = foregroundServiceTypes;
+ mDelegationService = delegationService;
+ }
+
+ /**
+ * A service delegates a foreground service state to a clientUID using a instanceName.
+ * This delegation is uniquely identified by
+ * mDelegationService/mClientUid/mClientPid/mClientInstanceName
+ */
+ public boolean isSameDelegate(ForegroundServiceDelegationOptions that) {
+ return this.mDelegationService == that.mDelegationService
+ && this.mClientUid == that.mClientUid
+ && this.mClientPid == that.mClientPid
+ && this.mClientInstanceName.equals(that.mClientInstanceName);
+ }
+
+ /**
+ * Construct a component name for this delegate.
+ */
+ public ComponentName getComponentName() {
+ return new ComponentName(mClientPackageName, serviceCodeToString(mDelegationService)
+ + ":" + mClientInstanceName);
+ }
+
+ /**
+ * Get string description of this delegate options.
+ */
+ public String getDescription() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ForegroundServiceDelegate{")
+ .append("package:")
+ .append(mClientPackageName)
+ .append(",")
+ .append("service:")
+ .append(serviceCodeToString(mDelegationService))
+ .append(",")
+ .append("uid:")
+ .append(mClientUid)
+ .append(",")
+ .append("pid:")
+ .append(mClientPid)
+ .append(",")
+ .append("instance:")
+ .append(mClientInstanceName)
+ .append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Map the integer service code to string name.
+ * @param serviceCode
+ * @return
+ */
+ public static String serviceCodeToString(@DelegationService int serviceCode) {
+ switch (serviceCode) {
+ case DELEGATION_SERVICE_DEFAULT:
+ return "DEFAULT";
+ case DELEGATION_SERVICE_DATA_SYNC:
+ return "DATA_SYNC";
+ case DELEGATION_SERVICE_MEDIA_PLAYBACK:
+ return "MEDIA_PLAYBACK";
+ case DELEGATION_SERVICE_PHONE_CALL:
+ return "PHONE_CALL";
+ case DELEGATION_SERVICE_LOCATION:
+ return "LOCATION";
+ case DELEGATION_SERVICE_CONNECTED_DEVICE:
+ return "CONNECTED_DEVICE";
+ case DELEGATION_SERVICE_MEDIA_PROJECTION:
+ return "MEDIA_PROJECTION";
+ case DELEGATION_SERVICE_CAMERA:
+ return "CAMERA";
+ case DELEGATION_SERVICE_MICROPHONE:
+ return "MICROPHONE";
+ case DELEGATION_SERVICE_HEALTH:
+ return "HEALTH";
+ case DELEGATION_SERVICE_REMOTE_MESSAGING:
+ return "REMOTE_MESSAGING";
+ case DELEGATION_SERVICE_SYSTEM_EXEMPTED:
+ return "SYSTEM_EXEMPTED";
+ case DELEGATION_SERVICE_SPECIAL_USE:
+ return "SPECIAL_USE";
+ default:
+ return "(unknown:" + serviceCode + ")";
+ }
+ }
+
+ public static class Builder {
+ int mClientPid; // The actual app PID
+ int mClientUid; // The actual app UID
+ String mClientPackageName; // The actual app's package name
+ int mClientNotificationId; // The actual app's notification
+ IApplicationThread mClientAppThread; // The actual app's app thread
+ boolean mSticky; // Is it a sticky service
+ String mClientInstanceName; // The delegation service instance name
+ int mForegroundServiceTypes; // The foreground service types it consists of
+ @DelegationService int mDelegationService; // The internal service's name, i.e. VOIP
+
+ public Builder setClientPid(int clientPid) {
+ mClientPid = clientPid;
+ return this;
+ }
+
+ public Builder setClientUid(int clientUid) {
+ mClientUid = clientUid;
+ return this;
+ }
+
+ public Builder setClientPackageName(@NonNull String clientPackageName) {
+ mClientPackageName = clientPackageName;
+ return this;
+ }
+
+ public Builder setClientNotificationId(int clientNotificationId) {
+ mClientNotificationId = clientNotificationId;
+ return this;
+ }
+
+ public Builder setClientAppThread(@NonNull IApplicationThread clientAppThread) {
+ mClientAppThread = clientAppThread;
+ return this;
+ }
+
+ public Builder setClientInstanceName(@NonNull String clientInstanceName) {
+ mClientInstanceName = clientInstanceName;
+ return this;
+ }
+
+ public Builder setSticky(boolean isSticky) {
+ mSticky = isSticky;
+ return this;
+ }
+
+ public Builder setForegroundServiceTypes(int foregroundServiceTypes) {
+ mForegroundServiceTypes = foregroundServiceTypes;
+ return this;
+ }
+
+ public Builder setDelegationService(@DelegationService int delegationService) {
+ mDelegationService = delegationService;
+ return this;
+ }
+
+ public ForegroundServiceDelegationOptions build() {
+ return new ForegroundServiceDelegationOptions(mClientPid,
+ mClientUid,
+ mClientPackageName,
+ mClientAppThread,
+ mSticky,
+ mClientInstanceName,
+ mForegroundServiceTypes,
+ mDelegationService
+ );
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4b82ad863c8e..457bf91c4994 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -206,6 +206,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set.
long mLastSetFgsRestrictionTime;
+ // This is a service record of a FGS delegate (not a service record of a real service)
+ boolean mIsFgsDelegate;
+ @Nullable ForegroundServiceDelegation mFgsDelegation;
+
String stringName; // caching of toString
private int lastStartId; // identifier of most recent start request.
@@ -502,6 +506,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
pw.print(" foregroundId="); pw.print(foregroundId);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
}
+ if (mIsFgsDelegate) {
+ pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
+ }
pw.print(prefix); pw.print("createTime=");
TimeUtils.formatDuration(createRealTime, nowReal, pw);
pw.print(" startingBgTimeout=");
@@ -634,7 +641,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
serviceInfo.applicationInfo.uid,
serviceInfo.applicationInfo.longVersionCode,
serviceInfo.processName, serviceInfo.name);
- tracker.applyNewOwner(this);
+ if (tracker != null) {
+ tracker.applyNewOwner(this);
+ }
}
return tracker;
}