/*
 * Copyright (C) 2013 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 com.android.settings.accessibility;

import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.BidiFormatter;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.accessibility.AccessibilityManager;
import android.widget.CompoundButton;

import androidx.annotation.Nullable;

import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
import com.android.settingslib.accessibility.AccessibilityUtils;

import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/** Fragment for providing toggle bar and basic accessibility service setup. */
public class ToggleAccessibilityServicePreferenceFragment extends
        ToggleFeaturePreferenceFragment {

    private static final String TAG = "ToggleAccessibilityServicePreferenceFragment";
    private static final String KEY_HAS_LOGGED = "has_logged";
    private final AtomicBoolean mIsDialogShown = new AtomicBoolean(/* initialValue= */ false);

    private Dialog mWarningDialog;
    private ComponentName mTileComponentName;
    private BroadcastReceiver mPackageRemovedReceiver;
    private boolean mDisabledStateLogged = false;
    private long mStartTimeMillsForLogging = 0;

    @Override
    public int getMetricsCategory() {
        return getArguments().getInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Do not call super. We don't want to see the "Help & feedback" option on this page so as
        // not to confuse users who think they might be able to send feedback about a specific
        // accessibility service from this page.
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            if (savedInstanceState.containsKey(KEY_HAS_LOGGED)) {
                mDisabledStateLogged = savedInstanceState.getBoolean(KEY_HAS_LOGGED);
            }
        }
    }

    @Override
    protected void registerKeysToObserverCallback(
            AccessibilitySettingsContentObserver contentObserver) {
        super.registerKeysToObserverCallback(contentObserver);
        contentObserver.registerObserverCallback(key -> updateSwitchBarToggleSwitch());
    }

    @Override
    public void onStart() {
        super.onStart();
        final AccessibilityServiceInfo serviceInfo = getAccessibilityServiceInfo();
        if (serviceInfo == null) {
            getActivity().finishAndRemoveTask();
        } else if (!AccessibilityUtil.isSystemApp(serviceInfo)) {
            registerPackageRemoveReceiver();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        updateSwitchBarToggleSwitch();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (mStartTimeMillsForLogging > 0) {
            outState.putBoolean(KEY_HAS_LOGGED, mDisabledStateLogged);
        }
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onPreferenceToggled(String preferenceKey, boolean enabled) {
        ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
        logAccessibilityServiceEnabled(toggledService, enabled);
        if (!enabled) {
            logDisabledState(toggledService.getPackageName());
        }
        AccessibilityUtils.setAccessibilityServiceState(getPrefContext(), toggledService, enabled);
    }

    // IMPORTANT: Refresh the info since there are dynamically changing capabilities. For
    // example, before JellyBean MR2 the user was granting the explore by touch one.
    @Nullable
    AccessibilityServiceInfo getAccessibilityServiceInfo() {
        final List<AccessibilityServiceInfo> infos = AccessibilityManager.getInstance(
                getPrefContext()).getInstalledAccessibilityServiceList();

        for (int i = 0, count = infos.size(); i < count; i++) {
            AccessibilityServiceInfo serviceInfo = infos.get(i);
            ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
            if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
                    && mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
                return serviceInfo;
            }
        }
        return null;
    }

    @Override
    public Dialog onCreateDialog(int dialogId) {
        final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
        switch (dialogId) {
            case DialogEnums.ENABLE_WARNING_FROM_TOGGLE:
                if (info == null) {
                    return null;
                }
                mWarningDialog =
                        com.android.internal.accessibility.dialog.AccessibilityServiceWarning
                                .createAccessibilityServiceWarningDialog(getPrefContext(), info,
                                        v -> onAllowButtonFromEnableToggleClicked(),
                                        v -> onDenyButtonFromEnableToggleClicked(),
                                        v -> onDialogButtonFromUninstallClicked());
                return mWarningDialog;
            case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE:
                if (info == null) {
                    return null;
                }
                mWarningDialog =
                        com.android.internal.accessibility.dialog.AccessibilityServiceWarning
                                .createAccessibilityServiceWarningDialog(getPrefContext(), info,
                                        v -> onAllowButtonFromShortcutToggleClicked(),
                                        v -> onDenyButtonFromShortcutToggleClicked(),
                                        v -> onDialogButtonFromUninstallClicked());
                return mWarningDialog;
            case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT:
                if (info == null) {
                    return null;
                }
                mWarningDialog =
                        com.android.internal.accessibility.dialog.AccessibilityServiceWarning
                                .createAccessibilityServiceWarningDialog(getPrefContext(), info,
                                        v -> onAllowButtonFromShortcutClicked(),
                                        v -> onDenyButtonFromShortcutClicked(),
                                        v -> onDialogButtonFromUninstallClicked());
                return mWarningDialog;
            case DialogEnums.DISABLE_WARNING_FROM_TOGGLE:
                if (info == null) {
                    return null;
                }
                mWarningDialog = createDisableDialog(
                        getPrefContext(), info, this::onDialogButtonFromDisableToggleClicked);
                return mWarningDialog;
            default:
                return super.onCreateDialog(dialogId);
        }
    }

    /** Returns a {@link Dialog} to be shown to confirm that they want to disable a service. */
    private static Dialog createDisableDialog(Context context,
            AccessibilityServiceInfo info, DialogInterface.OnClickListener listener) {
        final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
        final CharSequence label =
                info.getResolveInfo().loadLabel(context.getPackageManager());
        CharSequence serviceName = BidiFormatter.getInstance(locale).unicodeWrap(label);

        return new AlertDialog.Builder(context)
                .setTitle(context.getString(R.string.disable_service_title, serviceName))
                .setCancelable(true)
                .setPositiveButton(R.string.accessibility_dialog_button_stop, listener)
                .setNegativeButton(R.string.accessibility_dialog_button_cancel, listener)
                .create();
    }

    @Override
    public int getDialogMetricsCategory(int dialogId) {
        switch (dialogId) {
            case DialogEnums.ENABLE_WARNING_FROM_TOGGLE:
            case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT:
            case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE:
                return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_ENABLE;
            case DialogEnums.DISABLE_WARNING_FROM_TOGGLE:
                return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_DISABLE;
            case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
                return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL;
            default:
                return super.getDialogMetricsCategory(dialogId);
        }
    }

    @Override
    int getUserShortcutTypes() {
        return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(),
                mComponentName);
    }

    @Override
    ComponentName getTileComponentName() {
        return mTileComponentName;
    }

    @Override
    CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) {
        final ComponentName componentName = getTileComponentName();
        if (componentName == null) {
            return null;
        }

        final CharSequence tileName = loadTileLabel(getPrefContext(), componentName);
        if (tileName == null) {
            return null;
        }

        final int titleResId = type == QuickSettingsTooltipType.GUIDE_TO_EDIT
                ? R.string.accessibility_service_qs_tooltip_content
                : R.string.accessibility_service_auto_added_qs_tooltip_content;
        return getString(titleResId, tileName);
    }

    @Override
    protected void updateSwitchBarToggleSwitch() {
        final boolean checked = isAccessibilityServiceEnabled();
        if (mToggleServiceSwitchPreference.isChecked() == checked) {
            return;
        }
        mToggleServiceSwitchPreference.setChecked(checked);
    }

    private boolean isAccessibilityServiceEnabled() {
        return AccessibilityUtils.getEnabledServicesFromSettings(getPrefContext())
                .contains(mComponentName);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    }

    private void registerPackageRemoveReceiver() {
        if (mPackageRemovedReceiver != null || getContext() == null) {
            return;
        }
        mPackageRemovedReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                final String packageName = intent.getData().getSchemeSpecificPart();
                if (TextUtils.equals(mComponentName.getPackageName(), packageName)) {
                    getActivity().finishAndRemoveTask();
                }
            }
        };
        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
        filter.addDataScheme("package");
        getContext().registerReceiver(mPackageRemovedReceiver, filter);
    }

    private void unregisterPackageRemoveReceiver() {
        if (mPackageRemovedReceiver == null || getContext() == null) {
            return;
        }
        getContext().unregisterReceiver(mPackageRemovedReceiver);
        mPackageRemovedReceiver = null;
    }

    boolean serviceSupportsAccessibilityButton() {
        final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
        return info != null
                && (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
    }

    private void handleConfirmServiceEnabled(boolean confirmed) {
        getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
        onPreferenceToggled(mPreferenceKey, confirmed);
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (isChecked != isAccessibilityServiceEnabled()) {
            onPreferenceClick(isChecked);
        }
    }

    @SuppressLint("MissingPermission")
    @Override
    public void onToggleClicked(ShortcutPreference preference) {
        final int shortcutTypes = getUserPreferredShortcutTypes();
        if (preference.isChecked()) {
            final boolean isWarningRequired =
                    getPrefContext().getSystemService(AccessibilityManager.class)
                            .isAccessibilityServiceWarningRequired(getAccessibilityServiceInfo());
            if (isWarningRequired) {
                preference.setChecked(false);
                showPopupDialog(DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE);
            } else {
                onAllowButtonFromShortcutToggleClicked();
            }
        } else {
            getPrefContext().getSystemService(AccessibilityManager.class)
                            .enableShortcutsForTargets(false, shortcutTypes,
                                    Set.of(mComponentName.flattenToString()),
                                    getPrefContext().getUserId());
        }
        mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
    }

    @Override
    public void onSettingsClicked(ShortcutPreference preference) {
        final boolean isWarningRequired =
                getPrefContext().getSystemService(AccessibilityManager.class)
                        .isAccessibilityServiceWarningRequired(getAccessibilityServiceInfo());
        if (isWarningRequired) {
            showPopupDialog(DialogEnums.ENABLE_WARNING_FROM_SHORTCUT);
        } else {
            onAllowButtonFromShortcutClicked();
        }
    }

    @Override
    protected void onProcessArguments(Bundle arguments) {
        super.onProcessArguments(arguments);
        // Settings title and intent.
        String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE);
        String settingsComponentName = arguments.getString(
                AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME);
        if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
            Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
                    ComponentName.unflattenFromString(settingsComponentName.toString()));
            if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
                mSettingsTitle = settingsTitle;
                mSettingsIntent = settingsIntent;
                setHasOptionsMenu(true);
            }
        }

        mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);

        // Settings animated image.
        final int animatedImageRes = arguments.getInt(
                AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES);
        if (animatedImageRes > 0) {
            mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                    .authority(mComponentName.getPackageName())
                    .appendPath(String.valueOf(animatedImageRes))
                    .build();
        }

        // Get Accessibility service name.
        AccessibilityServiceInfo info = getAccessibilityServiceInfo();
        mFeatureName = info == null ? "" : info.getResolveInfo().loadLabel(getPackageManager());

        if (arguments.containsKey(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME)) {
            final String tileServiceComponentName = arguments.getString(
                    AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME);
            mTileComponentName = ComponentName.unflattenFromString(tileServiceComponentName);
        }

        mStartTimeMillsForLogging = arguments.getLong(AccessibilitySettings.EXTRA_TIME_FOR_LOGGING);
    }

    private void onDialogButtonFromDisableToggleClicked(DialogInterface dialog, int which) {
        switch (which) {
            case DialogInterface.BUTTON_POSITIVE:
                handleConfirmServiceEnabled(/* confirmed= */ false);
                break;
            case DialogInterface.BUTTON_NEGATIVE:
                handleConfirmServiceEnabled(/* confirmed= */ true);
                break;
            default:
                throw new IllegalArgumentException("Unexpected button identifier");
        }
    }

    private void onDialogButtonFromUninstallClicked() {
        mWarningDialog.dismiss();
        final Intent uninstallIntent = createUninstallPackageActivityIntent();
        if (uninstallIntent == null) {
            return;
        }
        startActivity(uninstallIntent);
    }

    @Nullable
    private Intent createUninstallPackageActivityIntent() {
        final AccessibilityServiceInfo a11yServiceInfo = getAccessibilityServiceInfo();
        if (a11yServiceInfo == null) {
            Log.w(TAG, "createUnInstallIntent -- invalid a11yServiceInfo");
            return null;
        }
        final ApplicationInfo appInfo =
                a11yServiceInfo.getResolveInfo().serviceInfo.applicationInfo;
        final Uri packageUri = Uri.parse("package:" + appInfo.packageName);
        final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
        return uninstallIntent;
    }

    @Override
    public void onStop() {
        super.onStop();
        unregisterPackageRemoveReceiver();
    }

    @Override
    protected int getPreferenceScreenResId() {
        // TODO(b/171272809): Add back when controllers move to static type
        return 0;
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    @Override
    protected int getDefaultShortcutTypes() {
        AccessibilityServiceInfo info = getAccessibilityServiceInfo();
        boolean isAccessibilityTool = info != null && info.isAccessibilityTool();
        return !isAccessibilityTool || getTileComponentName() == null
                ? super.getDefaultShortcutTypes()
                : ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
    }

    private void onAllowButtonFromEnableToggleClicked() {
        handleConfirmServiceEnabled(/* confirmed= */ true);
        if (serviceSupportsAccessibilityButton()) {
            mIsDialogShown.set(false);
            showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
        }
        if (mWarningDialog != null) {
            mWarningDialog.dismiss();
        }
    }

    private void onDenyButtonFromEnableToggleClicked() {
        handleConfirmServiceEnabled(/* confirmed= */ false);
        mWarningDialog.dismiss();
    }

    @SuppressLint("MissingPermission")
    void onAllowButtonFromShortcutToggleClicked() {
        mShortcutPreference.setChecked(true);

        final int shortcutTypes = getUserPreferredShortcutTypes();
        getPrefContext().getSystemService(AccessibilityManager.class)
                .enableShortcutsForTargets(true, shortcutTypes,
                        Set.of(mComponentName.flattenToString()), getPrefContext().getUserId());

        mIsDialogShown.set(false);
        showPopupDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);

        if (mWarningDialog != null) {
            mWarningDialog.dismiss();
        }

        mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
    }

    private void onDenyButtonFromShortcutToggleClicked() {
        mShortcutPreference.setChecked(false);

        mWarningDialog.dismiss();
    }

    private void onAllowButtonFromShortcutClicked() {
        mIsDialogShown.set(false);
        EditShortcutsPreferenceFragment.showEditShortcutScreen(
                getContext(),
                getMetricsCategory(),
                getShortcutTitle(),
                mComponentName,
                getIntent()
        );

        if (mWarningDialog != null) {
            mWarningDialog.dismiss();
        }
    }

    private void onDenyButtonFromShortcutClicked() {
        mWarningDialog.dismiss();
    }

    private boolean onPreferenceClick(boolean isChecked) {
        if (isChecked) {
            mToggleServiceSwitchPreference.setChecked(false);
            getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
                    /* disableService */ false);
            final boolean isWarningRequired =
                    getPrefContext().getSystemService(AccessibilityManager.class)
                            .isAccessibilityServiceWarningRequired(getAccessibilityServiceInfo());
            if (isWarningRequired) {
                showPopupDialog(DialogEnums.ENABLE_WARNING_FROM_TOGGLE);
            } else {
                onAllowButtonFromEnableToggleClicked();
            }
        } else {
            mToggleServiceSwitchPreference.setChecked(true);
            getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED,
                    /* enableService */ true);
            showDialog(DialogEnums.DISABLE_WARNING_FROM_TOGGLE);
        }
        return true;
    }

    private void showPopupDialog(int dialogId) {
        if (mIsDialogShown.compareAndSet(/* expect= */ false, /* update= */ true)) {
            showDialog(dialogId);
            setOnDismissListener(
                    dialog -> mIsDialogShown.compareAndSet(/* expect= */ true, /* update= */
                            false));
        }
    }

    private void logDisabledState(String packageName) {
        if (mStartTimeMillsForLogging > 0 && !mDisabledStateLogged) {
            AccessibilityStatsLogUtils.logDisableNonA11yCategoryService(
                    packageName,
                    SystemClock.elapsedRealtime() - mStartTimeMillsForLogging);
            mDisabledStateLogged = true;
        }
    }
}
