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

Commit 61283ecc authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Exempt sync requests by FG app from app-standby

Test: manual test with request sync, etc
Bug: 72443754
Change-Id: Iecf2d3a8c54451324a02ca2762bda72aa219bd92
parent 2ef26bf2
Loading
Loading
Loading
Loading
+28 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.commands.requestsync;

import android.accounts.Account;
import android.content.ContentResolver;
import android.content.SyncRequest;
import android.os.Bundle;

import java.net.URISyntaxException;
@@ -28,12 +29,31 @@ public class RequestSync {
    private String[] mArgs;
    private int mNextArg;
    private String mCurArgData;
    private boolean mIsForegroundRequest;

    enum Operation {
        REQUEST_SYNC {
            @Override
            void invoke(RequestSync caller) {
                ContentResolver.requestSync(caller.mAccount, caller.mAuthority, caller.mExtras);
                if (caller.mIsForegroundRequest) {
                    caller.mExtras.putBoolean(
                            ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC, true);
                } else {
                    caller.mExtras.putBoolean(
                            ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC, true);
                    System.out.println(
                            "Making a sync request as a background app.\n"
                            + "Note: request may be throttled by App Standby.\n"
                            + "To override this behavior and run a sync immediately,"
                            + " pass a -f option.\n");
                }
                final SyncRequest request =
                        new SyncRequest.Builder()
                                .setSyncAdapter(caller.mAccount, caller.mAuthority)
                                .setExtras(caller.mExtras)
                                .syncOnce()
                                .build();
                ContentResolver.requestSync(request);
            }
        },
        ADD_PERIODIC_SYNC {
@@ -191,6 +211,10 @@ public class RequestSync {
                final String key = nextArgRequired();
                final String value = nextArgRequired();
                mExtras.putBoolean(key, Boolean.valueOf(value));

            } else if (opt.equals("-f") || opt.equals("--foreground")) {
                mIsForegroundRequest = true;

            } else {
                System.err.println("Error: Unknown option: " + opt);
                showUsage();
@@ -267,6 +291,9 @@ public class RequestSync {
                "       -n|--account-name <ACCOUNT-NAME>\n" +
                "       -t|--account-type <ACCOUNT-TYPE>\n" +
                "       -a|--authority <AUTHORITY>\n" +
                "    App-standby related options\n" +
                "\n" +
                "       -f|--foreground (Exempt a sync from app standby)\n" +
                "    ContentResolver extra options:\n" +
                "      --is|--ignore-settings: Add SYNC_EXTRAS_IGNORE_SETTINGS\n" +
                "      --ib|--ignore-backoff: Add SYNC_EXTRAS_IGNORE_BACKOFF\n" +
+21 −7
Original line number Diff line number Diff line
@@ -165,6 +165,26 @@ public abstract class ContentResolver {
    /** {@hide} Flag to allow sync to occur on metered network. */
    public static final String SYNC_EXTRAS_DISALLOW_METERED = "allow_metered";

    /**
     * {@hide} Flag only used by the requestsync command to treat a request as if it was made by
     * a foreground app.
     *
     * Only the system and the shell user can set it.
     *
     * This extra is "virtual". Once passed to the system server, it'll be removed from the bundle.
     */
    public static final String SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC = "force_fg_sync";

    /**
     * {@hide} Flag only used by the requestsync command to treat a request as if it was made by
     * a background app.
     *
     * Only the system and the shell user can set it.
     *
     * This extra is "virtual". Once passed to the system server, it'll be removed from the bundle.
     */
    public static final String SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC = "force_bg_sync";

    /**
     * Set by the SyncManager to request that the SyncAdapter initialize itself for
     * the given account/authority pair. One required initialization step is to
@@ -2435,13 +2455,7 @@ public abstract class ContentResolver {
    public static void addPeriodicSync(Account account, String authority, Bundle extras,
            long pollFrequency) {
        validateSyncExtrasBundle(extras);
        if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false)
                || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false)
                || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false)
                || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false)
                || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false)
                || extras.getBoolean(SYNC_EXTRAS_FORCE, false)
                || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) {
        if (invalidPeriodicExtras(extras)) {
            throw new IllegalArgumentException("illegal extras were set");
        }
        try {
+73 −7
Original line number Diff line number Diff line
@@ -48,8 +48,8 @@ import android.os.Bundle;
import android.os.FactoryTest;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -446,7 +446,7 @@ public final class ContentService extends IContentService.Stub {
                SyncManager syncManager = getSyncManager();
                if (syncManager != null) {
                    syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
                            uri.getAuthority());
                            uri.getAuthority(), /*isAppStandbyExempted=*/ isUidInForeground(uid));
                }
            }

@@ -502,6 +502,9 @@ public final class ContentService extends IContentService.Stub {
        int userId = UserHandle.getCallingUserId();
        int uId = Binder.getCallingUid();

        validateExtras(uId, extras);
        final boolean isForegroundSyncRequest = isForegroundSyncRequest(uId, extras);

        // This makes it so that future permission checks will be in the context of this
        // process rather than the caller's process. We will restore this before returning.
        long identityToken = clearCallingIdentity();
@@ -509,7 +512,8 @@ public final class ContentService extends IContentService.Stub {
            SyncManager syncManager = getSyncManager();
            if (syncManager != null) {
                syncManager.scheduleSync(account, userId, uId, authority, extras,
                        SyncStorageEngine.AuthorityInfo.UNDEFINED);
                        SyncStorageEngine.AuthorityInfo.UNDEFINED,
                        /*isAppStandbyExempted=*/ isForegroundSyncRequest);
            }
        } finally {
            restoreCallingIdentity(identityToken);
@@ -548,6 +552,12 @@ public final class ContentService extends IContentService.Stub {
    public void syncAsUser(SyncRequest request, int userId) {
        enforceCrossUserPermission(userId, "no permission to request sync as user: " + userId);
        int callerUid = Binder.getCallingUid();

        final Bundle extras = request.getBundle();

        validateExtras(callerUid, extras);
        final boolean isForegroundSyncRequest = isForegroundSyncRequest(callerUid, extras);

        // This makes it so that future permission checks will be in the context of this
        // process rather than the caller's process. We will restore this before returning.
        long identityToken = clearCallingIdentity();
@@ -556,8 +566,6 @@ public final class ContentService extends IContentService.Stub {
            if (syncManager == null) {
                return;
            }

            Bundle extras = request.getBundle();
            long flextime = request.getSyncFlexTime();
            long runAtTime = request.getSyncRunTime();
            if (request.isPeriodic()) {
@@ -575,7 +583,8 @@ public final class ContentService extends IContentService.Stub {
            } else {
                syncManager.scheduleSync(
                        request.getAccount(), userId, callerUid, request.getProvider(), extras,
                        SyncStorageEngine.AuthorityInfo.UNDEFINED);
                        SyncStorageEngine.AuthorityInfo.UNDEFINED,
                        /*isAppStandbyExempted=*/ isForegroundSyncRequest);
            }
        } finally {
            restoreCallingIdentity(identityToken);
@@ -649,10 +658,13 @@ public final class ContentService extends IContentService.Stub {
                    "no permission to write the sync settings");
        }

        Bundle extras = new Bundle(request.getBundle());
        validateExtras(callingUid, extras);

        long identityToken = clearCallingIdentity();
        try {
            SyncStorageEngine.EndPoint info;
            Bundle extras = new Bundle(request.getBundle());

            Account account = request.getAccount();
            String provider = request.getProvider();
            info = new SyncStorageEngine.EndPoint(account, provider, userId);
@@ -787,6 +799,8 @@ public final class ContentService extends IContentService.Stub {
        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                "no permission to write the sync settings");

        validateExtras(Binder.getCallingUid(), extras);

        int userId = UserHandle.getCallingUserId();

        pollFrequency = clampPeriod(pollFrequency);
@@ -815,6 +829,8 @@ public final class ContentService extends IContentService.Stub {
        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                "no permission to write the sync settings");

        validateExtras(Binder.getCallingUid(), extras);

        final int callingUid = Binder.getCallingUid();

        int userId = UserHandle.getCallingUserId();
@@ -1239,6 +1255,56 @@ public final class ContentService extends IContentService.Stub {
        return SyncStorageEngine.AuthorityInfo.UNDEFINED;
    }

    private void validateExtras(int callingUid, Bundle extras) {
        if (extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC)
                || extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC)
                ) {
            switch (callingUid) {
                case Process.ROOT_UID:
                case Process.SHELL_UID:
                case Process.SYSTEM_UID:
                    break; // Okay
                default:
                    throw new SecurityException("Invalid extras specified.");
            }
        }
    }

    private boolean isForegroundSyncRequest(int callingUid, Bundle extras) {
        final boolean isForegroundRequest;
        if (extras.getBoolean(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC)) {
            isForegroundRequest = true;
        } else if (extras.getBoolean(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC)) {
            isForegroundRequest = false;
        } else {
            isForegroundRequest = isUidInForeground(callingUid);
        }
        extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC);
        extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC);

        return isForegroundRequest;
    }

    private boolean isUidInForeground(int uid) {
        // If the caller is ADB, we assume it's a background request by default, because
        // that's also the default of requests from the requestsync command.
        // The requestsync command will always set either SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC or
        // SYNC_VIRTUAL_EXTRAS_FORCE_BG_SYNC (for non-periodic sync requests),
        // so it shouldn't matter in practice.
        switch (uid) {
            case Process.SHELL_UID:
            case Process.ROOT_UID:
                return false;
        }
        final ActivityManagerInternal ami =
                LocalServices.getService(ActivityManagerInternal.class);
        if (ami != null) {
            return ami.getUidProcessState(uid)
                    <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
        }
        return false;
    }

    /**
     * Hide this class since it is not part of api,
     * but current unittest framework requires it to be public
+78 −25
Original line number Diff line number Diff line
@@ -576,9 +576,10 @@ public class SyncManager {
        mSyncStorageEngine = SyncStorageEngine.getSingleton();
        mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
            @Override
            public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) {
            public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras,
                    boolean isAppStandbyExempted) {
                scheduleSync(info.account, info.userId, reason, info.provider, extras,
                        AuthorityInfo.UNDEFINED);
                        AuthorityInfo.UNDEFINED, isAppStandbyExempted);
            }
        });

@@ -608,7 +609,8 @@ public class SyncManager {
                if (!removed) {
                    scheduleSync(null, UserHandle.USER_ALL,
                            SyncOperation.REASON_SERVICE_CHANGED,
                            type.authority, null, AuthorityInfo.UNDEFINED);
                            type.authority, null, AuthorityInfo.UNDEFINED,
                            /*isAppStandbyExempted=*/ false);
                }
            }
        }, mSyncHandler);
