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

Commit b08eb3ec authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "SHORT_SERVICE: Step 2: Connect to Service.onTimeout()."

parents 2b2cfe57 1ed041cd
Loading
Loading
Loading
Loading
+121 −21
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
@@ -1286,6 +1287,8 @@ public final class ActiveServices {
                return;
            }

            maybeStopShortFgsTimeoutLocked(service);

            final int uid = service.appInfo.uid;
            final String packageName = service.name.getPackageName();
            final String serviceName = service.name.getClassName();
@@ -1469,6 +1472,8 @@ public final class ActiveServices {
                }
            }

            maybeStopShortFgsTimeoutLocked(r);

            final int uid = r.appInfo.uid;
            final String packageName = r.name.getPackageName();
            final String serviceName = r.name.getClassName();
@@ -1836,6 +1841,14 @@ public final class ActiveServices {
                        +  String.format("0x%08X", manifestType)
                        + " in service element of manifest file");
                }
                if ((foregroundServiceType & FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) != 0
                        && foregroundServiceType != FOREGROUND_SERVICE_TYPE_SHORT_SERVICE) {
                    Slog.w(TAG_SERVICE, "startForeground(): FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
                            + " is combined with other types. SHORT_SERVICE will be ignored.");
                    // In this case, the service will be handled as a non-short, regular FGS
                    // anyway, so we just remove the SHORT_SERVICE type.
                    foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
                }
            }

            boolean alreadyStartedOp = false;
