/*
 * Copyright (C) 2017 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.tv.settings.accessibility;

import static android.content.Context.ACCESSIBILITY_SERVICE;

import static com.android.tv.settings.util.InstrumentationUtils.logToggleInteracted;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.admin.DevicePolicyManager;
import android.app.tvsettings.TvSettingsEnums;
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityManager;
import android.util.ArrayMap;

import androidx.annotation.Keep;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;
import androidx.preference.TwoStatePreference;

import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.accessibility.AccessibilityUtils;
import com.android.tv.settings.R;
import com.android.tv.settings.SettingsPreferenceFragment;
import com.android.tv.settings.overlay.FlavorUtils;

import java.util.List;
import java.util.Set;
import java.util.Map;

/**
 * Fragment for Accessibility settings
 */
@Keep
public class AccessibilityFragment extends SettingsPreferenceFragment {
    private static final String TOGGLE_HIGH_TEXT_CONTRAST_KEY = "toggle_high_text_contrast";
    private static final String TOGGLE_AUDIO_DESCRIPTION_KEY = "toggle_audio_description";
    private static final String TOGGLE_BOLD_TEXT_KEY = "toggle_bold_text";
    private static final String COLOR_CORRECTION_TWOPANEL_KEY = "color_correction_only_twopanel";
    private static final String COLOR_CORRECTION_CLASSIC_KEY = "color_correction_only_classic";
    private static final String ACCESSIBILITY_SHORTCUT_KEY = "accessibility_shortcut";
    private static final int BOLD_TEXT_ADJUSTMENT = 500;
    private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1;

    PreferenceCategory mServicesPrefCategory;
    PreferenceCategory mControlsPrefCategory;

    private final Map<ComponentName, PreferenceCategory>
            mServiceComponentNameToPreferenceCategoryMap = new ArrayMap<>();

    private enum AccessibilityCategory {
        SCREEN_READERS("accessibility_screen_readers_category",
                R.array.config_preinstalled_screen_reader_services),
        DISPLAY("accessibility_display_category",
                R.array.config_preinstalled_display_services),
        INTERACTION_CONTROLS("accessibility_interaction_controls_category",
                R.array.config_preinstalled_interaction_control_services),
        AUDIO_AND_ONSCREEN_TEXT("accessibility_audio_and_onscreen_text_category",
                R.array.config_preinstalled_audio_and_onscreen_text_services),
        EXPERIMENTAL("accessibility_experimental_category",
                R.array.config_preinstalled_experimental_services),
        SERVICES("accessibility_services_category",
                R.array.config_preinstalled_additional_services);

        final String key;
        final int servicesArrayId;

        AccessibilityCategory(String key, int servicesArrayId) {
            this.key = key;
            this.servicesArrayId = servicesArrayId;
        }

        String getKey() {
            return this.key;
        }

        int getServicesArrayId() {
            return this.servicesArrayId;
        }
    }

    private AccessibilityManager.AccessibilityStateChangeListener
            mAccessibilityStateChangeListener = enabled -> refreshServices();

    /**
     * Create a new instance of the fragment
     * @return New fragment instance
     */
    public static AccessibilityFragment newInstance() {
        return new AccessibilityFragment();
    }

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

