1 /* 2 * Copyright (C) 2019 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.server.wm; 18 19 import android.app.Activity; 20 import android.content.ComponentName; 21 import android.content.ContentProvider; 22 import android.content.ContentProviderClient; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.database.Cursor; 26 import android.net.Uri; 27 import android.os.Build; 28 import android.os.Bundle; 29 import android.os.RemoteException; 30 import android.server.wm.CommandSession.ActivityCallback; 31 import android.server.wm.CommandSession.ConfigInfo; 32 import android.util.ArrayMap; 33 import android.util.Log; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 38 import java.util.ArrayList; 39 import java.util.function.Consumer; 40 41 /** 42 * Let other testing packages put information for test cases to verify. 43 * 44 * This is a global container that there is no connection between test cases and testing components. 45 * If a precise communication is required, use {@link CommandSession.ActivitySessionClient} instead. 46 * 47 * <p>Sample:</p> 48 * <pre> 49 * // In test case: 50 * void testSomething() { 51 * TestJournalContainer.start(); 52 * // Launch the testing activity. 53 * // ... 54 * assertTrue(TestJournalContainer.get(COMPONENT_NAME_OF_TESTING_ACTIVITY).extras 55 * .getBoolean("test")); 56 * } 57 * 58 * // In the testing activity: 59 * protected void onResume() { 60 * super.onResume(); 61 * TestJournalProvider.putExtras(this, bundle -> bundle.putBoolean("test", true)); 62 * } 63 * </pre> 64 */ 65 public class TestJournalProvider extends ContentProvider { 66 private static final boolean DEBUG = "eng".equals(Build.TYPE); 67 private static final String TAG = TestJournalProvider.class.getSimpleName(); 68 private static final Uri URI = Uri.parse("content://android.server.wm.testjournalprovider"); 69 70 /** Indicates who owns the events. */ 71 private static final String EXTRA_KEY_OWNER = "key_owner"; 72 /** Puts a {@link ActivityCallback} into the journal container for who receives the callback. */ 73 private static final String METHOD_ADD_CALLBACK = "add_callback"; 74 /** Sets the {@link ConfigInfo} for who reports the configuration info. */ 75 private static final String METHOD_SET_LAST_CONFIG_INFO = "set_last_config_info"; 76 /** Puts any additional information. */ 77 private static final String METHOD_PUT_EXTRAS = "put_extras"; 78 /** For test app to put resident data from test case. */ 79 private static final String METHOD_PUT_RESIDENT_EXTRAS = "put_resident_extras"; 80 /** For test app to get resident data from test case. */ 81 private static final String METHOD_GET_RESIDENT_EXTRAS = "get_resident_extras"; 82 83 /** Avoid accidentally getting data from {@link #TestJournalContainer} in another process. */ 84 private static boolean sCrossProcessAccessGuard; 85 86 @Override onCreate()87 public boolean onCreate() { 88 sCrossProcessAccessGuard = true; 89 return true; 90 } 91 92 @Override call(String method, String arg, Bundle extras)93 public Bundle call(String method, String arg, Bundle extras) { 94 switch (method) { 95 case METHOD_ADD_CALLBACK: 96 ensureExtras(method, extras); 97 TestJournalContainer.getInstance().addCallback( 98 extras.getString(EXTRA_KEY_OWNER), extras.getParcelable(method)); 99 break; 100 101 case METHOD_SET_LAST_CONFIG_INFO: 102 ensureExtras(method, extras); 103 TestJournalContainer.getInstance().setLastConfigInfo( 104 extras.getString(EXTRA_KEY_OWNER), extras.getParcelable(method)); 105 break; 106 107 case METHOD_PUT_EXTRAS: 108 ensureExtras(method, extras); 109 TestJournalContainer.getInstance().putExtras( 110 extras.getString(EXTRA_KEY_OWNER), extras); 111 break; 112 113 case METHOD_PUT_RESIDENT_EXTRAS: 114 ensureExtras(method, extras); 115 TestJournalContainer.getInstance().mResidentData.put(arg, extras); 116 break; 117 118 case METHOD_GET_RESIDENT_EXTRAS: 119 return TestJournalContainer.getInstance().mResidentData.get(arg); 120 } 121 return null; 122 } 123 ensureExtras(String method, Bundle extras)124 private static void ensureExtras(String method, Bundle extras) { 125 if (extras == null) { 126 throw new IllegalArgumentException( 127 "The calling method=" + method + " does not allow null bundle"); 128 } 129 extras.setClassLoader(TestJournal.class.getClassLoader()); 130 if (DEBUG) { 131 extras.size(); // Trigger unparcel for printing plain text. 132 Log.i(TAG, method + " extras=" + extras); 133 } 134 } 135 136 /** Records the activity is called with the given callback. */ putActivityCallback(Activity activity, ActivityCallback callback)137 public static void putActivityCallback(Activity activity, ActivityCallback callback) { 138 try (TestJournalClient client = TestJournalClient.create(activity, 139 activity.getComponentName())) { 140 client.addCallback(callback); 141 } 142 } 143 144 /** Puts information about the activity. */ putExtras(Activity activity, Consumer<Bundle> bundleFiller)145 public static void putExtras(Activity activity, Consumer<Bundle> bundleFiller) { 146 putExtras(activity, activity.getComponentName(), bundleFiller); 147 } 148 149 /** Puts information about the component. */ putExtras(Context context, ComponentName owner, Consumer<Bundle> bundleFiller)150 public static void putExtras(Context context, ComponentName owner, 151 Consumer<Bundle> bundleFiller) { 152 putExtras(context, componentNameToKey(owner), bundleFiller); 153 } 154 155 /** Puts information about the keyword. */ putExtras(Context context, String owner, Consumer<Bundle> bundleFiller)156 public static void putExtras(Context context, String owner, Consumer<Bundle> bundleFiller) { 157 try (TestJournalClient client = TestJournalClient.create(context, owner)) { 158 final Bundle extras = new Bundle(); 159 bundleFiller.accept(extras); 160 client.putExtras(extras); 161 } 162 } 163 164 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)165 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 166 String sortOrder) { 167 return null; 168 } 169 170 @Override getType(Uri uri)171 public String getType(Uri uri) { 172 return null; 173 } 174 175 @Override insert(Uri uri, ContentValues values)176 public Uri insert(Uri uri, ContentValues values) { 177 return null; 178 } 179 180 @Override delete(Uri uri, String selection, String[] selectionArgs)181 public int delete(Uri uri, String selection, String[] selectionArgs) { 182 return 0; 183 } 184 185 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)186 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 187 return 0; 188 } 189 componentNameToKey(ComponentName name)190 private static String componentNameToKey(ComponentName name) { 191 return name.flattenToShortString(); 192 } 193 194 /** 195 * Executes from the testing component to put their info to {@link TestJournalProvider}. 196 * The caller can be from any process or package. 197 */ 198 public static class TestJournalClient implements AutoCloseable { 199 private static final String EMPTY_ARG = ""; 200 private final ContentProviderClient mClient; 201 private final String mOwner; 202 TestJournalClient(ContentProviderClient client, String owner)203 public TestJournalClient(ContentProviderClient client, String owner) { 204 mClient = client; 205 mOwner = owner; 206 } 207 callWithExtras(String method, Bundle extras)208 private void callWithExtras(String method, Bundle extras) { 209 extras.putString(EXTRA_KEY_OWNER, mOwner); 210 try { 211 mClient.call(method, EMPTY_ARG, extras); 212 } catch (RemoteException e) { 213 throw new RuntimeException(e); 214 } 215 } 216 addCallback(ActivityCallback callback)217 public void addCallback(ActivityCallback callback) { 218 final Bundle extras = new Bundle(); 219 extras.putParcelable(METHOD_ADD_CALLBACK, callback); 220 callWithExtras(METHOD_ADD_CALLBACK, extras); 221 } 222 setLastConfigInfo(ConfigInfo configInfo)223 public void setLastConfigInfo(ConfigInfo configInfo) { 224 final Bundle extras = new Bundle(); 225 extras.putParcelable(METHOD_SET_LAST_CONFIG_INFO, configInfo); 226 callWithExtras(METHOD_SET_LAST_CONFIG_INFO, extras); 227 } 228 putExtras(Bundle extras)229 public void putExtras(Bundle extras) { 230 callWithExtras(METHOD_PUT_EXTRAS, extras); 231 } 232 233 /** Puts the resident data with customized owner key. */ putResidentExtras(String owner, Bundle extras)234 public void putResidentExtras(String owner, Bundle extras) { 235 try { 236 mClient.call(METHOD_PUT_RESIDENT_EXTRAS, owner, extras); 237 } catch (RemoteException e) { 238 throw new RuntimeException(e); 239 } 240 } 241 242 /** Gets the resident data according to the owner key. */ getResidentExtras(String owner)243 public Bundle getResidentExtras(String owner) { 244 try { 245 return mClient.call(METHOD_GET_RESIDENT_EXTRAS, owner, null /* extras */); 246 } catch (RemoteException e) { 247 throw new RuntimeException(e); 248 } 249 } 250 251 @Override close()252 public void close() { 253 mClient.close(); 254 } 255 256 @NonNull create(Context context, ComponentName owner)257 static TestJournalClient create(Context context, ComponentName owner) { 258 return create(context, componentNameToKey(owner)); 259 } 260 261 @NonNull create(Context context, String owner)262 static TestJournalClient create(Context context, String owner) { 263 final ContentProviderClient client = context.getContentResolver() 264 .acquireContentProviderClient(URI); 265 if (client == null) { 266 throw new RuntimeException("Unable to acquire " + URI); 267 } 268 return new TestJournalClient(client, owner); 269 } 270 } 271 272 /** The basic unit to store testing information. */ 273 public static class TestJournal { 274 @NonNull 275 public final ArrayList<ActivityCallback> callbacks = new ArrayList<>(); 276 @NonNull 277 public final Bundle extras = new Bundle(); 278 @Nullable 279 public ConfigInfo lastConfigInfo; 280 } 281 282 /** 283 * The container lives in test case side. It stores the information from testing components. 284 * The caller must be in the same process as {@link TestJournalProvider}. 285 */ 286 public static class TestJournalContainer { 287 private static TestJournalContainer sInstance; 288 private final ArrayMap<String, TestJournal> mContainer = new ArrayMap<>(); 289 /** The data in this container won't be cleared by {@link #start()}. */ 290 private final ArrayMap<String, Bundle> mResidentData = new ArrayMap<>(); 291 TestJournalContainer()292 private TestJournalContainer() { 293 } 294 295 @NonNull get(ComponentName owner)296 public static TestJournal get(ComponentName owner) { 297 return get(componentNameToKey(owner)); 298 } 299 300 @NonNull get(String owner)301 public static TestJournal get(String owner) { 302 return getInstance().getTestJournal(owner); 303 } 304 305 /** Removes and returns the resident data by the owner key. */ 306 @Nullable takeResidentData(String owner)307 public static Bundle takeResidentData(String owner) { 308 return getInstance().mResidentData.remove(owner); 309 } 310 311 /** Puts the resident data. */ putResidentData(String owner, Bundle extras)312 public static void putResidentData(String owner, Bundle extras) { 313 getInstance().mResidentData.put(owner, extras); 314 } 315 316 /** 317 * Perform the action which may have thread safety concerns when accessing the fields of 318 * {@link TestJournal}. 319 */ withThreadSafeAccess(Runnable action)320 public static void withThreadSafeAccess(Runnable action) { 321 synchronized (getInstance()) { 322 action.run(); 323 } 324 } 325 getTestJournal(String owner)326 private synchronized TestJournal getTestJournal(String owner) { 327 TestJournal info = mContainer.get(owner); 328 if (info == null) { 329 info = new TestJournal(); 330 mContainer.put(owner, info); 331 } 332 return info; 333 } 334 addCallback(String owner, ActivityCallback callback)335 synchronized void addCallback(String owner, ActivityCallback callback) { 336 getTestJournal(owner).callbacks.add(callback); 337 } 338 setLastConfigInfo(String owner, ConfigInfo configInfo)339 synchronized void setLastConfigInfo(String owner, ConfigInfo configInfo) { 340 getTestJournal(owner).lastConfigInfo = configInfo; 341 } 342 putExtras(String owner, Bundle extras)343 synchronized void putExtras(String owner, Bundle extras) { 344 getTestJournal(owner).extras.putAll(extras); 345 } 346 getInstance()347 private synchronized static TestJournalContainer getInstance() { 348 if (!TestJournalProvider.sCrossProcessAccessGuard) { 349 throw new IllegalAccessError(TestJournalProvider.class.getSimpleName() 350 + " is not alive in this process"); 351 } 352 if (sInstance == null) { 353 sInstance = new TestJournalContainer(); 354 } 355 return sInstance; 356 } 357 358 /** 359 * The method should be called when we are only interested in the following events. It 360 * actually clears the previous records. 361 */ 362 @NonNull start()363 public static TestJournalContainer start() { 364 final TestJournalContainer instance = getInstance(); 365 synchronized (instance) { 366 instance.mContainer.clear(); 367 } 368 return instance; 369 } 370 } 371 } 372