1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.content.syncmanager.cts; 17 18 import static android.content.syncmanager.cts.common.Values.ACCOUNT_1_A; 19 import static android.content.syncmanager.cts.common.Values.APP1_AUTHORITY; 20 import static android.content.syncmanager.cts.common.Values.APP1_PACKAGE; 21 22 import static com.android.compatibility.common.util.BundleUtils.makeBundle; 23 import static com.android.compatibility.common.util.ConnectivityUtils.assertNetworkConnected; 24 import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting; 25 import static com.android.compatibility.common.util.SystemUtil.runCommandAndPrintOnLogcat; 26 27 import static junit.framework.TestCase.assertEquals; 28 29 import static org.junit.Assert.assertTrue; 30 31 import android.accounts.Account; 32 import android.app.usage.UsageStatsManager; 33 import android.content.ContentResolver; 34 import android.content.Context; 35 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.AddAccount; 36 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.ClearSyncInvocations; 37 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.GetSyncInvocations; 38 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.RemoveAllAccounts; 39 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.SetResult; 40 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Request.SetResult.Result; 41 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.Response; 42 import android.content.syncmanager.cts.SyncManagerCtsProto.Payload.SyncInvocation; 43 import android.os.Bundle; 44 import android.support.test.InstrumentationRegistry; 45 import android.support.test.filters.LargeTest; 46 import android.support.test.runner.AndroidJUnit4; 47 import android.util.Log; 48 49 import com.android.compatibility.common.util.AmUtils; 50 import com.android.compatibility.common.util.BatteryUtils; 51 import com.android.compatibility.common.util.OnFailureRule; 52 import com.android.compatibility.common.util.ParcelUtils; 53 import com.android.compatibility.common.util.SystemUtil; 54 import com.android.compatibility.common.util.TestUtils; 55 import com.android.compatibility.common.util.TestUtils.BooleanSupplierWithThrow; 56 57 import org.junit.After; 58 import org.junit.Before; 59 import org.junit.Rule; 60 import org.junit.Test; 61 import org.junit.runner.Description; 62 import org.junit.runner.RunWith; 63 import org.junit.runners.model.Statement; 64 65 66 // TODO Don't run if no network is available. 67 68 @LargeTest 69 @RunWith(AndroidJUnit4.class) 70 public class CtsSyncManagerTest { 71 private static final String TAG = "CtsSyncManagerTest"; 72 73 public static final int DEFAULT_TIMEOUT_SECONDS = 30; 74 75 public static final boolean DEBUG = false; 76 private static final int TIMEOUT_MS = 10 * 60 * 1000; 77 78 @Rule 79 public final OnFailureRule mDumpOnFailureRule = new OnFailureRule(TAG) { 80 @Override 81 protected void onTestFailure(Statement base, Description description, Throwable t) { 82 runCommandAndPrintOnLogcat(TAG, "dumpsys content"); 83 runCommandAndPrintOnLogcat(TAG, "dumpsys jobscheduler"); 84 } 85 }; 86 87 protected final BroadcastRpc mRpc = new BroadcastRpc(); 88 89 Context mContext; 90 ContentResolver mContentResolver; 91 92 @Before 93 public void setUp() throws Exception { 94 assertNetworkConnected(InstrumentationRegistry.getContext()); 95 96 BatteryUtils.runDumpsysBatteryUnplug(); 97 98 AmUtils.setStandbyBucket(APP1_PACKAGE, UsageStatsManager.STANDBY_BUCKET_ACTIVE); 99 100 mContext = InstrumentationRegistry.getContext(); 101 mContentResolver = mContext.getContentResolver(); 102 103 ContentResolver.setMasterSyncAutomatically(true); 104 105 Thread.sleep(1000); // Don't make the system too busy... 106 } 107 108 @After 109 public void tearDown() throws Exception { 110 resetSyncConfig(); 111 BatteryUtils.runDumpsysBatteryReset(); 112 } 113 114 private static void resetSyncConfig() { 115 putGlobalSetting("sync_manager_constants", "null"); 116 } 117 118 private static void writeSyncConfig( 119 int initialSyncRetryTimeInSeconds, 120 float retryTimeIncreaseFactor, 121 int maxSyncRetryTimeInSeconds, 122 int maxRetriesWithAppStandbyExemption) { 123 putGlobalSetting("sync_manager_constants", 124 "initial_sync_retry_time_in_seconds=" + initialSyncRetryTimeInSeconds + "," + 125 "retry_time_increase_factor=" + retryTimeIncreaseFactor + "," + 126 "max_sync_retry_time_in_seconds=" + maxSyncRetryTimeInSeconds + "," + 127 "max_retries_with_app_standby_exemption=" + maxRetriesWithAppStandbyExemption); 128 } 129 130 /** Return the part of "dumpsys content" that's relevant to the current sync status. */ 131 private String getSyncDumpsys() { 132 final String out = SystemUtil.runCommandAndExtractSection("dumpsys content", 133 "^Active Syncs:.*", false, 134 "^Sync Statistics", false); 135 return out; 136 } 137 138 private void waitUntil(String message, BooleanSupplierWithThrow predicate) throws Exception { 139 TestUtils.waitUntil(message, TIMEOUT_MS, predicate); 140 } 141 142 private void removeAllAccounts() throws Exception { 143 mRpc.invoke(APP1_PACKAGE, 144 rb -> rb.setRemoveAllAccounts(RemoveAllAccounts.newBuilder())); 145 146 Thread.sleep(1000); 147 148 AmUtils.waitForBroadcastIdle(); 149 150 waitUntil("Dumpsys still mentions " + ACCOUNT_1_A, 151 () -> !getSyncDumpsys().contains(ACCOUNT_1_A.name)); 152 153 Thread.sleep(1000); 154 } 155 156 private void clearSyncInvocations(String packageName) throws Exception { 157 mRpc.invoke(packageName, 158 rb -> rb.setClearSyncInvocations(ClearSyncInvocations.newBuilder())); 159 } 160 161 private void addAccountAndLetInitialSyncRun(Account account, String authority) 162 throws Exception { 163 // Add the first account, which will trigger an initial sync. 164 mRpc.invoke(APP1_PACKAGE, 165 rb -> rb.setAddAccount(AddAccount.newBuilder().setName(account.name))); 166 167 waitUntil("Syncable isn't initialized", 168 () -> ContentResolver.getIsSyncable(account, authority) == 1); 169 170 waitUntil("Periodic sync should set up", 171 () -> ContentResolver.getPeriodicSyncs(account, authority).size() == 1); 172 assertEquals("Periodic should be 24h", 173 24 * 60 * 60, ContentResolver.getPeriodicSyncs(account, authority).get(0).period); 174 } 175 176 @Test 177 public void testInitialSync() throws Exception { 178 removeAllAccounts(); 179 180 mRpc.invoke(APP1_PACKAGE, rb -> rb.setClearSyncInvocations( 181 ClearSyncInvocations.newBuilder())); 182 183 // Add the first account, which will trigger an initial sync. 184 addAccountAndLetInitialSyncRun(ACCOUNT_1_A, APP1_AUTHORITY); 185 186 // Check the sync request parameters. 187 188 Response res = mRpc.invoke(APP1_PACKAGE, 189 rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder())); 190 assertEquals(1, res.getSyncInvocations().getSyncInvocationsCount()); 191 192 SyncInvocation si = res.getSyncInvocations().getSyncInvocations(0); 193 194 assertEquals(ACCOUNT_1_A.name, si.getAccountName()); 195 assertEquals(ACCOUNT_1_A.type, si.getAccountType()); 196 assertEquals(APP1_AUTHORITY, si.getAuthority()); 197 198 Bundle extras = ParcelUtils.fromBytes(si.getExtras().toByteArray()); 199 assertTrue(extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE)); 200 } 201 202 @Test 203 public void testSoftErrorRetriesActiveApp() throws Exception { 204 removeAllAccounts(); 205 206 // Let the initial sync happen. 207 addAccountAndLetInitialSyncRun(ACCOUNT_1_A, APP1_AUTHORITY); 208 209 writeSyncConfig(2, 1, 2, 3); 210 211 clearSyncInvocations(APP1_PACKAGE); 212 213 AmUtils.setStandbyBucket(APP1_PACKAGE, UsageStatsManager.STANDBY_BUCKET_ACTIVE); 214 215 // Set soft error. 216 mRpc.invoke(APP1_PACKAGE, rb -> 217 rb.setSetResult(SetResult.newBuilder().setResult(Result.SOFT_ERROR))); 218 219 Bundle b = makeBundle( 220 "testSoftErrorRetriesActiveApp", true, 221 ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 222 223 ContentResolver.requestSync(ACCOUNT_1_A, APP1_AUTHORITY, b); 224 225 // First sync + 3 retries == 4, so should be called more than 4 times. 226 // But it's active, so it should retry more than that. 227 waitUntil("Should retry more than 3 times.", () -> { 228 final Response res = mRpc.invoke(APP1_PACKAGE, 229 rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder())); 230 final int calls = res.getSyncInvocations().getSyncInvocationsCount(); 231 Log.i(TAG, "NumSyncInvocations=" + calls); 232 return calls > 4; // Arbitrarily bigger than 4. 233 }); 234 } 235 236 // WIP This test doesn't work yet. 237 // @Test 238 // public void testSoftErrorRetriesFrequentApp() throws Exception { 239 // runTest(() -> { 240 // removeAllAccounts(); 241 // 242 // // Let the initial sync happen. 243 // addAccountAndLetInitialSyncRun(ACCOUNT_1_A, APP1_AUTHORITY); 244 // 245 // writeSyncConfig(2, 1, 2, 3); 246 // 247 // clearSyncInvocations(APP1_PACKAGE); 248 // 249 // AmUtils.setStandbyBucket(APP1_PACKAGE, UsageStatsManager.STANDBY_BUCKET_FREQUENT); 250 // 251 // // Set soft error. 252 // mRpc.invoke(APP1_PACKAGE, rb -> 253 // rb.setSetResult(SetResult.newBuilder().setResult(Result.SOFT_ERROR))); 254 // 255 // Bundle b = makeBundle( 256 // "testSoftErrorRetriesFrequentApp", true, 257 // ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 258 // 259 // ContentResolver.requestSync(ACCOUNT_1_A, APP1_AUTHORITY, b); 260 // 261 // waitUntil("Should retry more than 3 times.", () -> { 262 // final Response res = mRpc.invoke(APP1_PACKAGE, 263 // rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder())); 264 // final int calls = res.getSyncInvocations().getSyncInvocationsCount(); 265 // Log.i(TAG, "NumSyncInvocations=" + calls); 266 // return calls >= 4; // First sync + 3 retries == 4, so at least 4 times. 267 // }); 268 // 269 // Thread.sleep(10_000); 270 // 271 // // One more retry is okay because of how the job scheduler throttle jobs, but no further. 272 // final Response res = mRpc.invoke(APP1_PACKAGE, 273 // rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder())); 274 // final int calls = res.getSyncInvocations().getSyncInvocationsCount(); 275 // assertTrue("# of syncs must be equal or less than 5, but was " + calls, calls <= 5); 276 // }); 277 // } 278 } 279