FocusFinderTest Workarounds...

Bug 2437360

The FocusFinder tests were failing on large width screens, because
the focus model takes into account the width of the widgets. The
prior layout had buttons that spanned the entire screen, and this
caused the focus behavior to change across different screen sizes.
This does not seem correct, but it is how the framework is coded.
Create a new layout that has four equally-sized center buttons to
avoid this problem.

Finally, the FocusFinder#findNearestTouchables seems to not take
into account the touch coordinates when calculating the delta
offset for the down and right focus directions which thus makes
the distance greater than the "edge slop" causing it to fail.
This also does not seem correct but probably due to a lack of
my understanding (Bug 2986136). Work around the problems using
more simple cases that touch on the outer edges of the widgets
that do not trigger that problem.

(Backport from Froyo. match_parent attributes changed to fill_parent
and strings added to strings.xml to get around translation errors)

Change-Id: I1b33e254c4d15c29f85713758ccf61366b6071da
diff --git a/tests/res/layout/focus_finder_layout.xml b/tests/res/layout/focus_finder_layout.xml
new file mode 100644
index 0000000..166d01a
--- /dev/null
+++ b/tests/res/layout/focus_finder_layout.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+    <TableLayout android:id="@+id/layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true">
+        <TableRow>
+            <android.view.cts.TestButton android:id="@+id/top_left_button"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:text="@string/top_left" />
+            <android.view.cts.TestButton android:id="@+id/top_right_button"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:text="@string/top_right" />
+        </TableRow>
+        <TableRow>
+            <android.view.cts.TestButton android:id="@+id/bottom_left_button"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:text="@string/bottom_left" />
+            <android.view.cts.TestButton android:id="@+id/bottom_right_button"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:text="@string/bottom_right" />
+        </TableRow>  
+    </TableLayout>
+</RelativeLayout>
+
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
index 37b625c..95f4737 100644
--- a/tests/res/values/strings.xml
+++ b/tests/res/values/strings.xml
@@ -167,4 +167,8 @@
    <string name="version_cur">base</string>
    <string name="version_old">base</string>
    <string name="version_v3">base</string>
+   <string name="top_left">TL</string>
+   <string name="top_right">TR</string>
+   <string name="bottom_left">BL</string>
+   <string name="bottom_right">BR</string>
 </resources>
diff --git a/tests/src/android/view/cts/FocusFinderStubActivity.java b/tests/src/android/view/cts/FocusFinderStubActivity.java
index 20150f1..ad6b826 100644
--- a/tests/src/android/view/cts/FocusFinderStubActivity.java
+++ b/tests/src/android/view/cts/FocusFinderStubActivity.java
@@ -16,139 +16,35 @@
 
 package android.view.cts;
 
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.LinearLayout;
-import android.widget.Button;
-import android.widget.TextView;
-import android.view.Gravity;
-import android.view.ViewGroup;
-import android.content.Context;
+import com.android.cts.stub.R;
 
-/**
- * Holds a few buttons of various sizes and horizontal placements in a vertical
- * layout to excercise some core focus searching.
- */
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.Button;
+
 public class FocusFinderStubActivity extends Activity {
 
-    private LinearLayout mLayout;
+    public ViewGroup layout;
 
-    private Button mTopWide;
-    private Button mMidSkinny1Left;
-    private Button mBottomWide;
+    public Button topLeftButton;
 
-    private Button mMidSkinny2Right;
-    public static final String mTopWideLable = "top wide";
-    public static final String mBottomWideLable = "bottom wide";
-    public static final String mMidSkinny1LeftLable = "mid skinny 1(L)";
-    public static final String mMidSkinny2RightLable = "mid skinny 2(R)";
+    public Button topRightButton;
 
-    public LinearLayout getLayout() {
-        return mLayout;
-    }
+    public Button bottomLeftButton;
 
-    public Button getTopWide() {
-        return mTopWide;
-    }
-
-    public Button getMidSkinny1Left() {
-        return mMidSkinny1Left;
-    }
-
-    public Button getMidSkinny2Right() {
-        return mMidSkinny2Right;
-    }
-
-    public Button getBottomWide() {
-        return mBottomWide;
-    }
+    public Button bottomRightButton;
 
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-
-        mLayout = new LinearLayout(this);
-        mLayout.setOrientation(LinearLayout.VERTICAL);
-        mLayout.setHorizontalGravity(Gravity.LEFT);
-        mLayout.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.FILL_PARENT,
-                ViewGroup.LayoutParams.FILL_PARENT));
-
-        mTopWide = makeWide(mTopWideLable);
-        mLayout.addView(mTopWide);
-
-        mMidSkinny1Left = addSkinny(mLayout, mMidSkinny1LeftLable, false);
-
-        mMidSkinny2Right = addSkinny(mLayout,mMidSkinny2RightLable, true);
-
-        mBottomWide = makeWide(mBottomWideLable);
-        mLayout.addView(mBottomWide);
-
-        setContentView(mLayout);
+        setContentView(R.layout.focus_finder_layout);
+        layout = (ViewGroup) findViewById(R.id.layout);
+        topLeftButton = (Button) findViewById(R.id.top_left_button);
+        topRightButton = (Button) findViewById(R.id.top_right_button);
+        bottomLeftButton = (Button) findViewById(R.id.bottom_left_button);
+        bottomRightButton = (Button) findViewById(R.id.bottom_right_button);
     }
