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

Commit c27e56b3 authored by JW Wang's avatar JW Wang
Browse files

Allow #disconnect to be called safely on connection timeout (2/n)

Now #disconnect won't throw if the previous #connect failed due to
timeout. Note we also introduce generation id so we won't receive
notifications or modifications from the previous client to disrupt
UiAutomation's status when making the next connection.

Bug: 147785023
Test: m
Change-Id: Idf77207124494bd78770b8ea5d9ac4b1fd1a490a
parent bacec345
Loading
Loading
Loading
Loading
+75 −31
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import android.accessibilityservice.AccessibilityService.IAccessibilityServiceCl
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.TestApi;
@@ -60,6 +61,8 @@ import com.android.internal.util.function.pooled.PooledLambda;
import libcore.io.IoUtils;
import libcore.io.IoUtils;


import java.io.IOException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeoutException;
@@ -116,6 +119,28 @@ public final class UiAutomation {
    /** Rotation constant: Freeze rotation to 270 degrees . */
    /** Rotation constant: Freeze rotation to 270 degrees . */
    public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
    public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;


    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            ConnectionState.DISCONNECTED,
            ConnectionState.CONNECTING,
            ConnectionState.CONNECTED,
            ConnectionState.FAILED
    })
    private @interface ConnectionState {
        /** The initial state before {@link #connect} or after {@link #disconnect} is called. */
        int DISCONNECTED = 0;
        /**
         * The temporary state after {@link #connect} is called. Will transition to
         * {@link #CONNECTED} or {@link #FAILED} depending on whether {@link #connect} succeeds or
         * not.
         */
        int CONNECTING = 1;
        /** The state when {@link #connect} has succeeded. */
        int CONNECTED = 2;
        /** The state when {@link #connect} has failed. */
        int FAILED = 3;
    }

    /**
    /**
     * UiAutomation supresses accessibility services by default. This flag specifies that
     * UiAutomation supresses accessibility services by default. This flag specifies that
     * existing accessibility services should continue to run, and that new ones may start.
     * existing accessibility services should continue to run, and that new ones may start.
@@ -144,12 +169,14 @@ public final class UiAutomation {


    private long mLastEventTimeMillis;
    private long mLastEventTimeMillis;


    private boolean mIsConnecting;
    private @ConnectionState int mConnectionState = ConnectionState.DISCONNECTED;


    private boolean mIsDestroyed;
    private boolean mIsDestroyed;


    private int mFlags;
    private int mFlags;


    private int mGenerationId = 0;

    /**
    /**
     * Listener for observing the {@link AccessibilityEvent} stream.
     * Listener for observing the {@link AccessibilityEvent} stream.
     */
     */