@@ -656,7 +658,8 @@ public class SyncManager {
            if (mAccountManagerInternal.hasAccountAccess(account, uid)) {
                scheduleSync(account, UserHandle.getUserId(uid),
                        SyncOperation.REASON_ACCOUNTS_UPDATED,
                        null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS);
                        null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS,
                        /*isAppStandbyExempted=*/ false);
            }
        });

@@ -881,9 +884,10 @@ public class SyncManager {
     *           Use {@link AuthorityInfo#UNDEFINED} to sync all authorities.
     */
    public void scheduleSync(Account requestedAccount, int userId, int reason,
                             String requestedAuthority, Bundle extras, int targetSyncState) {
            String requestedAuthority, Bundle extras, int targetSyncState,
            boolean isAppStandbyExempted) {
        scheduleSync(requestedAccount, userId, reason, requestedAuthority, extras, targetSyncState,
                0 /* min delay */, true /* checkIfAccountReady */);
                0 /* min delay */, true /* checkIfAccountReady */, isAppStandbyExempted);
    }

    /**
@@ -891,7 +895,8 @@ public class SyncManager {
     */
    private void scheduleSync(Account requestedAccount, int userId, int reason,
            String requestedAuthority, Bundle extras, int targetSyncState,
                             final long minDelayMillis, boolean checkIfAccountReady) {
            final long minDelayMillis, boolean checkIfAccountReady,
            boolean isAppStandbyExempted) {
        final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
        if (extras == null) {
            extras = new Bundle();
@@ -1009,7 +1014,8 @@ public class SyncManager {
                                        && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
                                    scheduleSync(account.account, userId, reason, authority,
                                            finalExtras, targetSyncState, minDelayMillis,
                                            true /* checkIfAccountReady */);
                                            true /* checkIfAccountReady */,
                                            isAppStandbyExempted);
                                }
                            }
                        ));