@@ -1886,14 +1899,21 @@ public final class ActiveServices {

                int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
                if (!ignoreForeground) {
                    // TODO(short-service): There's a known long-standing bug that allows
                    // a abound service to become "foreground" if setForeground() is called
                    // (without actually "starting" it).
                    // Unfortunately we can't just "fix" it because some apps are relying on it,
                    // but this will cause a problem to short-fgs, so we should disallow it if
                    // this happens and the type is SHORT_SERVICE.
                    //
                    // OTOH, if a valid short-service (which has to be "started"), happens to
                    if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
                            && !r.startRequested) {
                        // There's a long standing bug that allows a bound service to become
                        // a foreground service *even when it's not started*.
                        // Unfortunately, there are apps relying on this behavior, so we can't just
                        // suddenly disallow it.
                        // However, this would be very problematic if used with a short-FGS, so we
                        // explicitly disallow this combination.
                        // TODO(short-service): Change to another exception type?
                        throw new IllegalStateException(
                                "startForeground(SHORT_SERVICE) called on a service that's not"
                                + " started.");
                    }

                    // If a valid short-service (which has to be "started"), happens to
                    // also be bound, then we still _will_ apply a timeout, because it still has
                    // to be stopped.
                    if (r.mStartForegroundCount == 0) {
@@ -1944,12 +1964,20 @@ public final class ActiveServices {
                        //     If the transition is _not_ allowed... keep the timeout?
                        //
                        //   B. Short -> Short:
                        //     This should be the same as case A
                        //     If this is allowed, the new timeout should start.
                        //     Allowed, but the timeout won't reset. The original timeout is used.
                        //   C. Other -> short:
                        //     This should always be allowed.
                        //     A timeout should start.

                        // For now, let's just disallow transition from / to SHORT_SERVICE.
                        final boolean isNewTypeShortFgs =
                                foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
                        if (r.isShortFgs() != isNewTypeShortFgs) {
                            // TODO(short-service): We should (probably) allow it.
                            throw new IllegalArgumentException("Changing FGS type from / to "
                                    + " SHORT_SERVICE is now allowed");
                        }

                        // The second or later time startForeground() is called after service is
                        // started. Check for app state again.
                        setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
@@ -2106,8 +2134,11 @@ public final class ActiveServices {
                    mAm.notifyPackageUse(r.serviceInfo.packageName,
                            PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);

                    // TODO(short-service): Start counting a timeout.

                    // Note, we'll get here if setForeground(SHORT_SERVICE) is called on a
                    // already short-fgs.
                    // In that case, because ShortFgsInfo is already set, this method
                    // will be noop.
                    maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r);
                } else {
                    if (DEBUG_FOREGROUND_SERVICE) {
                        Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2141,7 +2172,7 @@ public final class ActiveServices {
                    decActiveForegroundAppLocked(smap, r);
                }

                // TODO(short-service): Stop the timeout. (any better place to do it?)
                maybeStopShortFgsTimeoutLocked(r);

                // Adjust notification handling before setting isForeground to false, because
                // that state is relevant to the notification policy side.
@@ -2886,6 +2917,74 @@ public final class ActiveServices {
        psr.setHasReportedForegroundServices(anyForeground);
    }

    void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
    }

    /**
     * If {@code sr} is of a short-fgs, start a short-FGS timeout.
     */
    private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr) {
        if (!sr.isShortFgs()) {
            return;
        }
        if (DEBUG_SHORT_SERVICE) {
            Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
        }
        if (sr.hasShortFgsInfo()) {
            sr.getShortFgsInfo().update();
        } else {
            sr.setShortFgsInfo(SystemClock.uptimeMillis());
        }
        unscheduleShortFgsTimeoutLocked(sr); // Do it just in case

        final Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
        mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getTimeoutTime());
    }

    /**
     * Stop the timeout for a ServiceRecord, if it's of a short-FGS.
     */
    private void maybeStopShortFgsTimeoutLocked(ServiceRecord sr) {
        if (DEBUG_SHORT_SERVICE) {
            Slog.i(TAG_SERVICE, "Stop short FGS timeout: " + sr);
        }
        sr.clearShortFgsInfo();
        unscheduleShortFgsTimeoutLocked(sr);
    }

    void onShortFgsTimeout(ServiceRecord sr) {
        synchronized (mAm) {
            if (!sr.shouldTriggerShortFgsTimeout()) {
                return;
            }
            Slog.i(TAG_SERVICE, "Short FGS timed out: " + sr);
            try {
                sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId());
            } catch (RemoteException e) {
                // TODO(short-service): Anything to do here?
            }
            // Schedule the ANR timeout.
            final Message msg = mAm.mHandler.obtainMessage(
                    ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
            mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
        }
    }

    void onShortFgsAnrTimeout(ServiceRecord sr) {
        // TODO(short-service): Implement ANR, and change the WTF to e().
        // Also make sure to call waitingOnAMSLockStarted().
        synchronized (mAm) {
            if (!sr.shouldTriggerShortFgsAnr()) {
                return;
            }
        }

        Slog.wtf(TAG_SERVICE, "Short FGS ANR'ed (NOT IMPLEMENTED YET): " + sr);
    }

    private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
        psr.mAllowlistManager = false;
        for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
@@ -2897,6 +2996,7 @@ public final class ActiveServices {
        }
    }

    // TODO(short-service): Hmm what is it? Should we stop the timeout here?
    private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
        final ProcessServiceRecord psr = service.app.mServices;
        psr.stopService(service);
