1 /* 2 * Copyright (C) 2017 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.autofillservice.cts.activities; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.app.Activity; 23 import android.autofillservice.cts.R; 24 import android.autofillservice.cts.testcore.AutofillTestWatcher; 25 import android.autofillservice.cts.testcore.MyAutofillCallback; 26 import android.autofillservice.cts.testcore.Timeouts; 27 import android.graphics.Bitmap; 28 import android.graphics.Rect; 29 import android.os.Bundle; 30 import android.view.PixelCopy; 31 import android.view.View; 32 import android.view.WindowInsets; 33 import android.view.autofill.AutofillManager; 34 35 import androidx.annotation.NonNull; 36 37 import com.android.compatibility.common.util.RetryableException; 38 import com.android.compatibility.common.util.SynchronousPixelCopy; 39 import com.android.compatibility.common.util.Timeout; 40 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 44 /** 45 * Base class for all activities in this test suite 46 */ 47 public abstract class AbstractAutoFillActivity extends Activity { 48 49 private final CountDownLatch mDestroyedLatch = new CountDownLatch(1); 50 protected final String mTag = getClass().getSimpleName(); 51 private MyAutofillCallback mCallback; 52 53 /** 54 * Run an action in the UI thread, and blocks caller until the action is finished. 55 */ syncRunOnUiThread(Runnable action)56 public final void syncRunOnUiThread(Runnable action) { 57 syncRunOnUiThread(action, Timeouts.UI_TIMEOUT.ms()); 58 } 59 60 /** 61 * Run an action in the UI thread, and blocks caller until the action is finished or it times 62 * out. 63 */ syncRunOnUiThread(Runnable action, long timeoutMs)64 public final void syncRunOnUiThread(Runnable action, long timeoutMs) { 65 final CountDownLatch latch = new CountDownLatch(1); 66 runOnUiThread(() -> { 67 action.run(); 68 latch.countDown(); 69 }); 70 try { 71 if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) { 72 throw new RetryableException("action on UI thread timed out after %d ms", 73 timeoutMs); 74 } 75 } catch (InterruptedException e) { 76 Thread.currentThread().interrupt(); 77 throw new RuntimeException("Interrupted", e); 78 } 79 } 80 getAutofillManager()81 public AutofillManager getAutofillManager() { 82 return getSystemService(AutofillManager.class); 83 } 84 85 /** 86 * Takes a screenshot from the whole activity. 87 * 88 * <p><b>Note:</b> this screenshot only contains the contents of the activity, it doesn't 89 * include the autofill UIs; if you need to check that, please use 90 * {@link UiBot#takeScreenshot()} instead. 91 */ takeScreenshot()92 public Bitmap takeScreenshot() { 93 return takeScreenshot(findViewById(android.R.id.content).getRootView()); 94 } 95 96 /** 97 * Takes a screenshot from the a view. 98 */ takeScreenshot(View view)99 public Bitmap takeScreenshot(View view) { 100 final Rect srcRect = new Rect(); 101 syncRunOnUiThread(() -> view.getGlobalVisibleRect(srcRect)); 102 final Bitmap dest = Bitmap.createBitmap( 103 srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888); 104 105 final SynchronousPixelCopy copy = new SynchronousPixelCopy(); 106 final int copyResult = copy.request(getWindow(), srcRect, dest); 107 assertThat(copyResult).isEqualTo(PixelCopy.SUCCESS); 108 109 return dest; 110 } 111 112 /** 113 * Registers and returns a custom callback for autofill events. 114 * 115 * <p>Note: caller doesn't need to call {@link #unregisterCallback()}, it will be automatically 116 * unregistered on {@link #finish()}. 117 */ registerCallback()118 public MyAutofillCallback registerCallback() { 119 assertWithMessage("already registered").that(mCallback).isNull(); 120 mCallback = new MyAutofillCallback(); 121 getAutofillManager().registerCallback(mCallback); 122 return mCallback; 123 } 124 125 /** 126 * Unregister the callback from the {@link AutofillManager}. 127 * 128 * <p>This method just need to be called when a test case wants to explicitly test the behavior 129 * of the activity when the callback is unregistered. 130 */ unregisterCallback()131 public void unregisterCallback() { 132 assertWithMessage("not registered").that(mCallback).isNotNull(); 133 unregisterNonNullCallback(); 134 } 135 136 /** 137 * Waits until {@link #onDestroy()} is called. 138 */ waitUntilDestroyed(@onNull Timeout timeout)139 public void waitUntilDestroyed(@NonNull Timeout timeout) throws InterruptedException { 140 if (!mDestroyedLatch.await(timeout.ms(), TimeUnit.MILLISECONDS)) { 141 throw new RetryableException(timeout, "activity %s not destroyed", this); 142 } 143 } 144 unregisterNonNullCallback()145 private void unregisterNonNullCallback() { 146 getAutofillManager().unregisterCallback(mCallback); 147 mCallback = null; 148 } 149 150 @Override onCreate(Bundle savedInstanceState)151 protected void onCreate(Bundle savedInstanceState) { 152 super.onCreate(savedInstanceState); 153 // TODO(b/309578419): Make activities handle insets properly and then remove this. 154 getTheme().applyStyle(R.style.OptOutEdgeToEdge, /* force */ false); 155 AutofillTestWatcher.registerActivity("onCreate()", this); 156 } 157 158 @Override onDestroy()159 protected void onDestroy() { 160 super.onDestroy(); 161 162 // Activitiy is typically unregistered at finish(), but we need to unregister here too 163 // for the cases where it's destroyed due to a config change (like device rotation). 164 AutofillTestWatcher.unregisterActivity("onDestroy()", this); 165 mDestroyedLatch.countDown(); 166 } 167 168 @Override finish()169 public void finish() { 170 finishOnly(); 171 AutofillTestWatcher.unregisterActivity("finish()", this); 172 } 173 174 /** 175 * Finishes the activity, without unregistering it from {@link AutofillTestWatcher}. 176 */ finishOnly()177 public void finishOnly() { 178 if (mCallback != null) { 179 unregisterNonNullCallback(); 180 } 181 super.finish(); 182 } 183 184 /** 185 * Clears focus from input fields. 186 */ clearFocus()187 public void clearFocus() { 188 throw new UnsupportedOperationException("Not implemented by " + getClass()); 189 } 190 191 /** 192 * Get insets of the root window 193 */ getRootWindowInsets()194 public WindowInsets getRootWindowInsets() { 195 return getWindow().getDecorView().getRootWindowInsets(); 196 } 197 } 198