Merge "AppStandby exemption: sync requested by FG apps" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
929ce0086a
@@ -29,23 +29,21 @@ public class RequestSync {
|
||||
private String[] mArgs;
|
||||
private int mNextArg;
|
||||
private String mCurArgData;
|
||||
private boolean mIsForegroundRequest;
|
||||
|
||||
private int mExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE;
|
||||
|
||||
enum Operation {
|
||||
REQUEST_SYNC {
|
||||
@Override
|
||||
void invoke(RequestSync caller) {
|
||||
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);
|
||||
final int flag = caller.mExemptionFlag;
|
||||
caller.mExtras.putInt(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG, flag);
|
||||
if (flag == ContentResolver.SYNC_EXEMPTION_NONE) {
|
||||
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");
|
||||
+ " pass a -f or -F option (use -h for help).\n");
|
||||
}
|
||||
final SyncRequest request =
|
||||
new SyncRequest.Builder()
|
||||
@@ -213,7 +211,10 @@ public class RequestSync {
|
||||
mExtras.putBoolean(key, Boolean.valueOf(value));
|
||||
|
||||
} else if (opt.equals("-f") || opt.equals("--foreground")) {
|
||||
mIsForegroundRequest = true;
|
||||
mExemptionFlag = ContentResolver.SYNC_EXEMPTION_ACTIVE;
|
||||
|
||||
} else if (opt.equals("-F") || opt.equals("--top")) {
|
||||
mExemptionFlag = ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP;
|
||||
|
||||
} else {
|
||||
System.err.println("Error: Unknown option: " + opt);
|
||||
@@ -293,7 +294,9 @@ public class RequestSync {
|
||||
" -a|--authority <AUTHORITY>\n" +
|
||||
" App-standby related options\n" +
|
||||
"\n" +
|
||||
" -f|--foreground (Exempt a sync from app standby)\n" +
|
||||
" -f|--foreground (cause WORKING_SET, FREQUENT sync adapters" +
|
||||
" to run immediately)\n" +
|
||||
" -F|--top (cause even RARE sync adapters to run immediately)\n" +
|
||||
" ContentResolver extra options:\n" +
|
||||
" --is|--ignore-settings: Add SYNC_EXTRAS_IGNORE_SETTINGS\n" +
|
||||
" --ib|--ignore-backoff: Add SYNC_EXTRAS_IGNORE_BACKOFF\n" +
|
||||
|
||||
@@ -183,10 +183,13 @@ public final class UsageStatsManager {
|
||||
public static final int REASON_SUB_USAGE_SLICE_PINNED = 0x0009;
|
||||
/** @hide */
|
||||
public static final int REASON_SUB_USAGE_SLICE_PINNED_PRIV = 0x000A;
|
||||
/** @hide */
|
||||
public static final int REASON_SUB_USAGE_EXEMPTED_SYNC_START = 0x000B;
|
||||
|
||||
/** @hide */
|
||||
public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001;
|
||||
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = false, prefix = { "STANDBY_BUCKET_" }, value = {
|
||||
STANDBY_BUCKET_EXEMPTED,
|
||||
@@ -665,6 +668,9 @@ public final class UsageStatsManager {
|
||||
case REASON_SUB_USAGE_SLICE_PINNED_PRIV:
|
||||
sb.append("slpp");
|
||||
break;
|
||||
case REASON_SUB_USAGE_EXEMPTED_SYNC_START:
|
||||
sb.append("es");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -243,4 +243,12 @@ public abstract class UsageStatsManagerInternal {
|
||||
*/
|
||||
public abstract void reportAppJobState(String packageName, @UserIdInt int userId,
|
||||
int numDeferredJobs, long timeSinceLastJobRun);
|
||||
|
||||
/**
|
||||
* Report a sync that was scheduled by an active app is about to be executed.
|
||||
*
|
||||
* @param packageName name of the package that owns the sync adapter.
|
||||
* @param userId which user the app is associated with
|
||||
*/
|
||||
public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
|
||||
}
|
||||
|
||||
@@ -166,24 +166,13 @@ public abstract class ContentResolver {
|
||||
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.
|
||||
* {@hide} Integer extra containing a SyncExemption flag.
|
||||
*
|
||||
* 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";
|
||||
public static final String SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG = "v_exemption";
|
||||
|
||||
/**
|
||||
* Set by the SyncManager to request that the SyncAdapter initialize itself for
|
||||
@@ -525,6 +514,38 @@ public abstract class ContentResolver {
|
||||
*/
|
||||
public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1;
|
||||
|
||||
/**
|
||||
* No exception, throttled by app standby normally.
|
||||
* @hide
|
||||
*/
|
||||
public static final int SYNC_EXEMPTION_NONE = 0;
|
||||
|
||||
/**
|
||||
* When executing a sync with this exemption, we'll put the target app in the ACTIVE bucket
|
||||
* for 10 minutes. This will allow the sync adapter to schedule/run further syncs and jobs.
|
||||
*
|
||||
* Note this will still *not* let RARE apps to run syncs, because they still won't get network
|
||||
* connection.
|
||||
* @hide
|
||||
*/
|
||||
public static final int SYNC_EXEMPTION_ACTIVE = 1;
|
||||
|
||||
/**
|
||||
* In addition to {@link #SYNC_EXEMPTION_ACTIVE}, we put the sync adapter app in the
|
||||
* temp whitelist for 10 minutes, so that even RARE apps can run syncs right away.
|
||||
* @hide
|
||||
*/
|
||||
public static final int SYNC_EXEMPTION_ACTIVE_WITH_TEMP = 2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = false, prefix = { "SYNC_EXEMPTION_" }, value = {
|
||||
SYNC_EXEMPTION_NONE,
|
||||
SYNC_EXEMPTION_ACTIVE,
|
||||
SYNC_EXEMPTION_ACTIVE_WITH_TEMP,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SyncExemption {}
|
||||
|
||||
// Always log queries which take 500ms+; shorter queries are
|
||||
// sampled accordingly.
|
||||
private static final boolean ENABLE_CONTENT_SAMPLE = false;
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.app.job.JobInfo;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentResolver.SyncExemption;
|
||||
import android.content.Context;
|
||||
import android.content.IContentService;
|
||||
import android.content.ISyncStatusObserver;
|
||||
@@ -78,7 +79,7 @@ import java.util.List;
|
||||
*/
|
||||
public final class ContentService extends IContentService.Stub {
|
||||
static final String TAG = "ContentService";
|
||||
static final boolean DEBUG = false;
|
||||
static final boolean DEBUG = true;
|
||||
|
||||
public static class Lifecycle extends SystemService {
|
||||
private ContentService mService;
|
||||
@@ -451,7 +452,7 @@ public final class ContentService extends IContentService.Stub {
|
||||
SyncManager syncManager = getSyncManager();
|
||||
if (syncManager != null) {
|
||||
syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
|
||||
uri.getAuthority(), /*isAppStandbyExempted=*/ isUidInForeground(uid));
|
||||
uri.getAuthority(), getSyncExemptionForCaller(uid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,7 +509,7 @@ public final class ContentService extends IContentService.Stub {
|
||||
int uId = Binder.getCallingUid();
|
||||
|
||||
validateExtras(uId, extras);
|
||||
final boolean isForegroundSyncRequest = isForegroundSyncRequest(uId, extras);
|
||||
final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(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.
|
||||
@@ -518,7 +519,7 @@ public final class ContentService extends IContentService.Stub {
|
||||
if (syncManager != null) {
|
||||
syncManager.scheduleSync(account, userId, uId, authority, extras,
|
||||
SyncStorageEngine.AuthorityInfo.UNDEFINED,
|
||||
/*isAppStandbyExempted=*/ isForegroundSyncRequest);
|
||||
syncExemption);
|
||||
}
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
@@ -561,7 +562,7 @@ public final class ContentService extends IContentService.Stub {
|
||||
final Bundle extras = request.getBundle();
|
||||
|
||||
validateExtras(callerUid, extras);
|
||||
final boolean isForegroundSyncRequest = isForegroundSyncRequest(callerUid, extras);
|
||||
final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(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.
|
||||
@@ -589,7 +590,7 @@ public final class ContentService extends IContentService.Stub {
|
||||
syncManager.scheduleSync(
|
||||
request.getAccount(), userId, callerUid, request.getProvider(), extras,
|
||||
SyncStorageEngine.AuthorityInfo.UNDEFINED,
|
||||
/*isAppStandbyExempted=*/ isForegroundSyncRequest);
|
||||
syncExemption);
|
||||
}
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
@@ -777,13 +778,15 @@ public final class ContentService extends IContentService.Stub {
|
||||
"no permission to write the sync settings");
|
||||
enforceCrossUserPermission(userId,
|
||||
"no permission to modify the sync settings for user " + userId);
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final int syncExemptionFlag = getSyncExemptionForCaller(callingUid);
|
||||
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
SyncManager syncManager = getSyncManager();
|
||||
if (syncManager != null) {
|
||||
syncManager.getSyncStorageEngine().setSyncAutomatically(account, userId,
|
||||
providerName, sync);
|
||||
providerName, sync, syncExemptionFlag);
|
||||
}
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
@@ -964,11 +967,14 @@ public final class ContentService extends IContentService.Stub {
|
||||
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
|
||||
"no permission to write the sync settings");
|
||||
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
SyncManager syncManager = getSyncManager();
|
||||
if (syncManager != null) {
|
||||
syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
|
||||
syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId,
|
||||
getSyncExemptionForCaller(callingUid));
|
||||
}
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
@@ -1263,9 +1269,7 @@ public final class ContentService extends IContentService.Stub {
|
||||
}
|
||||
|
||||
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)
|
||||
) {
|
||||
if (extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG)) {
|
||||
switch (callingUid) {
|
||||
case Process.ROOT_UID:
|
||||
case Process.SHELL_UID:
|
||||
@@ -1277,39 +1281,36 @@ public final class ContentService extends IContentService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@SyncExemption
|
||||
private int getSyncExemptionForCaller(int callingUid) {
|
||||
return getSyncExemptionAndCleanUpExtrasForCaller(callingUid, null);
|
||||
}
|
||||
|
||||
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;
|
||||
@SyncExemption
|
||||
private int getSyncExemptionAndCleanUpExtrasForCaller(int callingUid, Bundle extras) {
|
||||
if (extras != null) {
|
||||
final int exemption =
|
||||
extras.getInt(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG, -1);
|
||||
|
||||
// Need to remove the virtual extra.
|
||||
extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG);
|
||||
if (exemption != -1) {
|
||||
return exemption;
|
||||
}
|
||||
}
|
||||
final ActivityManagerInternal ami =
|
||||
LocalServices.getService(ActivityManagerInternal.class);
|
||||
if (ami != null) {
|
||||
return ami.getUidProcessState(uid)
|
||||
<= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
|
||||
final int procState = (ami != null)
|
||||
? ami.getUidProcessState(callingUid)
|
||||
: ActivityManager.PROCESS_STATE_NONEXISTENT;
|
||||
|
||||
if (procState <= ActivityManager.PROCESS_STATE_TOP) {
|
||||
return ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP;
|
||||
}
|
||||
return false;
|
||||
if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
|
||||
return ContentResolver.SYNC_EXEMPTION_ACTIVE;
|
||||
}
|
||||
return ContentResolver.SYNC_EXEMPTION_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.content;
|
||||
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.server.AppStateTracker;
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
class SyncAdapterStateFetcher {
|
||||
|
||||
private final HashMap<Pair<Integer, String>, Integer> mBucketCache =
|
||||
new HashMap<>();
|
||||
|
||||
public SyncAdapterStateFetcher() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sync adapter state with a cache.
|
||||
*/
|
||||
public int getStandbyBucket(int userId, String packageName) {
|
||||
final Pair<Integer, String> key = Pair.create(userId, packageName);
|
||||
final Integer cached = mBucketCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
final UsageStatsManagerInternal usmi =
|
||||
LocalServices.getService(UsageStatsManagerInternal.class);
|
||||
if (usmi == null) {
|
||||
return -1; // Unknown.
|
||||
}
|
||||
|
||||
final int value = usmi.getAppStandbyBucket(packageName, userId,
|
||||
SystemClock.elapsedRealtime());
|
||||
mBucketCache.put(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return UID active state.
|
||||
*/
|
||||
public boolean isAppActive(int uid) {
|
||||
final AppStateTracker ast =
|
||||
LocalServices.getService(AppStateTracker.class);
|
||||
if (ast == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ast.isUidActive(uid);
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,11 @@ import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentResolver.SyncExemption;
|
||||
import android.content.Context;
|
||||
import android.content.ISyncAdapter;
|
||||
import android.content.ISyncAdapterUnsyncableAccountCallback;
|
||||
@@ -70,6 +72,7 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
@@ -88,6 +91,8 @@ import android.util.Slog;
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.internal.notification.SystemNotificationChannels;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.DeviceIdleController;
|
||||
import com.android.server.DeviceIdleController.LocalService;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.job.JobSchedulerInternal;
|
||||
@@ -550,10 +555,6 @@ public class SyncManager {
|
||||
return mJobScheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be created after {@link ContentService#systemReady()} so that
|
||||
* {@link PackageManager} is ready to query.
|
||||
*/
|
||||
public SyncManager(Context context, boolean factoryTest) {
|
||||
// Initialize the SyncStorageEngine first, before registering observers
|
||||
// and creating threads and so on; it may fail if the disk is full.
|
||||
@@ -566,9 +567,9 @@ public class SyncManager {
|
||||
mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
|
||||
@Override
|
||||
public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras,
|
||||
boolean isAppStandbyExempted) {
|
||||
@SyncExemption int syncExemptionFlag) {
|
||||
scheduleSync(info.account, info.userId, reason, info.provider, extras,
|
||||
AuthorityInfo.UNDEFINED, isAppStandbyExempted);
|
||||
AuthorityInfo.UNDEFINED, syncExemptionFlag);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -599,7 +600,7 @@ public class SyncManager {
|
||||
scheduleSync(null, UserHandle.USER_ALL,
|
||||
SyncOperation.REASON_SERVICE_CHANGED,
|
||||
type.authority, null, AuthorityInfo.UNDEFINED,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
}
|
||||
}
|
||||
}, mSyncHandler);
|
||||
@@ -649,7 +650,7 @@ public class SyncManager {
|
||||
scheduleSync(account, UserHandle.getUserId(uid),
|
||||
SyncOperation.REASON_ACCOUNTS_UPDATED,
|
||||
null, null, AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -883,9 +884,9 @@ public class SyncManager {
|
||||
*/
|
||||
public void scheduleSync(Account requestedAccount, int userId, int reason,
|
||||
String requestedAuthority, Bundle extras, int targetSyncState,
|
||||
boolean isAppStandbyExempted) {
|
||||
@SyncExemption int syncExemptionFlag) {
|
||||
scheduleSync(requestedAccount, userId, reason, requestedAuthority, extras, targetSyncState,
|
||||
0 /* min delay */, true /* checkIfAccountReady */, isAppStandbyExempted);
|
||||
0 /* min delay */, true /* checkIfAccountReady */, syncExemptionFlag);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -894,7 +895,7 @@ public class SyncManager {
|
||||
private void scheduleSync(Account requestedAccount, int userId, int reason,
|
||||
String requestedAuthority, Bundle extras, int targetSyncState,
|
||||
final long minDelayMillis, boolean checkIfAccountReady,
|
||||
boolean isAppStandbyExempted) {
|
||||
@SyncExemption int syncExemptionFlag) {
|
||||
final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
@@ -904,7 +905,7 @@ public class SyncManager {
|
||||
+ requestedAuthority
|
||||
+ " reason=" + reason
|
||||
+ " checkIfAccountReady=" + checkIfAccountReady
|
||||
+ " isAppStandbyExempted=" + isAppStandbyExempted);
|
||||
+ " syncExemptionFlag=" + syncExemptionFlag);
|
||||
}
|
||||
|
||||
AccountAndUser[] accounts = null;
|
||||
@@ -1016,7 +1017,7 @@ public class SyncManager {
|
||||
scheduleSync(account.account, userId, reason, authority,
|
||||
finalExtras, targetSyncState, minDelayMillis,
|
||||
true /* checkIfAccountReady */,
|
||||
isAppStandbyExempted);
|
||||
syncExemptionFlag);
|
||||
}
|
||||
}
|
||||
));
|
||||
@@ -1067,7 +1068,7 @@ public class SyncManager {
|
||||
sendOnUnsyncableAccount(mContext, syncAdapterInfo, account.userId,
|
||||
() -> scheduleSync(account.account, account.userId, reason,
|
||||
authority, finalExtras, targetSyncState, minDelayMillis,
|
||||
false, isAppStandbyExempted));
|
||||
false, syncExemptionFlag));
|
||||
} else {
|
||||
// Initialisation sync.
|
||||
Bundle newExtras = new Bundle();
|
||||
@@ -1086,7 +1087,7 @@ public class SyncManager {
|
||||
new SyncOperation(account.account, account.userId,
|
||||
owningUid, owningPackage, reason, source,
|
||||
authority, newExtras, allowParallelSyncs,
|
||||
isAppStandbyExempted),
|
||||
syncExemptionFlag),
|
||||
minDelayMillis
|
||||
);
|
||||
}
|
||||
@@ -1103,7 +1104,7 @@ public class SyncManager {
|
||||
postScheduleSyncMessage(
|
||||
new SyncOperation(account.account, account.userId,
|
||||
owningUid, owningPackage, reason, source,
|
||||
authority, extras, allowParallelSyncs, isAppStandbyExempted),
|
||||
authority, extras, allowParallelSyncs, syncExemptionFlag),
|
||||
minDelayMillis
|
||||
);
|
||||
}
|
||||
@@ -1217,12 +1218,12 @@ public class SyncManager {
|
||||
* ms to batch syncs.
|
||||
*/
|
||||
public void scheduleLocalSync(Account account, int userId, int reason, String authority,
|
||||
boolean isAppStandbyExempted) {
|
||||
@SyncExemption int syncExemptionFlag) {
|
||||
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 */,
|
||||
isAppStandbyExempted);
|
||||
syncExemptionFlag);
|
||||
}
|
||||
|
||||
public SyncAdapterType[] getSyncAdapterTypes(int userId) {
|
||||
@@ -1493,7 +1494,7 @@ public class SyncManager {
|
||||
|
||||
// If any of the duplicate ones has exemption, then we inherit it.
|
||||
if (!syncOperation.isPeriodic) {
|
||||
boolean inheritAppStandbyExemption = false;
|
||||
int inheritedSyncExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE;
|
||||
|
||||
// Check currently running syncs
|
||||
for (ActiveSyncContext asc: mActiveSyncContexts) {
|
||||
@@ -1534,10 +1535,11 @@ public class SyncManager {
|
||||
// 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) {
|
||||
if ((minDelay == 0)
|
||||
&& (syncToRun.syncExemptionFlag < syncOperation.syncExemptionFlag)) {
|
||||
syncToRun = syncOperation;
|
||||
inheritedSyncExemptionFlag =
|
||||
Math.max(inheritedSyncExemptionFlag, syncToRun.syncExemptionFlag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1551,9 +1553,8 @@ public class SyncManager {
|
||||
if (isLoggable) {
|
||||
Slog.v(TAG, "Cancelling duplicate sync " + op);
|
||||
}
|
||||
if (op.isAppStandbyExempted) {
|
||||
inheritAppStandbyExemption = true;
|
||||
}
|
||||
inheritedSyncExemptionFlag =
|
||||
Math.max(inheritedSyncExemptionFlag, op.syncExemptionFlag);
|
||||
cancelJob(op, "scheduleSyncOperationH-duplicate");
|
||||
}
|
||||
}
|
||||
@@ -1570,8 +1571,9 @@ public class SyncManager {
|
||||
}
|
||||
|
||||
// If any of the duplicates had exemption, we exempt the current one.
|
||||
if (inheritAppStandbyExemption) {
|
||||
syncOperation.isAppStandbyExempted = true;
|
||||
//
|
||||
if (inheritedSyncExemptionFlag > ContentResolver.SYNC_EXEMPTION_NONE) {
|
||||
syncOperation.syncExemptionFlag = inheritedSyncExemptionFlag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1591,7 +1593,7 @@ public class SyncManager {
|
||||
|
||||
// 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
|
||||
final int jobFlags = syncOperation.isAppStandbyExempted()
|
||||
? JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY : 0;
|
||||
|
||||
JobInfo.Builder b = new JobInfo.Builder(syncOperation.jobId,
|
||||
@@ -1615,6 +1617,19 @@ public class SyncManager {
|
||||
b.setRequiresCharging(true);
|
||||
}
|
||||
|
||||
if (syncOperation.syncExemptionFlag
|
||||
== ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP) {
|
||||
DeviceIdleController.LocalService dic =
|
||||
LocalServices.getService(DeviceIdleController.LocalService.class);
|
||||
if (dic != null) {
|
||||
dic.addPowerSaveTempWhitelistApp(Process.SYSTEM_UID,
|
||||
syncOperation.owningPackage,
|
||||
mConstants.getKeyExemptionTempWhitelistDurationInSeconds() * 1000,
|
||||
UserHandle.getUserId(syncOperation.owningUid),
|
||||
/* sync=*/ false, "sync by top app");
|
||||
}
|
||||
}
|
||||
|
||||
getJobScheduler().scheduleAsPackage(b.build(), syncOperation.owningPackage,
|
||||
syncOperation.target.userId, syncOperation.wakeLockName());
|
||||
}
|
||||
@@ -1736,7 +1751,7 @@ public class SyncManager {
|
||||
mContext.getOpPackageName());
|
||||
for (Account account : accounts) {
|
||||
scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
|
||||
AuthorityInfo.NOT_INITIALIZED, /*isAppStandbyExempted=*/ false);
|
||||
AuthorityInfo.NOT_INITIALIZED, ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1930,7 +1945,10 @@ public class SyncManager {
|
||||
|
||||
protected void dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll) {
|
||||
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
|
||||
dumpSyncState(ipw);
|
||||
|
||||
final SyncAdapterStateFetcher buckets = new SyncAdapterStateFetcher();
|
||||
|
||||
dumpSyncState(ipw, buckets);
|
||||
mConstants.dump(pw, "");
|
||||
dumpSyncAdapters(ipw);
|
||||
|
||||
@@ -1991,7 +2009,7 @@ public class SyncManager {
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected void dumpPendingSyncs(PrintWriter pw) {
|
||||
protected void dumpPendingSyncs(PrintWriter pw, SyncAdapterStateFetcher buckets) {
|
||||
List<SyncOperation> pendingSyncs = getAllPendingSyncs();
|
||||
|
||||
pw.print("Pending Syncs: ");
|
||||
@@ -2001,14 +2019,14 @@ public class SyncManager {
|
||||
int count = 0;
|
||||
for (SyncOperation op: pendingSyncs) {
|
||||
if (!op.isPeriodic) {
|
||||
pw.println(op.dump(null, false));
|
||||
pw.println(op.dump(null, false, buckets));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
pw.println();
|
||||
}
|
||||
|
||||
protected void dumpPeriodicSyncs(PrintWriter pw) {
|
||||
protected void dumpPeriodicSyncs(PrintWriter pw, SyncAdapterStateFetcher buckets) {
|
||||
List<SyncOperation> pendingSyncs = getAllPendingSyncs();
|
||||
|
||||
pw.print("Periodic Syncs: ");
|
||||
@@ -2018,7 +2036,7 @@ public class SyncManager {
|
||||
int count = 0;
|
||||
for (SyncOperation op: pendingSyncs) {
|
||||
if (op.isPeriodic) {
|
||||
pw.println(op.dump(null, false));
|
||||
pw.println(op.dump(null, false, buckets));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -2075,7 +2093,7 @@ public class SyncManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void dumpSyncState(PrintWriter pw) {
|
||||
protected void dumpSyncState(PrintWriter pw, SyncAdapterStateFetcher buckets) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
pw.print("Data connected: "); pw.println(mDataConnectionIsConnected);
|
||||
@@ -2150,13 +2168,13 @@ public class SyncManager {
|
||||
sb.setLength(0);
|
||||
pw.print(formatDurationHMS(sb, durationInSeconds));
|
||||
pw.print(" - ");
|
||||
pw.print(activeSyncContext.mSyncOperation.dump(pm, false));
|
||||
pw.print(activeSyncContext.mSyncOperation.dump(pm, false, buckets));
|
||||
pw.println();
|
||||
}
|
||||
pw.println();
|
||||
|
||||
dumpPendingSyncs(pw);
|
||||
dumpPeriodicSyncs(pw);
|
||||
dumpPendingSyncs(pw, buckets);
|
||||
dumpPeriodicSyncs(pw, buckets);
|
||||
|
||||
// Join the installed sync adapter with the accounts list and emit for everything.
|
||||
pw.println("Sync Status");
|
||||
@@ -3219,7 +3237,7 @@ public class SyncManager {
|
||||
scheduleSync(syncTargets.account, syncTargets.userId,
|
||||
SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider,
|
||||
null, AuthorityInfo.NOT_INITIALIZED,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3286,7 +3304,7 @@ public class SyncManager {
|
||||
syncAdapterInfo.componentName.getPackageName(), SyncOperation.REASON_PERIODIC,
|
||||
SyncStorageEngine.SOURCE_PERIODIC, extras,
|
||||
syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID,
|
||||
pollFrequencyMillis, flexMillis, /*isAppStandbyExempted=*/ false);
|
||||
pollFrequencyMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
|
||||
final int syncOpState = computeSyncOpState(op);
|
||||
switch (syncOpState) {
|
||||
@@ -3431,6 +3449,15 @@ public class SyncManager {
|
||||
Slog.v(TAG, syncContext.toString());
|
||||
}
|
||||
}
|
||||
if (op.isAppStandbyExempted()) {
|
||||
final UsageStatsManagerInternal usmi = LocalServices.getService(
|
||||
UsageStatsManagerInternal.class);
|
||||
if (usmi != null) {
|
||||
usmi.reportExemptedSyncStart(op.owningPackage,
|
||||
UserHandle.getUserId(op.owningUid));
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to the sync adapter.
|
||||
int targetUid;
|
||||
ComponentName targetComponent;
|
||||
@@ -3604,7 +3631,7 @@ public class SyncManager {
|
||||
|
||||
syncOperation.retries++;
|
||||
if (syncOperation.retries > mConstants.getMaxRetriesWithAppStandbyExemption()) {
|
||||
syncOperation.isAppStandbyExempted = false;
|
||||
syncOperation.syncExemptionFlag = ContentResolver.SYNC_EXEMPTION_NONE;
|
||||
}
|
||||
|
||||
// the operation failed so increase the backoff time
|
||||
@@ -3672,7 +3699,7 @@ public class SyncManager {
|
||||
syncOperation.reason,
|
||||
syncOperation.syncSource, info.provider, new Bundle(),
|
||||
syncOperation.allowParallelSyncs,
|
||||
syncOperation.isAppStandbyExempted));
|
||||
syncOperation.syncExemptionFlag));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ public class SyncManagerConstants extends ContentObserver {
|
||||
private static final int DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION = 5;
|
||||
private int mMaxRetriesWithAppStandbyExemption = DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION;
|
||||
|
||||
private static final String KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS =
|
||||
"exemption_temp_whitelist_duration_in_seconds";
|
||||
private static final int DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS = 10 * 60;
|
||||
private int mKeyExemptionTempWhitelistDurationInSeconds
|
||||
= DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS;
|
||||
|
||||
protected SyncManagerConstants(Context context) {
|
||||
super(null);
|
||||
mContext = context;
|
||||
@@ -97,6 +103,11 @@ public class SyncManagerConstants extends ContentObserver {
|
||||
mMaxRetriesWithAppStandbyExemption = parser.getInt(
|
||||
KEY_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION,
|
||||
DEF_MAX_RETRIES_WITH_APP_STANDBY_EXEMPTION);
|
||||
|
||||
mKeyExemptionTempWhitelistDurationInSeconds = parser.getInt(
|
||||
KEY_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS,
|
||||
DEF_EXEMPTION_TEMP_WHITELIST_DURATION_IN_SECONDS);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +135,12 @@ public class SyncManagerConstants extends ContentObserver {
|
||||
}
|
||||
}
|
||||
|
||||
public int getKeyExemptionTempWhitelistDurationInSeconds() {
|
||||
synchronized (mLock) {
|
||||
return mKeyExemptionTempWhitelistDurationInSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
synchronized (mLock) {
|
||||
pw.print(prefix);
|
||||
@@ -144,6 +161,10 @@ public class SyncManagerConstants extends ContentObserver {
|
||||
pw.print(prefix);
|
||||
pw.print(" mMaxRetriesWithAppStandbyExemption=");
|
||||
pw.println(mMaxRetriesWithAppStandbyExemption);
|
||||
|
||||
pw.print(prefix);
|
||||
pw.print(" mKeyExemptionTempWhitelistDurationInSeconds=");
|
||||
pw.println(mKeyExemptionTempWhitelistDurationInSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.content;
|
||||
import android.accounts.Account;
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentResolver.SyncExemption;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
@@ -98,33 +99,33 @@ public class SyncOperation {
|
||||
/** jobId of the JobScheduler job corresponding to this sync */
|
||||
public int jobId;
|
||||
|
||||
/** Whether this operation should be exempted from the app-standby throttling. */
|
||||
public boolean isAppStandbyExempted;
|
||||
@SyncExemption
|
||||
public int syncExemptionFlag;
|
||||
|
||||
public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
|
||||
int reason, int source, String provider, Bundle extras,
|
||||
boolean allowParallelSyncs, boolean isAppStandbyExempted) {
|
||||
boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag) {
|
||||
this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
|
||||
reason, source, extras, allowParallelSyncs, isAppStandbyExempted);
|
||||
reason, source, extras, allowParallelSyncs, syncExemptionFlag);
|
||||
}
|
||||
|
||||
private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
|
||||
int reason, int source, Bundle extras, boolean allowParallelSyncs,
|
||||
boolean isAppStandbyExempted) {
|
||||
@SyncExemption int syncExemptionFlag) {
|
||||
this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false,
|
||||
NO_JOB_ID, 0, 0, isAppStandbyExempted);
|
||||
NO_JOB_ID, 0, 0, syncExemptionFlag);
|
||||
}
|
||||
|
||||
public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) {
|
||||
this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource,
|
||||
new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId,
|
||||
periodMillis, flexMillis, /*isAppStandbyExempted=*/ false);
|
||||
periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
}
|
||||
|
||||
public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
|
||||
int reason, int source, Bundle extras, boolean allowParallelSyncs,
|
||||
boolean isPeriodic, int sourcePeriodicId, long periodMillis,
|
||||
long flexMillis, boolean isAppStandbyExempted) {
|
||||
long flexMillis, @SyncExemption int syncExemptionFlag) {
|
||||
this.target = info;
|
||||
this.owningUid = owningUid;
|
||||
this.owningPackage = owningPackage;
|
||||
@@ -138,7 +139,7 @@ public class SyncOperation {
|
||||
this.flexMillis = flexMillis;
|
||||
this.jobId = NO_JOB_ID;
|
||||
this.key = toKey();
|
||||
this.isAppStandbyExempted = isAppStandbyExempted;
|
||||
this.syncExemptionFlag = syncExemptionFlag;
|
||||
}
|
||||
|
||||
/* Get a one off sync operation instance from a periodic sync. */
|
||||
@@ -148,7 +149,7 @@ public class SyncOperation {
|
||||
}
|
||||
SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource,
|
||||
new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */,
|
||||
periodMillis, flexMillis, /*isAppStandbyExempted=*/ false);
|
||||
periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
return op;
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ public class SyncOperation {
|
||||
periodMillis = other.periodMillis;
|
||||
flexMillis = other.flexMillis;
|
||||
this.key = other.key;
|
||||
isAppStandbyExempted = other.isAppStandbyExempted;
|
||||
syncExemptionFlag = other.syncExemptionFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +236,7 @@ public class SyncOperation {
|
||||
jobInfoExtras.putLong("flexMillis", flexMillis);
|
||||
jobInfoExtras.putLong("expectedRuntime", expectedRuntime);
|
||||
jobInfoExtras.putInt("retries", retries);
|
||||
jobInfoExtras.putBoolean("isAppStandbyExempted", isAppStandbyExempted);
|
||||
jobInfoExtras.putInt("syncExemptionFlag", syncExemptionFlag);
|
||||
return jobInfoExtras;
|
||||
}
|
||||
|
||||
@@ -256,7 +257,7 @@ public class SyncOperation {
|
||||
Bundle extras;
|
||||
boolean allowParallelSyncs, isPeriodic;
|
||||
long periodMillis, flexMillis;
|
||||
boolean isAppStandbyExempted;
|
||||
int syncExemptionFlag;
|
||||
|
||||
if (!jobExtras.getBoolean("SyncManagerJob", false)) {
|
||||
return null;
|
||||
@@ -275,7 +276,8 @@ public class SyncOperation {
|
||||
initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID);
|
||||
periodMillis = jobExtras.getLong("periodMillis");
|
||||
flexMillis = jobExtras.getLong("flexMillis");
|
||||
isAppStandbyExempted = jobExtras.getBoolean("isAppStandbyExempted", false);
|
||||
syncExemptionFlag = jobExtras.getInt("syncExemptionFlag",
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
extras = new Bundle();
|
||||
|
||||
PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras");
|
||||
@@ -298,7 +300,7 @@ public class SyncOperation {
|
||||
new SyncStorageEngine.EndPoint(account, provider, userId);
|
||||
SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source,
|
||||
extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis,
|
||||
isAppStandbyExempted);
|
||||
syncExemptionFlag);
|
||||
op.jobId = jobExtras.getInt("jobId");
|
||||
op.expectedRuntime = jobExtras.getLong("expectedRuntime");
|
||||
op.retries = jobExtras.getInt("retries");
|
||||
@@ -361,10 +363,10 @@ public class SyncOperation {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dump(null, true);
|
||||
return dump(null, true, null);
|
||||
}
|
||||
|
||||
String dump(PackageManager pm, boolean shorter) {
|
||||
String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("JobId=").append(jobId)
|
||||
.append(" ")
|
||||
@@ -385,8 +387,18 @@ public class SyncOperation {
|
||||
if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
|
||||
sb.append(" EXPEDITED");
|
||||
}
|
||||
if (isAppStandbyExempted) {
|
||||
sb.append(" STANDBY-EXEMPTED");
|
||||
switch (syncExemptionFlag) {
|
||||
case ContentResolver.SYNC_EXEMPTION_NONE:
|
||||
break;
|
||||
case ContentResolver.SYNC_EXEMPTION_ACTIVE:
|
||||
sb.append(" STANDBY-EXEMPTED");
|
||||
break;
|
||||
case ContentResolver.SYNC_EXEMPTION_ACTIVE_WITH_TEMP:
|
||||
sb.append(" STANDBY-EXEMPTED(TOP)");
|
||||
break;
|
||||
default:
|
||||
sb.append(" ExemptionFlag=" + syncExemptionFlag);
|
||||
break;
|
||||
}
|
||||
sb.append(" Reason=");
|
||||
sb.append(reasonToString(pm, reason));
|
||||
@@ -397,21 +409,31 @@ public class SyncOperation {
|
||||
SyncManager.formatDurationHMS(sb, flexMillis);
|
||||
sb.append(")");
|
||||
}
|
||||
if (retries > 0) {
|
||||
sb.append(" Retries=");
|
||||
sb.append(retries);
|
||||
}
|
||||
if (!shorter) {
|
||||
sb.append(" Owner={");
|
||||
UserHandle.formatUid(sb, owningUid);
|
||||
sb.append(" ");
|
||||
sb.append(owningPackage);
|
||||
if (appStates != null) {
|
||||
sb.append(" [");
|
||||
sb.append(appStates.getStandbyBucket(
|
||||
UserHandle.getUserId(owningUid), owningPackage));
|
||||
sb.append("]");
|
||||
|
||||
if (appStates.isAppActive(owningUid)) {
|
||||
sb.append(" [ACTIVE]");
|
||||
}
|
||||
}
|
||||
sb.append("}");
|
||||
if (!extras.keySet().isEmpty()) {
|
||||
sb.append(" ");
|
||||
extrasToStringBuilder(extras, sb);
|
||||
}
|
||||
}
|
||||
if (retries > 0) {
|
||||
sb.append(" Retries=");
|
||||
sb.append(retries);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -464,6 +486,10 @@ public class SyncOperation {
|
||||
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
|
||||
}
|
||||
|
||||
boolean isAppStandbyExempted() {
|
||||
return syncExemptionFlag != ContentResolver.SYNC_EXEMPTION_NONE;
|
||||
}
|
||||
|
||||
static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
|
||||
if (bundle == null) {
|
||||
sb.append("null");
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.accounts.AccountManager;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentResolver.SyncExemption;
|
||||
import android.content.Context;
|
||||
import android.content.ISyncStatusObserver;
|
||||
import android.content.PeriodicSync;
|
||||
@@ -341,7 +342,7 @@ public class SyncStorageEngine {
|
||||
|
||||
/** Called when a sync is needed on an account(s) due to some change in state. */
|
||||
public void onSyncRequest(EndPoint info, int reason, Bundle extras,
|
||||
boolean exemptFromAppStandby);
|
||||
@SyncExemption int syncExemptionFlag);
|
||||
}
|
||||
|
||||
interface PeriodicSyncAddedListener {
|
||||
@@ -647,7 +648,7 @@ public class SyncStorageEngine {
|
||||
}
|
||||
|
||||
public void setSyncAutomatically(Account account, int userId, String providerName,
|
||||
boolean sync) {
|
||||
boolean sync, @SyncExemption int syncExemptionFlag) {
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Slog.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
|
||||
+ ", user " + userId + " -> " + sync);
|
||||
@@ -677,7 +678,7 @@ public class SyncStorageEngine {
|
||||
if (sync) {
|
||||
requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName,
|
||||
new Bundle(),
|
||||
/* exemptFromAppStandby=*/ false);
|
||||
syncExemptionFlag);
|
||||
}
|
||||
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
|
||||
queueBackup();
|
||||
@@ -739,7 +740,7 @@ public class SyncStorageEngine {
|
||||
}
|
||||
if (syncable == AuthorityInfo.SYNCABLE) {
|
||||
requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle(),
|
||||
/*exemptFromAppStandby=*/ false); // Or the caller FG state?
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
}
|
||||
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
|
||||
}
|
||||
@@ -900,7 +901,8 @@ public class SyncStorageEngine {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setMasterSyncAutomatically(boolean flag, int userId) {
|
||||
public void setMasterSyncAutomatically(boolean flag, int userId,
|
||||
@SyncExemption int syncExemptionFlag) {
|
||||
synchronized (mAuthorities) {
|
||||
Boolean auto = mMasterSyncAutomatically.get(userId);
|
||||
if (auto != null && auto.equals(flag)) {
|
||||
@@ -912,7 +914,7 @@ public class SyncStorageEngine {
|
||||
if (flag) {
|
||||
requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null,
|
||||
new Bundle(),
|
||||
/*exemptFromAppStandby=*/ false); // Or the caller FG state?
|
||||
syncExemptionFlag);
|
||||
}
|
||||
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
|
||||
mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
|
||||
@@ -2046,7 +2048,8 @@ public class SyncStorageEngine {
|
||||
String value = c.getString(c.getColumnIndex("value"));
|
||||
if (name == null) continue;
|
||||
if (name.equals("listen_for_tickles")) {
|
||||
setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
|
||||
setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0,
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
} else if (name.startsWith("sync_provider_")) {
|
||||
String provider = name.substring("sync_provider_".length(),
|
||||
name.length());
|
||||
@@ -2143,11 +2146,11 @@ public class SyncStorageEngine {
|
||||
}
|
||||
|
||||
private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras,
|
||||
boolean exemptFromAppStandby) {
|
||||
@SyncExemption int syncExemptionFlag) {
|
||||
if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
|
||||
&& mSyncRequestListener != null) {
|
||||
mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras,
|
||||
exemptFromAppStandby);
|
||||
syncExemptionFlag);
|
||||
} else {
|
||||
SyncRequest.Builder req =
|
||||
new SyncRequest.Builder()
|
||||
@@ -2159,7 +2162,7 @@ public class SyncStorageEngine {
|
||||
}
|
||||
|
||||
private void requestSync(Account account, int userId, int reason, String authority,
|
||||
Bundle extras, boolean exemptFromAppStandby) {
|
||||
Bundle extras, @SyncExemption int syncExemptionFlag) {
|
||||
// If this is happening in the system process, then call the syncrequest listener
|
||||
// to make a request back to the SyncManager directly.
|
||||
// If this is probably a test instance, then call back through the ContentResolver
|
||||
@@ -2168,7 +2171,7 @@ public class SyncStorageEngine {
|
||||
&& mSyncRequestListener != null) {
|
||||
mSyncRequestListener.onSyncRequest(
|
||||
new EndPoint(account, authority, userId),
|
||||
reason, extras, exemptFromAppStandby);
|
||||
reason, extras, syncExemptionFlag);
|
||||
} else {
|
||||
ContentResolver.requestSync(account, authority, extras);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.server.content;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentResolver;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
import android.test.AndroidTestCase;
|
||||
@@ -60,7 +61,7 @@ public class SyncOperationTest extends AndroidTestCase {
|
||||
"authority1",
|
||||
b1,
|
||||
false,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
|
||||
// Same as op1 but different time infos
|
||||
SyncOperation op2 = new SyncOperation(account1, 0,
|
||||
@@ -69,7 +70,7 @@ public class SyncOperationTest extends AndroidTestCase {
|
||||
"authority1",
|
||||
b1,
|
||||
false,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
|
||||
// Same as op1 but different authority
|
||||
SyncOperation op3 = new SyncOperation(account1, 0,
|
||||
@@ -78,7 +79,7 @@ public class SyncOperationTest extends AndroidTestCase {
|
||||
"authority2",
|
||||
b1,
|
||||
false,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
|
||||
// Same as op1 but different account
|
||||
SyncOperation op4 = new SyncOperation(account2, 0,
|
||||
@@ -87,7 +88,7 @@ public class SyncOperationTest extends AndroidTestCase {
|
||||
"authority1",
|
||||
b1,
|
||||
false,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
|
||||
// Same as op1 but different bundle
|
||||
SyncOperation op5 = new SyncOperation(account1, 0,
|
||||
@@ -96,7 +97,7 @@ public class SyncOperationTest extends AndroidTestCase {
|
||||
"authority1",
|
||||
b2,
|
||||
false,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
|
||||
assertEquals(op1.key, op2.key);
|
||||
assertNotSame(op1.key, op3.key);
|
||||
@@ -117,7 +118,7 @@ public class SyncOperationTest extends AndroidTestCase {
|
||||
"authority1",
|
||||
b1,
|
||||
false,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
|
||||
PersistableBundle pb = op1.toJobInfoExtras();
|
||||
SyncOperation op2 = SyncOperation.maybeCreateFromJobExtras(pb);
|
||||
@@ -145,7 +146,7 @@ public class SyncOperationTest extends AndroidTestCase {
|
||||
Bundle extras = new Bundle();
|
||||
SyncOperation periodic = new SyncOperation(ep, 0, "package", 0, 0, extras, false, true,
|
||||
SyncOperation.NO_JOB_ID, 60000, 10000,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
ContentResolver.SYNC_EXEMPTION_NONE);
|
||||
SyncOperation oneoff = periodic.createOneTimeSyncOperation();
|
||||
assertFalse("Conversion to oneoff sync failed.", oneoff.isPeriodic);
|
||||
assertEquals("Period not restored", periodic.periodMillis, oneoff.periodMillis);
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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.content;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.RenamingDelegatingContext;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.test.mock.MockContext;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.test.suitebuilder.annotation.Suppress;
|
||||
|
||||
import com.android.internal.os.AtomicFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
/**
|
||||
* Test for SyncStorageEngine.
|
||||
*
|
||||
* bit FrameworksServicesTests:com.android.server.content.SyncStorageEngineTest
|
||||
*
|
||||
* TODO Broken. Fix it. b/62485315
|
||||
*/
|
||||
@Suppress
|
||||
public class SyncStorageEngineTest extends AndroidTestCase {
|
||||
|
||||
protected Account account1;
|
||||
protected Account account2;
|
||||
protected ComponentName syncService1;
|
||||
protected String authority1 = "testprovider";
|
||||
protected Bundle defaultBundle;
|
||||
protected final int DEFAULT_USER = 0;
|
||||
|
||||
/* Some default poll frequencies. */
|
||||
final long dayPoll = (60 * 60 * 24);
|
||||
final long dayFuzz = 60;
|
||||
final long thousandSecs = 1000;
|
||||
final long thousandSecsFuzz = 100;
|
||||
|
||||
MockContentResolver mockResolver;
|
||||
SyncStorageEngine engine;
|
||||
|
||||
private File getSyncDir() {
|
||||
return new File(new File(getContext().getFilesDir(), "system"), "sync");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
account1 = new Account("a@example.com", "example.type");
|
||||
account2 = new Account("b@example.com", "example.type");
|
||||
syncService1 = new ComponentName("com.example", "SyncService");
|
||||
// Default bundle.
|
||||
defaultBundle = new Bundle();
|
||||
defaultBundle.putInt("int_key", 0);
|
||||
defaultBundle.putString("string_key", "hello");
|
||||
// Set up storage engine.
|
||||
mockResolver = new MockContentResolver();
|
||||
engine = SyncStorageEngine.newTestInstance(
|
||||
new TestContext(mockResolver, getContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we handle the case of a history row being old enough to purge before the
|
||||
* corresponding sync is finished. This can happen if the clock changes while we are syncing.
|
||||
*
|
||||
*/
|
||||
// TODO: this test causes AidlTest to fail. Omit for now
|
||||
// @SmallTest
|
||||
public void testPurgeActiveSync() throws Exception {
|
||||
final Account account = new Account("a@example.com", "example.type");
|
||||
final String authority = "testprovider";
|
||||
|
||||
MockContentResolver mockResolver = new MockContentResolver();
|
||||
|
||||
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
|
||||
new TestContext(mockResolver, getContext()));
|
||||
long time0 = 1000;
|
||||
SyncOperation op = new SyncOperation(account, 0, 0, "foo",
|
||||
SyncOperation.REASON_PERIODIC,
|
||||
SyncStorageEngine.SOURCE_LOCAL,
|
||||
authority,
|
||||
Bundle.EMPTY, true,
|
||||
/*isAppStandbyExempted=*/ false);
|
||||
long historyId = engine.insertStartSyncEvent(op, time0);
|
||||
long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
|
||||
engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
public void testAuthorityPersistence() throws Exception {
|
||||
final Account account1 = new Account("a@example.com", "example.type");
|
||||
final Account account2 = new Account("b@example.com", "example.type.2");
|
||||
final String authority1 = "testprovider1";
|
||||
final String authority2 = "testprovider2";
|
||||
|
||||
engine.setMasterSyncAutomatically(false, 0);
|
||||
|
||||
engine.setIsSyncable(account1, 0, authority1, 1);
|
||||
engine.setSyncAutomatically(account1, 0, authority1, true);
|
||||
|
||||
engine.setIsSyncable(account2, 0, authority1, 1);
|
||||
engine.setSyncAutomatically(account2, 0, authority1, true);
|
||||
|
||||
engine.setIsSyncable(account1, 0, authority2, 1);
|
||||
engine.setSyncAutomatically(account1, 0, authority2, false);
|
||||
|
||||
engine.setIsSyncable(account2, 0, authority2, 0);
|
||||
engine.setSyncAutomatically(account2, 0, authority2, true);
|
||||
|
||||
engine.writeAllState();
|
||||
engine.clearAndReadState();
|
||||
|
||||
assertEquals(true, engine.getSyncAutomatically(account1, 0, authority1));
|
||||
assertEquals(true, engine.getSyncAutomatically(account2, 0, authority1));
|
||||
assertEquals(false, engine.getSyncAutomatically(account1, 0, authority2));
|
||||
assertEquals(true, engine.getSyncAutomatically(account2, 0, authority2));
|
||||
|
||||
assertEquals(1, engine.getIsSyncable(account1, 0, authority1));
|
||||
assertEquals(1, engine.getIsSyncable(account2, 0, authority1));
|
||||
assertEquals(1, engine.getIsSyncable(account1, 0, authority2));
|
||||
assertEquals(0, engine.getIsSyncable(account2, 0, authority2));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testListenForTicklesParsing() throws Exception {
|
||||
byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
|
||||
+ "<accounts>\n"
|
||||
+ "<listenForTickles user=\"0\" enabled=\"false\" />"
|
||||
+ "<listenForTickles user=\"1\" enabled=\"true\" />"
|
||||
+ "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
|
||||
+ "<authority id=\"1\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
|
||||
+ "</accounts>\n").getBytes();
|
||||
|
||||
MockContentResolver mockResolver = new MockContentResolver();
|
||||
final TestContext testContext = new TestContext(mockResolver, getContext());
|
||||
|
||||
File syncDir = getSyncDir();
|
||||
syncDir.mkdirs();
|
||||
AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
|
||||
FileOutputStream fos = accountInfoFile.startWrite();
|
||||
fos.write(accountsFileData);
|
||||
accountInfoFile.finishWrite(fos);
|
||||
|
||||
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
|
||||
|
||||
assertEquals(false, engine.getMasterSyncAutomatically(0));
|
||||
assertEquals(true, engine.getMasterSyncAutomatically(1));
|
||||
assertEquals(true, engine.getMasterSyncAutomatically(2));
|
||||
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
public void testAuthorityRenaming() throws Exception {
|
||||
final Account account1 = new Account("acc1", "type1");
|
||||
final Account account2 = new Account("acc2", "type2");
|
||||
final String authorityContacts = "contacts";
|
||||
final String authorityCalendar = "calendar";
|
||||
final String authorityOther = "other";
|
||||
final String authorityContactsNew = "com.android.contacts";
|
||||
final String authorityCalendarNew = "com.android.calendar";
|
||||
|
||||
MockContentResolver mockResolver = new MockContentResolver();
|
||||
|
||||
final TestContext testContext = new TestContext(mockResolver, getContext());
|
||||
|
||||
byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
|
||||
+ "<accounts>\n"
|
||||
+ "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
|
||||
+ "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
|
||||
+ "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
|
||||
+ "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
|
||||
+ "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
|
||||
+ "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
|
||||
+ "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
|
||||
+ " authority=\"com.android.calendar\" />\n"
|
||||
+ "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
|
||||
+ " authority=\"com.android.contacts\" />\n"
|
||||
+ "</accounts>\n").getBytes();
|
||||
|
||||
File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
|
||||
syncDir.mkdirs();
|
||||
AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
|
||||
FileOutputStream fos = accountInfoFile.startWrite();
|
||||
fos.write(accountsFileData);
|
||||
accountInfoFile.finishWrite(fos);
|
||||
|
||||
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
|
||||
|
||||
assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityContacts));
|
||||
assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityCalendar));
|
||||
assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityOther));
|
||||
assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityContactsNew));
|
||||
assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityCalendarNew));
|
||||
|
||||
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContacts));
|
||||
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendar));
|
||||
assertEquals(true, engine.getSyncAutomatically(account2, 0, authorityOther));
|
||||
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContactsNew));
|
||||
assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendarNew));
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testSyncableMigration() throws Exception {
|
||||
final Account account = new Account("acc", "type");
|
||||
|
||||
MockContentResolver mockResolver = new MockContentResolver();
|
||||
|
||||
final TestContext testContext = new TestContext(mockResolver, getContext());
|
||||
|
||||
byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
|
||||
+ "<accounts>\n"
|
||||
+ "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
|
||||
+ "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
|
||||
+ "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
|
||||
+ " authority=\"other3\" />\n"
|
||||
+ "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
|
||||
+ " authority=\"other4\" />\n"
|
||||
+ "</accounts>\n").getBytes();
|
||||
|
||||
File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
|
||||
syncDir.mkdirs();
|
||||
AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
|
||||
FileOutputStream fos = accountInfoFile.startWrite();
|
||||
fos.write(accountsFileData);
|
||||
accountInfoFile.finishWrite(fos);
|
||||
|
||||
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
|
||||
|
||||
assertEquals(-1, engine.getIsSyncable(account, 0, "other1"));
|
||||
assertEquals(1, engine.getIsSyncable(account, 0, "other2"));
|
||||
assertEquals(0, engine.getIsSyncable(account, 0, "other3"));
|
||||
assertEquals(1, engine.getIsSyncable(account, 0, "other4"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the API cannot cause a run-time reboot by passing in the empty string as an
|
||||
* authority. The problem here is that
|
||||
* {@link SyncStorageEngine#getOrCreateAuthorityLocked(account, provider)} would register
|
||||
* an empty authority which causes a RTE in {@link SyncManager#scheduleReadyPeriodicSyncs()}.
|
||||
* This is not strictly a SSE test, but it does depend on the SSE data structures.
|
||||
*/
|
||||
@SmallTest
|
||||
public void testExpectedIllegalArguments() throws Exception {
|
||||
try {
|
||||
ContentResolver.setSyncAutomatically(account1, "", true);
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
try {
|
||||
ContentResolver.addPeriodicSync(account1, "", Bundle.EMPTY, 84000L);
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
try {
|
||||
ContentResolver.removePeriodicSync(account1, "", Bundle.EMPTY);
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
try {
|
||||
ContentResolver.cancelSync(account1, "");
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
try {
|
||||
ContentResolver.setIsSyncable(account1, "", 0);
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
try {
|
||||
ContentResolver.cancelSync(account1, "");
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
try {
|
||||
ContentResolver.requestSync(account1, "", Bundle.EMPTY);
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
try {
|
||||
ContentResolver.getSyncStatus(account1, "");
|
||||
fail("empty provider string should throw IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
|
||||
// Make sure we aren't blocking null account/provider for those functions that use it
|
||||
// to specify ALL accounts/providers.
|
||||
ContentResolver.requestSync(null, null, Bundle.EMPTY);
|
||||
ContentResolver.cancelSync(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
class TestContext extends ContextWrapper {
|
||||
|
||||
ContentResolver mResolver;
|
||||
|
||||
private final Context mRealContext;
|
||||
|
||||
public TestContext(ContentResolver resolver, Context realContext) {
|
||||
super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
|
||||
mRealContext = realContext;
|
||||
mResolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return mRealContext.getResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFilesDir() {
|
||||
return mRealContext.getFilesDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enforceCallingOrSelfPermission(String permission, String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBroadcast(Intent intent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentResolver getContentResolver() {
|
||||
return mResolver;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
|
||||
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_START;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
|
||||
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN;
|
||||
@@ -186,6 +187,7 @@ public class AppStandbyController {
|
||||
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
|
||||
/** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */
|
||||
static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11;
|
||||
static final int MSG_REPORT_EXEMPTED_SYNC_START = 12;
|
||||
|
||||
long mCheckIdleIntervalMillis;
|
||||
long mAppIdleParoleIntervalMillis;
|
||||
@@ -202,6 +204,8 @@ public class AppStandbyController {
|
||||
long mPredictionTimeoutMillis;
|
||||
/** Maximum time a sync adapter associated with a CP should keep the buckets elevated. */
|
||||
long mSyncAdapterTimeoutMillis;
|
||||
/** Maximum time an exempted sync should keep the buckets elevated. */
|
||||
long mExemptedSyncAdapterTimeoutMillis;
|
||||
/** Maximum time a system interaction should keep the buckets elevated. */
|
||||
long mSystemInteractionTimeoutMillis;
|
||||
|
||||
@@ -375,6 +379,21 @@ public class AppStandbyController {
|
||||
}
|
||||
}
|
||||
|
||||
void reportExemptedSyncStart(String packageName, int userId) {
|
||||
if (!mAppIdleEnabled) return;
|
||||
|
||||
final long elapsedRealtime = mInjector.elapsedRealtime();
|
||||
|
||||
synchronized (mAppIdleLock) {
|
||||
AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId,
|
||||
STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_EXEMPTED_SYNC_START,
|
||||
0,
|
||||
elapsedRealtime + mExemptedSyncAdapterTimeoutMillis);
|
||||
maybeInformListeners(packageName, userId, elapsedRealtime,
|
||||
appUsage.currentBucket, appUsage.bucketingReason, false);
|
||||
}
|
||||
}
|
||||
|
||||
void setChargingState(boolean charging) {
|
||||
synchronized (mAppIdleLock) {
|
||||
if (mCharging != charging) {
|
||||
@@ -1274,6 +1293,11 @@ public class AppStandbyController {
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
void postReportExemptedSyncStart(String packageName, int userId) {
|
||||
mHandler.obtainMessage(MSG_REPORT_EXEMPTED_SYNC_START, userId, 0, packageName)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) {
|
||||
synchronized (mAppIdleLock) {
|
||||
mAppIdleHistory.dump(idpw, userId, pkg);
|
||||
@@ -1488,6 +1512,11 @@ public class AppStandbyController {
|
||||
checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2,
|
||||
mInjector.elapsedRealtime());
|
||||
break;
|
||||
|
||||
case MSG_REPORT_EXEMPTED_SYNC_START:
|
||||
reportExemptedSyncStart((String) msg.obj, msg.arg1);
|
||||
break;
|
||||
|
||||
default:
|
||||
super.handleMessage(msg);
|
||||
break;
|
||||
@@ -1550,6 +1579,7 @@ public class AppStandbyController {
|
||||
"system_update_usage_duration";
|
||||
private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout";
|
||||
private static final String KEY_SYNC_ADAPTER_HOLD_DURATION = "sync_adapter_duration";
|
||||
private static final String KEY_EXEMPTED_SYNC_HOLD_DURATION = "exempted_sync_duration";
|
||||
private static final String KEY_SYSTEM_INTERACTION_HOLD_DURATION =
|
||||
"system_interaction_duration";
|
||||
public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR;
|
||||
@@ -1557,6 +1587,7 @@ public class AppStandbyController {
|
||||
public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = 2 * ONE_HOUR;
|
||||
public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT = 10 * ONE_MINUTE;
|
||||
public static final long DEFAULT_SYNC_ADAPTER_TIMEOUT = 10 * ONE_MINUTE;
|
||||
public static final long DEFAULT_EXEMPTED_SYNC_TIMEOUT = 10 * ONE_MINUTE;
|
||||
|
||||
private final KeyValueListParser mParser = new KeyValueListParser(',');
|
||||
|
||||
@@ -1632,6 +1663,9 @@ public class AppStandbyController {
|
||||
mSyncAdapterTimeoutMillis = mParser.getDurationMillis
|
||||
(KEY_SYNC_ADAPTER_HOLD_DURATION,
|
||||
COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYNC_ADAPTER_TIMEOUT);
|
||||
mExemptedSyncAdapterTimeoutMillis = mParser.getDurationMillis
|
||||
(KEY_EXEMPTED_SYNC_HOLD_DURATION,
|
||||
COMPRESS_TIME ? ONE_MINUTE : DEFAULT_EXEMPTED_SYNC_TIMEOUT);
|
||||
mSystemInteractionTimeoutMillis = mParser.getDurationMillis
|
||||
(KEY_SYSTEM_INTERACTION_HOLD_DURATION,
|
||||
COMPRESS_TIME ? ONE_MINUTE : DEFAULT_SYSTEM_INTERACTION_TIMEOUT);
|
||||
|
||||
@@ -1279,5 +1279,10 @@ public class UsageStatsService extends SystemService implements
|
||||
public void onAdminDataAvailable() {
|
||||
mAppStandby.onAdminDataAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportExemptedSyncStart(String packageName, int userId) {
|
||||
mAppStandby.postReportExemptedSyncStart(packageName, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user