Make ConfirmationActivity display duration configurable

Allow configuration of the duration that ConfigurationActivity should be visible through intent extra data.

Bug: 143577686
Test: ./gradlew wear:wear:connectedCheck --info --daemon -Pandroid.testInstrumentationRunnerArguments.class=androidx.wear.widget.ConfirmationActivityTest and androidx.wear.widget.ConfirmationActivityTest (from within Android studio against a Cuttlefish AVD and a Watch round emulator)
Change-Id: Ibc780bc5c7f8ce7a72a2fe022a498061682dd910
diff --git a/wear/wear/api/1.1.0-alpha01.txt b/wear/wear/api/1.1.0-alpha01.txt
index f6415c8..b6b0a6d 100644
--- a/wear/wear/api/1.1.0-alpha01.txt
+++ b/wear/wear/api/1.1.0-alpha01.txt
@@ -5,6 +5,8 @@
     ctor public ConfirmationActivity();
     method protected void onAnimationFinished();
     method public void onCreate(android.os.Bundle!);
+    field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8
+    field public static final String EXTRA_ANIMATION_DURATION_MS = "androidx.wear.activity.extra.ANIMATION_DURATION_MS";
     field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE";
     field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE";
     field public static final int FAILURE_ANIMATION = 3; // 0x3
diff --git a/wear/wear/api/current.txt b/wear/wear/api/current.txt
index f6415c8..b6b0a6d 100644
--- a/wear/wear/api/current.txt
+++ b/wear/wear/api/current.txt
@@ -5,6 +5,8 @@
     ctor public ConfirmationActivity();
     method protected void onAnimationFinished();
     method public void onCreate(android.os.Bundle!);
+    field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8
+    field public static final String EXTRA_ANIMATION_DURATION_MS = "androidx.wear.activity.extra.ANIMATION_DURATION_MS";
     field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE";
     field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE";
     field public static final int FAILURE_ANIMATION = 3; // 0x3
diff --git a/wear/wear/api/public_plus_experimental_1.1.0-alpha01.txt b/wear/wear/api/public_plus_experimental_1.1.0-alpha01.txt
index f6415c8..b6b0a6d 100644
--- a/wear/wear/api/public_plus_experimental_1.1.0-alpha01.txt
+++ b/wear/wear/api/public_plus_experimental_1.1.0-alpha01.txt
@@ -5,6 +5,8 @@
     ctor public ConfirmationActivity();
     method protected void onAnimationFinished();
     method public void onCreate(android.os.Bundle!);
+    field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8
+    field public static final String EXTRA_ANIMATION_DURATION_MS = "androidx.wear.activity.extra.ANIMATION_DURATION_MS";
     field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE";
     field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE";
     field public static final int FAILURE_ANIMATION = 3; // 0x3
diff --git a/wear/wear/api/public_plus_experimental_current.txt b/wear/wear/api/public_plus_experimental_current.txt
index f6415c8..b6b0a6d 100644
--- a/wear/wear/api/public_plus_experimental_current.txt
+++ b/wear/wear/api/public_plus_experimental_current.txt
@@ -5,6 +5,8 @@
     ctor public ConfirmationActivity();
     method protected void onAnimationFinished();
     method public void onCreate(android.os.Bundle!);
+    field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8
+    field public static final String EXTRA_ANIMATION_DURATION_MS = "androidx.wear.activity.extra.ANIMATION_DURATION_MS";
     field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE";
     field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE";
     field public static final int FAILURE_ANIMATION = 3; // 0x3
diff --git a/wear/wear/api/restricted_1.1.0-alpha01.txt b/wear/wear/api/restricted_1.1.0-alpha01.txt
index 9db49de..79c0d86 100644
--- a/wear/wear/api/restricted_1.1.0-alpha01.txt
+++ b/wear/wear/api/restricted_1.1.0-alpha01.txt
@@ -5,6 +5,8 @@
     ctor public ConfirmationActivity();
     method protected void onAnimationFinished();
     method public void onCreate(android.os.Bundle!);
