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.content.cts;
17 
18 import android.app.ActivityManager;
19 import android.content.Context;
20 import android.content.ContextWrapper;
21 import android.test.AndroidTestCase;
22 import android.test.suitebuilder.annotation.LargeTest;
23 import android.util.Log;
24 
25 import java.lang.reflect.Field;
26 import java.util.Arrays;
27 import java.util.concurrent.atomic.AtomicBoolean;
28 import java.util.concurrent.atomic.AtomicInteger;
29 
30 public class ContextMoreTest extends AndroidTestCase {
31     private static final String TAG = "ContextMoreTest";
32 
33     /**
34      * Test for {@link Context#getSystemService)}.
35      *
36      * Call it repeatedly from multiple threads, and:
37      * - Make sure getSystemService(ActivityManager) will always return non-null.
38      * - If ContextImpl.mServiceCache is accessible via reflection, clear it once in a while and
39      * make sure getSystemService() still returns non-null.
40      */
41     @LargeTest
testGetSystemService_multiThreaded()42     public void testGetSystemService_multiThreaded() throws Exception {
43         // # of times the tester Runnable has been executed.
44         final AtomicInteger totalCount = new AtomicInteger(0);
45 
46         // # of times the tester Runnable has failed.
47         final AtomicInteger failCount = new AtomicInteger(0);
48 
49         // Run the threads until this becomes true.
50         final AtomicBoolean stop = new AtomicBoolean(false);
51 
52         final Context context = getContext();
53         final Object[] serviceCache = findServiceCache(context);
54         if (serviceCache == null) {
55             Log.w(TAG, "mServiceCache not found.");
56         }
57 
58         final Runnable tester = () -> {
59             for (;;) {
60                 final int pass = totalCount.incrementAndGet();
61 
62                 final Object service = context.getSystemService(ActivityManager.class);
63                 if (service == null) {
64                     failCount.incrementAndGet(); // Fail!
65                 }
66 
67                 if (stop.get()) {
68                     return;
69                 }
70 
71                 // Yield the CPU.
72                 try {
73                     Thread.sleep(0);
74                 } catch (InterruptedException e) {
75                 }
76 
77                 // Once in a while, force clear mServiceCache.
78                 if ((serviceCache != null) && ((pass % 7) == 0)) {
79                     Arrays.fill(serviceCache, null);
80                 }
81             }
82         };
83 
84         final int NUM_THREADS = 20;
85 
86         // Create and start the threads...
87         final Thread[] threads = new Thread[NUM_THREADS];
88         for (int i = 0; i < NUM_THREADS; i++) {
89             threads[i] = new Thread(tester);
90         }
91         for (int i = 0; i < NUM_THREADS; i++) {
92             threads[i].start();
93         }
94 
95         Thread.sleep(10 * 1000);
96 
97         stop.set(true);
98 
99         // Wait for them to stop...
100         for (int i = 0; i < NUM_THREADS; i++) {
101             threads[i].join();
102         }
103 
104         assertEquals(0, failCount.get());
105         assertTrue("totalCount must be bigger than " + NUM_THREADS
106                 + " but was " + totalCount.get(), totalCount.get() > NUM_THREADS);
107     }
108 
109     /**
110      * Find a field by name using reflection.
111      */
readField(Object instance, String fieldName)112     private static Object readField(Object instance, String fieldName) {
113         final Field f;
114         try {
115             f = instance.getClass().getDeclaredField(fieldName);
116             f.setAccessible(true);
117             final Object ret = f.get(instance);
118             if (ret == null) {
119                 return null;
120             }
121             return ret;
122         } catch (NoSuchFieldException | IllegalAccessException e) {
123             return null;
124         }
125     }
126 
127     /**
128      * Try to find the mServiceCache field from a Context. Returns null if none found.
129      */
findServiceCache(Context context)130     private static Object[] findServiceCache(Context context) {
131         // Find the base context.
132         while (context instanceof ContextWrapper) {
133             context = ((ContextWrapper) context).getBaseContext();
134         }
135         // Try to find the mServiceCache field.
136         final Object serviceCache = readField(context, "mServiceCache");
137         if (serviceCache instanceof Object[]) {
138             return (Object[]) serviceCache;
139         }
140         return null;
141     }
142 }
143