-
-    // just to get toString non-sucky
-    private static class MyButton extends Button {
-
-        public MyButton(Context context) {
-            super(context);
-        }
-
-        @Override
-        public String toString() {
-            return getText().toString();
-        }
-    }
-
-    private Button makeWide(String label) {
-        Button button = new MyButton(this);
-        button.setText(label);
-        button.setLayoutParams(new LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.FILL_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT));
-        return button;
-    }
-
-    /**
-     * Add a skinny button that takes up just less than half of the screen
-     * horizontally.
-     *
-     * @param root
-     *            The layout to add the button to.
-     * @param label
-     *            The label of the button.
-     * @param atRight
-     *            Which side to put the button on.
-     * @return The newly created button.
-     */
-    private Button addSkinny(LinearLayout root, String label, boolean atRight) {
-        Button button = new MyButton(this);
-        button.setText(label);
-        button.setLayoutParams(new LinearLayout.LayoutParams(0, // width
-                ViewGroup.LayoutParams.WRAP_CONTENT, 480));
-
-        TextView filler = new TextView(this);
-        filler.setText("filler");
-        filler.setLayoutParams(new LinearLayout.LayoutParams(0, // width
-                ViewGroup.LayoutParams.WRAP_CONTENT, 520));
-
-        LinearLayout ll = new LinearLayout(this);
-        ll.setOrientation(LinearLayout.HORIZONTAL);
-        ll.setLayoutParams(new LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.FILL_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT));
-
-        if (atRight) {
-            ll.addView(filler);
-            ll.addView(button);
-            root.addView(ll);
-        } else {
-            ll.addView(button);
-            ll.addView(filler);
-            root.addView(ll);
-        }
-        return button;
-    }
-
 }
