1 /*
2  * Copyright (C) 2022 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 
17 package android.appenumeration.cts;
18 
19 import static android.appenumeration.cts.Constants.ACCOUNT_NAME;
20 import static android.appenumeration.cts.Constants.ACCOUNT_TYPE;
21 import static android.appenumeration.cts.Constants.ACCOUNT_TYPE_SHARED_USER;
22 import static android.appenumeration.cts.Constants.ACTION_GET_IS_SYNCABLE;
23 import static android.appenumeration.cts.Constants.ACTION_GET_PERIODIC_SYNCS;
24 import static android.appenumeration.cts.Constants.ACTION_GET_SYNCADAPTER_CONTROL_PANEL;
25 import static android.appenumeration.cts.Constants.ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY;
26 import static android.appenumeration.cts.Constants.ACTION_GET_SYNCADAPTER_TYPES;
27 import static android.appenumeration.cts.Constants.ACTION_GET_SYNC_AUTOMATICALLY;
28 import static android.appenumeration.cts.Constants.ACTION_REQUEST_PERIODIC_SYNC;
29 import static android.appenumeration.cts.Constants.ACTION_REQUEST_SYNC_AND_AWAIT_STATUS;
30 import static android.appenumeration.cts.Constants.ACTION_SET_SYNC_AUTOMATICALLY;
31 import static android.appenumeration.cts.Constants.AUTHORITY_SUFFIX;
32 import static android.appenumeration.cts.Constants.EXTRA_ACCOUNT;
33 import static android.appenumeration.cts.Constants.EXTRA_AUTHORITY;
34 import static android.appenumeration.cts.Constants.QUERIES_NOTHING;
35 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SHARED_USER;
36 import static android.appenumeration.cts.Constants.QUERIES_PACKAGE;
37 import static android.appenumeration.cts.Constants.SERVICE_CLASS_SYNC_ADAPTER;
38 import static android.appenumeration.cts.Constants.TARGET_SYNCADAPTER;
39 import static android.appenumeration.cts.Constants.TARGET_SYNCADAPTER_AUTHORITY;
40 import static android.appenumeration.cts.Constants.TARGET_SYNCADAPTER_SHARED_USER;
41 import static android.appenumeration.cts.Utils.allowTestApiAccess;
42 import static android.appenumeration.cts.Utils.resetTestApiAccess;
43 import static android.content.Intent.EXTRA_COMPONENT_NAME;
44 
45 import static org.hamcrest.MatcherAssert.assertThat;
46 import static org.hamcrest.Matchers.hasItemInArray;
47 import static org.hamcrest.Matchers.not;
48 import static org.hamcrest.Matchers.notNullValue;
49 import static org.hamcrest.Matchers.nullValue;
50 import static org.hamcrest.core.Is.is;
51 import static org.junit.Assert.assertThrows;
52 
53 import android.accounts.Account;
54 import android.accounts.AccountManager;
55 import android.app.PendingIntent;
56 import android.content.ComponentName;
57 import android.content.Intent;
58 import android.content.PeriodicSync;
59 import android.content.SyncAdapterType;
60 import android.os.Bundle;
61 import android.os.Process;
62 
63 import androidx.test.ext.junit.runners.AndroidJUnit4;
64 import androidx.test.filters.FlakyTest;
65 
66 import org.junit.AfterClass;
67 import org.junit.BeforeClass;
68 import org.junit.Test;
69 import org.junit.runner.RunWith;
70 
71 import java.util.List;
72 
73 @RunWith(AndroidJUnit4.class)
74 public class SyncAdapterEnumerationTests extends AppEnumerationTestsBase {
75 
76     private static AccountManager sAccountManager;
77 
78     private static final Account ACCOUNT_SYNCADAPTER = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
79     private static final Account ACCOUNT_SYNCADAPTER_SHARED_USER = new Account(ACCOUNT_NAME,
80             ACCOUNT_TYPE_SHARED_USER);
81 
82     @BeforeClass
setUpAccounts()83     public static void setUpAccounts() {
84         sAccountManager = AccountManager.get(sContext);
85         assertThat(sAccountManager.addAccountExplicitly(ACCOUNT_SYNCADAPTER,
86                 null /* password */, null /* userdata */), is(true));
87         assertThat(sAccountManager.addAccountExplicitly(ACCOUNT_SYNCADAPTER_SHARED_USER,
88                 null /* password */, null /* userdata */), is(true));
89     }
90 
91     @AfterClass
tearDownAccounts()92     public static void tearDownAccounts() {
93         assertThat(sAccountManager.removeAccountExplicitly(ACCOUNT_SYNCADAPTER),
94                 is(true));
95         assertThat(sAccountManager.removeAccountExplicitly(ACCOUNT_SYNCADAPTER_SHARED_USER),
96                 is(true));
97     }
98 
99     @Test
queriesPackage_getSyncAdapterTypes_canSeeSyncAdapterTarget()100     public void queriesPackage_getSyncAdapterTypes_canSeeSyncAdapterTarget() throws Exception {
101         assertVisible(QUERIES_PACKAGE, TARGET_SYNCADAPTER, this::getSyncAdapterTypes);
102     }
103 
104     @Test
queriesNothing_getSyncAdapterTypes_cannotSeeSyncAdapterTarget()105     public void queriesNothing_getSyncAdapterTypes_cannotSeeSyncAdapterTarget() throws Exception {
106         assertNotVisible(QUERIES_NOTHING, TARGET_SYNCADAPTER, this::getSyncAdapterTypes);
107         assertNotVisible(QUERIES_NOTHING, TARGET_SYNCADAPTER_SHARED_USER,
108                 this::getSyncAdapterTypes);
109     }
110 
111     @FlakyTest(bugId = 328849197)
112     @Test
queriesNothingSharedUser_getSyncAdapterTypes_canSeeSyncAdapterSharedUserTarget()113     public void queriesNothingSharedUser_getSyncAdapterTypes_canSeeSyncAdapterSharedUserTarget()
114             throws Exception {
115         assertVisible(QUERIES_NOTHING_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER,
116                 this::getSyncAdapterTypes);
117     }
118 
119     @Test
queriesPackage_getSyncAdapterPackages_canSeeSyncAdapterTarget()120     public void queriesPackage_getSyncAdapterPackages_canSeeSyncAdapterTarget() throws Exception {
121         try {
122             allowTestApiAccess(QUERIES_PACKAGE);
123 
124             assertVisible(QUERIES_PACKAGE, TARGET_SYNCADAPTER,
125                     this::getSyncAdapterPackagesForAuthorityAsUser);
126         } finally {
127             resetTestApiAccess(QUERIES_PACKAGE);
128         }
129     }
130 
131     @Test
queriesNothing_getSyncAdapterPackages_cannotSeeSyncAdapterTarget()132     public void queriesNothing_getSyncAdapterPackages_cannotSeeSyncAdapterTarget()
133             throws Exception {
134         try {
135             allowTestApiAccess(QUERIES_NOTHING);
136 
137             assertNotVisible(QUERIES_NOTHING, TARGET_SYNCADAPTER,
138                     this::getSyncAdapterPackagesForAuthorityAsUser);
139             assertNotVisible(QUERIES_NOTHING, TARGET_SYNCADAPTER_SHARED_USER,
140                     this::getSyncAdapterPackagesForAuthorityAsUser);
141         } finally {
142             resetTestApiAccess(QUERIES_NOTHING);
143         }
144     }
145 
146     @Test
queriesNothingSharedUser_getSyncAdapterPackages_canSeeSyncAdapterSharedUserTarget()147     public void queriesNothingSharedUser_getSyncAdapterPackages_canSeeSyncAdapterSharedUserTarget()
148             throws Exception {
149         try {
150             allowTestApiAccess(QUERIES_NOTHING_SHARED_USER);
151 
152             assertVisible(QUERIES_NOTHING_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER,
153                     this::getSyncAdapterPackagesForAuthorityAsUser);
154         } finally {
155             resetTestApiAccess(QUERIES_NOTHING_SHARED_USER);
156         }
157     }
158 
159     @Test
queriesPackage_requestSync_canSeeSyncAdapterTarget()160     public void queriesPackage_requestSync_canSeeSyncAdapterTarget() throws Exception {
161         assertThat(
162                 requestSyncAndAwaitStatus(QUERIES_PACKAGE, ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER),
163                 is(true));
164     }
165 
166     @FlakyTest(bugId = 328849197)
queriesNothingSharedUser_requestSync_canSeeSyncAdapterSharedUserTarget()167     public void queriesNothingSharedUser_requestSync_canSeeSyncAdapterSharedUserTarget()
168             throws Exception {
169         assertThat(requestSyncAndAwaitStatus(QUERIES_NOTHING_SHARED_USER,
170                 ACCOUNT_SYNCADAPTER_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER), is(true));
171     }
172 
173     @Test
queriesNothing_requestSync_cannotSeeSyncAdapterTarget()174     public void queriesNothing_requestSync_cannotSeeSyncAdapterTarget() {
175         assertThrows(MissingCallbackException.class,
176                 () -> requestSyncAndAwaitStatus(QUERIES_NOTHING, ACCOUNT_SYNCADAPTER,
177                         TARGET_SYNCADAPTER));
178         assertThrows(MissingCallbackException.class,
179                 () -> requestSyncAndAwaitStatus(QUERIES_NOTHING, ACCOUNT_SYNCADAPTER_SHARED_USER,
180                         TARGET_SYNCADAPTER_SHARED_USER));
181     }
182 
183     @Test
queriesPackage_getRunningServiceControlPanel_canSeeSyncAdapterTarget()184     public void queriesPackage_getRunningServiceControlPanel_canSeeSyncAdapterTarget()
185             throws Exception {
186         assertThat(getSyncAdapterControlPanel(QUERIES_PACKAGE, ACCOUNT_SYNCADAPTER,
187                 TARGET_SYNCADAPTER), notNullValue());
188     }
189 
190     @Test
queriesNothing_getRunningServiceControlPanel_cannotSeeSyncAdapterTarget()191     public void queriesNothing_getRunningServiceControlPanel_cannotSeeSyncAdapterTarget()
192             throws Exception {
193         assertThat(getSyncAdapterControlPanel(QUERIES_NOTHING, ACCOUNT_SYNCADAPTER,
194                 TARGET_SYNCADAPTER), nullValue());
195     }
196 
197     @Test
queriesNothing_getIsSyncable_cannotSeeSyncAdapterTarget()198     public void queriesNothing_getIsSyncable_cannotSeeSyncAdapterTarget() throws Exception {
199         assertThat(getIsSyncable(QUERIES_NOTHING, ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER),
200                 is(0));
201     }
202 
203     @Test
queriesPackage_getIsSyncable_canSeeSyncAdapterTarget()204     public void queriesPackage_getIsSyncable_canSeeSyncAdapterTarget() throws Exception {
205         assertThat(getIsSyncable(QUERIES_PACKAGE, ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER),
206                 not(is(0)));
207     }
208 
209     @Test
queriesNothing_getSyncAutomatically_cannotSeeSyncAdapterTarget()210     public void queriesNothing_getSyncAutomatically_cannotSeeSyncAdapterTarget() throws Exception {
211         setSyncAutomatically(TARGET_SYNCADAPTER, ACCOUNT_SYNCADAPTER);
212         assertThat(getSyncAutomatically(QUERIES_NOTHING, TARGET_SYNCADAPTER), is(false));
213     }
214 
215     @Test
queriesPackage_getSyncAutomatically_canSeeSyncAdapterTarget()216     public void queriesPackage_getSyncAutomatically_canSeeSyncAdapterTarget() throws Exception {
217         setSyncAutomatically(TARGET_SYNCADAPTER, ACCOUNT_SYNCADAPTER);
218         assertThat(getSyncAutomatically(QUERIES_PACKAGE, TARGET_SYNCADAPTER), is(true));
219     }
220 
221     @Test
queriesNothing_getPeriodicSyncs_cannotSeeSyncAdapterTarget()222     public void queriesNothing_getPeriodicSyncs_cannotSeeSyncAdapterTarget() throws Exception {
223         assertThat(requestPeriodicSync(TARGET_SYNCADAPTER, ACCOUNT_SYNCADAPTER), is(true));
224         assertThat(getPeriodicSyncs(QUERIES_NOTHING, ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER),
225                 not(hasItemInArray(TARGET_SYNCADAPTER_AUTHORITY)));
226     }
227 
228     @Test
queriesPackage_getPeriodicSyncs_canSeeSyncAdapterTarget()229     public void queriesPackage_getPeriodicSyncs_canSeeSyncAdapterTarget() throws Exception {
230         assertThat(requestPeriodicSync(TARGET_SYNCADAPTER, ACCOUNT_SYNCADAPTER), is(true));
231         assertThat(getPeriodicSyncs(QUERIES_PACKAGE, ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER),
232                 hasItemInArray(TARGET_SYNCADAPTER_AUTHORITY));
233     }
234 
235 
getSyncAdapterTypes(String sourcePackageName)236     private String[] getSyncAdapterTypes(String sourcePackageName) throws Exception {
237         final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
238                 /* intentExtra */ null, ACTION_GET_SYNCADAPTER_TYPES);
239         final List<SyncAdapterType> types = response.getParcelableArrayList(
240                 Intent.EXTRA_RETURN_RESULT, SyncAdapterType.class);
241         return types.stream()
242                 .map(type -> type.getPackageName())
243                 .distinct()
244                 .toArray(String[]::new);
245     }
246 
getSyncAdapterPackagesForAuthorityAsUser(String sourcePackageName, String targetPackageName)247     private String[] getSyncAdapterPackagesForAuthorityAsUser(String sourcePackageName,
248             String targetPackageName) throws Exception {
249         final Bundle extraData = new Bundle();
250         extraData.putString(EXTRA_AUTHORITY, targetPackageName + AUTHORITY_SUFFIX);
251         extraData.putInt(Intent.EXTRA_USER, Process.myUserHandle().getIdentifier());
252         final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
253                 extraData, ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY);
254         return response.getStringArray(Intent.EXTRA_PACKAGES);
255     }
256 
requestSyncAndAwaitStatus(String sourcePackageName, Account account, String targetPackageName)257     private boolean requestSyncAndAwaitStatus(String sourcePackageName, Account account,
258             String targetPackageName) throws Exception {
259         final Bundle extraData = new Bundle();
260         extraData.putParcelable(EXTRA_ACCOUNT, account);
261         extraData.putString(EXTRA_AUTHORITY, targetPackageName + AUTHORITY_SUFFIX);
262         final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
263                 extraData, ACTION_REQUEST_SYNC_AND_AWAIT_STATUS);
264         return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
265     }
266 
getSyncAdapterControlPanel(String sourcePackageName, Account account, String targetPackageName)267     private PendingIntent getSyncAdapterControlPanel(String sourcePackageName, Account account,
268             String targetPackageName) throws Exception {
269         final ComponentName componentName = new ComponentName(
270                 targetPackageName, SERVICE_CLASS_SYNC_ADAPTER);
271         final Bundle extraData = new Bundle();
272         extraData.putParcelable(EXTRA_ACCOUNT, account);
273         extraData.putString(EXTRA_AUTHORITY, targetPackageName + AUTHORITY_SUFFIX);
274         extraData.putParcelable(EXTRA_COMPONENT_NAME, componentName);
275         final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
276                 extraData, ACTION_GET_SYNCADAPTER_CONTROL_PANEL);
277         return response.getParcelable(Intent.EXTRA_RETURN_RESULT, PendingIntent.class);
278     }
279 
requestPeriodicSync(String providerPackageName, Account account)280     private boolean requestPeriodicSync(String providerPackageName, Account account)
281             throws Exception {
282         final String authority = providerPackageName + AUTHORITY_SUFFIX;
283         final Bundle extraData = new Bundle();
284         extraData.putParcelable(EXTRA_ACCOUNT, account);
285         extraData.putString(EXTRA_AUTHORITY, authority);
286         final Bundle response = sendCommandBlocking(providerPackageName,
287                 null /* targetPackageName */, extraData, ACTION_REQUEST_PERIODIC_SYNC);
288         return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
289     }
290 
setSyncAutomatically(String providerPackageName, Account account)291     private void setSyncAutomatically(String providerPackageName, Account account)
292             throws Exception {
293         final String authority = providerPackageName + AUTHORITY_SUFFIX;
294         final Bundle extraData = new Bundle();
295         extraData.putParcelable(EXTRA_ACCOUNT, account);
296         extraData.putString(EXTRA_AUTHORITY, authority);
297         sendCommandBlocking(providerPackageName, null /* targetPackageName */, extraData,
298                 ACTION_SET_SYNC_AUTOMATICALLY);
299     }
300 
getSyncAutomatically(String sourcePackageName, String targetPackageName)301     private boolean getSyncAutomatically(String sourcePackageName, String targetPackageName)
302             throws Exception {
303         final String authority = targetPackageName + AUTHORITY_SUFFIX;
304         final Bundle extraData = new Bundle();
305         extraData.putString(EXTRA_AUTHORITY, authority);
306         final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
307                 extraData, ACTION_GET_SYNC_AUTOMATICALLY);
308         return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
309     }
310 
getIsSyncable(String sourcePackageName, Account account, String targetPackageName)311     private int getIsSyncable(String sourcePackageName, Account account, String targetPackageName)
312             throws Exception {
313         final String authority = targetPackageName + AUTHORITY_SUFFIX;
314         final Bundle extraData = new Bundle();
315         extraData.putParcelable(EXTRA_ACCOUNT, account);
316         extraData.putString(EXTRA_AUTHORITY, authority);
317         final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
318                 extraData, ACTION_GET_IS_SYNCABLE);
319         return response.getInt(Intent.EXTRA_RETURN_RESULT);
320     }
321 
getPeriodicSyncs(String sourcePackageName, Account account, String targetPackageName)322     private String[] getPeriodicSyncs(String sourcePackageName, Account account,
323             String targetPackageName) throws Exception {
324         final String authority = targetPackageName + AUTHORITY_SUFFIX;
325         final Bundle extraData = new Bundle();
326         extraData.putParcelable(EXTRA_ACCOUNT, account);
327         extraData.putString(EXTRA_AUTHORITY, authority);
328         final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
329                 extraData, ACTION_GET_PERIODIC_SYNCS);
330         final List<PeriodicSync> list = response.getParcelableArrayList(Intent.EXTRA_RETURN_RESULT,
331                 PeriodicSync.class);
332         return list.stream()
333                 .map(sync -> sync.authority)
334                 .distinct()
335                 .toArray(String[]::new);
336     }
337 }
338