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

Commit 76be79a7 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Exempt sync requests by FG app from app-standby"

parents 00f255e7 61283ecc
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