-
diff --git a/tests/src/android/view/cts/TestButton.java b/tests/src/android/view/cts/TestButton.java
new file mode 100644
index 0000000..23b6ff0
--- /dev/null
+++ b/tests/src/android/view/cts/TestButton.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 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 android.view.cts;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+public class TestButton extends Button {
+
+    public TestButton(Context context) {
+        super(context);
+    }
+
+    public TestButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public TestButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public String toString() {
+        return getText().toString();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/FocusFinderTest.java b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
index 1d40a94..1c52ee6 100644
--- a/tests/tests/view/src/android/view/cts/FocusFinderTest.java
+++ b/tests/tests/view/src/android/view/cts/FocusFinderTest.java
@@ -16,26 +16,26 @@
 
 package android.view.cts;
 
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+
 import android.graphics.Rect;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.FocusFinder;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
-import android.widget.LinearLayout;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
 
 @TestTargetClass(FocusFinder.class)
 public class FocusFinderTest extends ActivityInstrumentationTestCase2<FocusFinderStubActivity> {
 
     private FocusFinder mFocusFinder;
-    private LinearLayout mLayout;
-    private Button mTopWide;
-    private Button mMidSkinny1Left;
-    private Button mMidSkinny2Right;
-    private Button mBottomWide;
+    private ViewGroup mLayout;
+    private Button mTopLeft;
+    private Button mTopRight;
+    private Button mBottomLeft;
+    private Button mBottomRight;
 
     public FocusFinderTest() {
         super("com.android.cts.stub", FocusFinderStubActivity.class);
@@ -45,11 +45,11 @@
     protected void setUp() throws Exception {
         super.setUp();
         mFocusFinder = FocusFinder.getInstance();
-        mLayout = getActivity().getLayout();
-        mTopWide = getActivity().getTopWide();
-        mMidSkinny1Left = getActivity().getMidSkinny1Left();
-        mMidSkinny2Right = getActivity().getMidSkinny2Right();
-        mBottomWide = getActivity().getBottomWide();
+        mLayout = getActivity().layout;
+        mTopLeft = getActivity().topLeftButton;
+        mTopRight = getActivity().topRightButton;
+        mBottomLeft = getActivity().bottomLeftButton;
+        mBottomRight = getActivity().bottomRightButton;
     }
 
     @TestTargetNew(
@@ -71,25 +71,29 @@
         args = {ViewGroup.class, View.class, int.class}
     )
     public void testFindNextFocus() {
-        Button view = (Button)mFocusFinder.findNextFocus(mLayout, mBottomWide, View.FOCUS_UP);
-        assertEquals(mMidSkinny2Right, view);
-        view = (Button)mFocusFinder.findNextFocus(mLayout, mMidSkinny2Right, View.FOCUS_LEFT);
-        assertEquals(mMidSkinny1Left, view);
-        view = (Button)mFocusFinder.findNextFocus(mLayout, mMidSkinny1Left, View.FOCUS_RIGHT);
-        assertEquals(mMidSkinny2Right, view);
-        view = (Button)mFocusFinder.findNextFocus(mLayout, mTopWide, View.FOCUS_DOWN);
-        assertEquals(mMidSkinny1Left, view);
-        view = (Button)mFocusFinder.findNextFocus(mLayout, null, View.FOCUS_DOWN);
-        assertEquals(mTopWide, view);
+        /*
+         * Go clockwise around the buttons from the top left searching for focus.
+         *
+         * +---+---+
+         * | 1 | 2 |
+         * +---+---+
+         * | 3 | 4 |
+         * +---+---+
+         */
+        assertNextFocus(mTopLeft, View.FOCUS_RIGHT, mTopRight);
+        assertNextFocus(mTopRight, View.FOCUS_DOWN, mBottomRight);
+        assertNextFocus(mBottomRight, View.FOCUS_LEFT, mBottomLeft);
+        assertNextFocus(mBottomLeft, View.FOCUS_UP, mTopLeft);
 
-        view = (Button)mFocusFinder.findNextFocus(mLayout, null, View.FOCUS_UP);
-        assertEquals(mBottomWide, view);
+        assertNextFocus(null, View.FOCUS_RIGHT, mTopLeft);
+        assertNextFocus(null, View.FOCUS_DOWN, mTopLeft);
+        assertNextFocus(null, View.FOCUS_LEFT, mBottomRight);
+        assertNextFocus(null, View.FOCUS_UP, mBottomRight);
+    }
 
-        view = (Button)mFocusFinder.findNextFocus(mLayout, null, View.FOCUS_LEFT);
-        assertEquals(mBottomWide, view);
-
-        view = (Button)mFocusFinder.findNextFocus(mLayout, null, View.FOCUS_RIGHT);
-        assertEquals(mTopWide, view);
+    private void assertNextFocus(View currentFocus, int direction, View expectedNextFocus) {
+        View actualNextFocus = mFocusFinder.findNextFocus(mLayout, currentFocus, direction);
+        assertEquals(expectedNextFocus, actualNextFocus);
     }
 
     @TestTargetNew(
@@ -99,20 +103,43 @@
         args = {ViewGroup.class, Rect.class, int.class}
     )
     public void testFindNextFocusFromRect() {
-        Rect mTempRect = new Rect();
-        mTempRect.set(0, mTopWide.getTop(), 0, mTopWide.getTop());
-        Button view = (Button)mFocusFinder.findNextFocusFromRect(mLayout, mTempRect,
-                View.FOCUS_DOWN);
-        assertEquals(mTopWide, view);
-        mTempRect.set(0, mBottomWide.getTop(), 0, mBottomWide.getTop());
-        view = (Button)mFocusFinder.findNextFocusFromRect(mLayout, mTempRect, View.FOCUS_UP);
-        assertEquals(mMidSkinny1Left, view);
-        mTempRect.set(mMidSkinny1Left.getRight(), 0, mMidSkinny1Left.getRight(), 0);
-        view = (Button)mFocusFinder.findNextFocusFromRect(mLayout, mTempRect, View.FOCUS_RIGHT);
-        assertEquals(mMidSkinny2Right, view);
-        mTempRect.set(mMidSkinny2Right.getLeft(), 0, mMidSkinny2Right.getLeft(), 0);
-        view = (Button)mFocusFinder.findNextFocusFromRect(mLayout, mTempRect, View.FOCUS_LEFT);
-        assertEquals(mMidSkinny1Left, view);
+        /*
+         * Create a small rectangle on the border between the top left and top right buttons.
+         *
+         * +---+---+
+         * |  [ ]  |
+         * +---+---+
+         * |   |   |
+         * +---+---+
+         */
+        Rect rect = new Rect();
+        mTopLeft.getDrawingRect(rect);
+        rect.offset(mTopLeft.getWidth() / 2, 0);
+        rect.inset(mTopLeft.getWidth() / 4, mTopLeft.getHeight() / 4);
+
+        assertNextFocusFromRect(rect, View.FOCUS_LEFT, mTopLeft);
+        assertNextFocusFromRect(rect, View.FOCUS_RIGHT, mTopRight);
+
+        /*
+         * Create a small rectangle on the border between the top left and bottom left buttons.
+         *
+         * +---+---+
+         * |   |   |
+         * +[ ]+---+
+         * |   |   |
+         * +---+---+
+         */
+        mTopLeft.getDrawingRect(rect);
+        rect.offset(0, mTopRight.getHeight() / 2);
+        rect.inset(mTopLeft.getWidth() / 4, mTopLeft.getHeight() / 4);
+
+        assertNextFocusFromRect(rect, View.FOCUS_UP, mTopLeft);
+        assertNextFocusFromRect(rect, View.FOCUS_DOWN, mBottomLeft);
+    }
+
+    private void assertNextFocusFromRect(Rect rect, int direction, View expectedNextFocus) {
+        View actualNextFocus = mFocusFinder.findNextFocusFromRect(mLayout, rect, direction);
+        assertEquals(expectedNextFocus, actualNextFocus);
     }
 
     @TestTargetNew(
@@ -122,38 +149,52 @@
         args = {ViewGroup.class, int.class, int.class, int.class, int[].class}
     )
     public void testFindNearestTouchable() {
+        /*
+         * Table layout with two rows and coordinates are relative to those parent rows.
+         * Lines outside the box signify touch points used in the tests.
+         *      |
+         *   +---+---+
+         *   | 1 | 2 |--
+         *   +---+---+
+         * --| 3 | 4 |
+         *   +---+---+
+         *         |
+         */
+
+        // 1
+        int x = mTopLeft.getWidth() / 2 - 5;
+        int y = 0;
         int[] deltas = new int[2];
-        int bound = 3;
-        int x = mTopWide.getLeft();
-        int y = mTopWide.getTop() - bound;
-        Button view = (Button)mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_DOWN,
-                deltas);
-        assertEquals(mTopWide, view);
+        View view = mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_DOWN, deltas);
+        assertEquals(mTopLeft, view);
         assertEquals(0, deltas[0]);
-        assertEquals(mTopWide.getTop(), deltas[1]);
-        deltas = new int[2];
-        x = mBottomWide.getLeft();
-        y = mBottomWide.getBottom() + bound;
-        view = (Button)mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_UP, deltas);
-        assertEquals(mBottomWide, view);
-        assertEquals(0, deltas[0]);
-        assertEquals(-(y - mBottomWide.getBottom() + 1), deltas[1]);
-
-        deltas = new int[2];
-        x = mMidSkinny1Left.getLeft() - bound;
-        y = mMidSkinny1Left.getTop();
-        view = (Button)mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_RIGHT, deltas);
-        assertEquals(mTopWide, view);
-        assertEquals(mMidSkinny1Left.getLeft(), deltas[0]);
         assertEquals(0, deltas[1]);
 
+        // 2
         deltas = new int[2];
-        x = mTopWide.getRight() + bound;
-        y = mTopWide.getBottom();
-        view = (Button)mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_LEFT, deltas);
-        assertEquals(mTopWide, view);
-        assertEquals(-(x - mTopWide.getRight() + 1), deltas[0]);
+        x = mTopRight.getRight();
+        y = mTopRight.getBottom() / 2;
+        view = mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_LEFT, deltas);
+        assertEquals(mTopRight, view);
+        assertEquals(-1, deltas[0]);
         assertEquals(0, deltas[1]);
+
+        // 3
+        deltas = new int[2];
+        x = 0;
+        y = mTopLeft.getBottom() + mBottomLeft.getHeight() / 2;
+        view = mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_RIGHT, deltas);
+        assertEquals(mBottomLeft, view);
+        assertEquals(0, deltas[0]);
+        assertEquals(0, deltas[1]);
+
+        // 4
+        deltas = new int[2];
+        x = mBottomRight.getRight();
+        y = mTopRight.getBottom() + mBottomRight.getBottom();
+        view = mFocusFinder.findNearestTouchable(mLayout, x, y, View.FOCUS_UP, deltas);
+        assertEquals(mBottomRight, view);
+        assertEquals(0, deltas[0]);
+        assertEquals(-1, deltas[1]);
     }
-
 }