summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/View.java5
-rw-r--r--core/java/android/view/ViewGroup.java19
-rw-r--r--core/java/android/view/ViewRootImpl.java35
-rw-r--r--core/tests/coretests/Android.mk2
-rw-r--r--core/tests/coretests/src/android/widget/focus/RequestFocus.java2
-rw-r--r--core/tests/coretests/src/android/widget/focus/RequestFocusTest.java105
6 files changed, 135 insertions, 33 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 87104f4..38f055f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3788,6 +3788,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onFocusChanged(false, 0, null);
refreshDrawableState();
+
+ // The view cleared focus and invoked the callbacks, so now is the
+ // time to give focus to the the first focusable from the top to
+ // ensure that the gain focus is announced after clear focus.
+ getRootView().requestFocus(FOCUS_FORWARD);
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index bc147ac..a2e85a3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -675,11 +675,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public void clearFocus() {
- super.clearFocus();
-
- // clear any child focus if it exists
- if (mFocused != null) {
+ if (DBG) {
+ System.out.println(this + " clearFocus()");
+ }
+ if (mFocused == null) {
+ super.clearFocus();
+ } else {
mFocused.clearFocus();
+ mFocused = null;
}
}
@@ -691,12 +694,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (DBG) {
System.out.println(this + " unFocus()");
}
-
- super.unFocus();
- if (mFocused != null) {
+ if (mFocused == null) {
+ super.unFocus();
+ } else {
mFocused.unFocus();
+ mFocused = null;
}
- mFocused = null;
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1c3bbfa..1930a5e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -174,6 +174,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
View mView;
View mFocusedView;
View mRealFocusedView; // this is not set to null in touch mode
+ View mOldFocusedView;
int mViewVisibility;
boolean mAppVisible = true;
int mOrigWindowType = -1;
@@ -2272,32 +2273,33 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void requestChildFocus(View child, View focused) {
checkThread();
- if (mFocusedView != focused) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused);
- scheduleTraversals();
+
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(TAG, "Request child focus: focus now " + focused);
}
+
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused);
+ scheduleTraversals();
+
mFocusedView = mRealFocusedView = focused;
- if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now "
- + mFocusedView);
}
public void clearChildFocus(View child) {
checkThread();
- View oldFocus = mFocusedView;
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(TAG, "Clearing child focus");
+ }
+
+ mOldFocusedView = mFocusedView;
- if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus");
- mFocusedView = mRealFocusedView = null;
- if (mView != null && !mView.hasFocus()) {
- // If a view gets the focus, the listener will be invoked from requestChildFocus()
- if (!mView.requestFocus(View.FOCUS_FORWARD)) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
- }
- } else if (oldFocus != null) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+ // Invoke the listener only if there is no view to take focus
+ if (focusSearch(null, View.FOCUS_FORWARD) == null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null);
}
- }
+ mFocusedView = mRealFocusedView = null;
+ }
public void focusableViewAvailable(View v) {
checkThread();
@@ -2770,6 +2772,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mView.unFocus();
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
mFocusedView = null;
+ mOldFocusedView = null;
return true;
}
}
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index b81f774..88f3f34 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \
$(call all-java-files-under, EnabledTestApp/src)
LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib mockwebserver guava
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib mockwebserver guava littlemock
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksCoreTests
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..f2eba23 100644
--- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
@@ -16,21 +16,28 @@
package android.widget.focus;
-import android.widget.focus.RequestFocus;
-import com.android.frameworks.coretests.R;
+import static com.google.testing.littlemock.LittleMock.inOrder;
+import static com.google.testing.littlemock.LittleMock.mock;
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 com.google.testing.littlemock.LittleMock.InOrder;
/**
* {@link RequestFocusTest} is set up to exercise cases where the views that
* have focus become invisible or GONE.
*/
-public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFocus> {
+public class RequestFocusTest extends ActivityInstrumentationTestCase2<RequestFocus> {
private Button mTopLeftButton;
private Button mBottomLeftButton;
@@ -39,7 +46,7 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc
private Handler mHandler;
public RequestFocusTest() {
- super("com.android.frameworks.coretests", RequestFocus.class);
+ super(RequestFocus.class);
}
@Override
@@ -94,4 +101,90 @@ public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFoc
e.getClass().getName());
}
}
+
+ /**
+ * This tests checks the case in which the first focusable View clears focus.
+ * In such a case the framework tries to give the focus to another View starting
+ * from the top. Hence, the framework will try to give focus to the view that
+ * wants to clear its focus.
+ *
+ * @throws Exception If an error occurs.
+ */
+ @UiThreadTest
+ public void testOnFocusChangeCallbackOrderWhenClearingFocusOfFirstFocusable()
+ throws Exception {
+ // Get the first focusable.
+ Button clearingFocusButton = mTopLeftButton;
+ Button gainingFocusButton = mTopLeftButton;
+
+ // Make sure that the clearing focus View is the first focusable.
+ View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null,
+ View.FOCUS_FORWARD);
+ assertSame("The clearing focus button is the first focusable.",
+ clearingFocusButton, focusCandidate);
+ assertSame("The gaining focus button is the first focusable.",
+ gainingFocusButton, focusCandidate);
+
+ // Focus the clearing focus button.
+ clearingFocusButton.requestFocus();
+ assertTrue(clearingFocusButton.hasFocus());
+
+ // Register the invocation order checker.
+ CombinedListeners mock = mock(CombinedListeners.class);
+ clearingFocusButton.setOnFocusChangeListener(mock);
+ gainingFocusButton.setOnFocusChangeListener(mock);
+ clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(mock);
+
+ // Try to clear focus.
+ clearingFocusButton.clearFocus();
+
+ // Check that no callback was invoked since focus did not move.
+ InOrder inOrder = inOrder(mock);
+ inOrder.verify(mock).onFocusChange(clearingFocusButton, false);
+ inOrder.verify(mock).onGlobalFocusChanged(clearingFocusButton, gainingFocusButton);
+ inOrder.verify(mock).onFocusChange(gainingFocusButton, true);
+ }
+
+ public interface CombinedListeners extends OnFocusChangeListener, OnGlobalFocusChangeListener {}
+
+ /**
+ * This tests check whether the on focus change callbacks are invoked in
+ * the proper order when a View loses focus and the framework gives it to
+ * the fist focusable one.
+ *
+ * @throws Exception
+ */
+ @UiThreadTest
+ public void testOnFocusChangeCallbackOrderWhenClearingFocusOfNotFirstFocusable()
+ throws Exception {
+ Button clearingFocusButton = mTopRightButton;
+ Button gainingFocusButton = mTopLeftButton;
+
+ // Make sure that the clearing focus View is not the first focusable.
+ View focusCandidate = clearingFocusButton.getRootView().getParent().focusSearch(null,
+ View.FOCUS_FORWARD);
+ assertNotSame("The clearing focus button is not the first focusable.",
+ clearingFocusButton, focusCandidate);
+ assertSame("The gaining focus button is the first focusable.",
+ gainingFocusButton, focusCandidate);
+
+ // Focus the clearing focus button.
+ clearingFocusButton.requestFocus();
+ assertTrue(clearingFocusButton.hasFocus());
+
+ // Register the invocation order checker.
+ CombinedListeners mock = mock(CombinedListeners.class);
+ clearingFocusButton.setOnFocusChangeListener(mock);
+ gainingFocusButton.setOnFocusChangeListener(mock);
+ clearingFocusButton.getViewTreeObserver().addOnGlobalFocusChangeListener(mock);
+
+ // Try to clear focus.
+ clearingFocusButton.clearFocus();
+
+ // Check that no callback was invoked since focus did not move.
+ InOrder inOrder = inOrder(mock);
+ inOrder.verify(mock).onFocusChange(clearingFocusButton, false);
+ inOrder.verify(mock).onGlobalFocusChanged(clearingFocusButton, gainingFocusButton);
+ inOrder.verify(mock).onFocusChange(gainingFocusButton, true);
+ }
}