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

Commit d116d7c7 authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

Fixing memory leaks in the accessiiblity layer.

1. AccessibilityInteractionConnections were removed from the
   AccessiiblityManagerService but their DeathRecipents were
   not unregistered, thus every removed interaction connection
   was essentially leaking. Such connection is registered in
   the system for every ViewRootImpl when accessiiblity is
   enabled and inregistered when disabled.

2. Every AccessibilityEvent and AccessiilbityEventInfo obtained
   from a widnow content querying accessibility service had a
   handle to a binder proxy over which to make queries. Hoewever,
   holding a proxy to a remote binder prevents the latter from
   being garbage collected. Therefore, now the events and infos
   have a connection id insteand and the hindden singleton
   AccessiiblityInteaction client via which queries are made
   has a registry with the connections. This class looks up
   the connection given its id before making an IPC. Now the
   connection is stored in one place and when an accessibility
   service is disconnected the system sets the connection to
   null so the binder object in the system process can be GCed.
   Note that before this change a bad implemented accessibility
   service could cache events or infos causing a leak in the
   system process. This should never happen.

3. SparseArray was not clearing the reference to the last moved
   element while garbage collecting thus causing a leak.

bug:5664337

Change-Id: Id397f614b026d43bd7b57bb7f8186bca5cdfcff9
parent 50b20425
Loading
Loading
Loading
Loading
+24 −9
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package android.accessibilityservice;

import com.android.internal.os.HandlerCaller;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
@@ -25,8 +23,11 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;

import com.android.internal.os.HandlerCaller;

