/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view.accessibility;

import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;

import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.RemoteAction;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import android.view.IWindow;
import android.view.SurfaceControl;
import android.view.View;
import android.view.accessibility.AccessibilityEvent.EventType;

import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
 * and provides facilities for querying the accessibility state of the system.
 * Accessibility events are generated when something notable happens in the user interface,
 * for example an {@link android.app.Activity} starts, the focus or selection of a
 * {@link android.view.View} changes etc. Parties interested in handling accessibility
 * events implement and register an accessibility service which extends
 * {@link android.accessibilityservice.AccessibilityService}.
 *
 * @see AccessibilityEvent
 * @see AccessibilityNodeInfo
 * @see android.accessibilityservice.AccessibilityService
 * @see Context#getSystemService
 * @see Context#ACCESSIBILITY_SERVICE
 */
@SystemService(Context.ACCESSIBILITY_SERVICE)
public final class AccessibilityManager {
    private static final boolean DEBUG = false;

    private static final String LOG_TAG = "AccessibilityManager";

    /** @hide */
    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 1 /* << 0 */;

    /** @hide */
    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 1 << 1;

    /** @hide */
    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 1 << 2;

    /** @hide */
    public static final int STATE_FLAG_DISPATCH_DOUBLE_TAP = 1 << 3;

    /** @hide */
    public static final int STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES = 1 << 4;

    /** @hide */
    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED = 1 << 8;
    /** @hide */
    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED = 1 << 9;
    /** @hide */
    public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 1 << 10;
    /** @hide */
    public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 1 << 11;
    /** @hide */
    public static final int STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED = 1 << 12;

    /** @hide */
    public static final int DALTONIZER_DISABLED = -1;

    /** @hide */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;

    /** @hide */
    public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;

    /** @hide */
    public static final int AUTOCLICK_DELAY_DEFAULT = 600;

    /**
     * Activity action: Launch UI to manage which accessibility service or feature is assigned
     * to the navigation bar Accessibility button.
     * <p>
     * Input: Nothing.
     * </p>
     * <p>
     * Output: Nothing.
     * </p>
     *
     * @hide
     */
    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
            "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";

    /** @hide */
    public static final int FLASH_REASON_CALL = 1;

    /** @hide */
    public static final int FLASH_REASON_ALARM = 2;

    /** @hide */
    public static final int FLASH_REASON_NOTIFICATION = 3;

    /** @hide */
    public static final int FLASH_REASON_PREVIEW = 4;

    /**
     * Annotations for content flag of UI.
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, prefix = { "FLAG_CONTENT_" }, value = {
            FLAG_CONTENT_ICONS,
            FLAG_CONTENT_TEXT,
            FLAG_CONTENT_CONTROLS
    })
    public @interface ContentFlag {}

    /**
     * Annotations for reason of Flash notification.
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "FLASH_REASON_" }, value = {
            FLASH_REASON_CALL,
            FLASH_REASON_ALARM,
            FLASH_REASON_NOTIFICATION,
            FLASH_REASON_PREVIEW
    })
    public @interface FlashNotificationReason {}

    /**
     * Use this flag to indicate the content of a UI that times out contains icons.
     *
     * @see #getRecommendedTimeoutMillis(int, int)
     */
    public static final int FLAG_CONTENT_ICONS = 1;

    /**
     * Use this flag to indicate the content of a UI that times out contains text.
     *
     * @see #getRecommendedTimeoutMillis(int, int)
     */
    public static final int FLAG_CONTENT_TEXT = 2;

    /**
     * Use this flag to indicate the content of a UI that times out contains interactive controls.
     *
     * @see #getRecommendedTimeoutMillis(int, int)
     */
    public static final int FLAG_CONTENT_CONTROLS = 4;

    @UnsupportedAppUsage
    static final Object sInstanceSync = new Object();

    @UnsupportedAppUsage
    private static AccessibilityManager sInstance;

    @UnsupportedAppUsage
    private final Object mLock = new Object();

    @UnsupportedAppUsage
    private IAccessibilityManager mService;

    @UnsupportedAppUsage
    final int mUserId;

    @UnsupportedAppUsage
    final Handler mHandler;

    final Handler.Callback mCallback;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    boolean mIsEnabled;

    int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;

    int mInteractiveUiTimeout;
    int mNonInteractiveUiTimeout;

    boolean mIsTouchExplorationEnabled;

    @UnsupportedAppUsage(trackingBug = 123768939L)
    boolean mIsHighTextContrastEnabled;

    boolean mIsAudioDescriptionByDefaultRequested;

    // accessibility tracing state
    int mAccessibilityTracingState = 0;

    AccessibilityPolicy mAccessibilityPolicy;

    private int mPerformingAction = 0;

    /** The stroke width of the focus rectangle in pixels */
    private int mFocusStrokeWidth;
    /** The color of the focus rectangle */
    private int mFocusColor;

    @UnsupportedAppUsage
    private final ArrayMap<AccessibilityStateChangeListener, Handler>
            mAccessibilityStateChangeListeners = new ArrayMap<>();

    private final ArrayMap<TouchExplorationStateChangeListener, Handler>
            mTouchExplorationStateChangeListeners = new ArrayMap<>();

    private final ArrayMap<HighTextContrastChangeListener, Handler>
            mHighTextContrastStateChangeListeners = new ArrayMap<>();

    private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
            mServicesStateChangeListeners = new ArrayMap<>();

    private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
            mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();

    private boolean mRequestFromAccessibilityTool;

    /**
     * Map from a view's accessibility id to the list of request preparers set for that view
     */
    private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;

    /**
     * Binder for flash notification.
     *
     * @see #startFlashNotificationSequence(Context, int)
     */
    private final Binder mBinder = new Binder();

    /**
     * Listener for the system accessibility state. To listen for changes to the
     * accessibility state on the device, implement this interface and register
     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
     */
    public interface AccessibilityStateChangeListener {

        /**
         * Called when the accessibility enabled state changes.
         *
         * @param enabled Whether accessibility is enabled.
         */
        void onAccessibilityStateChanged(boolean enabled);
    }

    /**
     * Listener for the system touch exploration state. To listen for changes to
     * the touch exploration state on the device, implement this interface and
     * register it with the system by calling
     * {@link #addTouchExplorationStateChangeListener}.
     */
    public interface TouchExplorationStateChangeListener {

        /**
         * Called when the touch exploration enabled state changes.
         *
         * @param enabled Whether touch exploration is enabled.
         */
        void onTouchExplorationStateChanged(boolean enabled);
    }

    /**
     * Listener for changes to the state of accessibility services.
     *
     * <p>
     * This refers to changes to {@link AccessibilityServiceInfo}, including:
     * <ul>
     *     <li>Whenever a service is enabled or disabled, or its info has been set or removed.</li>
     *     <li>Whenever a metadata attribute of any running service's info changes.</li>
     * </ul>
     *
     * @see #getEnabledAccessibilityServiceList for a list of infos of the enabled accessibility
     * services.
     * @see #addAccessibilityServicesStateChangeListener
     *
     */
    public interface AccessibilityServicesStateChangeListener {

        /**
         * Called when the state of accessibility services changes.
         *
         * @param manager The manager that is calling back
         */
        void onAccessibilityServicesStateChanged(@NonNull  AccessibilityManager manager);
    }

    /**
     * Listener for the system high text contrast state. To listen for changes to
     * the high text contrast state on the device, implement this interface and
     * register it with the system by calling
     * {@link #addHighTextContrastStateChangeListener}.
     *
     * @hide
     */
    public interface HighTextContrastChangeListener {

        /**
         * Called when the high text contrast enabled state changes.
         *
         * @param enabled Whether high text contrast is enabled.
         */
        void onHighTextContrastStateChanged(boolean enabled);
    }

