

package com.android.server.policy;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserManager;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.util.MathUtils;
import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;

import com.android.internal.R;
import com.android.server.LocalServices;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class EnableAccessibilityController {
    private static final String TAG = "EnableAccessibilityController";

    private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
    private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;

    public static final int MESSAGE_SPEAK_WARNING = 1;
    public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
    public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case MESSAGE_SPEAK_WARNING: {
                    String text = mContext.getString(R.string.continue_to_enable_accessibility);
                    mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
                } break;
                case MESSAGE_SPEAK_ENABLE_CANCELED: {
                    String text = mContext.getString(R.string.enable_accessibility_canceled);
                    mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
                } break;
                case MESSAGE_ENABLE_ACCESSIBILITY: {
                    enableAccessibility();
                    mTone.play();
                    mTts.speak(mContext.getString(R.string.accessibility_enabled),
                            TextToSpeech.QUEUE_FLUSH, null);
                } break;
            }
        }
    };

    private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
            .Stub.asInterface(ServiceManager.getService("accessibility"));


    private final Context mContext;
    private final Runnable mOnAccessibilityEnabledCallback;
    private final UserManager mUserManager;
    private final TextToSpeech mTts;
    private final Ringtone mTone;

    private final float mTouchSlop;

    private boolean mDestroyed;
    private boolean mCanceled;

    private float mFirstPointerDownX;
    private float mFirstPointerDownY;
    private float mSecondPointerDownX;
    private float mSecondPointerDownY;

    public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
        mContext = context;
        mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (mDestroyed) {
                    mTts.shutdown();
                }
            }
        });
        mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
        mTone.setStreamType(AudioManager.STREAM_MUSIC);
        mTouchSlop = context.getResources().getDimensionPixelSize(
                R.dimen.accessibility_touch_slop);
    }

    public static boolean canEnableAccessibilityViaGesture(Context context) {
        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
        // Accessibility is enabled and there is an enabled speaking
        // accessibility service, then we have nothing to do.
        if (accessibilityManager.isEnabled()
                && !accessibilityManager.getEnabledAccessibilityServiceList(
                        AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
            return false;
        }
        // If the global gesture is enabled and there is a speaking service
        // installed we are good to go, otherwise there is nothing to do.
        return Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
                && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
    }

    public static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
            Context context) {
        List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
        services.addAll(AccessibilityManager.getInstance(context)
                .getInstalledAccessibilityServiceList());
        Iterator<AccessibilityServiceInfo> iterator = services.iterator();
        while (iterator.hasNext()) {
            AccessibilityServiceInfo service = iterator.next();
            if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
                iterator.remove();
            }
        }
        return services;
    }

    public void onDestroy() {
        mDestroyed = true;
    }

    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
                && event.getPointerCount() == 2) {
            mFirstPointerDownX = event.getX(0);
            mFirstPointerDownY = event.getY(0);
            mSecondPointerDownX = event.getX(1);
            mSecondPointerDownY = event.getY(1);
            mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
                    SPEAK_WARNING_DELAY_MILLIS);
            mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
                   ENABLE_ACCESSIBILITY_DELAY_MILLIS);
            return true;
        }
        return false;
    }

    public boolean onTouchEvent(MotionEvent event) {
        final int pointerCount = event.getPointerCount();
        final int action = event.getActionMasked();
        if (mCanceled) {
            if (action == MotionEvent.ACTION_UP) {
                mCanceled = false;
            }
            return true;
        }
        switch (action) {
            case MotionEvent.ACTION_POINTER_DOWN: {
                if (pointerCount > 2) {
                    cancel();
                }
            } break;
            case MotionEvent.ACTION_MOVE: {
                final float firstPointerMove = MathUtils.dist(event.getX(0),
                        event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
                if (Math.abs(firstPointerMove) > mTouchSlop) {
                    cancel();
                }
                final float secondPointerMove = MathUtils.dist(event.getX(1),
                        event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
                if (Math.abs(secondPointerMove) > mTouchSlop) {
                    cancel();
                }
            } break;
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL: {
                cancel();
            } break;
        }
        return true;
    }

    private void cancel() {
        mCanceled = true;
        if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
            mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
        } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
            mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
        }
        mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
    }

    private void enableAccessibility() {
        if (enableAccessibility(mContext)) {
            mOnAccessibilityEnabledCallback.run();
        }
    }

    public static boolean enableAccessibility(Context context) {
        final IAccessibilityManager accessibilityManager = IAccessibilityManager
                .Stub.asInterface(ServiceManager.getService("accessibility"));
        final WindowManagerInternal windowManager = LocalServices.getService(
                WindowManagerInternal.class);
        final UserManager userManager = (UserManager) context.getSystemService(
                Context.USER_SERVICE);
        ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
        if (componentName == null) {
            return false;
        }

        boolean keyguardLocked = windowManager.isKeyguardLocked();
        final boolean hasMoreThanOneUser = userManager.getUsers().size() > 1;
        try {
            if (!keyguardLocked || !hasMoreThanOneUser) {
                final int userId = ActivityManager.getCurrentUser();
                accessibilityManager.enableAccessibilityService(componentName, userId);
            } else if (keyguardLocked) {
                accessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
                        componentName, true /* enableTouchExploration */);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "cannot enable accessibilty: " + e);
        }

        return true;
    }

    public static void disableAccessibility(Context context) {
        final IAccessibilityManager accessibilityManager = IAccessibilityManager
                .Stub.asInterface(ServiceManager.getService("accessibility"));
        ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
        if (componentName == null) {
            return;
        }

        final int userId = ActivityManager.getCurrentUser();
        try {
            accessibilityManager.disableAccessibilityService(componentName, userId);
        } catch (RemoteException e) {
            Log.e(TAG, "cannot disable accessibility " + e);
        }
    }

    public static boolean isAccessibilityEnabled(Context context) {
        final AccessibilityManager accessibilityManager =
                context.getSystemService(AccessibilityManager.class);
        List enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(
                AccessibilityServiceInfo.FEEDBACK_SPOKEN);
        return enabledServices != null && !enabledServices.isEmpty();
    }

    @Nullable
    public static ComponentName getInstalledSpeakingAccessibilityServiceComponent(
            Context context) {
        List<AccessibilityServiceInfo> services =
                getInstalledSpeakingAccessibilityServices(context);
        if (services.isEmpty()) {
            return null;
        }

        ServiceInfo serviceInfo = services.get(0).getResolveInfo().serviceInfo;
        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
    }
}
