1 /* 2 * Copyright 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 android.app.appsearch.testutil; 18 19 import android.annotation.NonNull; 20 import android.app.appsearch.observer.DocumentChangeInfo; 21 import android.app.appsearch.observer.ObserverCallback; 22 import android.app.appsearch.observer.SchemaChangeInfo; 23 24 import com.android.internal.annotations.GuardedBy; 25 26 import java.util.ArrayList; 27 import java.util.List; 28 29 /** 30 * An implementation of {@link android.app.appsearch.observer.ObserverCallback} for testing that 31 * caches its notifications in memory. 32 * 33 * <p>You should wait for all notifications to be delivered using {@link #waitForNotificationCount} 34 * before using the public lists to avoid concurrency issues. 35 * 36 * @hide 37 */ 38 public class TestObserverCallback implements ObserverCallback { 39 private final Object mLock = new Object(); 40 41 private final List<SchemaChangeInfo> mSchemaChanges = new ArrayList<>(); 42 private final List<DocumentChangeInfo> mDocumentChanges = new ArrayList<>(); 43 44 @GuardedBy("mLock") 45 private int mNotificationCountLocked = 0; 46 47 @Override onSchemaChanged(@onNull SchemaChangeInfo changeInfo)48 public void onSchemaChanged(@NonNull SchemaChangeInfo changeInfo) { 49 synchronized (mLock) { 50 mSchemaChanges.add(changeInfo); 51 incrementNotificationCountLocked(); 52 } 53 } 54 55 @Override onDocumentChanged(@onNull DocumentChangeInfo changeInfo)56 public void onDocumentChanged(@NonNull DocumentChangeInfo changeInfo) { 57 synchronized (mLock) { 58 mDocumentChanges.add(changeInfo); 59 incrementNotificationCountLocked(); 60 } 61 } 62 63 /** 64 * Waits for the total number of notifications this observer has seen to be equal to {@code 65 * expectedCount}. 66 * 67 * <p>If the number of cumulative received notifications is currently less than {@code 68 * expectedCount}, this method will block. 69 * 70 * @throws IllegalStateException If the current count of received notifications is currently 71 * greater than {@code expectedCount}. 72 */ waitForNotificationCount(int expectedCount)73 public void waitForNotificationCount(int expectedCount) throws InterruptedException { 74 while (true) { 75 synchronized (mLock) { 76 int actualCount = mNotificationCountLocked; 77 if (actualCount > expectedCount) { 78 throw new IllegalStateException( 79 "Caller was waiting for " 80 + expectedCount 81 + " notifications but there are" 82 + " already " 83 + actualCount 84 + ".\n Schema changes: " 85 + mSchemaChanges 86 + "\n Document changes: " 87 + mDocumentChanges); 88 } else if (actualCount == expectedCount) { 89 return; 90 } else { 91 mLock.wait(); 92 } 93 } 94 } 95 } 96 97 /** Gets all schema changes that have been observed so far. */ 98 // Note: although these are lists, their order is arbitrary and depends on the order in which 99 // the executor provided to GlobalSearchSessionShim#adObserver dispatches the notifications. 100 // Therefore they are declared as iterables instead of lists, to reduce the risk that they will 101 // be inspected by index. 102 @NonNull getSchemaChanges()103 public Iterable<SchemaChangeInfo> getSchemaChanges() { 104 return mSchemaChanges; 105 } 106 107 /** Gets all document changes that have been observed so far. */ 108 // Note: although these are lists, their order is arbitrary and depends on the order in which 109 // the executor provided to GlobalSearchSessionShim#adObserver dispatches the notifications. 110 // Therefore they are declared as iterables instead of lists, to reduce the risk that they will 111 // be inspected by index. 112 @NonNull getDocumentChanges()113 public Iterable<DocumentChangeInfo> getDocumentChanges() { 114 return mDocumentChanges; 115 } 116 117 /** Removes all notifications captured by this callback and resets the count to 0. */ clear()118 public void clear() { 119 synchronized (mLock) { 120 mSchemaChanges.clear(); 121 mDocumentChanges.clear(); 122 mNotificationCountLocked = 0; 123 mLock.notifyAll(); 124 } 125 } 126 incrementNotificationCountLocked()127 private void incrementNotificationCountLocked() { 128 synchronized (mLock) { 129 mNotificationCountLocked++; 130 mLock.notifyAll(); 131 } 132 } 133 } 134