@@ -1060,7 +1066,7 @@ public class SyncManager {
                        sendOnUnsyncableAccount(mContext, syncAdapterInfo, account.userId,
                                () -> scheduleSync(account.account, account.userId, reason,
                                        authority, finalExtras, targetSyncState, minDelayMillis,
                                        false));
                                        false, isAppStandbyExempted));
                    } else {
                        // Initialisation sync.
                        Bundle newExtras = new Bundle();
@@ -1078,7 +1084,8 @@ public class SyncManager {
                        postScheduleSyncMessage(
                                new SyncOperation(account.account, account.userId,
                                        owningUid, owningPackage, reason, source,
                                        authority, newExtras, allowParallelSyncs),
                                        authority, newExtras, allowParallelSyncs,
                                        isAppStandbyExempted),
                                minDelayMillis
                        );
                    }
@@ -1095,7 +1102,7 @@ public class SyncManager {
                    postScheduleSyncMessage(
                            new SyncOperation(account.account, account.userId,
                                    owningUid, owningPackage, reason, source,
                                    authority, extras, allowParallelSyncs),
                                    authority, extras, allowParallelSyncs, isAppStandbyExempted),
                            minDelayMillis
                    );
                }
@@ -1208,11 +1215,13 @@ public class SyncManager {
     * Schedule sync based on local changes to a provider. We wait for at least LOCAL_SYNC_DELAY
     * ms to batch syncs.
     */
    public void scheduleLocalSync(Account account, int userId, int reason, String authority) {
    public void scheduleLocalSync(Account account, int userId, int reason, String authority,
            boolean isAppStandbyExempted) {
        final Bundle extras = new Bundle();
        extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
        scheduleSync(account, userId, reason, authority, extras,
                AuthorityInfo.UNDEFINED, LOCAL_SYNC_DELAY, true /* checkIfAccountReady */);
                AuthorityInfo.UNDEFINED, LOCAL_SYNC_DELAY, true /* checkIfAccountReady */,
                isAppStandbyExempted);
    }

    public SyncAdapterType[] getSyncAdapterTypes(int userId) {
@@ -1480,7 +1489,11 @@ public class SyncManager {
        }

        // Check if duplicate syncs are pending. If found, keep one with least expected run time.

        // If any of the duplicate ones has exemption, then we inherit it.
        if (!syncOperation.isPeriodic) {
            boolean inheritAppStandbyExemption = false;

            // Check currently running syncs
            for (ActiveSyncContext asc: mActiveSyncContexts) {
                if (asc.mSyncOperation.key.equals(syncOperation.key)) {
@@ -1496,14 +1509,14 @@ public class SyncManager {
            long now = SystemClock.elapsedRealtime();
            syncOperation.expectedRuntime = now + minDelay;
            List<SyncOperation> pending = getAllPendingSyncs();
            SyncOperation opWithLeastExpectedRuntime = syncOperation;
            SyncOperation syncToRun = syncOperation;
            for (SyncOperation op : pending) {
                if (op.isPeriodic) {
                    continue;
                }
                if (op.key.equals(syncOperation.key)) {
                    if (opWithLeastExpectedRuntime.expectedRuntime > op.expectedRuntime) {
                        opWithLeastExpectedRuntime = op;
                    if (syncToRun.expectedRuntime > op.expectedRuntime) {
                        syncToRun = op;
                    }
                    duplicatesCount++;
                }
@@ -1511,26 +1524,54 @@ public class SyncManager {
            if (duplicatesCount > 1) {
                Slog.e(TAG, "FATAL ERROR! File a bug if you see this.");
            }

            if (syncOperation != syncToRun) {
                // If there's a duplicate with an earlier run time that's not exempted,
                // and if the current operation is exempted with no minDelay,
                // cancel the duplicate one and keep the current one.
                //
                // This means the duplicate one has a negative expected run time, but it hasn't
                // been executed possibly because of app-standby.

                if (syncOperation.isAppStandbyExempted
                        && (minDelay == 0)
                        && !syncToRun.isAppStandbyExempted) {
                    syncToRun = syncOperation;
                }
            }

            // Cancel all other duplicate syncs.
            for (SyncOperation op : pending) {
                if (op.isPeriodic) {
                    continue;
                }
                if (op.key.equals(syncOperation.key)) {
                    if (op != opWithLeastExpectedRuntime) {
                    if (op != syncToRun) {
                        if (isLoggable) {
                            Slog.v(TAG, "Cancelling duplicate sync " + op);
                        }
                        if (op.isAppStandbyExempted) {
                            inheritAppStandbyExemption = true;
                        }
                        cancelJob(op, "scheduleSyncOperationH-duplicate");
                    }
                }
            }
            if (opWithLeastExpectedRuntime != syncOperation) {
            if (syncToRun != syncOperation) {
                // Don't schedule because a duplicate sync with earlier expected runtime exists.
                if (isLoggable) {
                    Slog.v(TAG, "Not scheduling because a duplicate exists.");
                }

                // TODO Should we give the winning one SYNC_EXTRAS_APP_STANDBY_EXEMPTED
                // if the current one has it?
                return;
            }

            // If any of the duplicates had exemption, we exempt the current one.
            if (inheritAppStandbyExemption) {
                syncOperation.isAppStandbyExempted = true;
            }
        }

        // Syncs that are re-scheduled shouldn't get a new job id.
@@ -1547,12 +1588,18 @@ public class SyncManager {
        final int networkType = syncOperation.isNotAllowedOnMetered() ?
                JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY;

        // Note this logic means when an exempted sync fails,
        // the back-off one will inherit it too, and will be exempted from app-standby.
        final int jobFlags = syncOperation.isAppStandbyExempted
                ? JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY : 0;

        JobInfo.Builder b = new JobInfo.Builder(syncOperation.jobId,
                new ComponentName(mContext, SyncJobService.class))
                .setExtras(syncOperation.toJobInfoExtras())
                .setRequiredNetworkType(networkType)
                .setPersisted(true)
                .setPriority(priority);
                .setPriority(priority)
                .setFlags(jobFlags);

        if (syncOperation.isPeriodic) {
            b.setPeriodic(syncOperation.periodMillis, syncOperation.flexMillis);
@@ -1683,12 +1730,12 @@ public class SyncManager {
        EndPoint target = new EndPoint(null, null, userId);
        updateRunningAccounts(target);

        // Schedule sync for any accounts under started user.
        // Schedule sync for any accounts under started user, but only the NOT_INITIALIZED adapters.
        final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId,
                mContext.getOpPackageName());
        for (Account account : accounts) {
            scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
                    AuthorityInfo.NOT_INITIALIZED);
                    AuthorityInfo.NOT_INITIALIZED, /*isAppStandbyExempted=*/ false);
        }
    }

@@ -3144,7 +3191,8 @@ public class SyncManager {
            if (syncTargets != null) {
                scheduleSync(syncTargets.account, syncTargets.userId,
                        SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider,
                null, AuthorityInfo.NOT_INITIALIZED);
                        null, AuthorityInfo.NOT_INITIALIZED,
                        /*isAppStandbyExempted=*/ false);
            }
        }

@@ -3211,7 +3259,7 @@ public class SyncManager {
                    syncAdapterInfo.componentName.getPackageName(), SyncOperation.REASON_PERIODIC,
                    SyncStorageEngine.SOURCE_PERIODIC, extras,
                    syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID,
                    pollFrequencyMillis, flexMillis);
                    pollFrequencyMillis, flexMillis, /*isAppStandbyExempted=*/ false);

            final int syncOpState = computeSyncOpState(op);
            switch (syncOpState) {
@@ -3590,7 +3638,8 @@ public class SyncManager {
                                syncOperation.owningUid, syncOperation.owningPackage,
                                syncOperation.reason,
                                syncOperation.syncSource, info.provider, new Bundle(),
                                syncOperation.allowParallelSyncs));
                                syncOperation.allowParallelSyncs,
                                syncOperation.isAppStandbyExempted));
            }
        }

@@ -3808,6 +3857,10 @@ public class SyncManager {
        if (key.equals(ContentResolver.SYNC_EXTRAS_INITIALIZE)) {
            return true;
        }
//        if (key.equals(ContentResolver.SYNC_EXTRAS_APP_STANDBY_EXEMPTED)) {
//            return true;
//        }
        // No need to check virtual flags such as SYNC_VIRTUAL_EXTRAS_FORCE_FG_SYNC.
        return false;
    }

+21 −8

File changed.

Preview size limit exceeded, changes collapsed.

Loading