Merge Android 14 QPR3 to AOSP main
Bug: 346855327
Merged-In: Ia7316092c90271d1d8347dd49abbd2247d8fec20
Change-Id: I766a8309ccca8b7ebfa6022c39842a98bc1980be
diff --git a/Android.bp b/Android.bp
index 856eec1..21f8b50 100644
--- a/Android.bp
+++ b/Android.bp
@@ -96,4 +96,6 @@
],
aaptflags: ["--extra-packages com.android.car.rotary"],
+ // TODO(b/319708040): re-enable use_resource_processor
+ use_resource_processor: false,
}
diff --git a/src/com/android/car/rotary/Navigator.java b/src/com/android/car/rotary/Navigator.java
index ccaf357..a77dd73 100644
--- a/src/com/android/car/rotary/Navigator.java
+++ b/src/com/android/car/rotary/Navigator.java
@@ -111,6 +111,11 @@
mSurfaceViewHelper.clearHostApp(packageName);
}
+ /** Returns whether it supports AAOS template apps. */
+ boolean supportTemplateApp() {
+ return mSurfaceViewHelper.supportTemplateApp();
+ }
+
/** Adds the package name of the client app. */
void addClientApp(@NonNull CharSequence clientAppPackageName) {
mSurfaceViewHelper.addClientApp(clientAppPackageName);
@@ -185,7 +190,8 @@
// area),
// 3. and nextCandidate is different from candidate (if sourceNode is the first
// focusable node in the window, searching backward will return sourceNode itself).
- if (nextCandidate != null && currentFocusArea.equals(candidateFocusArea)
+ if (nextCandidate != null && currentFocusArea != null
+ && currentFocusArea.equals(candidateFocusArea)
&& !Utils.isFocusParkingView(nextCandidate)
&& !nextCandidate.equals(candidate)) {
// We need to skip nextTargetNode if:
@@ -235,7 +241,6 @@
break;
}
}
- currentFocusArea.recycle();
candidate.recycle();
if (sourceNode.equals(target)) {
L.e("Wrap-around on the same node");
@@ -439,9 +444,27 @@
// If the current focus area is an explicit focus area, use its focus area bounds to find
// nudge target as usual. Otherwise, use the tailored bounds, which was added as the last
// element of the list in maybeAddImplicitFocusArea().
- Rect currentFocusAreaBounds = Utils.isFocusArea(currentFocusArea)
- ? Utils.getBoundsInScreen(currentFocusArea)
- : candidateFocusAreasBounds.get(candidateFocusAreasBounds.size() - 1);
+ Rect currentFocusAreaBounds;
+ if (Utils.isFocusArea(currentFocusArea)) {
+ currentFocusAreaBounds = Utils.getBoundsInScreen(currentFocusArea);
+ } else if (candidateFocusAreasBounds.size() > 0) {
+ currentFocusAreaBounds =
+ candidateFocusAreasBounds.get(candidateFocusAreasBounds.size() - 1);
+ } else {
+ // TODO(b/323112198): this should never happen, but let's try to recover from this.
+ L.e("currentFocusArea is an implicit focus area but not added to"
+ + " currentFocusAreaBounds");
+ L.d("sourceNode:" + sourceNode);
+ L.d("currentFocusArea:" + currentFocusArea);
+ AccessibilityNodeInfo root = getRoot(sourceNode);
+ Utils.printDescendants(root, Utils.LOG_INDENT);
+ Utils.recycleNode(root);
+
+ currentFocusArea.recycle();
+ currentFocusArea = getAncestorFocusArea(sourceNode);
+ currentFocusAreaBounds = Utils.getBoundsInScreen(currentFocusArea);
+ L.d("updated currentFocusArea:" + currentFocusArea);
+ }
if (currentWindow.getType() != TYPE_INPUT_METHOD
|| shouldNudgeOutOfIme(sourceNode, currentFocusArea, candidateFocusAreas,
@@ -972,7 +995,8 @@
};
AccessibilityNodeInfo result = mTreeTraverser.findNodeOrAncestor(node, isFocusAreaOrRoot);
if (result == null || !Utils.isFocusArea(result)) {
- L.w("Couldn't find ancestor focus area for given node: " + node);
+ L.w("Ancestor focus area for node " + node + " is not an explicit FocusArea: "
+ + result);
}
return result;
}
diff --git a/src/com/android/car/rotary/RotaryService.java b/src/com/android/car/rotary/RotaryService.java
index 899f4a3..c6a84bb 100644
--- a/src/com/android/car/rotary/RotaryService.java
+++ b/src/com/android/car/rotary/RotaryService.java
@@ -125,6 +125,8 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
@@ -564,6 +566,8 @@
@Nullable private InputMethodManager mInputMethodManager;
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
private final BroadcastReceiver mAppInstallUninstallReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -622,9 +626,16 @@
mRotaryInputMethod = res.getString(R.string.rotary_input_method);
mDefaultTouchInputMethod = res.getString(R.string.default_touch_input_method);
+ L.d("mRotaryInputMethod:" + mRotaryInputMethod + ", mDefaultTouchInputMethod:"
+ + mDefaultTouchInputMethod);
validateImeConfiguration(mDefaultTouchInputMethod);
mTouchInputMethod = mPrefs.getString(TOUCH_INPUT_METHOD_PREFIX
+ mUserManager.getUserName(), mDefaultTouchInputMethod);
+ if (mTouchInputMethod.isEmpty()) {
+ // Workaround for b/323013736.
+ L.e("mTouchInputMethod shouldn't be empty!");
+ mTouchInputMethod = mDefaultTouchInputMethod;
+ }
validateImeConfiguration(mTouchInputMethod);
if (mRotaryInputMethod != null && mRotaryInputMethod.equals(getCurrentIme())) {
@@ -1003,9 +1014,12 @@
// mTouchInputMethod and save it so we can switch back after switching to the rotary
// input method.
String inputMethod = getCurrentIme();
- if (inputMethod != null && !inputMethod.equals(mRotaryInputMethod)) {
+ L.d("Current IME changed to " + inputMethod);
+ if (!TextUtils.isEmpty(inputMethod) && !inputMethod.equals(mRotaryInputMethod)) {
mTouchInputMethod = inputMethod;
String userName = mUserManager.getUserName();
+ L.d("Save mTouchInputMethod(" + mTouchInputMethod + ") for user "
+ + userName);
mPrefs.edit()
.putString(TOUCH_INPUT_METHOD_PREFIX + userName, mTouchInputMethod)
.apply();
@@ -1249,6 +1263,11 @@
switch (mAfterScrollAction) {
case FOCUS_PREVIOUS:
case FOCUS_NEXT: {
+ if (mFocusedNode == null) {
+ // TODO(326013682): find out why mFocusedNode is null.
+ L.w("mFocusedNode is null after injecting scroll event");
+ break;
+ }
if (mFocusedNode.equals(sourceNode)) {
break;
}
@@ -2033,11 +2052,19 @@
private void onForegroundActivityChanged(@NonNull AccessibilityNodeInfo root,
@NonNull AccessibilityWindowInfo window,
@Nullable CharSequence packageName, @Nullable CharSequence className) {
- // If the foreground app is a client app, store its package name.
- AccessibilityNodeInfo surfaceView = mNavigator.findSurfaceViewInRoot(root);
- if (surfaceView != null) {
- mNavigator.addClientApp(surfaceView.getPackageName());
- surfaceView.recycle();
+ if (mNavigator.supportTemplateApp()) {
+ // Check if there is a SurfaceView node to decide whether the foreground app is an
+ // AAOS template app. This is done on background thread to avoid ANR (b/322324727).
+ // TODO: find a better way to solve this to avoid potential race condition.
+ mExecutor.execute(() -> {
+ // If the foreground app is a client app, store its package name.
+ AccessibilityNodeInfo surfaceView =
+ mNavigator.findSurfaceViewInRoot(root);
+ if (surfaceView != null) {
+ mNavigator.addClientApp(surfaceView.getPackageName());
+ surfaceView.recycle();
+ }
+ });
}
ComponentName newActivity = packageName != null && className != null
@@ -2109,6 +2136,7 @@
}
if (enable) {
mFocusedNode = Utils.refreshNode(mFocusedNode);
+ L.v("After refresh, mFocusedNode is " + mFocusedNode);
if (mFocusedNode == null) {
L.w("Failed to enter direct manipulation mode because mFocusedNode is no longer "
+ "in view tree.");
@@ -2258,6 +2286,7 @@
*/
private void refreshSavedNodes() {
mFocusedNode = Utils.refreshNode(mFocusedNode);
+ L.v("After refresh, mFocusedNode is " + mFocusedNode);
mEditNode = Utils.refreshNode(mEditNode);
mLastTouchedNode = Utils.refreshNode(mLastTouchedNode);
mFocusArea = Utils.refreshNode(mFocusArea);
@@ -2428,6 +2457,7 @@
*/
private void maybeClearFocusInCurrentWindow(@Nullable AccessibilityNodeInfo targetFocus) {
mFocusedNode = Utils.refreshNode(mFocusedNode);
+ L.v("After refresh, mFocusedNode is " + mFocusedNode);
if (mFocusedNode == null
// No need to clear focus if mFocusedNode is not focused. However, when it's a node
// in a WebView or ComposeView, its state might not be up to date,
@@ -2519,7 +2549,8 @@
fpv.recycle();
return true;
}
- boolean result = performFocusAction(fpv);
+ // Don't call performFocusAction(fpv) because it might cause infinite loop (b/322137915).
+ boolean result = fpv.performAction(ACTION_FOCUS);
if (!result) {
L.w("Failed to perform ACTION_FOCUS on " + fpv);
}
diff --git a/src/com/android/car/rotary/SurfaceViewHelper.java b/src/com/android/car/rotary/SurfaceViewHelper.java
index 6427896..484ce2d 100644
--- a/src/com/android/car/rotary/SurfaceViewHelper.java
+++ b/src/com/android/car/rotary/SurfaceViewHelper.java
@@ -78,6 +78,11 @@
}
}
+ /** Returns whether it supports AAOS template apps. */
+ boolean supportTemplateApp() {
+ return !TextUtils.isEmpty(mHostApp);
+ }
+
/** Adds the package name of the client app. */
void addClientApp(@NonNull CharSequence clientAppPackageName) {
mClientApps.add(clientAppPackageName);
diff --git a/src/com/android/car/rotary/Utils.java b/src/com/android/car/rotary/Utils.java
index c78a40e..3668839 100644
--- a/src/com/android/car/rotary/Utils.java
+++ b/src/com/android/car/rotary/Utils.java
@@ -76,6 +76,7 @@
static final String COMPOSE_VIEW_CLASS_NAME = "androidx.compose.ui.platform.ComposeView";
@VisibleForTesting
static final String SURFACE_VIEW_CLASS_NAME = SurfaceView.class.getName();
+ static final String LOG_INDENT = " ";
private static final int FIND_FOCUS_MAX_TRY_COUNT = 3;
@@ -471,4 +472,17 @@
L.e("Failed to find focused node in " + root);
return null;
}
+
+ /** Prints the node and its descendants. */
+ static void printDescendants(@Nullable AccessibilityNodeInfo root, String indent) {
+ if (root == null) {
+ return;
+ }
+ L.d(indent + root);
+ for (int i = 0; i < root.getChildCount(); i++) {
+ AccessibilityNodeInfo child = root.getChild(i);
+ printDescendants(child, indent + LOG_INDENT);
+ Utils.recycleNode(child);
+ }
+ }
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 08cad5a..46f8719 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -42,4 +42,6 @@
],
compile_multilib: "both",
+ // TODO(b/319708040): re-enable use_resource_processor
+ use_resource_processor: false,
}