1 /* 2 * Copyright (C) 2016 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 com.android.cts.content; 18 19 import static com.android.cts.content.Utils.ALWAYS_SYNCABLE_AUTHORITY; 20 import static com.android.cts.content.Utils.SYNC_TIMEOUT_MILLIS; 21 import static com.android.cts.content.Utils.allowSyncAdapterRunInBackgroundAndDataInBackground; 22 import static com.android.cts.content.Utils.disallowSyncAdapterRunInBackgroundAndDataInBackground; 23 import static com.android.cts.content.Utils.getUiDevice; 24 import static com.android.cts.content.Utils.hasDataConnection; 25 import static com.android.cts.content.Utils.hasNotificationSupport; 26 import static com.android.cts.content.Utils.isWatch; 27 import static com.android.cts.content.Utils.requestSync; 28 import static com.android.cts.content.Utils.withAccount; 29 30 import static org.junit.Assume.assumeFalse; 31 import static org.junit.Assume.assumeTrue; 32 import static org.mockito.ArgumentMatchers.any; 33 import static org.mockito.Mockito.never; 34 import static org.mockito.Mockito.timeout; 35 import static org.mockito.Mockito.verify; 36 37 import android.app.ActivityManager; 38 import android.content.AbstractThreadedSyncAdapter; 39 import android.content.ContentResolver; 40 import android.content.Context; 41 import android.content.SyncRequest; 42 import android.content.res.Configuration; 43 import android.support.test.uiautomator.By; 44 import android.support.test.uiautomator.UiDevice; 45 import android.support.test.uiautomator.UiObject2; 46 import android.support.test.uiautomator.Until; 47 import android.util.Log; 48 49 import androidx.test.InstrumentationRegistry; 50 import androidx.test.rule.ActivityTestRule; 51 import androidx.test.runner.AndroidJUnit4; 52 53 import org.junit.After; 54 import org.junit.Before; 55 import org.junit.Rule; 56 import org.junit.Test; 57 import org.junit.rules.TestRule; 58 import org.junit.runner.RunWith; 59 60 import java.io.ByteArrayOutputStream; 61 import java.util.regex.Pattern; 62 63 /** 64 * Tests whether a sync adapter can access accounts. 65 */ 66 @RunWith(AndroidJUnit4.class) 67 public class CtsSyncAccountAccessOtherCertTestCases { 68 private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec 69 private static final String LOG_TAG = 70 CtsSyncAccountAccessOtherCertTestCases.class.getSimpleName(); 71 72 private static final Pattern PERMISSION_REQUESTED = Pattern.compile( 73 "Permission Requested|Permission requested"); 74 private static final Pattern ALLOW_SYNC = Pattern.compile("ALLOW|Allow"); 75 private static final String OPEN_NOTIFICATION_WATCH = "Open"; 76 77 @Rule 78 public final TestRule mFlakyTestRule = new FlakyTestRule(3); 79 80 @Rule 81 public final ActivityTestRule<StubActivity> activity = new ActivityTestRule(StubActivity.class); 82 83 @Before 84 public void setUp() throws Exception { 85 allowSyncAdapterRunInBackgroundAndDataInBackground(); 86 } 87 88 @After 89 public void tearDown() throws Exception { 90 disallowSyncAdapterRunInBackgroundAndDataInBackground(); 91 } 92 93 @Test 94 public void testAccountAccess_otherCertAsAuthenticatorCanNotSeeAccount() throws Exception { 95 assumeTrue(hasDataConnection()); 96 assumeTrue(hasNotificationSupport()); 97 assumeFalse(isRunningInVR()); 98 99 // If running in a test harness the Account Manager never denies access to an account. Hence 100 // the permission request will not trigger. b/72114924 101 assumeFalse(ActivityManager.isRunningInTestHarness()); 102 103 try (AutoCloseable ignored = withAccount(activity.getActivity())) { 104 AbstractThreadedSyncAdapter adapter = AlwaysSyncableSyncService.getInstance( 105 activity.getActivity()).setNewDelegate(); 106 107 SyncRequest request = requestSync(ALWAYS_SYNCABLE_AUTHORITY); 108 Log.i(LOG_TAG, "Sync requested " + request); 109 110 Thread.sleep(SYNC_TIMEOUT_MILLIS); 111 verify(adapter, never()).onPerformSync(any(), any(), any(), any(), any()); 112 Log.i(LOG_TAG, "Did not get onPerformSync"); 113 114 UiDevice uiDevice = getUiDevice(); 115 if (isWatch()) { 116 UiObject2 notification = findPermissionNotificationInStream(uiDevice); 117 notification.click(); 118 UiObject2 openButton = uiDevice.wait( 119 Until.findObject(By.text(OPEN_NOTIFICATION_WATCH)), UI_TIMEOUT_MILLIS); 120 if (openButton != null) { 121 // older sysui may not have the "open" button 122 openButton.click(); 123 } 124 } else { 125 uiDevice.openNotification(); 126 try { 127 UiObject2 permissionRequest = uiDevice.wait( 128 Until.findObject(By.text(PERMISSION_REQUESTED)), UI_TIMEOUT_MILLIS); 129 130 permissionRequest.click(); 131 } catch (Throwable t) { 132 ByteArrayOutputStream os = new ByteArrayOutputStream(); 133 getUiDevice().dumpWindowHierarchy(os); 134 135 Log.w(LOG_TAG, "Window hierarchy:"); 136 for (String line : os.toString("UTF-8").split("\n")) { 137 Log.w(LOG_TAG, line); 138 139 // Do not overwhelm logging 140 Thread.sleep(10); 141 } 142 143 throw t; 144 } 145 } 146 147 uiDevice.wait(Until.findObject(By.text(ALLOW_SYNC)), UI_TIMEOUT_MILLIS).click(); 148 149 ContentResolver.requestSync(request); 150 151 verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(), any(), any(), any(), 152 any()); 153 Log.i(LOG_TAG, "Got onPerformSync"); 154 } 155 } 156 157 private UiObject2 findPermissionNotificationInStream(UiDevice uiDevice) { 158 uiDevice.pressHome(); 159 swipeUp(uiDevice); 160 if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) { 161 return uiDevice.findObject(By.text(PERMISSION_REQUESTED)); 162 } 163 for (int i = 0; i < 100; i++) { 164 if (!swipeUp(uiDevice)) { 165 // We have reached the end of the stream and not found the target. 166 break; 167 } 168 if (uiDevice.hasObject(By.text(PERMISSION_REQUESTED))) { 169 return uiDevice.findObject(By.text(PERMISSION_REQUESTED)); 170 } 171 } 172 return null; 173 } 174 175 private boolean swipeUp(UiDevice uiDevice) { 176 int width = uiDevice.getDisplayWidth(); 177 int height = uiDevice.getDisplayHeight(); 178 return uiDevice.swipe( 179 width / 2 /* startX */, 180 height - 1 /* startY */, 181 width / 2 /* endX */, 182 1 /* endY */, 183 50 /* numberOfSteps */); 184 } 185 186 private boolean isRunningInVR() { 187 final Context context = InstrumentationRegistry.getTargetContext(); 188 return ((context.getResources().getConfiguration().uiMode & 189 Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_VR_HEADSET); 190 } 191 } 192