Home | History | Annotate | Download | only in cts
      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