    @Override
    public void onStop() {
        super.onStop();
        AccessibilityManager am = (AccessibilityManager)
                getContext().getSystemService(ACCESSIBILITY_SERVICE);
        if (am != null) {
            am.removeAccessibilityStateChangeListener(mAccessibilityStateChangeListener);
        }
    }

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.accessibility, null);

        final TwoStatePreference highContrastPreference =
                (TwoStatePreference) findPreference(TOGGLE_HIGH_TEXT_CONTRAST_KEY);
        highContrastPreference.setChecked(Settings.Secure.getInt(getContext().getContentResolver(),
                Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0) == 1);

        final TwoStatePreference audioDescriptionPreference =
                (TwoStatePreference) findPreference(TOGGLE_AUDIO_DESCRIPTION_KEY);
        audioDescriptionPreference.setChecked(Settings.Secure.getInt(
                getContext().getContentResolver(),
                Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, 0) == 1);

        final TwoStatePreference boldTextPreference =
                (TwoStatePreference) findPreference(TOGGLE_BOLD_TEXT_KEY);
        boldTextPreference.setChecked(Settings.Secure.getInt(
                getContext().getContentResolver(),
                Settings.Secure.FONT_WEIGHT_ADJUSTMENT, 0) == BOLD_TEXT_ADJUSTMENT);

        if (getContext()
                .getResources()
                .getBoolean(R.bool.config_showAccessibilityColorCorrection)) {
            Preference colorCorrectionPreferenceToSetVisible =
                    FlavorUtils.isTwoPanel(getContext())
                            ? (Preference) findPreference(COLOR_CORRECTION_TWOPANEL_KEY)
                            : (Preference) findPreference(COLOR_CORRECTION_CLASSIC_KEY);
            colorCorrectionPreferenceToSetVisible.setVisible(true);
        }

        mServicesPrefCategory = findPreference(AccessibilityCategory.SERVICES.getKey());
        mControlsPrefCategory = findPreference(AccessibilityCategory.INTERACTION_CONTROLS.getKey());
        populateServiceToPreferenceCategoryMaps();
        refreshServices();
        AccessibilityManager am = (AccessibilityManager)
                getContext().getSystemService(ACCESSIBILITY_SERVICE);
        if (am != null) {
            am.addAccessibilityStateChangeListener(mAccessibilityStateChangeListener);
        }
    }

    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        if (TextUtils.equals(preference.getKey(), TOGGLE_HIGH_TEXT_CONTRAST_KEY)) {
            logToggleInteracted(
                    TvSettingsEnums.SYSTEM_A11Y_HIGH_CONTRAST_TEXT,
                    ((SwitchPreference) preference).isChecked());
            Settings.Secure.putInt(getActivity().getContentResolver(),
                    Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
                    (((SwitchPreference) preference).isChecked() ? 1 : 0));
            return true;
        } else if (TextUtils.equals(preference.getKey(), TOGGLE_AUDIO_DESCRIPTION_KEY)) {
            logToggleInteracted(
                    TvSettingsEnums.SYSTEM_A11Y_AUDIO_DESCRIPTION,
                    ((SwitchPreference) preference).isChecked());
            Settings.Secure.putInt(getActivity().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT,
                    (((SwitchPreference) preference).isChecked() ? 1 : 0));
            return true;
        } else if (TextUtils.equals(preference.getKey(), TOGGLE_BOLD_TEXT_KEY)) {
            logToggleInteracted(
                    TvSettingsEnums.SYSTEM_A11Y_BOLD_TEXT,
                    ((SwitchPreference) preference).isChecked());
            Settings.Secure.putInt(getActivity().getContentResolver(),
                    Settings.Secure.FONT_WEIGHT_ADJUSTMENT,
                    (((SwitchPreference) preference).isChecked() ? BOLD_TEXT_ADJUSTMENT : 0));
            return true;
        } else {
            return super.onPreferenceTreeClick(preference);
        }
    }

    private void populateServiceToPreferenceCategoryMaps() {
        for (AccessibilityCategory accessibilityCategory : AccessibilityCategory.values()) {
            String[] services = getResources().getStringArray(
                    accessibilityCategory.getServicesArrayId());
            PreferenceCategory prefCategory = findPreference(accessibilityCategory.getKey());
            for (int i = 0; i < services.length; i++) {
                ComponentName component = ComponentName.unflattenFromString(services[i]);
                mServiceComponentNameToPreferenceCategoryMap.put(component, prefCategory);
            }
        }
    }

    private void refreshServices() {
        DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
        final List<AccessibilityServiceInfo> installedServiceInfos =
                getActivity().getSystemService(AccessibilityManager.class)
                        .getInstalledAccessibilityServiceList();
        final Set<ComponentName> enabledServices =
                AccessibilityUtils.getEnabledServicesFromSettings(getActivity());
        final List<String> permittedServices = dpm.getPermittedAccessibilityServices(
                UserHandle.myUserId());

        if (installedServiceInfos.size() == 0) {
            Preference pref = mControlsPrefCategory.findPreference(ACCESSIBILITY_SHORTCUT_KEY);
            if (pref != null) {
                mControlsPrefCategory.removePreference(pref);
            }
        }

        final boolean accessibilityEnabled = Settings.Secure.getInt(
                getActivity().getContentResolver(),
                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;

        for (final AccessibilityServiceInfo accInfo : installedServiceInfos) {
            final ServiceInfo serviceInfo = accInfo.getResolveInfo().serviceInfo;
            final ComponentName componentName = new ComponentName(serviceInfo.packageName,
                    serviceInfo.name);
            final boolean serviceEnabled = accessibilityEnabled
                    && enabledServices.contains(componentName);
            // permittedServices null means all accessibility services are allowed.
            final boolean serviceAllowed = permittedServices == null
                    || permittedServices.contains(serviceInfo.packageName);

            final String title = accInfo.getResolveInfo()
                    .loadLabel(getActivity().getPackageManager()).toString();

            final String key = "ServicePref:" + componentName.flattenToString();
            RestrictedPreference servicePref = findPreference(key);
            if (servicePref == null) {
                servicePref = new RestrictedPreference(getContext());
                servicePref.setKey(key);
            }
            servicePref.setTitle(title);
            servicePref.setSummary(serviceEnabled ? R.string.settings_on : R.string.settings_off);
            AccessibilityServiceFragment.prepareArgs(servicePref.getExtras(),
                    serviceInfo.packageName,
                    serviceInfo.name,
                    accInfo.getSettingsActivityName(),
                    title);

            if (serviceAllowed || serviceEnabled) {
                servicePref.setEnabled(true);
                servicePref.setFragment(AccessibilityServiceFragment.class.getName());
            } else {
                // Disable accessibility service that are not permitted.
                final EnforcedAdmin admin =
                        RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
                                getContext(), serviceInfo.packageName, UserHandle.myUserId());
                if (admin != null) {
                    servicePref.setDisabledByAdmin(admin);
                } else {
                    servicePref.setEnabled(false);
                }
                servicePref.setFragment(null);
            }

            // Make the screen reader component be the first preference in its preference category.
            final String screenReaderFlattenedComponentName = getResources().getString(
                    R.string.accessibility_screen_reader_flattened_component_name);
            if (componentName.flattenToString().equals(screenReaderFlattenedComponentName)) {
                servicePref.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX);
            }

            PreferenceCategory prefCategory = mServicesPrefCategory;
            if (mServiceComponentNameToPreferenceCategoryMap.containsKey(componentName)) {
                prefCategory = mServiceComponentNameToPreferenceCategoryMap.get(componentName);
            }
            // The method "addPreference" only adds the preference if it is not there already.
            prefCategory.addPreference(servicePref);
        }
        mServicesPrefCategory.setVisible(mServicesPrefCategory.getPreferenceCount() != 0);
        mControlsPrefCategory.setVisible(mControlsPrefCategory.getPreferenceCount() != 0);
    }

    @Override
    protected int getPageId() {
        return TvSettingsEnums.SYSTEM_A11Y;
    }
}