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.contentcaptureservice.cts;
17 
18 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
19 
20 import android.app.Activity;
21 import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
22 import android.os.Bundle;
23 import android.util.Log;
24 import android.view.View;
25 import android.view.contentcapture.ContentCaptureManager;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 
30 import java.util.concurrent.Callable;
31 import java.util.concurrent.CountDownLatch;
32 import java.util.concurrent.TimeUnit;
33 import java.util.concurrent.atomic.AtomicReference;
34 
35 /**
36  * Base class for all activities.
37  */
38 abstract class AbstractContentCaptureActivity extends Activity {
39 
40     private final String mTag = getClass().getSimpleName();
41 
42     private int mRealTaskId;
43 
44     @Nullable
getContentCaptureManager()45     public ContentCaptureManager getContentCaptureManager() {
46         return getSystemService(ContentCaptureManager.class);
47     }
48 
49     @Override
onCreate(Bundle savedInstanceState)50     protected void onCreate(Bundle savedInstanceState) {
51         mRealTaskId = getTaskId();
52         Log.d(mTag, "onCreate(): taskId=" + mRealTaskId + ", decorView=" + getDecorView());
53 
54         super.onCreate(savedInstanceState);
55     }
56 
57     @Override
onStart()58     protected void onStart() {
59         Log.d(mTag, "onStart()");
60         super.onStart();
61     }
62 
63     @Override
onResume()64     protected void onResume() {
65         Log.d(mTag, "onResume(): decorViewId=" + getDecorView().getAutofillId());
66         super.onResume();
67     }
68 
69     @Override
onPause()70     protected void onPause() {
71         Log.d(mTag, "onPause()");
72         super.onPause();
73     }
74 
75     @Override
onStop()76     protected void onStop() {
77         Log.d(mTag, "onStop()");
78         super.onStop();
79     }
80 
81     @Override
onDestroy()82     protected void onDestroy() {
83         Log.d(mTag, "onDestroy()");
84         super.onDestroy();
85     }
86 
87     @NonNull
getDecorView()88     public final View getDecorView() {
89         return getWindow().getDecorView();
90     }
91 
92     /**
93      * Asserts the events generated when this session was launched and finished,
94      * without any custom / dynamic operations in between.
95      */
assertDefaultEvents(@onNull Session session)96     public abstract void assertDefaultEvents(@NonNull Session session);
97 
98     /**
99      * Gets the real task id associated with the activity, as {@link #getTaskId()} returns
100      * {@code -1} after it's gone.
101      */
getRealTaskId()102     public final int getRealTaskId() {
103         return mRealTaskId;
104     }
105 
106     /**
107      * Runs an action in the UI thread, and blocks caller until the action is finished.
108      */
syncRunOnUiThread(@onNull Runnable action)109     public final void syncRunOnUiThread(@NonNull Runnable action) {
110         syncRunOnUiThread(action, Helper.GENERIC_TIMEOUT_MS);
111     }
112 
113     /**
114      * Calls an action in the UI thread, and blocks caller until the action is finished.
115      */
syncCallOnUiThread(@onNull Callable<T> action)116     public final <T> T syncCallOnUiThread(@NonNull Callable<T> action) throws Exception {
117         final AtomicReference<T> result = new AtomicReference<>();
118         final AtomicReference<Exception> exception  = new AtomicReference<>();
119         syncRunOnUiThread(() -> {
120             try {
121                 result.set(action.call());
122             } catch (Exception e) {
123                 exception.set(e);
124             }
125         });
126         final Exception e = exception.get();
127         if (e != null) {
128             throw e;
129         }
130         return result.get();
131     }
132 
133     /**
134      * Run an action in the UI thread, and blocks caller until the action is finished or it times
135      * out.
136      */
syncRunOnUiThread(@onNull Runnable action, long timeoutMs)137     public final void syncRunOnUiThread(@NonNull Runnable action, long timeoutMs) {
138         final CountDownLatch latch = new CountDownLatch(1);
139         runOnUiThread(() -> {
140             action.run();
141             latch.countDown();
142         });
143         try {
144             if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
145                 // TODO(b/120665995): throw RetryableException (once moved from Autofill to common)
146                 throw new IllegalStateException(
147                         String.format("action on UI thread timed out after %d ms", timeoutMs));
148             }
149         } catch (InterruptedException e) {
150             Thread.currentThread().interrupt();
151             throw new RuntimeException("Interrupted", e);
152         }
153     }
154 
155     /**
156      * Dumps the {@link ContentCaptureManager} state of the activity on logcat.
157      */
dumpIt()158     public void dumpIt() {
159         final String dump = runShellCommand(
160                 "dumpsys activity %s --contentcapture",  getComponentName().flattenToString());
161         Log.v(mTag, "dump it: " + dump);
162     }
163 }
164