/*
 * Copyright (C) 2021 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.os.PowerManager.THERMAL_STATUS_CRITICAL;
import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.Settings;
import android.view.ICrossWindowBlurEnabledListener;
import android.view.TunnelModeEnabledListener;

/**
 * Keeps track of the different factors that determine whether cross-window blur is enabled
 * or disabled. Also keeps a list of all interested listeners and notifies them when the
 * blur enabled state changes.
 */
final class BlurController {
    private final Context mContext;
    private final RemoteCallbackList<ICrossWindowBlurEnabledListener>
            mBlurEnabledListeners = new RemoteCallbackList<>();
    // We don't use the WM global lock, because the BlurController is not involved in window
    // drawing and only receives binder calls that don't need synchronization with the rest of WM
    private final Object mLock = new Object();
    private volatile boolean mBlurEnabled;
    private boolean mInPowerSaveMode;
    private boolean mCriticalThermalStatus;
    private boolean mBlurDisabledSetting;
    private boolean mTunnelModeEnabled = false;

    private TunnelModeEnabledListener mTunnelModeListener =
            new TunnelModeEnabledListener(Runnable::run) {
        @Override
        public void onTunnelModeEnabledChanged(boolean tunnelModeEnabled) {
            mTunnelModeEnabled = tunnelModeEnabled;
            updateBlurEnabled();
        }
    };

    BlurController(Context context, PowerManager powerManager) {
        mContext = context;

        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
        context.registerReceiverForAllUsers(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
                    // onReceive always gets called on the same thread, so there is no
                    // multi-threaded execution here. Thus, we don't have to hold mLock here.
                    mInPowerSaveMode = powerManager.isPowerSaveMode();
                    updateBlurEnabled();
                }
            }
        }, filter, null, null);
        mInPowerSaveMode = powerManager.isPowerSaveMode();

        context.getContentResolver().registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.DISABLE_WINDOW_BLURS), false,
                new ContentObserver(null) {
                    @Override
                    public void onChange(boolean selfChange) {
                        super.onChange(selfChange);
                        // onChange always gets called on the same thread, so there is no
                        // multi-threaded execution here. Thus, we don't have to hold mLock here.
                        mBlurDisabledSetting = getBlurDisabledSetting();
                        updateBlurEnabled();
                    }
                });
        mBlurDisabledSetting = getBlurDisabledSetting();

        powerManager.addThermalStatusListener((status) -> {
            mCriticalThermalStatus = status >= THERMAL_STATUS_CRITICAL;
            updateBlurEnabled();
        });
        mCriticalThermalStatus = powerManager.getCurrentThermalStatus() >= THERMAL_STATUS_CRITICAL;

        TunnelModeEnabledListener.register(mTunnelModeListener);

        updateBlurEnabled();
    }

    boolean registerCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) {
        if (listener == null) return false;
        mBlurEnabledListeners.register(listener);
        return getBlurEnabled();
    }

    void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) {
        if (listener == null) return;
        mBlurEnabledListeners.unregister(listener);
    }

    boolean getBlurEnabled() {
        return mBlurEnabled;
    }

    private void updateBlurEnabled() {
        synchronized (mLock) {
            final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurDisabledSetting
                    && !mInPowerSaveMode && !mTunnelModeEnabled && !mCriticalThermalStatus;
            if (mBlurEnabled == newEnabled) {
                return;
            }
            mBlurEnabled = newEnabled;
            notifyBlurEnabledChangedLocked(newEnabled);
        }
    }

    private void notifyBlurEnabledChangedLocked(boolean enabled) {
        int i = mBlurEnabledListeners.beginBroadcast();
        while (i > 0) {
            i--;
            ICrossWindowBlurEnabledListener listener =
                    mBlurEnabledListeners.getBroadcastItem(i);
            try {
                listener.onCrossWindowBlurEnabledChanged(enabled);
            } catch (RemoteException e) {
            }
        }
        mBlurEnabledListeners.finishBroadcast();
    }

    private boolean getBlurDisabledSetting() {
        return Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.DISABLE_WINDOW_BLURS, 0) == 1;
    }
}
