Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 30a76b53 authored by Hui Yu's avatar Hui Yu Committed by Android (Google) Code Review
Browse files

Merge "Initial implementation of foreground service delegate feature."

parents a89257d6 659beaad
Loading
Loading
Loading
Loading
+247 −14
Original line number Diff line number Diff line
@@ -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;
@@ -308,6 +309,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
@@ -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,6 +5037,20 @@ public final class ActiveServices {
                // Bump the process to the top of LRU list
                mAm.updateLruProcessLocked(r.app, false, null);
                updateServiceForegroundLocked(r.app.mServices, false);
                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);
@@ -4994,6 +5062,7 @@ public final class ActiveServices {
                                + r.shortInstanceName, e);
                        serviceProcessGoneLocked(r, enqueueOomAdj);
                    }
                }
            } else {
                if (DEBUG_SERVICE) Slog.v(
                    TAG_SERVICE, "Removed service that has no process: " + r);
@@ -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");
        }
    }
}
+25 −0
Original line number Diff line number Diff line
@@ -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);
}
+77 −0
Original line number Diff line number Diff line
@@ -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) {
@@ -18313,6 +18337,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
     */
+43 −0
Original line number Diff line number Diff line
@@ -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, "");
        }
+40 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading