DO NOT MERGE Focus the topmost heads-up notification

Bug: 172267951
Test: manual via kitchen sink
Change-Id: Ib73c2912b06bc9e62d5a64abbecf625f4c8c78a9
diff --git a/res/layout/basic_headsup_notification_template.xml b/res/layout/basic_headsup_notification_template.xml
index f2c2fa5..0f82a23 100644
--- a/res/layout/basic_headsup_notification_template.xml
+++ b/res/layout/basic_headsup_notification_template.xml
@@ -14,10 +14,11 @@
     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"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content">
+<com.android.car.ui.FocusArea
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
@@ -76,4 +77,4 @@
 
         </RelativeLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/res/layout/call_headsup_notification_template.xml b/res/layout/call_headsup_notification_template.xml
index c9ec748..d15f13d 100644
--- a/res/layout/call_headsup_notification_template.xml
+++ b/res/layout/call_headsup_notification_template.xml
@@ -15,7 +15,7 @@
     limitations under the License.
 -->
 
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -78,4 +78,4 @@
             </FrameLayout>
         </RelativeLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/res/layout/car_emergency_headsup_notification_template.xml b/res/layout/car_emergency_headsup_notification_template.xml
index 725f110..a2b71e1 100644
--- a/res/layout/car_emergency_headsup_notification_template.xml
+++ b/res/layout/car_emergency_headsup_notification_template.xml
@@ -15,7 +15,7 @@
     limitations under the License.
 -->
 
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -86,4 +86,4 @@
             </RelativeLayout>
         </FrameLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/res/layout/car_information_headsup_notification_template.xml b/res/layout/car_information_headsup_notification_template.xml
index d588864..0411c51 100644
--- a/res/layout/car_information_headsup_notification_template.xml
+++ b/res/layout/car_information_headsup_notification_template.xml
@@ -15,7 +15,7 @@
     limitations under the License.
 -->
 
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -88,4 +88,4 @@
             </RelativeLayout>
         </FrameLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/res/layout/car_warning_headsup_notification_template.xml b/res/layout/car_warning_headsup_notification_template.xml
index d588864..0411c51 100644
--- a/res/layout/car_warning_headsup_notification_template.xml
+++ b/res/layout/car_warning_headsup_notification_template.xml
@@ -15,7 +15,7 @@
     limitations under the License.
 -->
 
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -88,4 +88,4 @@
             </RelativeLayout>
         </FrameLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/res/layout/headsup_container.xml b/res/layout/headsup_container.xml
index db649b7..9eec539 100644
--- a/res/layout/headsup_container.xml
+++ b/res/layout/headsup_container.xml
@@ -45,14 +45,13 @@
         app:layout_constraintBottom_toBottomOf="@+id/gradient_edge"
         app:layout_constraintTop_toTopOf="parent"/>
 
-    <FrameLayout
+    <com.android.car.notification.headsup.HeadsUpContainerView
         android:id="@+id/headsup_content"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/headsup_notification_top_margin"
         app:layout_constraintEnd_toStartOf="parent"
         app:layout_constraintStart_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-    />
+        app:layout_constraintTop_toTopOf="parent"/>
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/inbox_headsup_notification_template.xml b/res/layout/inbox_headsup_notification_template.xml
index 6f58bef..ae217d9 100644
--- a/res/layout/inbox_headsup_notification_template.xml
+++ b/res/layout/inbox_headsup_notification_template.xml
@@ -15,7 +15,7 @@
     limitations under the License.
 -->
 
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -78,4 +78,4 @@
             </FrameLayout>
         </RelativeLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/res/layout/message_headsup_notification_template.xml b/res/layout/message_headsup_notification_template.xml
index abe2f6f..ea49d0e 100644
--- a/res/layout/message_headsup_notification_template.xml
+++ b/res/layout/message_headsup_notification_template.xml
@@ -15,7 +15,7 @@
     limitations under the License.
 -->
 
-<FrameLayout
+<com.android.car.ui.FocusArea
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -88,4 +88,4 @@
             </FrameLayout>
         </RelativeLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/res/layout/navigation_headsup_notification_template.xml b/res/layout/navigation_headsup_notification_template.xml
index 4268905..b4b83e8 100644
--- a/res/layout/navigation_headsup_notification_template.xml
+++ b/res/layout/navigation_headsup_notification_template.xml
@@ -14,10 +14,11 @@
     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"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content">
+<com.android.car.ui.FocusArea
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card_view"
@@ -75,4 +76,4 @@
             </FrameLayout>
         </RelativeLayout>
     </androidx.cardview.widget.CardView>
-</FrameLayout>
+</com.android.car.ui.FocusArea>
diff --git a/src/com/android/car/notification/headsup/HeadsUpContainerView.java b/src/com/android/car/notification/headsup/HeadsUpContainerView.java
new file mode 100644
index 0000000..44fbec3
--- /dev/null
+++ b/src/com/android/car/notification/headsup/HeadsUpContainerView.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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 com.android.car.notification.headsup;
+
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_FOCUS_DELEGATING_CONTAINER;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.notification.R;
+import com.android.car.ui.FocusArea;
+
+/**
+ * Container that is used to present heads-up notifications. It is responsible for delegating the
+ * focus to the topmost notification and ensuring that new HUNs gains focus automatically when
+ * one of the existing HUNs already has focus.
+ */
+public class HeadsUpContainerView extends FrameLayout {
+
+    private Handler mHandler;
+    private int mAnimationDuration;
+
+    public HeadsUpContainerView(@NonNull Context context) {
+        super(context);
+        init();
+    }
+
+    public HeadsUpContainerView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public HeadsUpContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public HeadsUpContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    private void init() {
+        mHandler = new Handler(Looper.getMainLooper());
+        mAnimationDuration = getResources().getInteger(R.integer.headsup_total_enter_duration_ms);
+
+        // This tag is required to make this container receive the focus request in order to
+        // delegate focus to its children, even though the container itself isn't focusable.
+        setContentDescription(ROTARY_FOCUS_DELEGATING_CONTAINER);
+        setClickable(false);
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        if (isInTouchMode()) {
+            return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+        }
+        return focusTopmostChild();
+    }
+
+    @Override
+    public void addView(View child) {
+        super.addView(child);
+
+        if (!isInTouchMode() && getFocusedChild() != null) {
+            // Request focus for the topmost child if one of the children is already focused.
+            // Wait for the duration of the heads-up animation for a smoother UI experience.
+            mHandler.postDelayed(() -> focusTopmostChild(), mAnimationDuration);
+        }
+    }
+
+    private boolean focusTopmostChild() {
+        int childCount = getChildCount();
+        if (childCount <= 0) {
+            return false;
+        }
+
+        View topMostChild = getChildAt(childCount - 1);
+        if (!(topMostChild instanceof FocusArea)) {
+            return false;
+        }
+
+        return topMostChild.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
+    }
+}