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

import static android.app.NotificationManager.IMPORTANCE_NONE;

import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.systemui.R;

import java.lang.IllegalArgumentException;
import java.util.List;
import java.util.Set;

/**
 * The guts of a notification revealed when performing a long press.
 */
public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
    private static final String TAG = "InfoGuts";

    private INotificationManager mINotificationManager;
    private String mPkg;
    private String mAppName;
    private int mAppUid;
    private List<NotificationChannel> mNotificationChannels;
    private NotificationChannel mSingleNotificationChannel;
    private boolean mIsSingleDefaultChannel;
    private StatusBarNotification mSbn;
    private int mStartingUserImportance;

    private TextView mNumChannelsView;
    private View mChannelDisabledView;
    private TextView mSettingsLinkView;
    private Switch mChannelEnabledSwitch;
    private CheckSaveListener mCheckSaveListener;
    private OnAppSettingsClickListener mAppSettingsClickListener;
    private PackageManager mPm;

    private NotificationGuts mGutsContainer;

    public NotificationInfo(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // Specify a CheckSaveListener to override when/if the user's changes are committed.
    public interface CheckSaveListener {
        // Invoked when importance has changed and the NotificationInfo wants to try to save it.
        // Listener should run saveImportance unless the change should be canceled.
        void checkSave(Runnable saveImportance);
    }

    public interface OnSettingsClickListener {
        void onClick(View v, NotificationChannel channel, int appUid);
    }

    public interface OnAppSettingsClickListener {
        void onClick(View v, Intent intent);
    }

    public void bindNotification(final PackageManager pm,
            final INotificationManager iNotificationManager,
            final String pkg,
            final List<NotificationChannel> notificationChannels,
            int startingUserImportance,
            final StatusBarNotification sbn,
            OnSettingsClickListener onSettingsClick,
            OnAppSettingsClickListener onAppSettingsClick,
            OnClickListener onDoneClick,
            CheckSaveListener checkSaveListener,
            final Set<String> nonBlockablePkgs)
            throws RemoteException {
        mINotificationManager = iNotificationManager;
        mPkg = pkg;
        mNotificationChannels = notificationChannels;
        mCheckSaveListener = checkSaveListener;
        mSbn = sbn;
        mPm = pm;
        mAppSettingsClickListener = onAppSettingsClick;
        mStartingUserImportance = startingUserImportance;
        mAppName = mPkg;
        Drawable pkgicon = null;
        CharSequence channelNameText = "";
        ApplicationInfo info = null;
        try {
            info = pm.getApplicationInfo(mPkg,
                    PackageManager.MATCH_UNINSTALLED_PACKAGES
                            | PackageManager.MATCH_DISABLED_COMPONENTS
                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
            if (info != null) {
                mAppUid = info.uid;
                mAppName = String.valueOf(pm.getApplicationLabel(info));
                pkgicon = pm.getApplicationIcon(info);
            }
        } catch (PackageManager.NameNotFoundException e) {
            // app is gone, just show package name and generic icon
            pkgicon = pm.getDefaultActivityIcon();
        }
        ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);

        int numTotalChannels = iNotificationManager.getNumNotificationChannelsForPackage(
                pkg, mAppUid, false /* includeDeleted */);
        if (mNotificationChannels.isEmpty()) {
            throw new IllegalArgumentException("bindNotification requires at least one channel");
        } else  {
            if (mNotificationChannels.size() == 1) {
                mSingleNotificationChannel = mNotificationChannels.get(0);
                // Special behavior for the Default channel if no other channels have been defined.
                mIsSingleDefaultChannel =
                        (mSingleNotificationChannel.getId()
                                .equals(NotificationChannel.DEFAULT_CHANNEL_ID) &&
                        numTotalChannels <= 1);
            } else {
                mSingleNotificationChannel = null;
                mIsSingleDefaultChannel = false;
            }
        }

        String channelsDescText;
        mNumChannelsView = findViewById(R.id.num_channels_desc);
        if (mIsSingleDefaultChannel) {
            channelsDescText = mContext.getString(R.string.notification_default_channel_desc);
        } else {
            switch (mNotificationChannels.size()) {
                case 1:
                    channelsDescText = String.format(mContext.getResources().getQuantityString(
                            R.plurals.notification_num_channels_desc, numTotalChannels),
                            numTotalChannels);
                    break;
                case 2:
                    channelsDescText = mContext.getString(
                            R.string.notification_channels_list_desc_2,
                            mNotificationChannels.get(0).getName(),
                            mNotificationChannels.get(1).getName());
                    break;
                default:
                    final int numOthers = mNotificationChannels.size() - 2;
                    channelsDescText = String.format(
                            mContext.getResources().getQuantityString(
                                    R.plurals.notification_channels_list_desc_2_and_others,
                                    numOthers),
                            mNotificationChannels.get(0).getName(),
                            mNotificationChannels.get(1).getName(),
                            numOthers);
            }
        }
        mNumChannelsView.setText(channelsDescText);

        if (mSingleNotificationChannel == null) {
            // Multiple channels don't use a channel name for the title.
            channelNameText = mContext.getString(R.string.notification_num_channels,
                    mNotificationChannels.size());
        } else if (mIsSingleDefaultChannel) {
            // If this is the default channel, don't use our channel-specific text.
            channelNameText = mContext.getString(R.string.notification_header_default_channel);
        } else {
            channelNameText = mSingleNotificationChannel.getName();
        }
        ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
        ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);

        // Set group information if this channel has an associated group.
        CharSequence groupName = null;
        if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
            final NotificationChannelGroup notificationChannelGroup =
                    iNotificationManager.getNotificationChannelGroupForPackage(
                            mSingleNotificationChannel.getGroup(), pkg, mAppUid);
            if (notificationChannelGroup != null) {
                groupName = notificationChannelGroup.getName();
            }
        }
        TextView groupNameView = ((TextView) findViewById(R.id.group_name));
        TextView groupDividerView = ((TextView) findViewById(R.id.pkg_group_divider));
        if (groupName != null) {
            groupNameView.setText(groupName);
            groupNameView.setVisibility(View.VISIBLE);
            groupDividerView.setVisibility(View.VISIBLE);
        } else {
            groupNameView.setVisibility(View.GONE);
            groupDividerView.setVisibility(View.GONE);
        }

        boolean nonBlockable = false;
        try {
            final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
            nonBlockable = Utils.isSystemPackage(getResources(), pm, pkgInfo)
                    && (mSingleNotificationChannel == null
                    || !mSingleNotificationChannel.isBlockableSystem());
        } catch (PackageManager.NameNotFoundException e) {
            // unlikely.
        }
        if (nonBlockablePkgs != null) {
            nonBlockable |= nonBlockablePkgs.contains(pkg);
        }

        bindButtons(nonBlockable);

        // Top-level importance group
        mChannelDisabledView = findViewById(R.id.channel_disabled);
        updateSecondaryText();

        // Settings button.
        final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
        if (mAppUid >= 0 && onSettingsClick != null) {
            settingsButton.setVisibility(View.VISIBLE);
            final int appUidF = mAppUid;
            settingsButton.setOnClickListener(
                    (View view) -> {
                        onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF);
                    });
            if (numTotalChannels > 1) {
                settingsButton.setText(R.string.notification_all_categories);
            } else {
                settingsButton.setText(R.string.notification_more_settings);
            }

        } else {
            settingsButton.setVisibility(View.GONE);
        }

        // Done button.
        final TextView doneButton = (TextView) findViewById(R.id.done);
        doneButton.setText(R.string.notification_done);
        doneButton.setOnClickListener(onDoneClick);

        // Optional settings link
        updateAppSettingsLink();
    }

    private boolean hasImportanceChanged() {
        return mSingleNotificationChannel != null &&
                mChannelEnabledSwitch != null &&
                mStartingUserImportance != getSelectedImportance();
    }

    private void saveImportance() {
        if (!hasImportanceChanged()) {
            return;
        }
        final int selectedImportance = getSelectedImportance();
        MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
                selectedImportance - mStartingUserImportance);
        mSingleNotificationChannel.setImportance(selectedImportance);
        mSingleNotificationChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
        try {
            mINotificationManager.updateNotificationChannelForPackage(
                    mPkg, mAppUid, mSingleNotificationChannel);
        } catch (RemoteException e) {
            // :(
        }
    }

    private int getSelectedImportance() {
        if (!mChannelEnabledSwitch.isChecked()) {
            return IMPORTANCE_NONE;
        } else {
            return mStartingUserImportance;
        }
    }

    private void bindButtons(final boolean nonBlockable) {
        // Enabled Switch
        mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
        mChannelEnabledSwitch.setChecked(
                mStartingUserImportance != IMPORTANCE_NONE);
        final boolean visible = !nonBlockable && mSingleNotificationChannel != null;
        mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);

        // Callback when checked.
        mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (mGutsContainer != null) {
                mGutsContainer.resetFalsingCheck();
            }
            updateSecondaryText();
            updateAppSettingsLink();
        });
    }

    @Override
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        if (mGutsContainer != null &&
                event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (mGutsContainer.isExposed()) {
                event.getText().add(mContext.getString(
                        R.string.notification_channel_controls_opened_accessibility, mAppName));
            } else {
                event.getText().add(mContext.getString(
                        R.string.notification_channel_controls_closed_accessibility, mAppName));
            }
        }
    }

    private void updateSecondaryText() {
        final boolean disabled = mSingleNotificationChannel != null &&
                getSelectedImportance() == IMPORTANCE_NONE;
        if (disabled) {
            mChannelDisabledView.setVisibility(View.VISIBLE);
            mNumChannelsView.setVisibility(View.GONE);
        } else {
            mChannelDisabledView.setVisibility(View.GONE);
            mNumChannelsView.setVisibility(mIsSingleDefaultChannel ? View.INVISIBLE : View.VISIBLE);
        }
    }

    private void updateAppSettingsLink() {
        mSettingsLinkView = findViewById(R.id.app_settings);
        Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel,
                mSbn.getId(), mSbn.getTag());
        if (settingsIntent != null && getSelectedImportance() != IMPORTANCE_NONE
                && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
            mSettingsLinkView.setVisibility(View.VISIBLE);
            mSettingsLinkView.setText(mContext.getString(R.string.notification_app_settings,
                    mSbn.getNotification().getSettingsText()));
            mSettingsLinkView.setOnClickListener((View view) -> {
                mAppSettingsClickListener.onClick(view, settingsIntent);
            });
        } else {
            mSettingsLinkView.setVisibility(View.GONE);
        }
    }

    private Intent getAppSettingsIntent(PackageManager pm, String packageName,
            NotificationChannel channel, int id, String tag) {
        Intent intent = new Intent(Intent.ACTION_MAIN)
                .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
                .setPackage(packageName);
        final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
                intent,
                PackageManager.MATCH_DEFAULT_ONLY
        );
        if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
            return null;
        }
        final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
        intent.setClassName(activityInfo.packageName, activityInfo.name);
        if (channel != null) {
            intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
        }
        intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
        intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
        return intent;
    }

    @Override
    public void setGutsParent(NotificationGuts guts) {
        mGutsContainer = guts;
    }

    @Override
    public boolean willBeRemoved() {
        return mChannelEnabledSwitch != null && !mChannelEnabledSwitch.isChecked();
    }

    @Override
    public View getContentView() {
        return this;
    }

    @Override
    public boolean handleCloseControls(boolean save, boolean force) {
        if (save && hasImportanceChanged()) {
            if (mCheckSaveListener != null) {
                mCheckSaveListener.checkSave(() -> { saveImportance(); });
            } else {
                saveImportance();
            }
        }
        return false;
    }

    @Override
    public int getActualHeight() {
        return getHeight();
    }
}
