Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e169358d authored by ryanlwlin's avatar ryanlwlin
Browse files

Fix the race condition that cause windows cache stale

AccessibilityCache clear the windows cache when receivng
windows_change events. If the windows change events is received right
during  we fetch the data from system_server, the data might be stale but
still put in the cache.

To fix it, we record the timestamp of the event which presents the valid
cahe timestamp. If the timestamp of fetching data is earlier than the
timestamp of the accessibility event, then we will discard the data.

Bug: 205836014
Test: atest CtsAccessibilityServiceTestCases
      atest AccessibilityCacheTest
Change-Id: I2f1c264dc7af1fbb5876143ba177f84836065d2b
parent f322e418
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ package android.view.accessibility;
import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;

import android.os.Build;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongArray;
@@ -71,6 +72,11 @@ public class AccessibilityCache {

    private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
    private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
    /**
     * The event time of the {@link AccessibilityEvent} which presents the populated windows cache
     * before it is stale.
     */
    private long mValidWindowCacheTimeStamp = 0;

    private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
    private int mInputFocusWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@@ -97,13 +103,20 @@ public class AccessibilityCache {
     * The key of SparseArray is display ID.
     *
     * @param windowsOnAllDisplays The accessibility windows of all displays.
     * @param populationTimeStamp The timestamp from {@link SystemClock#uptimeMillis()} when the
     *                            client requests the data.
     */
    public void setWindowsOnAllDisplays(
            SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays) {
            SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays,
            long populationTimeStamp) {
        synchronized (mLock) {
            if (DEBUG) {
                Log.i(LOG_TAG, "Set windows");
            }
            if (mValidWindowCacheTimeStamp > populationTimeStamp) {
                // Discard the windows because it might be stale.
                return;
            }
            clearWindowCacheLocked();
            if (windowsOnAllDisplays == null) {
                return;
@@ -224,6 +237,7 @@ public class AccessibilityCache {
                } break;

                case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
                    mValidWindowCacheTimeStamp = event.getEventTime();
                    if (event.getWindowChanges()
                            == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
                        // Don't need to clear all cache. Unless the changes are related to
@@ -232,6 +246,7 @@ public class AccessibilityCache {
                        break;
                    }
                case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
                    mValidWindowCacheTimeStamp = event.getEventTime();
                    clear();
                } break;
            }
+3 −1
Original line number Diff line number Diff line
@@ -435,8 +435,10 @@ public final class AccessibilityInteractionClient
                    }
                }

                long populationTimeStamp;
                final long identityToken = Binder.clearCallingIdentity();
                try {
                    populationTimeStamp = SystemClock.uptimeMillis();
                    windows = connection.getWindows();
                } finally {
                    Binder.restoreCallingIdentity(identityToken);
@@ -446,7 +448,7 @@ public final class AccessibilityInteractionClient
                }
                if (windows != null) {
                    if (sAccessibilityCache != null) {
                        sAccessibilityCache.setWindowsOnAllDisplays(windows);
                        sAccessibilityCache.setWindowsOnAllDisplays(windows, populationTimeStamp);
                    }
                    return windows;
                }
+49 −2
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.os.SystemClock;
import android.util.SparseArray;
import android.view.Display;
import android.view.View;
@@ -299,7 +300,8 @@ public class AccessibilityCacheTest {
            SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
            allWindows.put(Display.DEFAULT_DISPLAY, windowsIn1);
            allWindows.put(SECONDARY_DISPLAY_ID, windowsIn2);
            mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
            final long populationTimeStamp = SystemClock.uptimeMillis();
            mAccessibilityCache.setWindowsOnAllDisplays(allWindows, populationTimeStamp);
            // Gets windows at default display.
            windowsOut1 = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
@@ -338,6 +340,46 @@ public class AccessibilityCacheTest {
        }
    }

    @Test
    public void setInvalidWindowsAfterWindowsChangedEvent_notInCache() {
        final AccessibilityEvent event = new AccessibilityEvent(
                AccessibilityEvent.TYPE_WINDOWS_CHANGED);
        final long eventTime = 1000L;
        event.setEventTime(eventTime);
        mAccessibilityCache.onAccessibilityEvent(event);

        final AccessibilityWindowInfo windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1,
                SPECIFIC_WINDOW_LAYER);
        List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1);
        setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn, eventTime - 10);

        try {
            assertNull(getWindowsByDisplay(Display.DEFAULT_DISPLAY));
        } finally {
            windowInfo1.recycle();
        }
    }

    @Test
    public void setInvalidWindowsAfterStateChangedEvent_notInCache() {
        final AccessibilityEvent event = new AccessibilityEvent(
                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
        final long eventTime = 1000L;
        event.setEventTime(eventTime);
        mAccessibilityCache.onAccessibilityEvent(event);

        final AccessibilityWindowInfo windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1,
                SPECIFIC_WINDOW_LAYER);
        List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1);
        setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn, eventTime - 10);

        try {
            assertNull(getWindowsByDisplay(Display.DEFAULT_DISPLAY));
        } finally {
            windowInfo1.recycle();
        }
    }

    @Test
    public void addWindowThenStateChangedEvent_noLongerInCache() {
        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
@@ -1063,9 +1105,14 @@ public class AccessibilityCacheTest {
    }

    private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows) {
        setWindowsByDisplay(displayId, windows, SystemClock.uptimeMillis());
    }

    private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows,
            long populationTimeStamp) {
        SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
        allWindows.put(displayId, windows);
        mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
        mAccessibilityCache.setWindowsOnAllDisplays(allWindows, populationTimeStamp);
    }

    private List<AccessibilityWindowInfo> getWindowsByDisplay(int displayId) {