/**
 * An accessibility service runs in the background and receives callbacks by the system
 * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
@@ -219,7 +220,7 @@ public abstract class AccessibilityService extends Service {

    private AccessibilityServiceInfo mInfo;

    IAccessibilityServiceConnection mConnection;
    private int mConnectionId;

    /**
     * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
@@ -264,9 +265,11 @@ public abstract class AccessibilityService extends Service {
     * AccessibilityManagerService.
     */
    private void sendServiceInfo() {
        if (mInfo != null && mConnection != null) {
        IAccessibilityServiceConnection connection =
            AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
        if (mInfo != null && connection != null) {
            try {
                mConnection.setServiceInfo(mInfo);
                connection.setServiceInfo(mInfo);
            } catch (RemoteException re) {
                Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
            }
@@ -302,8 +305,9 @@ public abstract class AccessibilityService extends Service {
            mCaller = new HandlerCaller(context, this);
        }

        public void setConnection(IAccessibilityServiceConnection connection) {
            Message message = mCaller.obtainMessageO(DO_SET_SET_CONNECTION, connection);
        public void setConnection(IAccessibilityServiceConnection connection, int connectionId) {
            Message message = mCaller.obtainMessageIO(DO_SET_SET_CONNECTION, connectionId,
                    connection);
            mCaller.sendMessage(message);
        }

@@ -330,8 +334,19 @@ public abstract class AccessibilityService extends Service {
                    mTarget.onInterrupt();
                    return;
                case DO_SET_SET_CONNECTION :
                    mConnection = ((IAccessibilityServiceConnection) message.obj);
                    final int connectionId = message.arg1;
                    IAccessibilityServiceConnection connection =
                        (IAccessibilityServiceConnection) message.obj;
                    if (connection != null) {
                        AccessibilityInteractionClient.getInstance().addConnection(connectionId,
                                connection);
                        mConnectionId = connectionId;
                        mTarget.onServiceConnected();
                    } else {
                        AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
                        mConnectionId = AccessibilityInteractionClient.NO_ID;
                        // TODO: Do we need a onServiceDisconnected callback?
                    }
                    return;
                default :
                    Log.w(LOG_TAG, "Unknown message type " + message.what);
+1 −1
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ import android.view.accessibility.AccessibilityEvent;
 */
 oneway interface IEventListener {

    void setConnection(in IAccessibilityServiceConnection connection);
    void setConnection(in IAccessibilityServiceConnection connection, int connectionId);

    void onAccessibilityEvent(in AccessibilityEvent event);

+1 −0
Original line number Diff line number Diff line
@@ -134,6 +134,7 @@ public class SparseArray<E> implements Cloneable {
                if (i != o) {
                    keys[o] = keys[i];
                    values[o] = val;
                    values[i] = null;
                }

                o++;
+4 −32
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.view.accessibility;

import android.accessibilityservice.IAccessibilityServiceConnection;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -589,24 +588,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
        mPackageName = event.mPackageName;
    }

    /**
     * Sets the connection for interacting with the AccessibilityManagerService.
     *
     * @param connection The connection.
     *
     * @hide
     */
    @Override
    public void setConnection(IAccessibilityServiceConnection connection) {
        super.setConnection(connection);
        List<AccessibilityRecord> records = mRecords;
        final int recordCount = records.size();
        for (int i = 0; i < recordCount; i++) {
            AccessibilityRecord record = records.get(i);
            record.setConnection(connection);
        }
    }

    /**
     * Sets if this instance is sealed.
     *
@@ -821,23 +802,19 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
     * @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
     */
    public void initFromParcel(Parcel parcel) {
        if (parcel.readInt() == 1) {
            mConnection = IAccessibilityServiceConnection.Stub.asInterface(
                    parcel.readStrongBinder());
        }
        setSealed(parcel.readInt() == 1);
        mSealed = (parcel.readInt() == 1);
        mEventType = parcel.readInt();
        mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
        mEventTime = parcel.readLong();
        mConnectionId = parcel.readInt();
        readAccessibilityRecordFromParcel(this, parcel);

        // Read the records.
        final int recordCount = parcel.readInt();
        for (int i = 0; i < recordCount; i++) {
            AccessibilityRecord record = AccessibilityRecord.obtain();
            // Do this to write the connection only once.
            record.setConnection(mConnection);
            readAccessibilityRecordFromParcel(record, parcel);
            record.mConnectionId = mConnectionId;
            mRecords.add(record);
        }
    }
@@ -875,16 +852,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
     * {@inheritDoc}
     */
    public void writeToParcel(Parcel parcel, int flags) {
        if (mConnection == null) {
            parcel.writeInt(0);
        } else {
            parcel.writeInt(1);
            parcel.writeStrongBinder(mConnection.asBinder());
        }
        parcel.writeInt(isSealed() ? 1 : 0);
        parcel.writeInt(mEventType);
        TextUtils.writeToParcel(mPackageName, parcel, 0);
        parcel.writeLong(mEventTime);
        parcel.writeInt(mConnectionId);
        writeAccessibilityRecordToParcel(this, parcel, flags);

        // Write the records.
+167 −72
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.graphics.Rect;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;

import java.util.Collections;
import java.util.List;
@@ -61,6 +63,12 @@ import java.util.concurrent.atomic.AtomicInteger;
public final class AccessibilityInteractionClient
        extends IAccessibilityInteractionConnectionCallback.Stub {

    public static final int NO_ID = -1;

    private static final String LOG_TAG = "AccessibilityInteractionClient";

    private static final boolean DEBUG = false;

    private static final long TIMEOUT_INTERACTION_MILLIS = 5000;

    private static final Object sStaticLock = new Object();
@@ -83,6 +91,9 @@ public final class AccessibilityInteractionClient

    private final Rect mTempBounds = new Rect();

    private final SparseArray<IAccessibilityServiceConnection> mConnectionCache =
        new SparseArray<IAccessibilityServiceConnection>();

    /**
     * @return The singleton of this class.
     */
@@ -111,15 +122,16 @@ public final class AccessibilityInteractionClient
    /**
     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
     *
     * @param connection A connection for interacting with the system.
     * @param connectionId The id of a connection for interacting with the system.
     * @param accessibilityWindowId A unique window id.
     * @param accessibilityViewId A unique View accessibility id.
     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
     */
    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
            IAccessibilityServiceConnection connection, int accessibilityWindowId,
            int accessibilityViewId) {
    public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
            int accessibilityWindowId, int accessibilityViewId) {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
                        accessibilityWindowId, accessibilityViewId, interactionId, this,
@@ -128,11 +140,19 @@ public final class AccessibilityInteractionClient
                if (windowScale > 0) {
                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                            interactionId);
                finalizeAccessibilityNodeInfo(info, connection, windowScale);
                    finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
                    return info;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            /* ignore */
            if (DEBUG) {
                Log.w(LOG_TAG, "Error while calling remote"
                        + " findAccessibilityNodeInfoByAccessibilityId", re);
            }
        }
        return null;
    }
@@ -141,25 +161,36 @@ public final class AccessibilityInteractionClient
     * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed
     * in the currently active window and starts from the root View in the window.
     *
     * @param connection A connection for interacting with the system.
     * @param connectionId The id of a connection for interacting with the system.
     * @param viewId The id of the view.
     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
     */
    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(
            IAccessibilityServiceConnection connection, int viewId) {
    public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int connectionId,
            int viewId) {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
            final float windowScale = connection.findAccessibilityNodeInfoByViewIdInActiveWindow(
                    viewId, interactionId, this, Thread.currentThread().getId());
                final float windowScale =
                    connection.findAccessibilityNodeInfoByViewIdInActiveWindow(viewId,
                            interactionId, this, Thread.currentThread().getId());
                // If the scale is zero the call has failed.
                if (windowScale > 0) {
                    AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
                            interactionId);
                finalizeAccessibilityNodeInfo(info, connection, windowScale);
                    finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
                    return info;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            /* ignore */
            if (DEBUG) {
                Log.w(LOG_TAG, "Error while calling remote"
                        + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
            }
        }
        return null;
    }
@@ -169,25 +200,36 @@ public final class AccessibilityInteractionClient
     * insensitive containment. The search is performed in the currently
     * active window and starts from the root View in the window.
     *
     * @param connection A connection for interacting with the system.
     * @param connectionId The id of a connection for interacting with the system.
     * @param text The searched text.
     * @return A list of found {@link AccessibilityNodeInfo}s.
     */
    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow(
            IAccessibilityServiceConnection connection, String text) {
            int connectionId, String text) {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
            final float windowScale = connection.findAccessibilityNodeInfosByViewTextInActiveWindow(
                    text, interactionId, this, Thread.currentThread().getId());
                final float windowScale =
                    connection.findAccessibilityNodeInfosByViewTextInActiveWindow(text,
                            interactionId, this, Thread.currentThread().getId());
                // If the scale is zero the call has failed.
                if (windowScale > 0) {
                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                            interactionId);
                finalizeAccessibilityNodeInfos(infos, connection, windowScale);
                    finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
                    return infos;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            /* ignore */
            if (DEBUG) {
                Log.w(LOG_TAG, "Error while calling remote"
                        + " findAccessibilityNodeInfosByViewTextInActiveWindow", re);
            }
        }
        return null;
    }
@@ -198,17 +240,18 @@ public final class AccessibilityInteractionClient
     * id is specified and starts from the View whose accessibility id is
     * specified.
     *
     * @param connection A connection for interacting with the system.
     * @param connectionId The id of a connection for interacting with the system.
     * @param text The searched text.
     * @param accessibilityWindowId A unique window id.
     * @param accessibilityViewId A unique View accessibility id from where to start the search.
     *        Use {@link android.view.View#NO_ID} to start from the root.
     * @return A list of found {@link AccessibilityNodeInfo}s.
     */
    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(
            IAccessibilityServiceConnection connection, String text, int accessibilityWindowId,
            int accessibilityViewId) {
    public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(int connectionId,
            String text, int accessibilityWindowId, int accessibilityViewId) {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final float windowScale = connection.findAccessibilityNodeInfosByViewText(text,
                        accessibilityWindowId, accessibilityViewId, interactionId, this,
@@ -217,11 +260,19 @@ public final class AccessibilityInteractionClient
                if (windowScale > 0) {
                    List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
                            interactionId);
                finalizeAccessibilityNodeInfos(infos, connection, windowScale);
                    finalizeAccessibilityNodeInfos(infos, connectionId, windowScale);
                    return infos;
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            /* ignore */
            if (DEBUG) {
                Log.w(LOG_TAG, "Error while calling remote"
                        + " findAccessibilityNodeInfosByViewText", re);
            }
        }
        return Collections.emptyList();
    }
@@ -229,15 +280,17 @@ public final class AccessibilityInteractionClient
    /**
     * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
     *
     * @param connection A connection for interacting with the system.
     * @param connectionId The id of a connection for interacting with the system.
     * @param accessibilityWindowId The id of the window.
     * @param accessibilityViewId A unique View accessibility id.
     * @param action The action to perform.
     * @return Whether the action was performed.
     */
    public boolean performAccessibilityAction(IAccessibilityServiceConnection connection,
            int accessibilityWindowId, int accessibilityViewId, int action) {
    public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId,
            int accessibilityViewId, int action) {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final boolean success = connection.performAccessibilityAction(
                        accessibilityWindowId, accessibilityViewId, action, interactionId, this,
@@ -245,8 +298,15 @@ public final class AccessibilityInteractionClient
                if (success) {
                    return getPerformAccessibilityActionResult(interactionId);
                }
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            /* ignore */
            if (DEBUG) {
                Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
            }
        }
        return false;
    }
@@ -406,14 +466,14 @@ public final class AccessibilityInteractionClient
     * Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
     *
     * @param info The info.
     * @param connection The current connection to the system.
     * @param connectionId The id of the connection to the system.
     * @param windowScale The source window compatibility scale.
     */
    private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info,
            IAccessibilityServiceConnection connection, float windowScale) {
    private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
            float windowScale) {
        if (info != null) {
            applyCompatibilityScaleIfNeeded(info, windowScale);
            info.setConnection(connection);
            info.setConnectionId(connectionId);
            info.setSealed(true);
        }
    }
@@ -422,16 +482,16 @@ public final class AccessibilityInteractionClient
     * Finalize {@link AccessibilityNodeInfo}s before passing them to the client.
     *
     * @param infos The {@link AccessibilityNodeInfo}s.
     * @param connection The current connection to the system.
     * @param connectionId The id of the connection to the system.
     * @param windowScale The source window compatibility scale.
     */
    private void finalizeAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
            IAccessibilityServiceConnection connection, float windowScale) {
            int connectionId, float windowScale) {
        if (infos != null) {
            final int infosCount = infos.size();
            for (int i = 0; i < infosCount; i++) {
                AccessibilityNodeInfo info = infos.get(i);
                finalizeAccessibilityNodeInfo(info, connection, windowScale);
                finalizeAccessibilityNodeInfo(info, connectionId, windowScale);
            }
        }
    }
@@ -449,4 +509,39 @@ public final class AccessibilityInteractionClient
            return result;
        }
    }

    /**
     * Gets a cached accessibility service connection.
     *
     * @param connectionId The connection id.
     * @return The cached connection if such.
     */
    public IAccessibilityServiceConnection getConnection(int connectionId) {
        synchronized (mConnectionCache) {
            return mConnectionCache.get(connectionId);
        }
    }

    /**
     * Adds a cached accessibility service connection.
     *
     * @param connectionId The connection id.
     * @param connection The connection.
     */
    public void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
        synchronized (mConnectionCache) {
            mConnectionCache.put(connectionId, connection);
        }
    }

    /**
     * Removes a cached accessibility service connection.
     *
     * @param connectionId The connection id.
     */
    public void removeConnection(int connectionId) {
        synchronized (mConnectionCache) {
            mConnectionCache.remove(connectionId);
        }
    }
}
Loading