/*
 * Copyright (C) 2019 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.server.wm;

import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE;
import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;

import android.provider.AndroidDeviceConfig;
import android.provider.DeviceConfig;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.utils.DeviceConfigInterface;

import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * Settings constants that can modify the window manager's behavior.
 */
final class WindowManagerConstants {

    /**
     * The minimum duration between gesture exclusion logging for a given window in
     * milliseconds.
     *
     * Events that happen in-between will be silently dropped.
     *
     * A non-positive value disables logging.
     *
     * <p>Note: On Devices running Q, this key is in the "android:window_manager" namespace.
     *
     * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
     */
    static final String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
            "system_gesture_exclusion_log_debounce_millis";

    private static final int MIN_GESTURE_EXCLUSION_LIMIT_DP = 200;

    /** @see #KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS */
    long mSystemGestureExclusionLogDebounceTimeoutMillis;
    /** @see AndroidDeviceConfig#KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP */
    int mSystemGestureExclusionLimitDp;
    /** @see AndroidDeviceConfig#KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE */
    boolean mSystemGestureExcludedByPreQStickyImmersive;

    private final WindowManagerGlobalLock mGlobalLock;
    private final Runnable mUpdateSystemGestureExclusionCallback;
    private final DeviceConfigInterface mDeviceConfig;
    private final DeviceConfig.OnPropertiesChangedListener mListenerAndroid;
    private final DeviceConfig.OnPropertiesChangedListener mListenerWindowManager;

    WindowManagerConstants(WindowManagerService service, DeviceConfigInterface deviceConfig) {
        this(service.mGlobalLock, () -> service.mRoot.forAllDisplays(
                DisplayContent::updateSystemGestureExclusionLimit), deviceConfig);
    }

    @VisibleForTesting
    WindowManagerConstants(WindowManagerGlobalLock globalLock,
            Runnable updateSystemGestureExclusionCallback,
            DeviceConfigInterface deviceConfig) {
        mGlobalLock = Objects.requireNonNull(globalLock);
        mUpdateSystemGestureExclusionCallback = Objects.requireNonNull(updateSystemGestureExclusionCallback);
        mDeviceConfig = deviceConfig;
        mListenerAndroid = this::onAndroidPropertiesChanged;
        mListenerWindowManager = this::onWindowPropertiesChanged;
    }

    void start(Executor executor) {
        mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ANDROID, executor,
                mListenerAndroid);
        mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                executor, mListenerWindowManager);

        updateSystemGestureExclusionLogDebounceMillis();
        updateSystemGestureExclusionLimitDp();
        updateSystemGestureExcludedByPreQStickyImmersive();
    }

    @VisibleForTesting
    void dispose() {
        mDeviceConfig.removeOnPropertiesChangedListener(mListenerAndroid);
        mDeviceConfig.removeOnPropertiesChangedListener(mListenerWindowManager);
    }

    private void onAndroidPropertiesChanged(DeviceConfig.Properties properties) {
        synchronized (mGlobalLock) {
            boolean updateSystemGestureExclusionLimit = false;
            for (String name : properties.getKeyset()) {
                if (name == null) {
                    return;
                }
                switch (name) {
                    case KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP:
                        updateSystemGestureExclusionLimitDp();
                        updateSystemGestureExclusionLimit = true;
                        break;
                    case KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE:
                        updateSystemGestureExcludedByPreQStickyImmersive();
                        updateSystemGestureExclusionLimit = true;
                        break;
                    default:
                        break;
                }
            }
            if (updateSystemGestureExclusionLimit) {
                mUpdateSystemGestureExclusionCallback.run();
            }
        }
    }

    private void onWindowPropertiesChanged(DeviceConfig.Properties properties) {
        synchronized (mGlobalLock) {
            for (String name : properties.getKeyset()) {
                if (name == null) {
                    return;
                }
                switch (name) {
                    case KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS:
                        updateSystemGestureExclusionLogDebounceMillis();
                        break;
                    default:
                        break;
                }
            }
        }
    }

    private void updateSystemGestureExclusionLogDebounceMillis() {
        mSystemGestureExclusionLogDebounceTimeoutMillis =
                mDeviceConfig.getLong(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                        KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS, 0);
    }

    private void updateSystemGestureExclusionLimitDp() {
        mSystemGestureExclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
                mDeviceConfig.getInt(DeviceConfig.NAMESPACE_ANDROID,
                        KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
    }

    private void updateSystemGestureExcludedByPreQStickyImmersive() {
        mSystemGestureExcludedByPreQStickyImmersive = mDeviceConfig.getBoolean(
                DeviceConfig.NAMESPACE_ANDROID,
                KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
    }

    void dump(PrintWriter pw) {
        pw.println("WINDOW MANAGER CONSTANTS (dumpsys window constants):");

        pw.print("  "); pw.print(KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS);
        pw.print("="); pw.println(mSystemGestureExclusionLogDebounceTimeoutMillis);
        pw.print("  "); pw.print(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP);
        pw.print("="); pw.println(mSystemGestureExclusionLimitDp);
        pw.print("  "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
        pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
        pw.println();
    }
}