+    field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8
+    field public static final String EXTRA_ANIMATION_DURATION_MS = "androidx.wear.activity.extra.ANIMATION_DURATION_MS";
     field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE";
     field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE";
     field public static final int FAILURE_ANIMATION = 3; // 0x3
diff --git a/wear/wear/api/restricted_current.txt b/wear/wear/api/restricted_current.txt
index 9db49de..79c0d86 100644
--- a/wear/wear/api/restricted_current.txt
+++ b/wear/wear/api/restricted_current.txt
@@ -5,6 +5,8 @@
     ctor public ConfirmationActivity();
     method protected void onAnimationFinished();
     method public void onCreate(android.os.Bundle!);
+    field public static final int DEFAULT_ANIMATION_DURATION_MS = 1000; // 0x3e8
+    field public static final String EXTRA_ANIMATION_DURATION_MS = "androidx.wear.activity.extra.ANIMATION_DURATION_MS";
     field public static final String EXTRA_ANIMATION_TYPE = "androidx.wear.activity.extra.ANIMATION_TYPE";
     field public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE";
     field public static final int FAILURE_ANIMATION = 3; // 0x3
diff --git a/wear/wear/src/androidTest/AndroidManifest.xml b/wear/wear/src/androidTest/AndroidManifest.xml
index 9bbf118..a62627b 100644
--- a/wear/wear/src/androidTest/AndroidManifest.xml
+++ b/wear/wear/src/androidTest/AndroidManifest.xml
@@ -83,6 +83,16 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="androidx.wear.widget.ConfirmationActivityTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="androidx.wear.activity.ConfirmationActivity">
+        </activity>
+
         <!-- Test app is iOS compatible. -->
         <meta-data
             android:name="com.google.android.wearable.standalone"
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/ConfirmationActivityTest.java b/wear/wear/src/androidTest/java/androidx/wear/widget/ConfirmationActivityTest.java
new file mode 100644
index 0000000..4c8791f
--- /dev/null
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/ConfirmationActivityTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.widget;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.widget.Button;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.wear.activity.ConfirmationActivity;
+import androidx.wear.test.R;
+import androidx.wear.widget.util.WakeLockRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ConfirmationActivityTest {
+
+    // Number of milliseconds before the end of the duration period that we should check that the
+    // ConfirmationActivity still has focus
+    private static final int MILLIS_BEFORE_END_OF_DURATION = 150;
+
+    // Number of milliseconds after the end of the duration period that we should wait before the
+    // ConfirmationActivity still lost focus
+    private static final int MILLIS_AFTER_END_OF_DURATION = 150;
+
+    // Number of milliseconds to wait for the confirmation activity to display initially
+    private static final int MILLIS_TO_WAIT_FOR_ACTIVITY_TO_BE_DRAWN = 100;
+
+    @Rule
+    public final WakeLockRule wakeLock = new WakeLockRule();
+
+    @Rule
+    public final ActivityTestRule<ConfirmationActivityTestActivity> mActivityRule =
+            new ActivityTestRule<>(ConfirmationActivityTestActivity.class, true, true);
+
+    @Test
+    public void testConfirmationDialogShownForDefaultDuration() throws Throwable {
+        testConfirmationDialogShownForConfiguredDuration(
+                ConfirmationActivity.DEFAULT_ANIMATION_DURATION_MS);
+    }
+
+    @Test
+    public void testConfirmationDialogShownForShortDuration() throws Throwable {
+        int testDuration = ConfirmationActivity.DEFAULT_ANIMATION_DURATION_MS / 2;
+        // Check that the structure of the test is still valid
+        assertTrue(testDuration
+                > (MILLIS_BEFORE_END_OF_DURATION + MILLIS_TO_WAIT_FOR_ACTIVITY_TO_BE_DRAWN));
+
+        testConfirmationDialogShownForConfiguredDuration(testDuration);
+    }
+
+    @Test
+    public void testConfirmationDialogShownForLongerDuration() throws Throwable {
+        testConfirmationDialogShownForConfiguredDuration(
+                ConfirmationActivity.DEFAULT_ANIMATION_DURATION_MS * 2);
+    }
+
+    private void testConfirmationDialogShownForConfiguredDuration(int duration) throws Throwable {
+        // Wait for the test activity to be visible
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        // Check that the test activity currently hasWindowFocus()
+        assertTrue(mActivityRule.getActivity().hasWindowFocus());
+        Button button =
+                mActivityRule.getActivity().findViewById(R.id.show_confirmation_activity_button);
+
+        // GIVEN a display duration in milliseconds
+        mActivityRule.getActivity().setDuration(duration);
+        // WHEN we click on the button
+        mActivityRule.runOnUiThread(button::performClick);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        // THEN wait for the activity to be drawn
+        Thread.sleep(MILLIS_TO_WAIT_FOR_ACTIVITY_TO_BE_DRAWN);
+        // AND lose window focus to the confirmation activity on top
+        assertFalse(mActivityRule.getActivity().hasWindowFocus());
+        // AND wait until a short while before the confirmation activity is due to expire
+        Thread.sleep(duration - MILLIS_BEFORE_END_OF_DURATION
+                - MILLIS_TO_WAIT_FOR_ACTIVITY_TO_BE_DRAWN);
+        // AND confirm that the confirmation activity still has focus
+        assertFalse(mActivityRule.getActivity().hasWindowFocus());
+        // AND wait for until the confirmation activity should be gone
+        Thread.sleep(MILLIS_AFTER_END_OF_DURATION + MILLIS_BEFORE_END_OF_DURATION
+                + MILLIS_TO_WAIT_FOR_ACTIVITY_TO_BE_DRAWN);
+        // AND confirm that the test activity has focus again
+        assertTrue(mActivityRule.getActivity().hasWindowFocus());
+    }
+
+}
diff --git a/wear/wear/src/androidTest/java/androidx/wear/widget/ConfirmationActivityTestActivity.java b/wear/wear/src/androidTest/java/androidx/wear/widget/ConfirmationActivityTestActivity.java
new file mode 100644
index 0000000..2d80336
--- /dev/null
+++ b/wear/wear/src/androidTest/java/androidx/wear/widget/ConfirmationActivityTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.widget;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+
+import androidx.wear.activity.ConfirmationActivity;
+import androidx.wear.test.R;
+
+public class ConfirmationActivityTestActivity extends Activity {
+
+    /**
+     * Configurable duration in milliseconds to display the confirmation dialog for.
+     */
+    public void setDuration(int duration) {
+        mDuration = duration;
+    }
+
+    private int mDuration = ConfirmationActivity.DEFAULT_ANIMATION_DURATION_MS;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.confirmation_activity_test_layout);
+        Button button = findViewById(R.id.show_confirmation_activity_button);
+
+        button.setOnClickListener(
+                v -> {
+                    Intent intent = new Intent(ConfirmationActivityTestActivity.this,
+                            ConfirmationActivity.class);
+                    intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE,
+                            ConfirmationActivity.SUCCESS_ANIMATION);
+                    intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_DURATION_MS, mDuration);
+                    intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE, "A message");
+
+                    startActivity(intent);
+                }
+        );
+    }
+}
diff --git a/wear/wear/src/androidTest/res/layout/confirmation_activity_test_layout.xml b/wear/wear/src/androidTest/res/layout/confirmation_activity_test_layout.xml
new file mode 100644
index 0000000..5ef7d68
--- /dev/null
+++ b/wear/wear/src/androidTest/res/layout/confirmation_activity_test_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/accept_deny_dialog_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#333333"
+    tools:context="androidx.wear.widget.ConfirmationActivityTestActivity">
+    <Button
+        android:id="@+id/show_confirmation_activity_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Button" />
+</FrameLayout>
diff --git a/wear/wear/src/main/java/androidx/wear/activity/ConfirmationActivity.java b/wear/wear/src/main/java/androidx/wear/activity/ConfirmationActivity.java
index 875c241..9359302 100644
--- a/wear/wear/src/main/java/androidx/wear/activity/ConfirmationActivity.java
+++ b/wear/wear/src/main/java/androidx/wear/activity/ConfirmationActivity.java
@@ -47,19 +47,49 @@
  * <dd>Displays a generic failure page with an optional message.
  * </dl>
  *
