Don't notify parents of views made focusable off UI thread

Clickable now implies focusable by default. While views are
only supposed to be modified on the UI thread, before O
this wasn't enforced when setting a view as clickable. Now,
it is enforced by virtue of clickable setting focusable for
auto-focusable views.

In order to prevent crashes, detect this situation for pre-O
target-sdks and skip notifying parents of a view made
focusable if:
- it was made focusable by the new auto-focusable mechanic.
- and this is occuring off the UI thread.

The net result in these cases will be a newly-focusable view
that won't immediately receive focus if it was supposed to
(though this will be exceedingly rare).

Bug: 34815147
Test: Verified that the crashing app in the bug (JWLibrary)
      doesn't crash after this change.
Change-Id: I90e9a082cb1e1a3fec75ae5159f2f2c8540ffdc2
This commit is contained in:
Evan Rosky
2017-02-07 17:09:29 -08:00
parent 3238f730bd
commit f038cb454b

View File

@@ -858,6 +858,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static boolean sHasFocusableExcludeAutoFocusable;
/**
* Prior to O, auto-focusable didn't exist and views marked as clickable weren't implicitly
* made focusable by default. As a result, apps could (incorrectly) change the clickable
* setting of views off the UI thread. Now that clickable can effect the focusable state,
* changing the clickable attribute off the UI thread will cause an exception (since changing
* the focusable state checks). In order to prevent apps from crashing, we will handle this
* specific case and just not notify parents on new focusables resulting from marking views
* clickable from outside the UI thread.
*/
private static boolean sAutoFocusableOffUIThreadWontNotifyParents;
/** @hide */
@IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
@Retention(RetentionPolicy.SOURCE)
@@ -4182,6 +4193,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
sHasFocusableExcludeAutoFocusable = targetSdkVersion < Build.VERSION_CODES.O;
sAutoFocusableOffUIThreadWontNotifyParents = targetSdkVersion < Build.VERSION_CODES.O;
sCompatibilityDone = true;
}
}
@@ -12112,6 +12125,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int privateFlags = mPrivateFlags;
// If focusable is auto, update the FOCUSABLE bit.
int focusableChangedByAuto = 0;
if (((mViewFlags & FOCUSABLE_AUTO) != 0)
&& (changed & (FOCUSABLE_MASK | CLICKABLE | FOCUSABLE_IN_TOUCH_MODE)) != 0) {
int newFocus = NOT_FOCUSABLE;
@@ -12121,8 +12135,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mViewFlags = (mViewFlags & ~FOCUSABLE_IN_TOUCH_MODE);
}
mViewFlags = (mViewFlags & ~FOCUSABLE) | newFocus;
int focusChanged = (old & FOCUSABLE) ^ (newFocus & FOCUSABLE);
changed = (changed & ~FOCUSABLE) | focusChanged;
focusableChangedByAuto = (old & FOCUSABLE) ^ (newFocus & FOCUSABLE);
changed = (changed & ~FOCUSABLE) | focusableChangedByAuto;
}
/* Check if the FOCUSABLE bit has changed */
@@ -12137,7 +12151,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Tell the view system that we are now available to take focus
* if no one else already has it.
*/
if (mParent != null) mParent.focusableViewAvailable(this);
if (mParent != null) {
ViewRootImpl viewRootImpl = getViewRootImpl();
if (!sAutoFocusableOffUIThreadWontNotifyParents
|| focusableChangedByAuto == 0
|| viewRootImpl == null
|| viewRootImpl.mThread == Thread.currentThread()) {
mParent.focusableViewAvailable(this);
}
}
}
}