@@ -250,13 +277,15 @@ public final class UiAutomation {
    public void connectWithTimeout(int flags, long timeoutMillis) throws TimeoutException {
    public void connectWithTimeout(int flags, long timeoutMillis) throws TimeoutException {
        synchronized (mLock) {
        synchronized (mLock) {
            throwIfConnectedLocked();
            throwIfConnectedLocked();
            if (mIsConnecting) {
            if (mConnectionState == ConnectionState.CONNECTING) {
                return;
                return;
            }
            }
            mIsConnecting = true;
            mConnectionState = ConnectionState.CONNECTING;
            mRemoteCallbackThread = new HandlerThread("UiAutomation");
            mRemoteCallbackThread = new HandlerThread("UiAutomation");
            mRemoteCallbackThread.start();
            mRemoteCallbackThread.start();
            mClient = new IAccessibilityServiceClientImpl(mRemoteCallbackThread.getLooper());
            // Increment the generation since we are about to interact with a new client
            mClient = new IAccessibilityServiceClientImpl(
                    mRemoteCallbackThread.getLooper(), ++mGenerationId);
        }
        }


        try {
        try {
@@ -269,14 +298,14 @@ public final class UiAutomation {


        synchronized (mLock) {
        synchronized (mLock) {
            final long startTimeMillis = SystemClock.uptimeMillis();
            final long startTimeMillis = SystemClock.uptimeMillis();
            try {
            while (true) {
            while (true) {
                    if (isConnectedLocked()) {
                if (mConnectionState == ConnectionState.CONNECTED) {
                    break;
                    break;
                }
                }
                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
                final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
                if (remainingTimeMillis <= 0) {
                if (remainingTimeMillis <= 0) {
                    mConnectionState = ConnectionState.FAILED;
                    throw new TimeoutException("Timeout while connecting " + this);
                    throw new TimeoutException("Timeout while connecting " + this);
                }
                }
                try {
                try {
@@ -285,9 +314,6 @@ public final class UiAutomation {
                    /* ignore */
                    /* ignore */
                }
                }
            }
            }
            } finally {
                mIsConnecting = false;
            }
        }
        }
    }
    }


@@ -310,12 +336,17 @@ public final class UiAutomation {
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public void disconnect() {
    public void disconnect() {
        synchronized (mLock) {
        synchronized (mLock) {
            if (mIsConnecting) {
            if (mConnectionState == ConnectionState.CONNECTING) {
                throw new IllegalStateException(
                throw new IllegalStateException(
                        "Cannot call disconnect() while connecting " + this);
                        "Cannot call disconnect() while connecting " + this);
            }
            }
            throwIfNotConnectedLocked();
            if (mConnectionState == ConnectionState.DISCONNECTED) {
                return;
            }
            mConnectionState = ConnectionState.DISCONNECTED;
            mConnectionId = CONNECTION_ID_UNDEFINED;
            mConnectionId = CONNECTION_ID_UNDEFINED;
            // Increment the generation so we no longer interact with the existing client
            ++mGenerationId;
        }
        }
        try {
        try {
            // Calling out without a lock held.
            // Calling out without a lock held.
@@ -1245,18 +1276,14 @@ public final class UiAutomation {
        return stringBuilder.toString();
        return stringBuilder.toString();
    }
    }


    private boolean isConnectedLocked() {
        return mConnectionId != CONNECTION_ID_UNDEFINED;
    }

    private void throwIfConnectedLocked() {
    private void throwIfConnectedLocked() {
        if (mConnectionId != CONNECTION_ID_UNDEFINED) {
        if (mConnectionState == ConnectionState.CONNECTED) {
            throw new IllegalStateException("UiAutomation not connected, " + this);
            throw new IllegalStateException("UiAutomation connected, " + this);
        }
        }
    }
    }


    private void throwIfNotConnectedLocked() {
    private void throwIfNotConnectedLocked() {
        if (!isConnectedLocked()) {
        if (mConnectionState != ConnectionState.CONNECTED) {
            throw new IllegalStateException("UiAutomation not connected, " + this);
            throw new IllegalStateException("UiAutomation not connected, " + this);
        }
        }
    }
    }
@@ -1273,11 +1300,25 @@ public final class UiAutomation {


    private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
    private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {


        public IAccessibilityServiceClientImpl(Looper looper) {
        public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
            super(null, looper, new Callbacks() {
            super(null, looper, new Callbacks() {
                private final int mGenerationId = generationId;
                /**
                 * True if UiAutomation doesn't interact with this client anymore.
                 * Used by methods below to stop sending notifications or changing members
                 * of {@link UiAutomation}.
                 */
                private boolean isGenerationChangedLocked() {
                    return mGenerationId != UiAutomation.this.mGenerationId;
                }

                @Override
                @Override
                public void init(int connectionId, IBinder windowToken) {
                public void init(int connectionId, IBinder windowToken) {
                    synchronized (mLock) {
                    synchronized (mLock) {
                        if (isGenerationChangedLocked()) {
                            return;
                        }
                        mConnectionState = ConnectionState.CONNECTED;
                        mConnectionId = connectionId;
                        mConnectionId = connectionId;
                        mLock.notifyAll();
                        mLock.notifyAll();
                    }
                    }
@@ -1311,6 +1352,9 @@ public final class UiAutomation {
                public void onAccessibilityEvent(AccessibilityEvent event) {
                public void onAccessibilityEvent(AccessibilityEvent event) {
                    final OnAccessibilityEventListener listener;
                    final OnAccessibilityEventListener listener;
                    synchronized (mLock) {
                    synchronized (mLock) {
                        if (isGenerationChangedLocked()) {
                            return;
                        }
                        mLastEventTimeMillis = event.getEventTime();
                        mLastEventTimeMillis = event.getEventTime();
                        if (mWaitingForEventDelivery) {
                        if (mWaitingForEventDelivery) {
                            mEventQueue.add(AccessibilityEvent.obtain(event));
                            mEventQueue.add(AccessibilityEvent.obtain(event));