diff options
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;      }  |