1 /*
2  * Copyright (C) 2021 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.example.helloworld.lib;
18 
19 import static org.hamcrest.MatcherAssert.assertThat;
20 import static org.hamcrest.core.IsEqual.equalTo;
21 import static org.hamcrest.core.IsInstanceOf.instanceOf;
22 import static org.junit.Assert.assertEquals;
23 
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.res.AssetFileDescriptor;
29 import android.content.res.AssetManager;
30 import android.content.res.Resources;
31 import android.content.res.TypedArray;
32 import android.content.res.XmlResourceParser;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.Process;
36 
37 import com.android.compatibility.common.util.MatcherUtils;
38 import com.android.internal.util.XmlUtils;
39 
40 import org.hamcrest.Matcher;
41 import org.hamcrest.Matchers;
42 import org.hamcrest.core.StringStartsWith;
43 import org.xmlpull.v1.XmlPullParser;
44 
45 import java.io.FileNotFoundException;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.util.concurrent.Semaphore;
49 import java.util.concurrent.TimeUnit;
50 
51 public class TestUtils {
52     public static final String TEST_APP_PACKAGE = "com.example.helloworld";
53     public static final String TEST_ACTIVITY_NAME = "com.example.helloworld.TestActivity";
54 
55     public static final String TEST_NAME_EXTRA_KEY = "TEST_NAME";
56     public static final String TEST_ASSERT_SUCCESS_EXTRA_KEY = "ASSERT_SUCCESS";
57 
58     public static final String TEST_STATUS_ACTION = "android.content.pm.cts.TEST_STATUS_UPDATE";
59 
60     public static final String PID_STATUS_PID_KEY = "TEST_PID";
61     public static final String TEST_STATUS_RESULT_KEY = "TEST_RESULT";
62     public static final String TEST_STATUS_RESULT_SUCCESS = "Success";
63 
64     public static final String RES_XML_PATH = "res/xml/test_xml.xml";
65     public static final String RES_XML_ATTRS_PATH = "res/xml/test_xml_attrs.xml";
66     public static final String RES_DRAWABLE_HDPI_PATH = "res/drawable-hdpi-v4/background.jpg";
67     public static final String RES_DRAWABLE_MDPI_PATH = "res/drawable-mdpi-v4/background.jpg";
68 
69     public static final String RES_IDENTIFIER = TEST_APP_PACKAGE + ":string/inc_string";
70 
71     public static final int RES_STRING = 0x7f021000;
72     public static final int RES_ARRAY = 0x7f051000;
73     public static final int RES_STYLE = 0x7f061000;
74     public static final int[] RES_STYLEABLE = {0x7f011000, 0x7f011001, 0x7f011002};
75 
76     public enum AssertionType {
77         ASSERT_SUCCESS,
78         ASSERT_READ_FAILURE,
79     }
80 
81     public static final String TEST_GET_IDENTIFIER = "checkGetIdentifier";
82 
checkGetIdentifier(Resources res, AssertionType type)83     public static void checkGetIdentifier(Resources res, AssertionType type) throws Exception {
84         testReadSuccessAndFailure(type,
85                 () -> res.getIdentifier(RES_IDENTIFIER, "", ""),
86                 Matchers.not(equalTo(0)), equalTo(0));
87     }
88 
89     public static final String TEST_GET_RESOURCE_NAME = "checkGetResourceName";
90 
checkGetResourceName(Resources res, AssertionType type)91     public static void checkGetResourceName(Resources res, AssertionType type) throws Exception {
92         testReadSuccessAndFailureException(type,
93                 () -> res.getResourceName(RES_STRING),
94                 equalTo(RES_IDENTIFIER), instanceOf(Resources.NotFoundException.class));
95     }
96 
97     public static final String TEST_GET_STRING = "checkGetString";
98 
checkGetString(Resources res, AssertionType type)99     public static void checkGetString(Resources res, AssertionType type) throws Exception {
100         testReadSuccessAndFailureException(type,
101                 () -> res.getString(RES_STRING),
102                 equalTo("true"), instanceOf(Resources.NotFoundException.class));
103     }
104 
105     public static final String TEST_GET_STRING_ARRAY = "checkGetStringArray";
106 
checkGetStringArray(Resources res, AssertionType type)107     public static void checkGetStringArray(Resources res, AssertionType type) throws Exception {
108         testReadSuccessAndFailureException(type,
109                 () -> res.getStringArray(RES_ARRAY),
110                 equalTo(new String[]{"true"}), instanceOf(Resources.NotFoundException.class));
111     }
112 
113     public static final String TEST_OPEN_XML = "checkOpenXmlResourceParser";
114 
checkOpenXmlResourceParser(Resources res, AssertionType type)115     public static void checkOpenXmlResourceParser(Resources res, AssertionType type)
116             throws Exception {
117         testReadSuccessAndFailureException(type,
118                 () -> {
119                     final AssetManager assets = res.getAssets();
120                     try (XmlResourceParser p = assets.openXmlResourceParser(RES_XML_PATH)) {
121                         XmlUtils.beginDocument(p, "Test");
122                         assertEquals(XmlPullParser.START_TAG, p.next());
123                         final String text = p.nextText();
124                         return text == null ? "" : text.trim();
125                     }
126                 },
127                 StringStartsWith.startsWith("Lorem ipsum dolor"),
128                 instanceOf(FileNotFoundException.class));
129     }
130 
131     public static final String TEST_APPLY_STYLE = "checkApplyStyle";
132 
checkApplyStyle(Resources res, AssertionType type)133     public static void checkApplyStyle(Resources res, AssertionType type) throws Exception {
134         testReadSuccessAndFailure(type,
135                 () -> {
136                     final Resources.Theme theme = res.newTheme();
137                     theme.applyStyle(RES_STYLE, true);
138                     final TypedArray values = theme.obtainStyledAttributes(RES_STYLEABLE);
139                     return new String[]{
140                             values.getString(0),
141                             values.getString(1),
142                             values.getString(2),
143                     };
144                 },
145                 equalTo(new String[]{"true", "true", "1"}),
146                 equalTo(new String[]{null, null, null}));
147     }
148 
149     public static final String TEST_XML_ATTRIBUTES = "checkXmlAttributes";
150 
checkXmlAttributes(Resources res, AssertionType type)151     public static void checkXmlAttributes(Resources res, AssertionType type) throws Exception {
152         testReadSuccessAndFailure(type,
153                 () -> {
154                     final AssetManager assets = res.getAssets();
155                     try (XmlResourceParser p = assets.openXmlResourceParser(RES_XML_ATTRS_PATH)) {
156                         XmlUtils.beginDocument(p, "Test");
157                         final TypedArray values = res.obtainAttributes(p, RES_STYLEABLE);
158                         final String[] results = {
159                                 values.getString(0),
160                                 values.getString(1),
161                                 values.getString(2),
162                         };
163                         values.recycle();
164                         return results;
165                     }
166                 },
167                 equalTo(new String[]{"true", "true", "1"}),
168                 equalTo(new String[]{null, null, null}));
169     }
170 
171     public static final String TEST_OPEN_FILE_MISSING = "checkOpenMissingFile";
172 
checkOpenMissingFile(Resources res, AssertionType type)173     public static void checkOpenMissingFile(Resources res, AssertionType type) throws Exception {
174         testReadSuccessAndFailureException(type,
175                 () -> {
176                     final AssetManager assets = res.getAssets();
177                     try (InputStream is = assets.openNonAsset(RES_DRAWABLE_HDPI_PATH)) {
178                         return true;
179                     }
180                 },
181                 equalTo(true), instanceOf(FileNotFoundException.class));
182     }
183 
184     public static final String TEST_OPEN_FILE_FD_MISSING = "checkOpenMissingFdFile";
185 
checkOpenMissingFdFile(Resources res, AssertionType type)186     public static void checkOpenMissingFdFile(Resources res, AssertionType type) throws Exception {
187         testReadSuccessAndFailureException(type,
188                 () -> {
189                     final AssetManager assets = res.getAssets();
190                     try (AssetFileDescriptor is = assets.openNonAssetFd(RES_DRAWABLE_HDPI_PATH)) {
191                         return true;
192                     }
193                 },
194                 equalTo(true), instanceOf(FileNotFoundException.class));
195     }
196 
197     private static class FailedWhileReadingException extends Exception {
198     }
199 
200     public static final String TEST_OPEN_FILE = "checkOpen";
201 
checkOpen(Resources res, AssertionType type)202     public static void checkOpen(Resources res, AssertionType type) throws Exception {
203         testReadSuccessAndFailureException(type,
204                 () -> {
205                     final AssetManager assets = res.getAssets();
206                     try (InputStream is = assets.openNonAsset(RES_DRAWABLE_MDPI_PATH)) {
207                         try {
208                             readFullStream(is);
209                         } catch (IOException e) {
210                             throw new FailedWhileReadingException();
211                         }
212                         return true;
213                     }
214                 }, equalTo(true), instanceOf(FailedWhileReadingException.class));
215     }
216 
217     public static final String TEST_OPEN_FILE_FD = "checkOpenFd";
218 
checkOpenFd(Resources res, AssertionType type)219     public static void checkOpenFd(Resources res, AssertionType type) throws Exception {
220         testReadSuccessAndFailureException(type,
221                 () -> {
222                     final AssetManager assets = res.getAssets();
223                     try (AssetFileDescriptor fd = assets.openNonAssetFd(RES_DRAWABLE_MDPI_PATH)) {
224                         try {
225                             readFullStream(fd.createInputStream());
226                         } catch (IOException e) {
227                             throw new FailedWhileReadingException();
228                         }
229                         return true;
230                     }
231                 }, equalTo(true), instanceOf(FailedWhileReadingException.class));
232     }
233 
234     /**
235      * Used to wait for the process to wait for a particular broadcast to be received.
236      */
237     public static class BroadcastDetector implements AutoCloseable {
238         private final Context mContext;
239         private final HandlerThread mThread;
240         private final Handler mHandler;
241         private final BroadcastReceiver mReceiver;
242         private final Semaphore mLatch = new Semaphore(0);
243         private volatile Exception mException;
244 
BroadcastDetector(Context context, IntentFilter intentFilter, IBroadcastCallback callback)245         public BroadcastDetector(Context context, IntentFilter intentFilter,
246                 IBroadcastCallback callback) {
247             mContext = context;
248             mThread = new HandlerThread(callback.toString(), Process.THREAD_PRIORITY_FOREGROUND);
249             mThread.start();
250             mHandler = new Handler(mThread.getLooper());
251             mReceiver = new BroadcastReceiver() {
252                 @Override
253                 public void onReceive(Context context, Intent intent) {
254                     try {
255                         if (callback.onReceive(context, intent)) {
256                             mLatch.release();
257                         }
258                     } catch (Exception e) {
259                         mException = e;
260                         mLatch.release();
261                     }
262                 }
263             };
264             context.registerReceiver(mReceiver, intentFilter, null, mHandler, Context.RECEIVER_EXPORTED);
265         }
266 
267         /**
268          * Returns true if the broadcast was received and
269          * {@link IBroadcastCallback#onReceive(Context, Intent)} returned true.
270          */
waitForBroadcast(long timeout, TimeUnit unit)271         public boolean waitForBroadcast(long timeout, TimeUnit unit)
272                 throws Exception {
273             if (!mLatch.tryAcquire(timeout, unit)) {
274                 return false;
275             }
276             if (mException != null) {
277                 throw mException;
278             }
279             return true;
280         }
281 
282         @Override
close()283         public void close() {
284             mContext.unregisterReceiver(mReceiver);
285             mThread.quit();
286         }
287     }
288 
289     public interface IBroadcastCallback {
onReceive(Context context, Intent intent)290         boolean onReceive(Context context, Intent intent) throws Exception;
291     }
292 
293     private interface ThrowingFunction<R> {
apply()294         R apply() throws Exception;
295     }
296 
testReadSuccessAndFailure(AssertionType assertType, ThrowingFunction<T> getValue, Matcher<? super T> checkSuccess, Matcher<? super T> checkFailure)297     private static <T> void testReadSuccessAndFailure(AssertionType assertType,
298             ThrowingFunction<T> getValue,
299             Matcher<? super T> checkSuccess,
300             Matcher<? super T> checkFailure) throws Exception {
301         final T value = getValue.apply();
302         if (assertType == AssertionType.ASSERT_SUCCESS) {
303             assertThat(value, checkSuccess);
304         } else {
305             assertThat(value, checkFailure);
306         }
307     }
308 
testReadSuccessAndFailureException(AssertionType assertType, ThrowingFunction<T> getValue, Matcher<? super T> checkSuccess, Matcher<Throwable> checkFailure)309     private static <T> void testReadSuccessAndFailureException(AssertionType assertType,
310             ThrowingFunction<T> getValue,
311             Matcher<? super T> checkSuccess,
312             Matcher<Throwable> checkFailure) throws Exception {
313         if (assertType == AssertionType.ASSERT_SUCCESS) {
314             assertThat(getValue.apply(), checkSuccess);
315         } else {
316             MatcherUtils.assertThrows(checkFailure, getValue::apply);
317         }
318     }
319 
readFullStream(InputStream is)320     private static void readFullStream(InputStream is) throws IOException {
321         final byte[] buffer = new byte[1024];
322         while (is.read(buffer) != -1) {
323         }
324     }
325 }
326