1 /*
2  * Copyright (C) 2024 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 com.android.adservices.shared.testing.concurrency;
17 
18 import static com.android.adservices.shared.testing.concurrency.SyncCallback.LOG_TAG;
19 
20 import static org.junit.Assert.assertThrows;
21 import static org.junit.Assume.assumeFalse;
22 import static org.junit.Assume.assumeTrue;
23 
24 import static java.lang.Thread.currentThread;
25 import static java.util.concurrent.TimeUnit.MILLISECONDS;
26 
27 import com.android.adservices.shared.meta_testing.FakeLogger;
28 import com.android.adservices.shared.testing.LogEntry;
29 import com.android.adservices.shared.testing.Logger.LogLevel;
30 import com.android.adservices.shared.testing.Logger.RealLogger;
31 import com.android.adservices.shared.testing.Nullable;
32 import com.android.adservices.shared.testing.SharedSidelessTestCase;
33 import com.android.adservices.shared.testing.StandardStreamsLogger;
34 
35 import com.google.common.collect.ImmutableList;
36 
37 import org.junit.Test;
38 
39 import java.lang.reflect.Constructor;
40 import java.util.concurrent.ArrayBlockingQueue;
41 import java.util.concurrent.TimeUnit;
42 
43 /** Base class for all {@code SyncCallback} implementations. */
44 public abstract class SyncCallbackTestCase<CB extends SyncCallback & FreezableToString>
45         extends SharedSidelessTestCase {
46 
47     protected static final long INJECTION_TIMEOUT_MS = 200;
48     protected static final long CALLBACK_TIMEOUT_MS = INJECTION_TIMEOUT_MS + 400;
49 
50     protected final FakeLogger mFakeLogger = new FakeLogger();
51 
52     private final SyncCallbackSettings.Builder mFakeLoggerSettingsBuilder =
53             new SyncCallbackSettings.Builder(mFakeLogger);
54     private final SyncCallbackSettings.Builder mDefaultSettingsBuilder =
55             mFakeLoggerSettingsBuilder
56                     .setFailIfCalledOnMainThread(supportsFailIfCalledOnMainThread())
57                     .setMaxTimeoutMs(CALLBACK_TIMEOUT_MS);
58 
59     // Used to set the name of the method returned by call() - cannot AtomicRefecence because
60     // call() might be called it might be called AFTER assertCalled()
61     private final ArrayBlockingQueue<String> mSetCalledMethodQueue = new ArrayBlockingQueue<>(1);
62 
63     protected final SyncCallbackSettings mDefaultSettings = mDefaultSettingsBuilder.build();
64 
65     protected final ConcurrencyHelper mConcurrencyHelper;
66 
67     // TODO(b/342448771): ideally should remove it, but the class hierarchy is messed up (as some
68     // classes are defined on side-less but the test on device-side)
SyncCallbackTestCase()69     protected SyncCallbackTestCase() {
70         this(StandardStreamsLogger.getInstance());
71     }
72 
SyncCallbackTestCase(RealLogger realLogger)73     protected SyncCallbackTestCase(RealLogger realLogger) {
74         mConcurrencyHelper = new ConcurrencyHelper(realLogger);
75     }
76 
77     /**
78      * Gets a new callback to be used in the test.
79      *
80      * <p>Each call should return a different object.
81      */
newCallback(SyncCallbackSettings settings)82     protected CB newCallback(SyncCallbackSettings settings) {
83         SyncCallback rawCallback = newRawCallback(settings);
84         if (rawCallback == null) {
85             throw new UnsupportedOperationException(
86                     "Must override this method or return non-null on newRawCallback()");
87         }
88         @SuppressWarnings("unchecked")
89         CB castCallback = (CB) (rawCallback);
90         return castCallback;
91     }
92 
93     /**
94      * Similar to {@link #newCallback(SyncCallbackSettings)}, but should be used by tests whose
95      * callback type is not available on earlier platform releases (like {@code
96      * android.os.OutcomeReceiver}).
97      */
98     @Nullable
newRawCallback(SyncCallbackSettings settings)99     protected SyncCallback newRawCallback(SyncCallbackSettings settings) {
100         return null;
101     }
102 
newFrozenCallback(SyncCallbackSettings settings)103     private CB newFrozenCallback(SyncCallbackSettings settings) {
104         CB callback = newCallback(settings);
105         callback.freezeToString();
106         return callback;
107     }
108 
109     /** Calls the callback and return the name of the called method. */
call(CB callback)110     protected final String call(CB callback) {
111         String methodName = callCallback(callback);
112         if (methodName == null) {
113             throw new IllegalStateException(
114                     "Callback " + callback + " returned null to callCallback()");
115         }
116         mSetCalledMethodQueue.offer(methodName);
117         return methodName;
118     }
119 
120     // NOTE: currently it just need one method, so we're always returning poll()
121 
122     /** Gets the name of the method returned by {@link #call(SyncCallback)}. */
getSetCalledMethodName()123     private String getSetCalledMethodName() throws InterruptedException {
124         String methodName = mSetCalledMethodQueue.poll(CALLBACK_TIMEOUT_MS, MILLISECONDS);
125         if (methodName == null) {
126             // Shouldn't happen...
127             throw new IllegalStateException(
128                     "Could not infer name of setCalled() method after "
129                             + CALLBACK_TIMEOUT_MS
130                             + " ms");
131         }
132         return methodName;
133     }
134 
135     /**
136      * {@code SyncCallback}s are expected to have 2 constructors (1 that doesn't take any parameter
137      * and 1 that takes a {@link SyncCallbackSettings}), so this methods return {@code true} by
138      * default, but should be overridden to return {@code false} if that's not the case (for
139      * example, if the {@code SyncCallback} uses a factory method or if it needs extra parameters
140      * like a {@code Context}.
141      */
providesExpectedConstructors()142     protected boolean providesExpectedConstructors() {
143         return true;
144     }
145 
146     /**
147      * Abstraction to "call" the callback.
148      *
149      * <p><b>Note:</b> must just call the callback right away, not in a background thread.
150      *
151      * @return representation of the method called (like "setCalled()" or "inject(foo)").
152      */
callCallback(CB callback)153     protected abstract String callCallback(CB callback);
154 
155     /**
156      * Checks whether the callback supports being constructor with a {@link SyncCallbackSettings
157      * settings} object that supports {@link SyncCallbackSettings#isFailIfCalledOnMainThread()
158      * failing if called in the main thread}.
159      *
160      * @return {@code true} by default.
161      */
supportsFailIfCalledOnMainThread()162     protected boolean supportsFailIfCalledOnMainThread() {
163         return true;
164     }
165 
166     /** Makes sure subclasses provide distinct callbacks, as some tests rely on that. */
167     @Test
testNewCallback()168     public final void testNewCallback() {
169         CB callback1 = newFrozenCallback(mDefaultSettings);
170         expect.withMessage("1st callback").that(callback1).isNotNull();
171 
172         CB callback2 = newFrozenCallback(mDefaultSettings);
173         expect.withMessage("2nd callback").that(callback2).isNotNull();
174         expect.withMessage("2nd callback").that(callback2).isNotSameInstanceAs(callback1);
175     }
176 
177     @Test
testHasExpectedConstructors()178     public final void testHasExpectedConstructors() throws Exception {
179         assumeTrue(
180                 "callback doesn't provide expected constructors", providesExpectedConstructors());
181 
182         CB callback = newFrozenCallback(mDefaultSettings);
183         @SuppressWarnings("unchecked")
184         Class<CB> callbackClass = (Class<CB>) callback.getClass();
185 
186         Constructor<CB> defaultConstructor = getConstructor(callbackClass);
187         expect.withMessage("Default constructor (%s)", callbackClass)
188                 .that(defaultConstructor)
189                 .isNotNull();
190 
191         Constructor<CB> settingsConstructor =
192                 getConstructor(callbackClass, SyncCallbackSettings.class);
193         expect.withMessage("%s(SyncCallbackSettings) constructor", callbackClass)
194                 .that(settingsConstructor)
195                 .isNotNull();
196     }
197 
198     @Test
testConstructor_cannotFailOnMainThread()199     public final void testConstructor_cannotFailOnMainThread() throws Exception {
200         assumeCannotFailIfCalledOnMainThread();
201         SyncCallbackSettings settings =
202                 mFakeLoggerSettingsBuilder.setFailIfCalledOnMainThread(true).build();
203 
204         assertThrows(IllegalArgumentException.class, () -> newFrozenCallback(settings));
205     }
206 
207     @Nullable
getConstructor(Class<CB> callbackClass, Class<?>... parameterTypes)208     private Constructor<CB> getConstructor(Class<CB> callbackClass, Class<?>... parameterTypes) {
209         try {
210             return callbackClass.getDeclaredConstructor(parameterTypes);
211         } catch (Exception e) {
212             mLog.e("Failed to get constructor for class %s: %s", callbackClass, e);
213             return null;
214         }
215     }
216 
217     @Test
testGetSettings()218     public final void testGetSettings() {
219         CB callback = newFrozenCallback(mDefaultSettings);
220 
221         expect.withMessage("getSettings()")
222                 .that(callback.getSettings())
223                 .isSameInstanceAs(mDefaultSettings);
224     }
225 
226     @Test
testGetId()227     public final void testGetId() {
228         CB callback1 = newFrozenCallback(mDefaultSettings);
229         String id1 = callback1.getId();
230         expect.withMessage("id").that(id1).isNotNull();
231 
232         CB callback2 = newFrozenCallback(mDefaultSettings);
233         String id2 = callback2.getId();
234         expect.withMessage("id2").that(id2).isNotNull();
235         expect.withMessage("id2").that(id2).isNotEqualTo(id1);
236     }
237 
238     @Test
testAssertCalled()239     public final void testAssertCalled() throws Exception {
240         var callback = newFrozenCallback(mDefaultSettings);
241         var log = new LogChecker(callback);
242 
243         // Check state before
244         expectIsCalledAndNumberCalls(callback, "before setCalled()", false, 0);
245 
246         Thread t = runAsync(INJECTION_TIMEOUT_MS, () -> call(callback));
247         callback.assertCalled();
248 
249         // Check state after
250         expectIsCalledAndNumberCalls(callback, "after setCalled()", true, 1);
251         String setCalled = getSetCalledMethodName();
252         expectLoggedCalls(
253                 log.d(setCalled + " called on " + t.getName()),
254                 log.v(setCalled + " returning"),
255                 log.d("assertCalled() called on " + currentThread().getName()),
256                 log.v("assertCalled() returning"));
257 
258         // Further calls - make sure number actual calls keeps increasing
259         // (don't need to call on bg because it's already called)
260         call(callback);
261         expect.withMessage("%s.getNumberActualCalls() after 2nd call", callback)
262                 .that(callback.getNumberActualCalls())
263                 .isEqualTo(2);
264     }
265 
266     @Test
testAssertCalled_neverCalled()267     public final void testAssertCalled_neverCalled() throws Exception {
268         var callback = newFrozenCallback(mDefaultSettings);
269         var log = new LogChecker(callback);
270 
271         var thrown =
272                 assertThrows(SyncCallbackTimeoutException.class, () -> callback.assertCalled());
273 
274         expect.withMessage("e.getTimeout()")
275                 .that(thrown.getTimeout())
276                 .isEqualTo(mDefaultSettings.getMaxTimeoutMs());
277         expect.withMessage("e.getUnit()()").that(thrown.getUnit()).isEqualTo(TimeUnit.MILLISECONDS);
278 
279         expectIsCalledAndNumberCalls(callback, "after setCalled()", false, 0);
280         expectLoggedCalls(
281                 log.d("assertCalled() called on " + currentThread().getName()),
282                 log.e("assertCalled() failed: " + thrown));
283     }
284 
285     @Test
testAssertCalled_interrupted()286     public final void testAssertCalled_interrupted() throws Exception {
287         var callback = newFrozenCallback(mDefaultSettings);
288         var log = new LogChecker(callback);
289         ArrayBlockingQueue<Throwable> actualFailureQueue = new ArrayBlockingQueue<>(1);
290 
291         // Must run it in another thread so it can be interrupted
292         Thread thread =
293                 startNewThread(
294                         () -> {
295                             try {
296                                 callback.assertCalled();
297                             } catch (Throwable t) {
298                                 actualFailureQueue.offer(t);
299                             }
300                         });
301         thread.interrupt();
302 
303         Throwable thrown = actualFailureQueue.poll(CALLBACK_TIMEOUT_MS, MILLISECONDS);
304         expect.withMessage("thrown exception")
305                 .that(thrown)
306                 .isInstanceOf(InterruptedException.class);
307 
308         expectIsCalledAndNumberCalls(callback, "after interrupted", false, 0);
309         expectLoggedCalls(
310                 log.d("assertCalled() called on " + thread.getName()),
311                 log.e("assertCalled() failed: " + thrown));
312     }
313 
314     @Test
testAssertCalled_multipleCalls()315     public final void testAssertCalled_multipleCalls() throws Exception {
316         SyncCallbackSettings settings = mDefaultSettingsBuilder.setExpectedNumberCalls(2).build();
317         CB callback = newFrozenCallback(settings);
318 
319         // 1st call
320         runAsync(INJECTION_TIMEOUT_MS, () -> call(callback));
321         assertThrows(SyncCallbackTimeoutException.class, () -> callback.assertCalled());
322 
323         expectIsCalledAndNumberCalls(callback, "after 1st setCalled()", false, 1);
324 
325         // 2nd call
326         runAsync(INJECTION_TIMEOUT_MS, () -> call(callback));
327         callback.assertCalled();
328 
329         expectIsCalledAndNumberCalls(callback, "after 2nd setCalled()", true, 2);
330 
331         // Further calls - make sure number actual calls keeps increasing
332         // (don't need to call on bg because it's already called)
333         call(callback);
334         expectIsCalledAndNumberCalls(callback, "after 3rd setCalled()", true, 3);
335     }
336 
337     @Test
testAssertCalled_multipleCallsFromMultipleCallbacks_firstFinishFirst()338     public final void testAssertCalled_multipleCallsFromMultipleCallbacks_firstFinishFirst()
339             throws Exception {
340         // Set a smaller timeout, as it's expect to fail multiple times
341         long injectionTimeoutMs = 20;
342         SyncCallbackSettings settings =
343                 mDefaultSettingsBuilder
344                         .setExpectedNumberCalls(4)
345                         .setMaxTimeoutMs(injectionTimeoutMs + 40)
346                         .build();
347         CB callback1 = newFrozenCallback(settings);
348         CB callback2 = newFrozenCallback(settings);
349 
350         // 1st call on 1st callback
351         runAsync(injectionTimeoutMs, () -> call(callback1));
352         assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled());
353         assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled());
354         expectIsCalled(callback1, "after 1st call on 1st callback", false);
355         expectIsCalled(callback2, "after 1st call on 1st callback", false);
356 
357         // 1st call on 2nd callback
358         runAsync(injectionTimeoutMs, () -> call(callback2));
359         assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled());
360         assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled());
361         expectIsCalled(callback1, "after 1st call on 2nd callback", false);
362         expectIsCalled(callback2, "after 1st call on 2nd callback", false);
363 
364         // 2nd call on 2nd callback
365         runAsync(injectionTimeoutMs, () -> call(callback2));
366         assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled());
367         assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled());
368         expectIsCalled(callback1, "after 2nd call on 2nd callback", false);
369         expectIsCalled(callback2, "after 2nd call on 2nd callback", false);
370 
371         // 2nd call on 1st callback
372         runAsync(injectionTimeoutMs, () -> call(callback1));
373         callback1.assertCalled();
374         callback2.assertCalled();
375         expectIsCalledAndNumberCalls(callback1, "after 2nd call on 1st callback", true, 2);
376         expectIsCalledAndNumberCalls(callback2, "after 2nd call on 1st callback", true, 2);
377 
378         // Further calls - make sure number actual calls keeps increasing
379         // (don't need to call on bg because it's already called)
380         call(callback1);
381         expectIsCalledAndNumberCalls(callback1, "after 3rd call on 1st callback", true, 3);
382         expectIsCalledAndNumberCalls(callback2, "after 3rd call on 1st callback", true, 2);
383         call(callback2);
384         expectIsCalledAndNumberCalls(callback1, "after 3rd call on 2nd callback", true, 3);
385         expectIsCalledAndNumberCalls(callback2, "after 3rd call on 2nd callback", true, 3);
386     }
387 
388     @Test
testAssertCalled_multipleCallsFromMultipleCallbacks_secondFinishFirst()389     public final void testAssertCalled_multipleCallsFromMultipleCallbacks_secondFinishFirst()
390             throws Exception {
391         // Set a smaller timeout, as it's expect to fail multiple times
392         long injectionTimeoutMs = 20;
393         SyncCallbackSettings settings =
394                 mDefaultSettingsBuilder
395                         .setExpectedNumberCalls(4)
396                         .setMaxTimeoutMs(injectionTimeoutMs + 40)
397                         .build();
398         CB callback1 = newFrozenCallback(settings);
399         CB callback2 = newFrozenCallback(settings);
400 
401         // 1st call on 1st callback
402         runAsync(injectionTimeoutMs, () -> call(callback1));
403         assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled());
404         assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled());
405         expectIsCalled(callback1, "after 1st call on 1st callback", false);
406         expectIsCalled(callback2, "after 1st call on 1st callback", false);
407 
408         // 1st call on 2nd callback
409         runAsync(injectionTimeoutMs, () -> call(callback2));
410         assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled());
411         assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled());
412         expectIsCalled(callback1, "after 1st call on 2nd callback", false);
413         expectIsCalled(callback2, "after 1st call on 2nd callback", false);
414 
415         // 2nd call on 1st callback
416         runAsync(injectionTimeoutMs, () -> call(callback1));
417         assertThrows(SyncCallbackTimeoutException.class, () -> callback1.assertCalled());
418         assertThrows(SyncCallbackTimeoutException.class, () -> callback2.assertCalled());
419         expectIsCalled(callback1, "after 2nd call on 1st callback", false);
420         expectIsCalled(callback2, "after 2nd call on 1st callback", false);
421 
422         // 2nd call on 2nd callback
423         runAsync(injectionTimeoutMs, () -> call(callback2));
424         callback1.assertCalled();
425         callback2.assertCalled();
426         expectIsCalledAndNumberCalls(callback1, "after 2nd call on 2nd callback", true, 2);
427         expectIsCalledAndNumberCalls(callback2, "after 2nd call on 2nd callback", true, 2);
428 
429         // Further calls - make sure number actual calls keeps increasing
430         // (don't need to call on bg because it's already called)
431         call(callback1);
432         expectIsCalledAndNumberCalls(callback1, "after 3rd call on 1st callback", true, 3);
433         expectIsCalledAndNumberCalls(callback2, "after 3rd call on 1st callback", true, 2);
434         call(callback2);
435         expectIsCalledAndNumberCalls(callback1, "after 3rd call on 2nd callback", true, 3);
436         expectIsCalledAndNumberCalls(callback2, "after 3rd call on 2nd callback", true, 3);
437     }
438 
439     @Test
testAssertCalled_failsWhenCalledOnMainThread()440     public final void testAssertCalled_failsWhenCalledOnMainThread() throws Exception {
441         assumeCanFailIfCalledOnMainThread();
442         SyncCallbackSettings settings =
443                 new SyncCallbackSettings.Builder(mFakeLogger, () -> Boolean.TRUE)
444                         .setMaxTimeoutMs(CALLBACK_TIMEOUT_MS)
445                         .setFailIfCalledOnMainThread(true)
446                         .build();
447 
448         var callback = newFrozenCallback(settings);
449         var log = new LogChecker(callback);
450 
451         // setCalled() passes...
452         // NOTE: not really the main thread, as it's emulated
453         Thread mainThread = runAsync(INJECTION_TIMEOUT_MS, () -> call(callback));
454 
455         String setCalled = getSetCalledMethodName();
456         var thrown = assertThrows(CalledOnMainThreadException.class, () -> callback.assertCalled());
457         expect.withMessage("thrown")
458                 .that(thrown)
459                 .hasMessageThat()
460                 .contains(setCalled + " called on main thread (" + mainThread.getName() + ")");
461 
462         expectIsCalledAndNumberCalls(callback, "after setCalled()", true, 1);
463 
464         expectLoggedCalls(
465                 log.d(setCalled + " called on " + mainThread.getName()),
466                 log.v(setCalled + " returning"),
467                 log.d("assertCalled() called on " + currentThread().getName()),
468                 log.e("assertCalled() failed: " + thrown));
469     }
470 
471     /** Helper method to assert the value of {@code isCalled()}. */
expectIsCalled( SyncCallback callback, String when, boolean expectedIsCalled)472     protected final void expectIsCalled(
473             SyncCallback callback, String when, boolean expectedIsCalled) {
474         expect.withMessage("%s.isCalled() %s", callback, when)
475                 .that(callback.isCalled())
476                 .isEqualTo(expectedIsCalled);
477     }
478 
479     /**
480      * Helper method to assert the value of {@code isCalled()} and {@code getNumberActualCalls()}.
481      */
expectIsCalledAndNumberCalls( SyncCallback callback, String when, boolean expectedIsCalled, int expectedNumberCalls)482     protected final void expectIsCalledAndNumberCalls(
483             SyncCallback callback, String when, boolean expectedIsCalled, int expectedNumberCalls) {
484         expectIsCalled(callback, when, expectedIsCalled);
485         expect.withMessage("%s.getNumberActualCalls() %s", callback, when)
486                 .that(callback.getNumberActualCalls())
487                 .isEqualTo(expectedNumberCalls);
488     }
489 
490     /** Helper methods to assert calls to the callback {@code logX()} methods. */
expectLoggedCalls(@ullable LogEntry... expectedEntries)491     public final void expectLoggedCalls(@Nullable LogEntry... expectedEntries) {
492         ImmutableList<LogEntry> entries = mFakeLogger.getEntries();
493         expect.withMessage("log entries").that(entries).containsExactlyElementsIn(expectedEntries);
494     }
495 
runAsync(long timeoutMs, Runnable r)496     protected final Thread runAsync(long timeoutMs, Runnable r) {
497         return mConcurrencyHelper.runAsync(timeoutMs, r);
498     }
499 
startNewThread(Runnable r)500     protected final Thread startNewThread(Runnable r) {
501         return mConcurrencyHelper.startNewThread(r);
502     }
503 
assumeCannotFailIfCalledOnMainThread()504     private void assumeCannotFailIfCalledOnMainThread() {
505         assumeFalse(
506                 "callback can fail if called on main thread", supportsFailIfCalledOnMainThread());
507     }
508 
assumeCanFailIfCalledOnMainThread()509     private void assumeCanFailIfCalledOnMainThread() {
510         assumeTrue(
511                 "callback cannot fail if called on main thread",
512                 supportsFailIfCalledOnMainThread());
513     }
514 
515     /** Helper class used to to assert calls to the callback {@code logX()} methods. */
516     protected static final class LogChecker {
517 
518         private final AbstractSyncCallback mCallback;
519 
LogChecker(SyncCallback callback)520         public LogChecker(SyncCallback callback) {
521             if (!(callback instanceof AbstractSyncCallback)) {
522                 throw new IllegalArgumentException(
523                         "Not an instance of AbstractSyncCallback: " + callback);
524             }
525             this.mCallback = AbstractSyncCallback.class.cast(callback);
526         }
527 
e(String expectedMessage)528         public LogEntry e(String expectedMessage) {
529             return new LogEntry(LogLevel.ERROR, LOG_TAG, mCallback + ": " + expectedMessage);
530         }
531 
d(String expectedMessage)532         public LogEntry d(String expectedMessage) {
533             return new LogEntry(
534                     LogLevel.DEBUG, LOG_TAG, mCallback.toStringLite() + ": " + expectedMessage);
535         }
536 
v(String expectedMessage)537         public LogEntry v(String expectedMessage) {
538             return new LogEntry(LogLevel.VERBOSE, LOG_TAG, mCallback + ": " + expectedMessage);
539         }
540     }
541 }
542