From c6fd88e213703a581fe4680259981f09ae0444f2 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Wed, 25 Jan 2012 23:01:44 -0800 Subject: Incorrect behavior of View clear focus. The framework tries to have a focused view all the time. For that purpose when a view's focus is cleared the focus is given to the first focusable found from the top. The implementation of this behavior was causing the following issues: 1. If the fist focusable View tries to clear its focus it was getting focus but the onFocusChange callbacks were not properly invoked. Specifically, the onFocusChange for gaining focus was called first and then the same callback for clearing focus. Note that the callback for clearing focus is called when the View is already focused. Also note that at the end the View did not clear its focus, hence no focus change callbacks should be invoked. 2. If not the first focusable View tries to clear focus, the focus is given to another one but the callback for getting focus was called before the one for clearing, so client code may be mislead that there is more than one focused view at a time. 3. (Nit) The implementaion of clearFocus and unFocus in ViewGroup was calling the super implementaion when there is a focused child. Since there could be only one focused View, having a focused child means that the group is not focused and the call to the super implementation is not needed. 4. Added unit tests that verify the correct behavior, i.e. the focus of the first focused view cannot be cleared which means that no focus change callbacks are invoked. The callbacks should be called in expected order. Now the view focus clear precedes the view focus gain callback. However, in between is invoked the global focus change callback with the correct values. We may want to call that one after the View callbacks. If needed we can revisit this. Change-Id: Iee80baf5c75c82d3cda09679e4949483cad475f1 --- .../src/android/widget/focus/RequestFocus.java | 2 - .../src/android/widget/focus/RequestFocusTest.java | 161 ++++++++++++++++++++- 2 files changed, 154 insertions(+), 9 deletions(-) (limited to 'core/tests') diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocus.java b/core/tests/coretests/src/android/widget/focus/RequestFocus.java index af9ee17..21d762a 100644 --- a/core/tests/coretests/src/android/widget/focus/RequestFocus.java +++ b/core/tests/coretests/src/android/widget/focus/RequestFocus.java @@ -21,9 +21,7 @@ import com.android.frameworks.coretests.R; import android.app.Activity; import android.os.Bundle; import android.os.Handler; -import android.widget.LinearLayout; import android.widget.Button; -import android.view.View; /** * Exercises cases where elements of the UI are requestFocus()ed. diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java index a78b0c9..baf587e 100644 --- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java +++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java @@ -16,21 +16,27 @@ package android.widget.focus; -import android.widget.focus.RequestFocus; -import com.android.frameworks.coretests.R; - import android.os.Handler; -import android.test.ActivityInstrumentationTestCase; +import android.test.ActivityInstrumentationTestCase2; +import android.test.UiThreadTest; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; -import android.widget.Button; import android.util.AndroidRuntimeException; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; +import android.widget.Button; + +import com.android.frameworks.coretests.R; + +import java.util.ArrayList; +import java.util.List; /** * {@link RequestFocusTest} is set up to exercise cases where the views that * have focus become invisible or GONE. */ -public class RequestFocusTest extends ActivityInstrumentationTestCase { +public class RequestFocusTest extends ActivityInstrumentationTestCase2 { private Button mTopLeftButton; private Button mBottomLeftButton; @@ -39,7 +45,7 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase mInvocations = new ArrayList(); + + public CallbackOrderChecker(View clearingFocusView, View gainingFocusView) { + mClearingFocusView = clearingFocusView; + mGainingFocusView = gainingFocusView; + } + + @Override + public void onFocusChange(View view, boolean hasFocus) { + CallbackInvocation invocation = new CallbackInvocation( + "OnFocusChangeListener#onFocusChange", new Object[] {view, hasFocus}); + mInvocations.add(invocation); + } + + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + CallbackInvocation invocation = new CallbackInvocation( + "OnFocusChangeListener#onFocusChange", new Object[] {oldFocus, newFocus}); + mInvocations.add(invocation); + } + + public void verify() { + assertSame("All focus change callback should be invoked.", 3, mInvocations.size()); + assertInvioked("Callback for View clearing focus explected.", 0, + "OnFocusChangeListener#onFocusChange", + new Object[] {mClearingFocusView, false}); + assertInvioked("Callback for View global focus change explected.", 1, + "OnFocusChangeListener#onFocusChange", new Object[] {mClearingFocusView, + mGainingFocusView}); + assertInvioked("Callback for View gaining focus explected.", 2, + "OnFocusChangeListener#onFocusChange", new Object[] {mGainingFocusView, true}); + } + + private void assertInvioked(String message, int order, String methodName, + Object[] arguments) { + CallbackInvocation invocation = mInvocations.get(order); + assertEquals(message, methodName, invocation.mMethodName); + assertEquals(message, arguments[0], invocation.mArguments[0]); + assertEquals(message, arguments[1], invocation.mArguments[1]); + } + } } -- cgit v1.1