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