Add clipboard indication text view

Fork system toast layout to show indication under clipboard overlay and
allow override to provide different indication text.

Bug: 361199935
Flag: com.android.systemui.show_clipboard_indication
Test: atest ClipboardOverlayControllerTest
Change-Id: I6e38d386e94fdbcbaf4101676aec6abdead2c3bb
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5c5edb1..b2ea6aa 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1559,6 +1559,13 @@
 }
 
 flag {
+   name: "show_clipboard_indication"
+   namespace: "systemui"
+   description: "Show indication text under the clipboard overlay when copied something"
+   bug: "361199935"
+}
+
+flag {
    name: "media_projection_dialog_behind_lockscreen"
    namespace: "systemui"
    description: "Ensure MediaProjection Dialog appears behind the lockscreen"
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 65005f8..572f063 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -32,6 +32,34 @@
         android:id="@+id/min_edge_guideline"
         app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
         android:orientation="vertical"/>
+    <!-- This toast-like indication layout was forked from text_toast.xml and will have the same
+         appearance as system toast. -->
+    <FrameLayout
+        android:id="@+id/indication_container"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:maxWidth="@*android:dimen/toast_width"
+        android:background="@android:drawable/toast_frame"
+        android:elevation="@*android:dimen/toast_elevation"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent">
+        <TextView
+            android:id="@+id/indication_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:paddingTop="12dp"
+            android:paddingBottom="12dp"
+            android:textAppearance="@*android:style/TextAppearance.Toast"/>
+    </FrameLayout>
     <!-- Negative horizontal margin because this container background must render beyond the thing
          it's constrained by (the actions themselves). -->
     <FrameLayout
@@ -47,7 +75,7 @@
         app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
         app:layout_constraintTop_toTopOf="@id/actions_container"
         app:layout_constraintEnd_toEndOf="@id/actions_container"
-        app:layout_constraintBottom_toBottomOf="parent"/>
+        app:layout_constraintBottom_toTopOf="@id/indication_container"/>
     <HorizontalScrollView
         android:id="@+id/actions_container"
         android:layout_width="0dp"
@@ -144,7 +172,7 @@
         android:visibility="gone"
         android:elevation="7dp"
         android:padding="8dp"
-        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/indication_container"
         app:layout_constraintStart_toStartOf="parent"
         android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
         android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
new file mode 100644
index 0000000..ddd6bc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.clipboardoverlay
+
+/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */
+interface ClipboardIndicationCallback {
+
+    /** Notifies the indication text changed. */
+    fun onIndicationTextChanged(text: CharSequence)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
new file mode 100644
index 0000000..be32723
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.clipboardoverlay
+
+/** Interface to provide the clipboard indication to be shown under the overlay. */
+interface ClipboardIndicationProvider {
+
+    /**
+     * Gets the indication text async.
+     *
+     * @param callback callback for getting the indication text.
+     */
+    fun getIndicationText(callback: ClipboardIndicationCallback)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
new file mode 100644
index 0000000..da94d5b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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 com.android.systemui.clipboardoverlay
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+open class ClipboardIndicationProviderImpl @Inject constructor() : ClipboardIndicationProvider {
+
+    override fun getIndicationText(callback: ClipboardIndicationCallback) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 65c01ed..ac74784 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -21,6 +21,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.Flags.clipboardImageTimeout;
 import static com.android.systemui.Flags.clipboardSharedTransitions;
+import static com.android.systemui.Flags.showClipboardIndication;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -99,6 +100,7 @@
     private final ClipboardTransitionExecutor mTransitionExecutor;
 
     private final ClipboardOverlayView mView;
+    private final ClipboardIndicationProvider mClipboardIndicationProvider;
 
     private Runnable mOnSessionCompleteListener;
     private Runnable mOnRemoteCopyTapped;
@@ -173,6 +175,13 @@
                 }
             };
 
+    private ClipboardIndicationCallback mIndicationCallback = new ClipboardIndicationCallback() {
+        @Override
+        public void onIndicationTextChanged(@NonNull CharSequence text) {
+            mView.setIndicationText(text);
+        }
+    };
+
     @Inject
     public ClipboardOverlayController(@OverlayWindowContext Context context,
             ClipboardOverlayView clipboardOverlayView,
@@ -185,11 +194,13 @@
             @Background Executor bgExecutor,
             ClipboardImageLoader clipboardImageLoader,
             ClipboardTransitionExecutor transitionExecutor,
+            ClipboardIndicationProvider clipboardIndicationProvider,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mClipboardImageLoader = clipboardImageLoader;
         mTransitionExecutor = transitionExecutor;
+        mClipboardIndicationProvider = clipboardIndicationProvider;
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
@@ -288,6 +299,9 @@
         boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
         mClipboardModel = model;
         mClipboardLogger.setClipSource(mClipboardModel.getSource());
+        if (showClipboardIndication()) {
+            mClipboardIndicationProvider.getIndicationText(mIndicationCallback);
+        }
         if (clipboardImageTimeout()) {
             if (shouldAnimate) {
                 reset();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 1762d82..7e4d762 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,6 +18,8 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.Flags.showClipboardIndication;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -53,6 +55,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 
@@ -103,6 +106,8 @@
     private View mShareChip;
     private View mRemoteCopyChip;
     private View mActionContainerBackground;
+    private View mIndicationContainer;
+    private TextView mIndicationText;
     private View mDismissButton;
     private LinearLayout mActionContainer;
     private ClipboardOverlayCallbacks mClipboardCallbacks;
@@ -136,6 +141,8 @@
         mShareChip = requireViewById(R.id.share_chip);
         mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
         mDismissButton = requireViewById(R.id.dismiss_button);
+        mIndicationContainer = requireViewById(R.id.indication_container);
+        mIndicationText = mIndicationContainer.findViewById(R.id.indication_text);
 
         bindDefaultActionChips();
 
@@ -208,6 +215,14 @@
         }
     }
 
+    void setIndicationText(CharSequence text) {
+        mIndicationText.setText(text);
+
+        // Set the visibility of clipboard indication based on the text is empty or not.
+        int visibility = text.isEmpty() ? View.GONE : View.VISIBLE;
+        mIndicationContainer.setVisibility(visibility);
+    }
+
     void setMinimized(boolean minimized) {
         if (minimized) {
             mMinimizedPreview.setVisibility(View.VISIBLE);
@@ -221,6 +236,18 @@
             mPreviewBorder.setVisibility(View.VISIBLE);
             mActionContainer.setVisibility(View.VISIBLE);
         }
+
+        if (showClipboardIndication()) {
+            // Adjust the margin of clipboard indication based on the minimized state.
+            int marginStart = minimized ? getResources().getDimensionPixelSize(
+                    R.dimen.overlay_action_container_margin_horizontal)
+                    : getResources().getDimensionPixelSize(
+                            R.dimen.overlay_action_container_minimum_edge_spacing);
+            ConstraintLayout.LayoutParams params =
+                    (ConstraintLayout.LayoutParams) mIndicationContainer.getLayoutParams();
+            params.setMarginStart(marginStart);
+            mIndicationContainer.setLayoutParams(params);
+        }
     }
 
     void setInsets(WindowInsets insets, int orientation) {
@@ -313,6 +340,7 @@
         setTranslationX(0);
         setAlpha(0);
         mActionContainerBackground.setVisibility(View.GONE);
+        mIndicationContainer.setVisibility(View.GONE);
         mDismissButton.setVisibility(View.GONE);
         mShareChip.setVisibility(View.GONE);
         mRemoteCopyChip.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
rename to packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
index 527819c..c81f0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
@@ -15,18 +15,26 @@
  */
 package com.android.systemui.clipboardoverlay.dagger
 
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProvider
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProviderImpl
 import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController
 import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl
 import dagger.Binds
 import dagger.Module
 
-/** Dagger Module for code in the clipboard overlay package. */
+/** Dagger Module to provide default implementations which could be overridden. */
 @Module
-interface ClipboardOverlaySuppressionModule {
+interface ClipboardOverlayOverrideModule {
 
     /** Provides implementation for [ClipboardOverlaySuppressionController]. */
     @Binds
     fun provideClipboardOverlaySuppressionController(
         clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl
     ): ClipboardOverlaySuppressionController
+
+    /** Provides implementation for [ClipboardIndicationProvider]. */
+    @Binds
+    fun provideClipboardIndicationProvider(
+        clipboardIndicationProviderImpl: ClipboardIndicationProviderImpl
+    ): ClipboardIndicationProvider
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 609b733..2e323d4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -29,7 +29,7 @@
 import com.android.systemui.accessibility.SystemActionsModule;
 import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.battery.BatterySaverModule;
-import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlaySuppressionModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayOverrideModule;
 import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
@@ -125,7 +125,7 @@
         AospPolicyModule.class,
         BatterySaverModule.class,
         CentralSurfacesModule.class,
-        ClipboardOverlaySuppressionModule.class,
+        ClipboardOverlayOverrideModule.class,
         CollapsedStatusBarFragmentStartableModule.class,
         ConnectingDisplayViewModel.StartableModule.class,
         DefaultBlueprintModule.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 6061063..5624815 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
 import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE;
+import static com.android.systemui.Flags.FLAG_SHOW_CLIPBOARD_INDICATION;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -121,6 +122,24 @@
 
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
+    private class FakeClipboardIndicationProvider implements ClipboardIndicationProvider {
+        private ClipboardIndicationCallback mIndicationCallback;
+
+        public void notifyIndicationTextChanged(CharSequence indicationText) {
+            if (mIndicationCallback != null) {
+                mIndicationCallback.onIndicationTextChanged(indicationText);
+            }
+        }
+
+        @Override
+        public void getIndicationText(ClipboardIndicationCallback callback) {
+            mIndicationCallback = callback;
+        }
+    }
+
+    private FakeClipboardIndicationProvider mClipboardIndicationProvider =
+            new FakeClipboardIndicationProvider();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -156,6 +175,7 @@
                 mExecutor,
                 mClipboardImageLoader,
                 mClipboardTransitionExecutor,
+                mClipboardIndicationProvider,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -305,6 +325,17 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SHOW_CLIPBOARD_INDICATION)
+    public void test_onIndicationTextChanged_setIndicationTextCorrectly() {
+        initController();
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mClipboardIndicationProvider.notifyIndicationTextChanged("copied");
+
+        verify(mClipboardOverlayView).setIndicationText("copied");
+    }
+
+    @Test
     @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
         initController();