    /**
     * Listener for the audio description by default state. To listen for
     * changes to the audio description by default state on the device,
     * implement this interface and register it with the system by calling
     * {@link #addAudioDescriptionRequestedChangeListener}.
     */
    public interface AudioDescriptionRequestedChangeListener {
        /**
         * Called when the audio description enabled state changes.
         *
         * @param enabled Whether audio description by default is enabled.
         */
        void onAudioDescriptionRequestedChanged(boolean enabled);
    }

    /**
     * Policy to inject behavior into the accessibility manager.
     *
     * @hide
     */
    public interface AccessibilityPolicy {
        /**
         * Checks whether accessibility is enabled.
         *
         * @param accessibilityEnabled Whether the accessibility layer is enabled.
         * @return whether accessibility is enabled.
         */
        boolean isEnabled(boolean accessibilityEnabled);

        /**
         * Notifies the policy for an accessibility event.
         *
         * @param event The event.
         * @param accessibilityEnabled Whether the accessibility layer is enabled.
         * @param relevantEventTypes The events relevant events.
         * @return The event to dispatch or null.
         */
        @Nullable AccessibilityEvent onAccessibilityEvent(@NonNull AccessibilityEvent event,
                boolean accessibilityEnabled, @EventType int relevantEventTypes);

        /**
         * Gets the list of relevant events.
         *
         * @param relevantEventTypes The relevant events.
         * @return The relevant events to report.
         */
        @EventType int getRelevantEventTypes(@EventType int relevantEventTypes);

        /**
         * Gets the list of installed services to report.
         *
         * @param installedService The installed services.
         * @return The services to report.
         */
        @NonNull List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
                @Nullable List<AccessibilityServiceInfo> installedService);