- * An optional message, included in the extra {@link #EXTRA_MESSAGE} will be displayed horizontally
- * centered below the animation.
+ * <p>An optional message, included in the extra {@link #EXTRA_MESSAGE} will be displayed
+ * horizontally centered below the animation.
+ *
+ * <p>An optional duration to keep the confirmation activity visible for, included in the extra
+ * {@link #EXTRA_ANIMATION_DURATION_MS}
+ *
+ * <p>Default duration is {@link #DEFAULT_ANIMATION_DURATION_MS}
  */
 public class ConfirmationActivity extends Activity {
 
+    /**
+     * Used as a string extra field on an intent for this activity to define the message that
+     * should be displayed to the user while the activity is visible.
+     */
     public static final String EXTRA_MESSAGE = "androidx.wear.activity.extra.MESSAGE";
+
+    /**
+     * The lookup key for an optional int that defines the animation type that should be
+     * displayed. Should be one of  {@link #SUCCESS_ANIMATION}, {@link #OPEN_ON_PHONE_ANIMATION},
+     * or {@link #FAILURE_ANIMATION}
+     *
+     * <p>If no value is specified it will default to {@link #SUCCESS_ANIMATION}
+     */
     public static final String EXTRA_ANIMATION_TYPE =
             "androidx.wear.activity.extra.ANIMATION_TYPE";
 
+    /**
+     * The lookup key for an optional int that defines the duration in milliseconds that the
+     * confirmation activity should be displayed.
+     *
+     * If no value is specified it will default to {@value #DEFAULT_ANIMATION_DURATION_MS}
+     */
+    public static final String EXTRA_ANIMATION_DURATION_MS =
+            "androidx.wear.activity.extra.ANIMATION_DURATION_MS";
+
     public static final int SUCCESS_ANIMATION = 1;
     public static final int OPEN_ON_PHONE_ANIMATION = 2;
     public static final int FAILURE_ANIMATION = 3;
 
+    /** Default animation duration in ms. **/
+    public static final int DEFAULT_ANIMATION_DURATION_MS =
+            ConfirmationOverlay.DEFAULT_ANIMATION_DURATION_MS;
+
     private static final SparseIntArray CONFIRMATION_OVERLAY_TYPES = new SparseIntArray();
 
     static {
@@ -77,6 +107,8 @@
         Intent intent = getIntent();
 
         int requestedType = intent.getIntExtra(EXTRA_ANIMATION_TYPE, SUCCESS_ANIMATION);
+        int animationDurationMillis = intent.getIntExtra(EXTRA_ANIMATION_DURATION_MS,
+                DEFAULT_ANIMATION_DURATION_MS);
         if (CONFIRMATION_OVERLAY_TYPES.indexOfKey(requestedType) < 0) {
             throw new IllegalArgumentException("Unknown type of animation: " + requestedType);
         }
@@ -87,6 +119,7 @@
         new ConfirmationOverlay()
                 .setType(type)
                 .setMessage(message)
+                .setDuration(animationDurationMillis)
                 .setOnAnimationFinishedListener(
                         new ConfirmationOverlay.OnAnimationFinishedListener() {
                             @Override