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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user