        /**
         * Gets the list of enabled accessibility services.
         *
         * @param feedbackTypeFlags The feedback type to query for.
         * @param enabledService The enabled services.
         * @return The services to report.
         */
        @Nullable List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
                @FeedbackType int feedbackTypeFlags,
                @Nullable List<AccessibilityServiceInfo> enabledService);
    }

    private final IAccessibilityManagerClient.Stub mClient =
            new IAccessibilityManagerClient.Stub() {
        @Override
        public void setState(int state) {
            // We do not want to change this immediately as the application may
            // have already checked that accessibility is on and fired an event,
            // that is now propagating up the view tree, Hence, if accessibility
            // is now off an exception will be thrown. We want to have the exception
            // enforcement to guard against apps that fire unnecessary accessibility
            // events when accessibility is off.
            mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
        }

        @Override
        public void notifyServicesStateChanged(long updatedUiTimeout) {
            updateUiTimeout(updatedUiTimeout);

            final ArrayMap<AccessibilityServicesStateChangeListener, Executor> listeners;
            synchronized (mLock) {
                if (mServicesStateChangeListeners.isEmpty()) {
                    return;
                }
                listeners = new ArrayMap<>(mServicesStateChangeListeners);
            }

            int numListeners = listeners.size();
            for (int i = 0; i < numListeners; i++) {
                final AccessibilityServicesStateChangeListener listener =
                        mServicesStateChangeListeners.keyAt(i);
                mServicesStateChangeListeners.valueAt(i).execute(() -> listener
                        .onAccessibilityServicesStateChanged(AccessibilityManager.this));
            }
        }

        @Override
        public void setRelevantEventTypes(int eventTypes) {
            mRelevantEventTypes = eventTypes;
        }

        @Override
        public void setFocusAppearance(int strokeWidth, int color) {
            synchronized (mLock) {
                updateFocusAppearanceLocked(strokeWidth, color);
            }
        }
    };

    /**
     * Get an AccessibilityManager instance (create one if necessary).
     *
     * @param context Context in which this manager operates.
     *
     * @hide
     */
    @UnsupportedAppUsage
    public static AccessibilityManager getInstance(Context context) {
        synchronized (sInstanceSync) {
            if (sInstance == null) {
                final int userId;
                if (Binder.getCallingUid() == Process.SYSTEM_UID
                        || context.checkCallingOrSelfPermission(
                                Manifest.permission.INTERACT_ACROSS_USERS)
                                        == PackageManager.PERMISSION_GRANTED
                        || context.checkCallingOrSelfPermission(
                                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                                        == PackageManager.PERMISSION_GRANTED) {
                    userId = UserHandle.USER_CURRENT;
                } else {
                    userId = context.getUserId();
                }
                sInstance = new AccessibilityManager(context, null, userId);
            }
        }
        return sInstance;
    }

    /**
     * Create an instance.
     *
     * @param context A {@link Context}.
     * @param service An interface to the backing service.
     * @param userId User id under which to run.
     *
     * @hide
     */
    public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
        // Constructor can't be chained because we can't create an instance of an inner class
        // before calling another constructor.
        mCallback = new MyCallback();
        mHandler = new Handler(context.getMainLooper(), mCallback);
        mUserId = userId;
        synchronized (mLock) {
            initialFocusAppearanceLocked(context.getResources());
            tryConnectToServiceLocked(service);
        }
    }

    /**
     * Create an instance.
     *
     * @param context A {@link Context}.
     * @param handler The handler to use
     * @param service An interface to the backing service.
     * @param userId User id under which to run.
     * @param serviceConnect {@code true} to connect the service or
     *                       {@code false} not to connect the service.
     *
     * @hide
     */
    @VisibleForTesting
    public AccessibilityManager(Context context, Handler handler, IAccessibilityManager service,
            int userId, boolean serviceConnect) {
        mCallback = new MyCallback();
        mHandler = handler;
        mUserId = userId;
        synchronized (mLock) {
            initialFocusAppearanceLocked(context.getResources());
            if (serviceConnect) {
                tryConnectToServiceLocked(service);
            }
        }
    }

    /**
     * @hide
     */
    public IAccessibilityManagerClient getClient() {
        return mClient;
    }

    /**
     * Unregisters the IAccessibilityManagerClient from the backing service
     * @hide
     */
    public boolean removeClient() {
        synchronized (mLock) {
            IAccessibilityManager service = getServiceLocked();
            if (service == null) {
                return false;
            }
            try {
                return service.removeClient(mClient, mUserId);
            } catch (RemoteException re) {
                Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
            }
        }
        return false;
    }

    /**
     * @hide
     */
    @VisibleForTesting
    public Handler.Callback getCallback() {
        return mCallback;
    }

    /**
     * Returns if the accessibility in the system is enabled.
     * <p>
     * <b>Note:</b> This query is used for sending {@link AccessibilityEvent}s, since events are
     * only needed if accessibility is on. Avoid changing UI or app behavior based on the state of
     * accessibility. While well-intentioned, doing this creates brittle, less
     * well-maintained code that works for some users but not others. Shared code leads to more
     * equitable experiences and less technical debt.
     *
     *<p>
     * For example, if you want to expose a unique interaction with your app, use
     * ViewCompat#addAccessibilityAction in AndroidX to make this interaction - ideally
     * with the same code path used for non-accessibility users - available to accessibility
     * services. Services can then expose this action in the way best fit for their users.
     *
     * @return True if accessibility is enabled, false otherwise.
     */
    public boolean isEnabled() {
        synchronized (mLock) {
            return mIsEnabled || hasAnyDirectConnection()
                    || (mAccessibilityPolicy != null && mAccessibilityPolicy.isEnabled(mIsEnabled));
        }
    }

    /**
     * @see AccessibilityInteractionClient#hasAnyDirectConnection
     * @hide
     */
    @TestApi
    public boolean hasAnyDirectConnection() {
        return AccessibilityInteractionClient.hasAnyDirectConnection();
    }

    /**
     * Returns if the touch exploration in the system is enabled.
     * <p>
     * <b>Note:</b> This query is used for dispatching hover events, such as
     * {@link android.view.MotionEvent#ACTION_HOVER_ENTER}, to accessibility services to manage
     * touch exploration. Avoid changing UI or app behavior based on the state of accessibility.
     * While well-intentioned, doing this creates brittle, less well-maintained code that works for
     * som users but not others. Shared code leads to more equitable experiences and less technical
     * debt.
     *
     * @return True if touch exploration is enabled, false otherwise.
     */
    public boolean isTouchExplorationEnabled() {
        synchronized (mLock) {
            IAccessibilityManager service = getServiceLocked();
            if (service == null) {
                return false;
            }
            return mIsTouchExplorationEnabled;
        }
    }

    /**
     * Returns if the high text contrast in the system is enabled.
     * <p>
     * <strong>Note:</strong> You need to query this only if you application is
     * doing its own rendering and does not rely on the platform rendering pipeline.
     * </p>
     *
     * @return True if high text contrast is enabled, false otherwise.
     *
     * @hide
     */
    @UnsupportedAppUsage
    public boolean isHighTextContrastEnabled() {
        synchronized (mLock) {
            IAccessibilityManager service = getServiceLocked();
            if (service == null) {
                return false;
            }
            return mIsHighTextContrastEnabled;
        }
    }

    /**
     * Sends an {@link AccessibilityEvent}.
     *
     * @param event The event to send.
     *
     * @throws IllegalStateException if accessibility is not enabled.
     *
     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
     * events is through calling
     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
     * instead of this method to allow predecessors to augment/filter events sent by
     * their descendants.
     */
    public void sendAccessibilityEvent(AccessibilityEvent event) {
        final IAccessibilityManager service;
        final int userId;
        final AccessibilityEvent dispatchedEvent;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
            event.setEventTime(SystemClock.uptimeMillis());
            if (event.getAction() == 0) {
                event.setAction(mPerformingAction);
            }
            if (mAccessibilityPolicy != null) {
                dispatchedEvent = mAccessibilityPolicy.onAccessibilityEvent(event,
                        mIsEnabled, mRelevantEventTypes);
                if (dispatchedEvent == null) {
                    return;
                }
            } else {
                dispatchedEvent = event;
            }
            if (!isEnabled()) {
                Looper myLooper = Looper.myLooper();
                if (myLooper == Looper.getMainLooper()) {
                    throw new IllegalStateException(
                            "Accessibility off. Did you forget to check that?");
                } else {
                    // If we're not running on the thread with the main looper, it's possible for
                    // the state of accessibility to change between checking isEnabled and
                    // calling this method. So just log the error rather than throwing the
                    // exception.
                    Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
                    return;
                }
            }
            if ((dispatchedEvent.getEventType() & mRelevantEventTypes) == 0) {
                if (DEBUG) {
                    Log.i(LOG_TAG, "Not dispatching irrelevant event: " + dispatchedEvent
                            + " that is not among "
                            + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
                }
                return;
            }
            userId = mUserId;
        }
        try {
            // it is possible that this manager is in the same process as the service but
            // client using it is called through Binder from another process. Example: MMS
            // app adds a SMS notification and the NotificationManagerService calls this method
            final long identityToken = Binder.clearCallingIdentity();
            try {
                service.sendAccessibilityEvent(dispatchedEvent, userId);
            } finally {
                Binder.restoreCallingIdentity(identityToken);
            }
            if (DEBUG) {
                Log.i(LOG_TAG, dispatchedEvent + " sent");
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error during sending " + dispatchedEvent + " ", re);
        } finally {
            if (event != dispatchedEvent) {
                event.recycle();
            }
            dispatchedEvent.recycle();
        }
    }

    /**
     * Requests feedback interruption from all accessibility services.
     */
    public void interrupt() {
        final IAccessibilityManager service;
        final int userId;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
            if (!isEnabled()) {
                Looper myLooper = Looper.myLooper();
                if (myLooper == Looper.getMainLooper()) {
                    throw new IllegalStateException(
                            "Accessibility off. Did you forget to check that?");
                } else {
                    // If we're not running on the thread with the main looper, it's possible for
                    // the state of accessibility to change between checking isEnabled and
                    // calling this method. So just log the error rather than throwing the
                    // exception.
                    Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
                    return;
                }
            }
            userId = mUserId;
        }
        try {
            service.interrupt(userId);
            if (DEBUG) {
                Log.i(LOG_TAG, "Requested interrupt from all services");
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
        }
    }

    /**
     * Returns the {@link ServiceInfo}s of the installed accessibility services.
     *
     * @return An unmodifiable list with {@link ServiceInfo}s.
     *
     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
     */
    @Deprecated
    public List<ServiceInfo> getAccessibilityServiceList() {
        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
        List<ServiceInfo> services = new ArrayList<>();
        final int infoCount = infos.size();
        for (int i = 0; i < infoCount; i++) {
            AccessibilityServiceInfo info = infos.get(i);
            services.add(info.getResolveInfo().serviceInfo);
        }
        return Collections.unmodifiableList(services);
    }

    /**
     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
     *
     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
     */
    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
        final IAccessibilityManager service;
        final int userId;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return Collections.emptyList();
            }
            userId = mUserId;
        }

        List<AccessibilityServiceInfo> services = null;
        try {
            services = service.getInstalledAccessibilityServiceList(userId).getList();
            if (DEBUG) {
                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
        }
        if (mAccessibilityPolicy != null) {
            services = mAccessibilityPolicy.getInstalledAccessibilityServiceList(services);
        }
        if (services != null) {
            return Collections.unmodifiableList(services);
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
     * for a given feedback type.
     *
     * @param feedbackTypeFlags The feedback type flags.
     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
     *
     * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
     * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
     * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
     * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
     * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
     */
    public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
            int feedbackTypeFlags) {
        final IAccessibilityManager service;
        final int userId;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return Collections.emptyList();
            }
            userId = mUserId;
        }

        List<AccessibilityServiceInfo> services = null;
        try {
            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
            if (DEBUG) {
                Log.i(LOG_TAG, "Enabled AccessibilityServices " + services);
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while obtaining the enabled AccessibilityServices. ", re);
        }
        if (mAccessibilityPolicy != null) {
            services = mAccessibilityPolicy.getEnabledAccessibilityServiceList(
                    feedbackTypeFlags, services);
        }
        if (services != null) {
            return Collections.unmodifiableList(services);
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Returns whether the user must be shown the AccessibilityService warning dialog
     * before the AccessibilityService (or any shortcut for the service) can be enabled.
     * @hide
     */
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public boolean isAccessibilityServiceWarningRequired(@NonNull AccessibilityServiceInfo info) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return true;
            }
        }
        try {
            return service.isAccessibilityServiceWarningRequired(info);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while checking isAccessibilityServiceWarningRequired: ", re);
            return true;
        }
    }

    /**
     * Registers an {@link AccessibilityStateChangeListener} for changes in
     * the global accessibility state of the system. Equivalent to calling
     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
     * with a null handler.
     *
     * @param listener The listener.
     * @return Always returns {@code true}.
     */
    public boolean addAccessibilityStateChangeListener(
            @NonNull AccessibilityStateChangeListener listener) {
        addAccessibilityStateChangeListener(listener, null);
        return true;
    }

    /**
     * Registers an {@link AccessibilityStateChangeListener} for changes in
     * the global accessibility state of the system. If the listener has already been registered,
     * the handler used to call it back is updated.
     *
     * @param listener The listener.
     * @param handler The handler on which the listener should be called back, or {@code null}
     *                for a callback on the process's main handler.
     */
    public void addAccessibilityStateChangeListener(
            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
        synchronized (mLock) {
            mAccessibilityStateChangeListeners
                    .put(listener, (handler == null) ? mHandler : handler);
        }
    }

    /**
     * Unregisters an {@link AccessibilityStateChangeListener}.
     *
     * @param listener The listener.
     * @return True if the listener was previously registered.
     */
    public boolean removeAccessibilityStateChangeListener(
            @NonNull AccessibilityStateChangeListener listener) {
        synchronized (mLock) {
            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
            mAccessibilityStateChangeListeners.remove(listener);
            return (index >= 0);
        }
    }

    /**
     * Registers a {@link TouchExplorationStateChangeListener} for changes in
     * the global touch exploration state of the system. Equivalent to calling
     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
     * with a null handler.
     *
     * @param listener The listener.
     * @return Always returns {@code true}.
     */
    public boolean addTouchExplorationStateChangeListener(
            @NonNull TouchExplorationStateChangeListener listener) {
        addTouchExplorationStateChangeListener(listener, null);
        return true;
    }

    /**
     * Registers an {@link TouchExplorationStateChangeListener} for changes in
     * the global touch exploration state of the system. If the listener has already been
     * registered, the handler used to call it back is updated.
     *
     * @param listener The listener.
     * @param handler The handler on which the listener should be called back, or {@code null}
     *                for a callback on the process's main handler.
     */
    public void addTouchExplorationStateChangeListener(
            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
        synchronized (mLock) {
            mTouchExplorationStateChangeListeners
                    .put(listener, (handler == null) ? mHandler : handler);
        }
    }

    /**
     * Unregisters a {@link TouchExplorationStateChangeListener}.
     *
     * @param listener The listener.
     * @return True if listener was previously registered.
     */
    public boolean removeTouchExplorationStateChangeListener(
            @NonNull TouchExplorationStateChangeListener listener) {
        synchronized (mLock) {
            int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
            mTouchExplorationStateChangeListeners.remove(listener);
            return (index >= 0);
        }
    }

    /**
     * Registers a {@link AccessibilityServicesStateChangeListener}.
     *
     * @param executor The executor.
     * @param listener The listener.
     */
    public void addAccessibilityServicesStateChangeListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull AccessibilityServicesStateChangeListener listener) {
        synchronized (mLock) {
            mServicesStateChangeListeners.put(listener, executor);
        }
    }

    /**
     * Registers a {@link AccessibilityServicesStateChangeListener}. This will execute a callback on
     * the process's main handler.
     *
     * @param listener The listener.
     *
     */
    public void addAccessibilityServicesStateChangeListener(
            @NonNull AccessibilityServicesStateChangeListener listener) {
        addAccessibilityServicesStateChangeListener(new HandlerExecutor(mHandler), listener);
    }

    /**
     * Unregisters a {@link AccessibilityServicesStateChangeListener}.
     *
     * @param listener The listener.
     * @return {@code true} if the listener was previously registered.
     */
    public boolean removeAccessibilityServicesStateChangeListener(
            @NonNull AccessibilityServicesStateChangeListener listener) {
        synchronized (mLock) {
            return mServicesStateChangeListeners.remove(listener) != null;
        }
    }

    /**
     * Whether the current accessibility request comes from an
     * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
     * property set to true.
     *
     * <p>
     * You can use this method inside {@link AccessibilityNodeProvider} to decide how to populate
     * your nodes.
     * </p>
     *
     * <p>
     * <strong>Note:</strong> The return value is valid only when an {@link AccessibilityNodeInfo}
     * request is in progress, can change from one request to another, and has no meaning when a
     * request is not in progress.
     * </p>
     *
     * @return True if the current request is from a tool that sets isAccessibilityTool.
     */
    public boolean isRequestFromAccessibilityTool() {
        return mRequestFromAccessibilityTool;
    }

    /**
     * Specifies whether the current accessibility request comes from an
     * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
     * property set to true.
     *
     * @hide
     */
    public void setRequestFromAccessibilityTool(boolean requestFromAccessibilityTool) {
        mRequestFromAccessibilityTool = requestFromAccessibilityTool;
    }

    /**
     * Registers a {@link AccessibilityRequestPreparer}.
     */
    public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
        if (mRequestPreparerLists == null) {
            mRequestPreparerLists = new SparseArray<>(1);
        }
        int id = preparer.getAccessibilityViewId();
        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
        if (requestPreparerList == null) {
            requestPreparerList = new ArrayList<>(1);
            mRequestPreparerLists.put(id, requestPreparerList);
        }
        requestPreparerList.add(preparer);
    }

    /**
     * Unregisters a {@link AccessibilityRequestPreparer}.
     */
    public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
        if (mRequestPreparerLists == null) {
            return;
        }
        int viewId = preparer.getAccessibilityViewId();
        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
        if (requestPreparerList != null) {
            requestPreparerList.remove(preparer);
            if (requestPreparerList.isEmpty()) {
                mRequestPreparerLists.remove(viewId);
            }
        }
    }

    /**
     * Get the recommended timeout for changes to the UI needed by this user. Controls should remain
     * on the screen for at least this long to give users time to react. Some users may need
     * extra time to review the controls, or to reach them, or to activate assistive technology
     * to activate the controls automatically.
     * <p>
     * Use the combination of content flags to indicate contents of UI. For example, use
     * {@code FLAG_CONTENT_ICONS | FLAG_CONTENT_TEXT} for message notification which contains
     * icons and text, or use {@code FLAG_CONTENT_TEXT | FLAG_CONTENT_CONTROLS} for button dialog
     * which contains text and button controls.
     * <p/>
     *
     * @param originalTimeout The timeout appropriate for users with no accessibility needs.
     * @param uiContentFlags The combination of flags {@link #FLAG_CONTENT_ICONS},
     *                       {@link #FLAG_CONTENT_TEXT} or {@link #FLAG_CONTENT_CONTROLS} to
     *                       indicate the contents of UI.
     * @return The recommended UI timeout for the current user in milliseconds.
     */
    public int getRecommendedTimeoutMillis(int originalTimeout, @ContentFlag int uiContentFlags) {
        boolean hasControls = (uiContentFlags & FLAG_CONTENT_CONTROLS) != 0;
        boolean hasIconsOrText = (uiContentFlags & FLAG_CONTENT_ICONS) != 0
                || (uiContentFlags & FLAG_CONTENT_TEXT) != 0;
        int recommendedTimeout = originalTimeout;
        if (hasControls) {
            recommendedTimeout = Math.max(recommendedTimeout, mInteractiveUiTimeout);
        }
        if (hasIconsOrText) {
            recommendedTimeout = Math.max(recommendedTimeout, mNonInteractiveUiTimeout);
        }
        return recommendedTimeout;
    }

    /**
     * Gets the strokeWidth of the focus rectangle. This value can be set by
     * {@link AccessibilityService}.
     *
     * @return The strokeWidth of the focus rectangle in pixels.
     *
     */
    public int getAccessibilityFocusStrokeWidth() {
        synchronized (mLock) {
            return mFocusStrokeWidth;
        }
    }

    /**
     * Gets the color of the focus rectangle. This value can be set by
     * {@link AccessibilityService}.
     *
     * @return The color of the focus rectangle.
     *
     */
    public @ColorInt int getAccessibilityFocusColor() {
        synchronized (mLock) {
            return mFocusColor;
        }
    }

    /**
     * Gets accessibility interaction connection tracing enabled state.
     *
     * @hide
     */
    public boolean isA11yInteractionConnectionTraceEnabled() {
        synchronized (mLock) {
            return ((mAccessibilityTracingState
                    & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED) != 0);
        }
    }

    /**
     * Gets accessibility interaction connection callback tracing enabled state.
     *
     * @hide
     */
    public boolean isA11yInteractionConnectionCBTraceEnabled() {
        synchronized (mLock) {
            return ((mAccessibilityTracingState
                    & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED) != 0);
        }
    }

    /**
     * Gets accessibility interaction client tracing enabled state.
     *
     * @hide
     */
    public boolean isA11yInteractionClientTraceEnabled() {
        synchronized (mLock) {
            return ((mAccessibilityTracingState
                    & STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED) != 0);
        }
    }

    /**
     * Gets accessibility service tracing enabled state.
     *
     * @hide
     */
    public boolean isA11yServiceTraceEnabled() {
        synchronized (mLock) {
            return ((mAccessibilityTracingState
                    & STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED) != 0);
        }
    }

    /**
     * Get the preparers that are registered for an accessibility ID
     *
     * @param id The ID of interest
     * @return The list of preparers, or {@code null} if there are none.
     *
     * @hide
     */
    public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
        if (mRequestPreparerLists == null) {
            return null;
        }
        return mRequestPreparerLists.get(id);
    }

    /**
     * Set the currently performing accessibility action in views.
     *
     * @param actionId the action id of {@link AccessibilityNodeInfo.AccessibilityAction}.
     * @hide
     */
    public void notifyPerformingAction(int actionId) {
        mPerformingAction = actionId;
    }

    /**
     * Get the id of {@link AccessibilityNodeInfo.AccessibilityAction} currently being performed.
     *
     * @hide
     */
    public int getPerformingAction() {
        return mPerformingAction;
    }

    /**
     * Registers a {@link HighTextContrastChangeListener} for changes in
     * the global high text contrast state of the system.
     *
     * @param listener The listener.
     *
     * @hide
     */
    public void addHighTextContrastStateChangeListener(
            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
        synchronized (mLock) {
            mHighTextContrastStateChangeListeners
                    .put(listener, (handler == null) ? mHandler : handler);
        }
    }

    /**
     * Unregisters a {@link HighTextContrastChangeListener}.
     *
     * @param listener The listener.
     *
     * @hide
     */
    public void removeHighTextContrastStateChangeListener(
            @NonNull HighTextContrastChangeListener listener) {
        synchronized (mLock) {
            mHighTextContrastStateChangeListeners.remove(listener);
        }
    }

    /**
     * Registers a {@link AudioDescriptionRequestedChangeListener}
     * for changes in the audio description by default state of the system.
     * The value could be read via {@link #isAudioDescriptionRequested}.
     *
     * @param executor The executor on which the listener should be called back.
     * @param listener The listener.
     */
    public void addAudioDescriptionRequestedChangeListener(
            @NonNull Executor executor,
            @NonNull AudioDescriptionRequestedChangeListener listener) {
        synchronized (mLock) {
            mAudioDescriptionRequestedChangeListeners.put(listener, executor);
        }
    }

    /**
     * Unregisters a {@link AudioDescriptionRequestedChangeListener}.
     *
     * @param listener The listener.
     * @return True if listener was previously registered.
     */
    public boolean removeAudioDescriptionRequestedChangeListener(
            @NonNull AudioDescriptionRequestedChangeListener listener) {
        synchronized (mLock) {
            return (mAudioDescriptionRequestedChangeListeners.remove(listener) != null);
        }
    }

    /**
     * Sets the {@link AccessibilityPolicy} controlling this manager.
     *
     * @param policy The policy.
     *
     * @hide
     */
    public void setAccessibilityPolicy(@Nullable AccessibilityPolicy policy) {
        synchronized (mLock) {
            mAccessibilityPolicy = policy;
        }
    }

    /**
     * Check if the accessibility volume stream is active.
     *
     * @return True if accessibility volume is active (i.e. some service has requested it). False
     * otherwise.
     * @hide
     */
    public boolean isAccessibilityVolumeStreamActive() {
        List<AccessibilityServiceInfo> serviceInfos =
                getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
        for (int i = 0; i < serviceInfos.size(); i++) {
            if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * Report a fingerprint gesture to accessibility. Only available for the system process.
     *
     * @param keyCode The key code of the gesture
     * @return {@code true} if accessibility consumes the event. {@code false} if not.
     * @hide
     */
    public boolean sendFingerprintGesture(int keyCode) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }
        try {
            return service.sendFingerprintGesture(keyCode);
        } catch (RemoteException e) {
            return false;
        }
    }

    /**
     * Returns accessibility window id from window token. Accessibility window id is the one
     * returned from AccessibilityWindowInfo.getId(). Only available for the system process.
     *
     * @param windowToken Window token to find accessibility window id.
     * @return Accessibility window id for the window token.
     *   AccessibilityWindowInfo.UNDEFINED_WINDOW_ID if accessibility window id not available for
     *   the token.
     * @hide
     */
    @SystemApi
    public int getAccessibilityWindowId(@Nullable IBinder windowToken) {
        if (windowToken == null) {
            return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
        }

        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
            }
        }
        try {
            return service.getAccessibilityWindowId(windowToken);
        } catch (RemoteException e) {
            return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
        }
    }

    /**
     * Associate the connection between the host View and the embedded SurfaceControlViewHost.
     *
     * @hide
     */
    public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.associateEmbeddedHierarchy(host, embedded);
        } catch (RemoteException e) {
            return;
        }
    }

    /**
     * Disassociate the connection between the host View and the embedded SurfaceControlViewHost.
     * The given token could be either from host side or embedded side.
     *
     * @hide
     */
    public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
        if (token == null) {
            return;
        }
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.disassociateEmbeddedHierarchy(token);
        } catch (RemoteException e) {
            return;
        }
    }

    /**
     * Sets the current state and notifies listeners, if necessary.
     *
     * @param stateFlags The state flags.
     */
    @UnsupportedAppUsage
    private void setStateLocked(int stateFlags) {
        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
        final boolean touchExplorationEnabled =
                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
        final boolean highTextContrastEnabled =
                (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
        final boolean audioDescriptionEnabled =
                (stateFlags & STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED) != 0;

        final boolean wasEnabled = isEnabled();
        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
        final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
        final boolean wasAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;

        // Ensure listeners get current state from isZzzEnabled() calls.
        mIsEnabled = enabled;
        mIsTouchExplorationEnabled = touchExplorationEnabled;
        mIsHighTextContrastEnabled = highTextContrastEnabled;
        mIsAudioDescriptionByDefaultRequested = audioDescriptionEnabled;

        if (wasEnabled != isEnabled()) {
            notifyAccessibilityStateChanged();
        }

        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
            notifyTouchExplorationStateChanged();
        }

        if (wasHighTextContrastEnabled != highTextContrastEnabled) {
            notifyHighTextContrastStateChanged();
        }

        if (wasAudioDescriptionByDefaultRequested
                != audioDescriptionEnabled) {
            notifyAudioDescriptionbyDefaultStateChanged();
        }

        updateAccessibilityTracingState(stateFlags);
    }

    /**
     * Find an installed service with the specified {@link ComponentName}.
     *
     * @param componentName The name to match to the service.
     *
     * @return The info corresponding to the installed service, or {@code null} if no such service
     * is installed.
     * @hide
     */
    public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
            ComponentName componentName) {
        final List<AccessibilityServiceInfo> installedServiceInfos =
                getInstalledAccessibilityServiceList();
        if ((installedServiceInfos == null) || (componentName == null)) {
            return null;
        }
        for (int i = 0; i < installedServiceInfos.size(); i++) {
            if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
                return installedServiceInfos.get(i);
            }
        }
        return null;
    }

    /**
     * Adds an accessibility interaction connection interface for a given window.
     * @param windowToken The window token to which a connection is added.
     * @param leashToken The leash token to which a connection is added.
     * @param connection The connection.
     *
     * @hide
     */
    public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
            String packageName, IAccessibilityInteractionConnection connection) {
        final IAccessibilityManager service;
        final int userId;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return View.NO_ID;
            }
            userId = mUserId;
        }
        try {
            return service.addAccessibilityInteractionConnection(windowToken, leashToken,
                    connection, packageName, userId);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
        }
        return View.NO_ID;
    }

    /**
     * Removed an accessibility interaction connection interface for a given window.
     * @param windowToken The window token to which a connection is removed.
     *
     * @hide
     */
    public void removeAccessibilityInteractionConnection(IWindow windowToken) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.removeAccessibilityInteractionConnection(windowToken);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
        }
    }

    /**
     * Perform the accessibility shortcut if the caller has permission.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void performAccessibilityShortcut() {
        performAccessibilityShortcut(null);
    }

    /**
     * Perform the accessibility shortcut for the given target which is assigned to the shortcut.
     *
     * @param targetName The flattened {@link ComponentName} string or the class name of a system
     *        class implementing a supported accessibility feature, or {@code null} if there's no
     *        specified target.
     * @hide
     */
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void performAccessibilityShortcut(@Nullable String targetName) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.performAccessibilityShortcut(targetName);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
        }
    }

    /**
     * Turns on or off a shortcut type of the accessibility features. The shortcut type is one
     * of the shortcut defined in the {@link ShortcutConstants.USER_SHORTCUT_TYPES}.
     *
     * @throws SecurityException if the app does not hold the
     *                           {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission
     * @hide
     */
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void enableShortcutsForTargets(boolean enable,
            @ShortcutConstants.UserShortcutType int shortcutTypes, @NonNull Set<String> targets,
            @UserIdInt int userId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.enableShortcutsForTargets(
                    enable, shortcutTypes, targets.stream().toList(), userId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Returns accessibility feature's component and the provided tile map. This includes the
     * TileService provided by the AccessibilityService or Accessibility Activity and the tile
     * component provided by the framework's feature.
     *
     * @return a map of a feature's component name, and its provided tile's component name. The
     * returned map's keys and values are not null. If a feature doesn't provide a tile, it won't
     * have an entry in this map.
     * @hide
     * @see ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE
     */
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    @NonNull
    public Map<ComponentName, ComponentName> getA11yFeatureToTileMap(@UserIdInt int userId) {
        final IAccessibilityManager service;
        Map<ComponentName, ComponentName> a11yFeatureToTileMap = new ArrayMap<>();
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return a11yFeatureToTileMap;
            }
        }
        try {
            Bundle a11yFeatureToTile = service.getA11yFeatureToTileMap(userId);
            for (String key : a11yFeatureToTile.keySet()) {
                ComponentName feature = ComponentName.unflattenFromString(key);
                if (feature == null) {
                    continue;
                }
                ComponentName tileService = a11yFeatureToTile.getParcelable(key,
                        ComponentName.class);
                if (tileService != null) {
                    a11yFeatureToTileMap.put(feature, tileService);
                }
            }
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
        return a11yFeatureToTileMap;
    }

    /**
     * Register the provided {@link RemoteAction} with the given actionId
     * <p>
     * To perform established system actions, an accessibility service uses the GLOBAL_ACTION
     * constants in {@link android.accessibilityservice.AccessibilityService}. To provide a
     * customized implementation for one of these actions, the id of the registered system action
     * must match that of the corresponding GLOBAL_ACTION constant. For example, to register a
     * Back action, {@code actionId} must be
     * {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK}
     * </p>
     * @param action The remote action to be registered with the given actionId as system action.
     * @param actionId The id uniquely identify the system action.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void registerSystemAction(@NonNull RemoteAction action, int actionId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.registerSystemAction(action, actionId);

            if (DEBUG) {
                Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered.");
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re);
        }
    }

   /**
     * Unregister a system action with the given actionId
     *
     * @param actionId The id uniquely identify the system action to be unregistered.
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public void unregisterSystemAction(int actionId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.unregisterSystemAction(actionId);

            if (DEBUG) {
                Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered.");
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re);
        }
    }

    /**
     * Notifies that the accessibility button in the system's navigation area has been clicked
     *
     * @param displayId The logical display id.
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
    public void notifyAccessibilityButtonClicked(int displayId) {
        notifyAccessibilityButtonClicked(displayId, null);
    }

    /**
     * Perform the accessibility button for the given target which is assigned to the button.
     *
     * @param displayId displayId The logical display id.
     * @param targetName The flattened {@link ComponentName} string or the class name of a system
     *        class implementing a supported accessibility feature, or {@code null} if there's no
     *        specified target.
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
    public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.notifyAccessibilityButtonClicked(displayId, targetName);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
        }
    }

    /**
     * Notifies that the visibility of the accessibility button in the system's navigation area
     * has changed.
     *
     * @param shown {@code true} if the accessibility button is visible within the system
     *                  navigation area, {@code false} otherwise
     * @hide
     */
    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.notifyAccessibilityButtonVisibilityChanged(shown);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
        }
    }

    /**
     * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
     * window. Intended for use by the System UI only.
     *
     * @param connection The connection to handle the actions. Set to {@code null} to avoid
     * affecting the actions.
     *
     * @hide
     */
    public void setPictureInPictureActionReplacingConnection(
            @Nullable IAccessibilityInteractionConnection connection) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.setPictureInPictureActionReplacingConnection(connection);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
        }
    }

    /**
     * Returns the list of shortcut target names currently assigned to the given shortcut.
     *
     * @param shortcutType The shortcut type.
     * @return The list of shortcut target names.
     * @hide
     */
    @TestApi
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    @NonNull
    public List<String> getAccessibilityShortcutTargets(
            @ShortcutConstants.UserShortcutType int shortcutType) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
        }
        if (service != null) {
            try {
                return service.getAccessibilityShortcutTargets(shortcutType);
            } catch (RemoteException re) {
                re.rethrowFromSystemServer();
            }
        }
        return Collections.emptyList();
    }

    /**
     * Returns the {@link AccessibilityShortcutInfo}s of the installed accessibility shortcut
     * targets, for specific user.
     *
     * @param context The context of the application.
     * @param userId The user id.
     * @return A list with {@link AccessibilityShortcutInfo}s.
     * @hide
     */
    @NonNull
    public List<AccessibilityShortcutInfo> getInstalledAccessibilityShortcutListAsUser(
            @NonNull Context context, @UserIdInt int userId) {
        final List<AccessibilityShortcutInfo> shortcutInfos = new ArrayList<>();
        final int flags = PackageManager.GET_ACTIVITIES
                | PackageManager.GET_META_DATA
                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                | PackageManager.MATCH_DIRECT_BOOT_AWARE
                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
        final Intent actionMain = new Intent(Intent.ACTION_MAIN);
        actionMain.addCategory(Intent.CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET);

        final PackageManager packageManager = context.getPackageManager();
        final List<ResolveInfo> installedShortcutList =
                packageManager.queryIntentActivitiesAsUser(actionMain, flags, userId);
        for (int i = 0; i < installedShortcutList.size(); i++) {
            final AccessibilityShortcutInfo shortcutInfo =
                    getShortcutInfo(context, installedShortcutList.get(i));
            if (shortcutInfo != null) {
                shortcutInfos.add(shortcutInfo);
            }
        }
        return shortcutInfos;
    }

    /**
     * Returns an {@link AccessibilityShortcutInfo} according to the given {@link ResolveInfo} of
     * an activity.
     *
     * @param context The context of the application.
     * @param resolveInfo The resolve info of an activity.
     * @return The AccessibilityShortcutInfo.
     */
    @Nullable
    private AccessibilityShortcutInfo getShortcutInfo(@NonNull Context context,
            @NonNull ResolveInfo resolveInfo) {
        final ActivityInfo activityInfo = resolveInfo.activityInfo;
        if (activityInfo == null || activityInfo.metaData == null
                || activityInfo.metaData.getInt(AccessibilityShortcutInfo.META_DATA) == 0) {
            return null;
        }
        try {
            return new AccessibilityShortcutInfo(context, activityInfo);
        } catch (XmlPullParserException | IOException exp) {
            Log.e(LOG_TAG, "Error while initializing AccessibilityShortcutInfo", exp);
        }
        return null;
    }

    /**
     *
     * Sets an {@link IMagnificationConnection} that manipulates magnification in SystemUI.
     *
     * @param connection The connection that manipulates magnification in SystemUI.
     * @hide
     */
    public void setMagnificationConnection(@Nullable
            IMagnificationConnection connection) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.setMagnificationConnection(connection);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error setting magnification connection", re);
        }
    }

    /**
     * Determines if users want to select sound track with audio description by default.
     * <p>
     * Audio description, also referred to as a video description, described video, or
     * more precisely called a visual description, is a form of narration used to provide
     * information surrounding key visual elements in a media work for the benefit of
     * blind and visually impaired consumers.
     * </p>
     * <p>
     * The method provides the preference value to content provider apps to select the
     * default sound track during playing a video or movie.
     * </p>
     * <p>
     * Add listener to detect the state change via
     * {@link #addAudioDescriptionRequestedChangeListener}
     * </p>
     * @return {@code true} if the audio description is enabled, {@code false} otherwise.
     */
    public boolean isAudioDescriptionRequested() {
        synchronized (mLock) {
            IAccessibilityManager service = getServiceLocked();
            if (service == null) {
                return false;
            }
            return mIsAudioDescriptionByDefaultRequested;
        }
    }

    /**
     * Sets the system audio caption enabled state.
     *
     * @param isEnabled The system audio captioning enabled state.
     * @param userId The user Id.
     * @hide
     */
    public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.setSystemAudioCaptioningEnabled(isEnabled, userId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Gets the system audio caption UI enabled state.
     *
     * @param userId The user Id.
     * @return the system audio caption UI enabled state.
     * @hide
     */
    public boolean isSystemAudioCaptioningUiEnabled(int userId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }
        try {
            return service.isSystemAudioCaptioningUiEnabled(userId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Sets the system audio caption UI enabled state.
     *
     * @param isEnabled The system audio captioning UI enabled state.
     * @param userId The user Id.
     * @hide
     */
    public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.setSystemAudioCaptioningUiEnabled(isEnabled, userId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }


    /**
     * Sets the {@link AccessibilityWindowAttributes} to the window associated with the given
     * window id.
     *
     * @param displayId The display id of the window.
     * @param windowId  The id of the window.
     * @param attributes The accessibility window attributes.
     * @hide
     */
    public void setAccessibilityWindowAttributes(int displayId, int windowId,
            AccessibilityWindowAttributes attributes) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.setAccessibilityWindowAttributes(displayId, windowId, mUserId, attributes);
        } catch (RemoteException re) {
            re.rethrowFromSystemServer();
        }
    }

    /**
     * Registers an {@link AccessibilityDisplayProxy}, so this proxy can access UI content specific
     * to its display.
     *
     * @param proxy the {@link AccessibilityDisplayProxy} to register.
     * @return {@code true} if the proxy is successfully registered.
     *
     * @throws IllegalArgumentException if the proxy's display is not currently tracked by a11y, is
     * {@link android.view.Display#DEFAULT_DISPLAY}, is or lower than
     * {@link android.view.Display#INVALID_DISPLAY}, or is already being proxy-ed.
     *
     * @throws SecurityException if the app does not hold the
     * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission or the
     * {@link Manifest.permission#CREATE_VIRTUAL_DEVICE} permission.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(allOf = {Manifest.permission.MANAGE_ACCESSIBILITY,
            Manifest.permission.CREATE_VIRTUAL_DEVICE})
    public boolean registerDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }

        try {
            return service.registerProxyForDisplay(proxy.mServiceClient, proxy.getDisplayId());
        }  catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Unregisters an {@link AccessibilityDisplayProxy}.
     *
     * @return {@code true} if the proxy is successfully unregistered.
     *
     * @throws SecurityException if the app does not hold the
     * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission or the
     * {@link Manifest.permission#CREATE_VIRTUAL_DEVICE} permission.
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(allOf = {Manifest.permission.MANAGE_ACCESSIBILITY,
            Manifest.permission.CREATE_VIRTUAL_DEVICE})
    public boolean unregisterDisplayProxy(@NonNull AccessibilityDisplayProxy proxy)  {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }
        try {
            return service.unregisterProxyForDisplay(proxy.getDisplayId());
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Start sequence (infinite) type of flash notification. Use {@code Context} to retrieve the
     * package name as the identifier of this flash notification.
     * The notification can be cancelled later by calling {@link #stopFlashNotificationSequence}
     * with same {@code Context}.
     * If the binder associated with this {@link AccessibilityManager} instance dies then the
     * sequence will stop automatically. It is strongly recommended to call
     * {@link #stopFlashNotificationSequence} within a reasonable amount of time after calling
     * this method.
     *
     * @param context The context in which this manager operates.
     * @param reason The triggering reason of flash notification.
     * @return {@code true} if flash notification works properly.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_FLASH_NOTIFICATION_SYSTEM_API)
    @TestApi
    @SystemApi(client = MODULE_LIBRARIES)
    public boolean startFlashNotificationSequence(@NonNull Context context,
            @FlashNotificationReason int reason) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }

        try {
            return service.startFlashNotificationSequence(context.getOpPackageName(),
                    reason, mBinder);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while start flash notification sequence", re);
            return false;
        }
    }

    /**
     * Stop sequence (infinite) type of flash notification. The flash notification with the
     * package name retrieved from {@code Context} as identifier will be stopped if exist.
     * It is strongly recommended to call this method within a reasonable amount of time after
     * calling {@link #startFlashNotificationSequence} method.
     *
     * @param context The context in which this manager operates.
     * @return {@code true} if flash notification stops properly.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_FLASH_NOTIFICATION_SYSTEM_API)
    @TestApi
    @SystemApi(client = MODULE_LIBRARIES)
    public boolean stopFlashNotificationSequence(@NonNull Context context) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }

        try {
            return service.stopFlashNotificationSequence(context.getOpPackageName());
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while stop flash notification sequence", re);
            return false;
        }
    }

    /**
     * Start event (finite) type of flash notification.
     *
     * @param context The context in which this manager operates.
     * @param reason The triggering reason of flash notification.
     * @param reasonPkg The package that trigger the flash notification.
     * @return {@code true} if flash notification works properly.
     * @hide
     */
    public boolean startFlashNotificationEvent(@NonNull Context context,
            @FlashNotificationReason int reason, @Nullable String reasonPkg) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }

        try {
            return service.startFlashNotificationEvent(context.getOpPackageName(),
                    reason, reasonPkg);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while start flash notification event", re);
            return false;
        }
    }

    /**
     * Determines if the accessibility target is allowed.
     *
     * @param packageName The name of the application attempting to perform the operation.
     * @param uid The user id of the application attempting to perform the operation.
     * @param userId The id of the user for whom to perform the operation.
     * @return {@code true} the accessibility target is allowed.
     * @hide
     */
    public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }

        try {
            return service.isAccessibilityTargetAllowed(packageName, uid, userId);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while check accessibility target status", re);
            return false;
        }
    }

    /**
     * Sends restricted dialog intent if the accessibility target is disallowed.
     *
     * @param packageName The name of the application attempting to perform the operation.
     * @param uid The user id of the application attempting to perform the operation.
     * @param userId The id of the user for whom to perform the operation.
     * @return {@code true} if the restricted dialog is shown.
     * @hide
     */
    public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return false;
            }
        }

        try {
            return service.sendRestrictedDialogIntent(packageName, uid, userId);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while show restricted dialog", re);
            return false;
        }
    }

    private IAccessibilityManager getServiceLocked() {
        if (mService == null) {
            tryConnectToServiceLocked(null);
        }
        return mService;
    }

    private void tryConnectToServiceLocked(IAccessibilityManager service) {
        if (service == null) {
            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
            if (iBinder == null) {
                return;
            }
            service = IAccessibilityManager.Stub.asInterface(iBinder);
        }

        try {
            final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
            setStateLocked(IntPair.first(userStateAndRelevantEvents));
            mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
            updateUiTimeout(service.getRecommendedTimeoutMillis());
            updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
            mService = service;
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
        }
    }

    /**
     * Notifies the registered {@link AccessibilityStateChangeListener}s.
     *
     * Note: this method notifies only the listeners of this single instance.
     * AccessibilityManagerService is responsible for calling this method on all of
     * its AccessibilityManager clients in order to notify all listeners.
     * @hide
     */
    public void notifyAccessibilityStateChanged() {
        final boolean isEnabled;
        final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
        synchronized (mLock) {
            if (mAccessibilityStateChangeListeners.isEmpty()) {
                return;
            }
            isEnabled = isEnabled();
            listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
        }

        final int numListeners = listeners.size();
        for (int i = 0; i < numListeners; i++) {
            final AccessibilityStateChangeListener listener = listeners.keyAt(i);
            listeners.valueAt(i).post(() ->
                    listener.onAccessibilityStateChanged(isEnabled));
        }
    }

    /**
     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
     */
    private void notifyTouchExplorationStateChanged() {
        final boolean isTouchExplorationEnabled;
        final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
        synchronized (mLock) {
            if (mTouchExplorationStateChangeListeners.isEmpty()) {
                return;
            }
            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
            listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
        }

        final int numListeners = listeners.size();
        for (int i = 0; i < numListeners; i++) {
            final TouchExplorationStateChangeListener listener = listeners.keyAt(i);
            listeners.valueAt(i).post(() ->
                    listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
        }
    }

    /**
     * Notifies the registered {@link HighTextContrastChangeListener}s.
     */
    private void notifyHighTextContrastStateChanged() {
        final boolean isHighTextContrastEnabled;
        final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
        synchronized (mLock) {
            if (mHighTextContrastStateChangeListeners.isEmpty()) {
                return;
            }
            isHighTextContrastEnabled = mIsHighTextContrastEnabled;
            listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
        }

        final int numListeners = listeners.size();
        for (int i = 0; i < numListeners; i++) {
            final HighTextContrastChangeListener listener = listeners.keyAt(i);
            listeners.valueAt(i).post(() ->
                    listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
        }
    }

    /**
     * Notifies the registered {@link AudioDescriptionStateChangeListener}s.
     */
    private void notifyAudioDescriptionbyDefaultStateChanged() {
        final boolean isAudioDescriptionByDefaultRequested;
        final ArrayMap<AudioDescriptionRequestedChangeListener, Executor> listeners;
        synchronized (mLock) {
            if (mAudioDescriptionRequestedChangeListeners.isEmpty()) {
                return;
            }
            isAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
            listeners = new ArrayMap<>(mAudioDescriptionRequestedChangeListeners);
        }

        final int numListeners = listeners.size();
        for (int i = 0; i < numListeners; i++) {
            final AudioDescriptionRequestedChangeListener listener = listeners.keyAt(i);
            listeners.valueAt(i).execute(() ->
                    listener.onAudioDescriptionRequestedChanged(
                        isAudioDescriptionByDefaultRequested));
        }
    }

    /**
     * Update mAccessibilityTracingState.
     */
    private void updateAccessibilityTracingState(int stateFlag) {
        synchronized (mLock) {
            mAccessibilityTracingState = stateFlag;
        }
    }

    /**
     * Update interactive and non-interactive UI timeout.
     *
     * @param uiTimeout A pair of {@code int}s. First integer for interactive one, and second
     *                  integer for non-interactive one.
     */
    private void updateUiTimeout(long uiTimeout) {
        mInteractiveUiTimeout = IntPair.first(uiTimeout);
        mNonInteractiveUiTimeout = IntPair.second(uiTimeout);
    }

    /**
     * Updates the stroke width and color of the focus rectangle.
     *
     * @param strokeWidth The strokeWidth of the focus rectangle.
     * @param color The color of the focus rectangle.
     */
    private void updateFocusAppearanceLocked(int strokeWidth, int color) {
        if (mFocusStrokeWidth == strokeWidth && mFocusColor == color) {
            return;
        }
        mFocusStrokeWidth = strokeWidth;
        mFocusColor = color;
    }

    /**
     * Sets the stroke width and color of the focus rectangle to default value.
     *
     * @param resource The resources.
     */
    private void initialFocusAppearanceLocked(Resources resource) {
        try {
            mFocusStrokeWidth = resource.getDimensionPixelSize(
                    R.dimen.accessibility_focus_highlight_stroke_width);
            mFocusColor = resource.getColor(R.color.accessibility_focus_highlight_color);
        } catch (Resources.NotFoundException re) {
            // Sets the stroke width and color to default value by hardcoded for making
            // the Talkback can work normally.
            mFocusStrokeWidth = (int) (4 * resource.getDisplayMetrics().density);
            mFocusColor = 0xbf39b500;
            Log.e(LOG_TAG, "Error while initialing the focus appearance data then setting to"
                    + " default value by hardcoded", re);
        }
    }

    /**
     * Determines if the accessibility button within the system navigation area is supported.
     *
     * @return {@code true} if the accessibility button is supported on this device,
     * {@code false} otherwise
     */
    public static boolean isAccessibilityButtonSupported() {
        final Resources res = Resources.getSystem();
        return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
    }

    private final class MyCallback implements Handler.Callback {
        public static final int MSG_SET_STATE = 1;

        @Override
        public boolean handleMessage(Message message) {
            switch (message.what) {
                case MSG_SET_STATE: {
                    // See comment at mClient
                    final int state = message.arg1;
                    synchronized (mLock) {
                        setStateLocked(state);
                    }
                } break;
            }
            return true;
        }
    }

    /**
     * Retrieves the window's transformation matrix and magnification spec.
     *
     * <p>
     * Used by callers outside of the AccessibilityManagerService process which need
     * this information, like {@link android.view.accessibility.DirectAccessibilityConnection}.
     * </p>
     *
     * @return The transformation spec
     * @hide
     */
    public IAccessibilityManager.WindowTransformationSpec getWindowTransformationSpec(
            int windowId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return null;
            }
        }
        try {
            return service.getWindowTransformationSpec(windowId);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
     * specified display.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
    public void attachAccessibilityOverlayToDisplay(
            int displayId, @NonNull SurfaceControl surfaceControl) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.attachAccessibilityOverlayToDisplay(
                    displayId, surfaceControl);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    /**
     * Notifies that the current a11y tiles in QuickSettings Panel has been changed
     *
     * @param userId            The userId of the user attempts to change the qs panel.
     * @param tileComponentNames A list of Accessibility feature's TileServices' component names
     *                           and the a11y platform tiles' component names
     * @hide
     */
    @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
    public void notifyQuickSettingsTilesChanged(
            @UserIdInt int userId, List<ComponentName> tileComponentNames) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.notifyQuickSettingsTilesChanged(userId, tileComponentNames);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }
}
