diff options
| author | 2022-11-16 21:48:13 +0000 | |
|---|---|---|
| committer | 2022-11-16 21:48:13 +0000 | |
| commit | 30a76b53abc60492c52630d0d8b94ececb7febb5 (patch) | |
| tree | 531b6408d92c9e8367f963796b3f8674d2300b3c | |
| parent | a89257d668915c73a1b8c2216875eed394c4a3df (diff) | |
| parent | 659beaad8ed5567409b17d109e03f16b1b62780f (diff) | |
Merge "Initial implementation of foreground service delegate feature."
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; } |