Support ActivityViews

Add an app:shouldRestoreFocus attribute and setShouldRestoreFocus()
method to FocusParkingView for use in an ActivityView. This makes the
FocusParkingView allow itself to be focused by the framework rather than
finding some other view to focus.

This prevents multiple views from being focused when an application with
an ActivityView is launched, one inside the ActivityView and one
outside.

Bug: 171334003
Bug: 171425596
Bug: 171425519
Test: replace maps with test activity, then nudge in and out
Test: replace maps with test activity, then nudge and rotate within it
Test: replace maps with test activity, then press Home button via rotary
Test: atest com.android.car.ui.FocusParkingViewTest
Test: atest com.android.car.ui.FocusParkingViewTouchModeTest
Change-Id: I3557e47c4b61f8936b629efbcf9adaa90e1c2c31
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
index 325de40..e706ae1 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
@@ -230,4 +230,28 @@
             assertThat(mFocusedByDefault.isFocused()).isTrue();
         });
     }
+
+    @Test
+    public void testRequestFocus_focusesFpvWhenShouldRestoreFocusIsFalse() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+            mFpv.setShouldRestoreFocus(false);
+
+            mFpv.requestFocus();
+            assertThat(mFpv.isFocused()).isTrue();
+        });
+    }
+
+    @Test
+    public void testRestoreDefaultFocus_focusesFpvWhenShouldRestoreFocusIsFalse() {
+        mFpv.post(() -> {
+            mView1.requestFocus();
+            assertThat(mView1.isFocused()).isTrue();
+            mFpv.setShouldRestoreFocus(false);
+
+            mFpv.restoreDefaultFocus();
+            assertThat(mFpv.isFocused()).isTrue();
+        });
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
index e5a2533..8927370 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingView.java
@@ -22,6 +22,7 @@
 import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
 
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.AttributeSet;
@@ -76,28 +77,41 @@
     @Nullable
     ViewGroup mScrollableContainer;
 
+    /**
+     * Whether to restore focus when the frameworks wants to focus this view. When false, this view
+     * allows itself to be focused instead. This should be false for the {@code FocusParkingView} in
+     * an {@code ActivityView}. The default value is true.
+     */
+    private boolean mShouldRestoreFocus;
+
     public FocusParkingView(Context context) {
         super(context);
-        init();
+        init(context, /* attrs= */ null);
     }
 
     public FocusParkingView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
-        init();
+        init(context, attrs);
     }
 
     public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init();
+        init(context, attrs);
     }
 
     public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init();
+        init(context, attrs);
     }
 
-    private void init() {
+    private void init(Context context, @Nullable AttributeSet attrs) {
+        if (attrs != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FocusParkingView);
+            mShouldRestoreFocus = a.getBoolean(R.styleable.FocusParkingView_shouldRestoreFocus,
+                    /* defValue= */ true);
+        }
+
         // This view is focusable, visible and enabled so it can take focus.
         setFocusable(View.FOCUSABLE);
         setVisibility(VISIBLE);
@@ -177,6 +191,9 @@
 
     @Override
     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        if (!mShouldRestoreFocus) {
+            return super.requestFocus(direction, previouslyFocusedRect);
+        }
         // Find a better target to focus instead of focusing this FocusParkingView when the
         // framework wants to focus it.
         return restoreFocusInRoot(/* checkForTouchMode= */ true);
@@ -184,11 +201,23 @@
 
     @Override
     public boolean restoreDefaultFocus() {
+        if (!mShouldRestoreFocus) {
+            return super.restoreDefaultFocus();
+        }
         // Find a better target to focus instead of focusing this FocusParkingView when the
         // framework wants to focus it.
         return restoreFocusInRoot(/* checkForTouchMode= */ true);
     }
 
+    /**
+     * Sets whether this view should restore focus when the framework wants to focus this view. When
+     * set to false, this view allows itself to be focused instead. This should be set to false for
+     * the {@code FocusParkingView} in an {@code ActivityView}.  The default value is true.
+     */
+    public void setShouldRestoreFocus(boolean shouldRestoreFocus) {
+        mShouldRestoreFocus = shouldRestoreFocus;
+    }
+
     private boolean restoreFocusInRoot(boolean checkForTouchMode) {
         // Don't do anything in touch mode if checkForTouchMode is true.
         if (checkForTouchMode && isInTouchMode()) {
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
index ab238cc..f38522f 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
@@ -236,4 +236,12 @@
         <!-- The ID of the target FocusArea when nudging down. -->
         <attr name="nudgeDown" format="reference"/>
     </declare-styleable>
+
+    <!-- Attributes for FocusParkingView. -->
+    <declare-styleable name="FocusParkingView">
+        <!-- Whether to restore focus when the frameworks wants to focus the FocusParkingView. When
+             false, the FocusParkingView allows itself to be focused instead. This should be false
+             for the FocusParkingView in an ActivityView. The default value is true. -->
+        <attr name="shouldRestoreFocus" format="boolean"/>
+    </declare-styleable>
 </resources>