@@ -4178,7 +4278,7 @@ public final class ActiveServices {
    /**
     * Reschedule service restarts based on if the extra delays are enabled or not.
     *
     * @param prevEnable The previous state of whether or not it's enabled.
     * @param prevEnabled The previous state of whether or not it's enabled.
     * @param curEnabled The current state of whether or not it's enabled.
     * @param now The uptimeMillis
     */
@@ -4863,13 +4963,6 @@ public final class ActiveServices {
            Slog.i(TAG, "Bring down service for " + debugReason + " :" + r.toString());
        }

        // TODO(short-service): Hmm, when the app stops a short-fgs, we should stop the timeout
        // here.
        // However we have a couple if's here and if these conditions are met, we stop here
        // without bringing down the service.
        // We need to make sure this can't be used (somehow) to keep having a short-FGS running
        // while having the timeout stopped.

        if (isServiceNeededLocked(r, knowConn, hasConn)) {
            return;
        }
@@ -4886,6 +4979,13 @@ public final class ActiveServices {
        //Slog.i(TAG, "Bring down service:");
        //r.dump("  ");

        if (r.isShortFgs()) {
            // FGS can be stopped without the app calling stopService() or stopSelf(),
            // due to force-app-standby, or from Task Manager.
            Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
            maybeStopShortFgsTimeoutLocked(r);
        }

        // Report to all of the connections that the service is no longer
        // available.
        ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
+3 −3
Original line number Diff line number Diff line
@@ -954,7 +954,7 @@ final class ActivityManagerConstants extends ContentObserver {
    static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 60_000;

    /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
    public static volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
    public volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;

    /**
     * If a "short service" doesn't finish within this after the timeout (
@@ -967,7 +967,7 @@ final class ActivityManagerConstants extends ContentObserver {
    static final long DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION = 5_000;

    /** @see #KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION */
    public static volatile long mShortFgsProcStateExtraWaitDuration =
    public volatile long mShortFgsProcStateExtraWaitDuration =
            DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION;

    /**
@@ -983,7 +983,7 @@ final class ActivityManagerConstants extends ContentObserver {
    static final long DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;

    /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
    public static volatile long mShortFgsAnrExtraWaitDuration =
    public volatile long mShortFgsAnrExtraWaitDuration =
            DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;

    private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
+8 −0
Original line number Diff line number Diff line
@@ -1563,6 +1563,8 @@ public class ActivityManagerService extends IActivityManager.Stub
    static final int WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG = 73;
    static final int DISPATCH_SENDING_BROADCAST_EVENT = 74;
    static final int DISPATCH_BINDING_SERVICE_EVENT = 75;
    static final int SERVICE_SHORT_FGS_TIMEOUT_MSG = 76;
    static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 77;
    static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1897,6 +1899,12 @@ public class ActivityManagerService extends IActivityManager.Stub
                    mBindServiceEventListeners.forEach(l ->
                            l.onBindingService((String) msg.obj, msg.arg1));
                } break;
                case SERVICE_SHORT_FGS_TIMEOUT_MSG: {
                    mServices.onShortFgsTimeout((ServiceRecord) msg.obj);
                } break;
                case SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG: {
                    mServices.onShortFgsAnrTimeout((ServiceRecord) msg.obj);
                } break;
            }
        }
    }
+8 −1
Original line number Diff line number Diff line
@@ -1818,7 +1818,14 @@ public class OomAdjuster {
                newAdj = PERCEPTIBLE_APP_ADJ;
                newProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;

            } else if (psr.hasForegroundServices() && !psr.hasNonShortForegroundServices()) {
            } else if (psr.hasForegroundServices()) {
                // If we get here, hasNonShortForegroundServices() must be false.

                // TODO(short-service): If all the short-fgs (there may be multiple) within the
                // process is post proc-state-demote time, then we need to skip this part.
                // ... Or, should we just ANR take care of timed-out short-FGS and shouldn't bother
                // lowering the procstate / oom-adj...?? (likely not.)

                // For short FGS.
                adjType = "fg-service-short";
                // We use MEDIUM_APP_ADJ + 1 so we can tell apart EJ (which uses MEDIUM_APP_ADJ + 1)
+164 −2
Original line number Diff line number Diff line
@@ -315,6 +315,87 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
    final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>();
                            // start() arguments that haven't yet been delivered.

    /**
     * Information specific to "SHORT_SERVICE" FGS.
     */
    class ShortFgsInfo {
        /** Time FGS started */
        private final long mStartTime;

        /**
         * Copied from {@link #mStartForegroundCount}. If this is different from the parent's,
         * that means this instance is stale.
         */
        private int mStartForegroundCount;

        /** Service's "start ID" when this short-service started. */
        private int mStartId;

        ShortFgsInfo(long startTime) {
            mStartTime = startTime;
            update();
        }

        /**
         * Update {@link #mStartForegroundCount} and {@link #mStartId}.
         * (but not {@link #mStartTime})
         */
        public void update() {
            this.mStartForegroundCount = ServiceRecord.this.mStartForegroundCount;
            this.mStartId = getLastStartId();
        }

        long getStartTime() {
            return mStartTime;
        }

        int getStartForegroundCount() {
            return mStartForegroundCount;
        }

        int getStartId() {
            return mStartId;
        }

        /**
         * @return whether this {@link ShortFgsInfo} is still "current" or not -- i.e.
         * it's "start foreground count" is the same as that of the ServiceRecord's.
         *
         * Note, we do _not_ check the "start id" here, because the start id increments if the
         * app calls startService() or startForegroundService() on the same service,
         * but that will _not_ update the ShortFgsInfo, and will not extend the timeout.
         *
         * TODO(short-service): Make sure, calling startService will not extend or remove the
         * timeout, in CTS.
         */
        boolean isCurrent() {
            return this.mStartForegroundCount == ServiceRecord.this.mStartForegroundCount;
        }

        /** Time when Service.onTimeout() should be called */
        long getTimeoutTime() {
            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration;
        }

        /** Time when the procstate should be lowered. */
        long getProcStateDemoteTime() {
            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
                    + ams.mConstants.mShortFgsProcStateExtraWaitDuration;
        }

        /** Time when the app should be declared ANR. */
        long getAnrTime() {
            return mStartTime + ams.mConstants.mShortFgsTimeoutDuration
                    + ams.mConstants.mShortFgsAnrExtraWaitDuration;
        }
    }

    /**
     * Keep track of short-fgs specific information. This field gets cleared when the timeout
     * stops.
     */
    private ShortFgsInfo mShortFgsInfo;

    void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
        final int N = list.size();
        for (int i=0; i<N; i++) {
@@ -456,6 +537,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
            }
        }
        proto.end(token);

        // TODO(short-service) Add FGS info
    }

    void dump(PrintWriter pw, String prefix) {
@@ -509,7 +592,24 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
        if (isForeground || foregroundId != 0) {
            pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
            pw.print(" foregroundId="); pw.print(foregroundId);
            pw.printf(" types=%08X", foregroundServiceType);
            pw.print(" foregroundNoti="); pw.println(foregroundNoti);

            if (isShortFgs() && mShortFgsInfo != null) {
                pw.print(prefix); pw.print("isShortFgs=true");
                pw.print(" startId="); pw.print(mShortFgsInfo.getStartId());
                pw.print(" startForegroundCount=");
                pw.print(mShortFgsInfo.getStartForegroundCount());
                pw.print(" startTime=");
                TimeUtils.formatDuration(mShortFgsInfo.getStartTime(), now, pw);
                pw.print(" timeout=");
                TimeUtils.formatDuration(mShortFgsInfo.getTimeoutTime(), now, pw);
                pw.print(" demoteTime=");
                TimeUtils.formatDuration(mShortFgsInfo.getProcStateDemoteTime(), now, pw);
                pw.print(" anrTime=");
                TimeUtils.formatDuration(mShortFgsInfo.getAnrTime(), now, pw);
                pw.println();
            }
        }
        if (mIsFgsDelegate) {
            pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
@@ -1238,4 +1338,66 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
        return isForeground
                && (foregroundServiceType == ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
    }

    public ShortFgsInfo getShortFgsInfo() {
        return isShortFgs() ? mShortFgsInfo : null;
    }

    /**
     * Call it when a short FGS starts.
     */
    public void setShortFgsInfo(long uptimeNow) {
        this.mShortFgsInfo = new ShortFgsInfo(uptimeNow);
    }

    /** @return whether {@link #mShortFgsInfo} is set or not. */
    public boolean hasShortFgsInfo() {
        return mShortFgsInfo != null;
    }

    /**
     * Call it when a short FGS stops.
     */
    public void clearShortFgsInfo() {
        this.mShortFgsInfo = null;
    }

    /**
     * @return true if it's a short FGS that's still up and running, and should be timed out.
     */
    public boolean shouldTriggerShortFgsTimeout() {
        if (!isAppAlive()) {
            return false;
        }
        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
                || !mShortFgsInfo.isCurrent()) {
            return false;
        }
        return mShortFgsInfo.getTimeoutTime() < SystemClock.uptimeMillis();
    }

    /**
     * @return true if it's a short FGS that's still up and running, and should be declared
     * an ANR.
     */
    public boolean shouldTriggerShortFgsAnr() {
        if (!isAppAlive()) {
            return false;
        }
        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
                || !mShortFgsInfo.isCurrent()) {
            return false;
        }
        return mShortFgsInfo.getAnrTime() < SystemClock.uptimeMillis();
    }

    private boolean isAppAlive() {
        if (app == null) {
            return false;
        }
        if (app.getThread() == null || app.isKilled() || app.isKilledByAm()) {
            return false;
        }
        return true;
    }
}