/*
 * 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.systemui.globalactions;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;

import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.app.IActivityManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Handler;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.telecom.TelecomManager;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.view.IWindowManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.lifecycle.LifecycleOwner;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.leak.RotationUtils;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;

import java.util.concurrent.Executor;

import javax.inject.Inject;
import javax.inject.Provider;

/**
 * Helper to show the global actions dialog.  Each item is an {@link Action} that may show depending
 * on whether the keyguard is showing, and whether the device is provisioned.
 * This version includes wallet.
 */
public class GlobalActionsDialog extends GlobalActionsDialogLite
        implements DialogInterface.OnDismissListener,
        DialogInterface.OnShowListener,
        ConfigurationController.ConfigurationListener,
        GlobalActionsPanelPlugin.Callbacks,
        LifecycleOwner {

    private static final String TAG = "GlobalActionsDialog";

    private final LockPatternUtils mLockPatternUtils;
    private final KeyguardStateController mKeyguardStateController;
    private final NotificationShadeDepthController mDepthController;
    private final SysUiState mSysUiState;
    private final ActivityStarter mActivityStarter;
    private final SysuiColorExtractor mSysuiColorExtractor;
    private final IStatusBarService mStatusBarService;
    private final NotificationShadeWindowController mNotificationShadeWindowController;
    private GlobalActionsPanelPlugin mWalletPlugin;

    @VisibleForTesting
    boolean mShowLockScreenCards = false;

    /**
     * @param context everything needs a context :(
     */
    @Inject
    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs,
            AudioManager audioManager, IDreamManager iDreamManager,
            DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
            BroadcastDispatcher broadcastDispatcher,
            TelephonyListenerManager telephonyListenerManager,
            GlobalSettings globalSettings, SecureSettings secureSettings,
            @Nullable Vibrator vibrator, @Main Resources resources,
            ConfigurationController configurationController, ActivityStarter activityStarter,
            KeyguardStateController keyguardStateController, UserManager userManager,
            TrustManager trustManager, IActivityManager iActivityManager,
            @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
            NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor,
            IStatusBarService statusBarService,
            NotificationShadeWindowController notificationShadeWindowController,
            IWindowManager iWindowManager,
            @Background Executor backgroundExecutor,
            UiEventLogger uiEventLogger,
            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {

        super(context, windowManagerFuncs,
                audioManager, iDreamManager,
                devicePolicyManager, lockPatternUtils,
                broadcastDispatcher, telephonyListenerManager,
                globalSettings, secureSettings,
                vibrator, resources,
                configurationController,
                keyguardStateController, userManager,
                trustManager, iActivityManager,
                telecomManager, metricsLogger,
                depthController, colorExtractor,
                statusBarService,
                notificationShadeWindowController,
                iWindowManager,
                backgroundExecutor,
                uiEventLogger,
                ringerModeTracker, sysUiState, handler);

        mLockPatternUtils = lockPatternUtils;
        mKeyguardStateController = keyguardStateController;
        mDepthController = depthController;
        mSysuiColorExtractor = colorExtractor;
        mStatusBarService = statusBarService;
        mNotificationShadeWindowController = notificationShadeWindowController;
        mSysUiState = sysUiState;
        mActivityStarter = activityStarter;
        keyguardStateController.addCallback(new KeyguardStateController.Callback() {
            @Override
            public void onUnlockedChanged() {
                if (mDialog != null) {
                    ActionsDialog dialog = (ActionsDialog) mDialog;
                    boolean unlocked = mKeyguardStateController.isUnlocked();
                    if (dialog.mWalletViewController != null) {
                        dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
                    }

                    if (unlocked) {
                        dialog.hideLockMessage();
                    }
                }
            }
        });

        // Listen for changes to show pay on the power menu while locked
        onPowerMenuLockScreenSettingsChanged();
        mGlobalSettings.registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
                false /* notifyForDescendants */,
                new ContentObserver(handler) {
                    @Override
                    public void onChange(boolean selfChange) {
                        onPowerMenuLockScreenSettingsChanged();
                    }
                });
    }

    /**
     * Show the global actions dialog (creating if necessary)
     *
     * @param keyguardShowing True if keyguard is showing
     */
    public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
            GlobalActionsPanelPlugin walletPlugin) {
        mWalletPlugin = walletPlugin;
        super.showOrHideDialog(keyguardShowing, isDeviceProvisioned);
    }

    /**
     * Returns the maximum number of power menu items to show based on which GlobalActions
     * layout is being used.
     */
    @VisibleForTesting
    @Override
    protected int getMaxShownPowerItems() {
        return getContext().getResources().getInteger(
                com.android.systemui.R.integer.power_menu_max_columns);
    }

    /**
     * Create the global actions dialog.
     *
     * @return A new dialog.
     */
    @Override
    protected ActionsDialogLite createDialog() {
        initDialogItems();

        mDepthController.setShowingHomeControls(true);
        ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter,
                this::getWalletViewController, mDepthController, mSysuiColorExtractor,
                mStatusBarService, mNotificationShadeWindowController,
                mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter);

        if (shouldShowLockMessage(dialog)) {
            dialog.showLockMessage();
        }
        dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
        dialog.setOnDismissListener(this);
        dialog.setOnShowListener(this);

        return dialog;
    }

    @Nullable
    private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() {
        if (mWalletPlugin == null) {
            return null;
        }
        return mWalletPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
    }

    /**
     * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
     * called when the quick access wallet requests that an intent be started (with lock screen
     * shown first if needed).
     */
    @Override
    public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) {
        mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
    }

    @Override
    protected int getEmergencyTextColor(Context context) {
        return context.getResources().getColor(
                com.android.systemui.R.color.global_actions_emergency_text);
    }

    @Override
    protected int getEmergencyIconColor(Context context) {
        return getContext().getResources().getColor(
                com.android.systemui.R.color.global_actions_emergency_text);
    }

    @Override
    protected int getEmergencyBackgroundColor(Context context) {
        return getContext().getResources().getColor(
                com.android.systemui.R.color.global_actions_emergency_background);
    }

    @Override
    protected int getGridItemLayoutResource() {
        return com.android.systemui.R.layout.global_actions_grid_item_v2;
    }

    @VisibleForTesting
    static class ActionsDialog extends ActionsDialogLite {

        private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory;
        @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
        private ResetOrientationData mResetOrientationData;
        @VisibleForTesting ViewGroup mLockMessageContainer;
        private TextView mLockMessage;

        ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
                Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory,
                NotificationShadeDepthController depthController,
                SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                NotificationShadeWindowController notificationShadeWindowController,
                SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
                MyPowerOptionsAdapter powerAdapter) {
            super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
                    adapter, overflowAdapter, depthController, sysuiColorExtractor,
                    statusBarService, notificationShadeWindowController, sysuiState,
                    onRotateCallback, keyguardShowing, powerAdapter);
            mWalletFactory = walletFactory;

            // Update window attributes
            Window window = getWindow();
            window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
            window.setLayout(MATCH_PARENT, MATCH_PARENT);
            window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
            window.addFlags(
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                            | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                            | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
            setTitle(R.string.global_actions);
            initializeLayout();
        }

        private boolean isWalletViewAvailable() {
            return mWalletViewController != null && mWalletViewController.getPanelContent() != null;
        }

        private void initializeWalletView() {
            if (mWalletFactory == null) {
                return;
            }
            mWalletViewController = mWalletFactory.get();
            if (!isWalletViewAvailable()) {
                return;
            }

            boolean isLandscapeWalletViewShown = mContext.getResources().getBoolean(
                    com.android.systemui.R.bool.global_actions_show_landscape_wallet_view);

            int rotation = RotationUtils.getRotation(mContext);
            boolean rotationLocked = RotationPolicy.isRotationLocked(mContext);
            if (rotation != RotationUtils.ROTATION_NONE) {
                if (rotationLocked) {
                    if (mResetOrientationData == null) {
                        mResetOrientationData = new ResetOrientationData();
                        mResetOrientationData.locked = true;
                        mResetOrientationData.rotation = rotation;
                    }

                    // Unlock rotation, so user can choose to rotate to portrait to see the panel.
                    // This call is posted so that the rotation does not change until post-layout,
                    // otherwise onConfigurationChanged() may not get invoked.
                    mGlobalActionsLayout.post(() ->
                            RotationPolicy.setRotationLockAtAngle(
                                    mContext, false, RotationUtils.ROTATION_NONE));

                    if (!isLandscapeWalletViewShown) {
                        return;
                    }
                }
            } else {
                if (!rotationLocked) {
                    if (mResetOrientationData == null) {
                        mResetOrientationData = new ResetOrientationData();
                        mResetOrientationData.locked = false;
                    }
                }

                boolean shouldLockRotation = !isLandscapeWalletViewShown;
                if (rotationLocked != shouldLockRotation) {
                    // Locks the screen to portrait if the landscape / seascape orientation does not
                    // show the wallet view, so the user doesn't accidentally hide the panel.
                    // This call is posted so that the rotation does not change until post-layout,
                    // otherwise onConfigurationChanged() may not get invoked.
                    mGlobalActionsLayout.post(() ->
                            RotationPolicy.setRotationLockAtAngle(
                            mContext, shouldLockRotation, RotationUtils.ROTATION_NONE));
                }
            }

            // Disable rotation suggestions, if enabled
            setRotationSuggestionsEnabled(false);

            FrameLayout panelContainer =
                    findViewById(com.android.systemui.R.id.global_actions_wallet);
            FrameLayout.LayoutParams panelParams =
                    new FrameLayout.LayoutParams(
                            FrameLayout.LayoutParams.MATCH_PARENT,
                            FrameLayout.LayoutParams.MATCH_PARENT);
            panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
                    com.android.systemui.R.dimen.global_actions_wallet_top_margin);
            View walletView = mWalletViewController.getPanelContent();
            panelContainer.addView(walletView, panelParams);
            // Smooth transitions when wallet is resized, which can happen when a card is added
            ViewGroup root = findViewById(com.android.systemui.R.id.global_actions_grid_root);
            if (root != null) {
                walletView.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> {
                    int oldHeight = ob - ot;
                    int newHeight = b - t;
                    if (oldHeight > 0 && oldHeight != newHeight) {
                        TransitionSet transition = new AutoTransition()
                                .setDuration(250)
                                .setOrdering(TransitionSet.ORDERING_TOGETHER);
                        TransitionManager.beginDelayedTransition(root, transition);
                    }
                });
            }
        }

        @Override
        protected int getLayoutResource() {
            return com.android.systemui.R.layout.global_actions_grid_v2;
        }

        @Override
        protected void initializeLayout() {
            super.initializeLayout();
            mLockMessageContainer = requireViewById(
                    com.android.systemui.R.id.global_actions_lock_message_container);
            mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message);
            initializeWalletView();
            getWindow().setBackgroundDrawable(mBackgroundDrawable);
        }

        @Override
        protected void showDialog() {
            mShowing = true;
            mNotificationShadeWindowController.setRequestTopUi(true, TAG);
            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
                    .commitUpdate(mContext.getDisplayId());

            ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
            root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
                root.setPadding(windowInsets.getStableInsetLeft(),
                        windowInsets.getStableInsetTop(),
                        windowInsets.getStableInsetRight(),
                        windowInsets.getStableInsetBottom());
                return WindowInsets.CONSUMED;
            });

            mBackgroundDrawable.setAlpha(0);
            float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
            ObjectAnimator alphaAnimator =
                    ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
            alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
            alphaAnimator.setDuration(183);
            alphaAnimator.addUpdateListener((animation) -> {
                float animatedValue = animation.getAnimatedFraction();
                int alpha = (int) (animatedValue * mScrimAlpha * 255);
                mBackgroundDrawable.setAlpha(alpha);
                mDepthController.updateGlobalDialogVisibility(animatedValue,
                        mGlobalActionsLayout);
            });

            ObjectAnimator xAnimator =
                    ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
            xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
            xAnimator.setDuration(350);

            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.playTogether(alphaAnimator, xAnimator);
            animatorSet.start();
        }

        @Override
        protected void dismissInternal() {
            super.dismissInternal();
        }

        @Override
        protected void completeDismiss() {
            dismissWallet();
            resetOrientation();
            super.completeDismiss();
        }

        private void dismissWallet() {
            if (mWalletViewController != null) {
                mWalletViewController.onDismissed();
                // The wallet controller should not be re-used after being dismissed.
                mWalletViewController = null;
            }
        }

        private void resetOrientation() {
            if (mResetOrientationData != null) {
                RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked,
                        mResetOrientationData.rotation);
            }
            setRotationSuggestionsEnabled(true);
        }

        @Override
        public void refreshDialog() {
            // ensure dropdown menus are dismissed before re-initializing the dialog
            dismissWallet();
            super.refreshDialog();
        }

        void hideLockMessage() {
            if (mLockMessageContainer.getVisibility() == View.VISIBLE) {
                mLockMessageContainer.animate().alpha(0).setDuration(150).setListener(
                        new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {
                                mLockMessageContainer.setVisibility(View.GONE);
                            }
                        }).start();
            }
        }

        void showLockMessage() {
            Drawable lockIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_lock);
            lockIcon.setTint(mContext.getColor(com.android.systemui.R.color.control_primary_text));
            mLockMessage.setCompoundDrawablesWithIntrinsicBounds(null, lockIcon, null, null);
            mLockMessageContainer.setVisibility(View.VISIBLE);
        }

        private static class ResetOrientationData {
            public boolean locked;
            public int rotation;
        }
    }

    /**
     * Determines whether or not debug mode has been activated for the Global Actions Panel.
     */
    private static boolean isPanelDebugModeEnabled(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
                Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED, 0) == 1;
    }

    /**
     * Determines whether or not the Global Actions menu should be forced to use the newer
     * grid-style layout.
     */
    private static boolean isForceGridEnabled(Context context) {
        return isPanelDebugModeEnabled(context);
    }

    private boolean shouldShowLockMessage(ActionsDialog dialog) {
        return isWalletAvailableAfterUnlock(dialog);
    }

    // Temporary while we move items out of the power menu
    private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) {
        boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                == STRONG_AUTH_REQUIRED_AFTER_BOOT;
        return !mKeyguardStateController.isUnlocked()
                && (!mShowLockScreenCards || isLockedAfterBoot)
                && dialog.isWalletViewAvailable();
    }

    private void onPowerMenuLockScreenSettingsChanged() {
        mShowLockScreenCards = mSecureSettings.getInt(
                Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0;
    }
}
