Verify reduced UiTranslationStateCallback calls.

We don't invoke methods when translation state doesn't change (e.g.,
for consecutive startTranslation() calls).

Bug: 192331240
Test: atest UiTranslationManagerTest
Change-Id: Idf3bcb0062686475216b39834dbd99def3530e66
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 7a0d6f1..cee9088 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -38,6 +38,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -808,6 +809,185 @@
     }
 
     @Test
+    public void testCallbackCalledOnceAfterDuplicateCalls() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        // Call startTranslation() multiple times; callback should only be called once.
+        // Note: The locales don't change with each call.
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        // Call pauseTranslation() multiple times; callback should only be called once.
+        pauseUiTranslation(contentCaptureContext);
+        pauseUiTranslation(contentCaptureContext);
+        pauseUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+        // Call resumeUiTranslation() multiple times; callback should only be called once.
+        resumeUiTranslation(contentCaptureContext);
+        resumeUiTranslation(contentCaptureContext);
+        resumeUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+    }
+
+    @Test
+    public void testCallbackCalledForStartTranslationWithDifferentLocales() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.ENGLISH, ULocale.FRENCH);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.ENGLISH, ULocale.GERMAN);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.ITALIAN, ULocale.GERMAN);
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.JAPANESE, ULocale.KOREAN);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.ENGLISH), eq(ULocale.FRENCH), any(String.class));
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.ENGLISH), eq(ULocale.GERMAN), any(String.class));
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.ITALIAN), eq(ULocale.GERMAN), any(String.class));
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(eq(ULocale.JAPANESE), eq(ULocale.KOREAN), any(String.class));
+
+        // Calling startTranslation() after pauseTranslation() should invoke the callback even if
+        // the locales are the same as it was before.
+        pauseUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext,
+                ULocale.JAPANESE, ULocale.KOREAN);
+
+        Mockito.verify(mockCallback, Mockito.times(2))
+                .onStarted(eq(ULocale.JAPANESE), eq(ULocale.KOREAN), any(String.class));
+    }
+
+    @Test
+    public void testCallbackCalledOnStartTranslationAfterPause() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        pauseUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        // Start after pause invokes onResumed(), NOT onStarted().
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+    }
+
+    @Test
+    public void testCallbackNotCalledOnResumeTranslationAfterStart() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.times(1))
+                .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+        resumeUiTranslation(contentCaptureContext);
+
+        Mockito.verify(mockCallback, Mockito.never())
+                .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+    }
+
+    @Test
+    public void testCallbackNotCalledOnPauseOrResumeTranslationWithoutStart() throws Throwable {
+        final Pair<List<AutofillId>, ContentCaptureContext> result =
+                enableServicesAndStartActivityForTranslation();
+
+        final List<AutofillId> views = result.first;
+        final ContentCaptureContext contentCaptureContext = result.second;
+
+        UiTranslationManager manager =
+                sContext.getSystemService(UiTranslationManager.class);
+        // Set response
+        sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+        // Register callback
+        final Executor executor = Executors.newSingleThreadExecutor();
+        UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+        manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+        pauseUiTranslation(contentCaptureContext);
+        resumeUiTranslation(contentCaptureContext);
+
+        Mockito.verifyZeroInteractions(mockCallback);
+    }
+
+    @Test
     public void testVirtualViewUiTranslation() throws Throwable {
         // Enable CTS ContentCaptureService
         CtsContentCaptureService contentcaptureService = enableContentCaptureService();
@@ -956,14 +1136,19 @@
 
     private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
             ContentCaptureContext contentCaptureContext) {
+        startUiTranslation(shouldPadContent, views, contentCaptureContext, ULocale.ENGLISH,
+                ULocale.FRENCH);
+    }
+
+    private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
+            ContentCaptureContext contentCaptureContext, ULocale sourceLocale,
+            ULocale targetLocale) {
         final UiTranslationManager manager = sContext.getSystemService(UiTranslationManager.class);
         runWithShellPermissionIdentity(() -> {
             // Call startTranslation API
             manager.startTranslation(
-                    new TranslationSpec(ULocale.ENGLISH,
-                            TranslationSpec.DATA_FORMAT_TEXT),
-                    new TranslationSpec(ULocale.FRENCH,
-                            TranslationSpec.DATA_FORMAT_TEXT),
+                    new TranslationSpec(sourceLocale, TranslationSpec.DATA_FORMAT_TEXT),
+                    new TranslationSpec(targetLocale, TranslationSpec.DATA_FORMAT_TEXT),
                     views, contentCaptureContext.getActivityId(),
                     shouldPadContent ? new UiTranslationSpec.Builder().setShouldPadContentForCompat(
                             true).build() : new UiTranslationSpec.Builder().build());