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

import android.app.Activity;
import android.app.ActivityManager;
import android.app.backup.BackupManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;

import androidx.annotation.VisibleForTesting;

import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.appinfo.AppButtonsPreferenceController;
import com.android.settings.applications.appinfo.ButtonActionDialogFragment;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryHistEntry;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Power usage detail fragment for each app, this fragment contains
 *
 * 1. Detail battery usage information for app(i.e. usage time, usage amount)
 * 2. Battery related controls for app(i.e uninstall, force stop)
 */
public class AdvancedPowerUsageDetail extends DashboardFragment implements
        ButtonActionDialogFragment.AppButtonsDialogListener,
        SelectorWithWidgetPreference.OnClickListener {

    public static final String TAG = "AdvancedPowerDetail";
    public static final String EXTRA_UID = "extra_uid";
    public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
    public static final String EXTRA_FOREGROUND_TIME = "extra_foreground_time";
    public static final String EXTRA_BACKGROUND_TIME = "extra_background_time";
    public static final String EXTRA_SCREEN_ON_TIME = "extra_screen_on_time";
    public static final String EXTRA_SLOT_TIME = "extra_slot_time";
    public static final String EXTRA_LABEL = "extra_label";
    public static final String EXTRA_ICON_ID = "extra_icon_id";
    public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent";
    public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount";

    private static final String KEY_PREF_HEADER = "header_view";
    private static final String KEY_PREF_UNRESTRICTED = "unrestricted_pref";
    private static final String KEY_PREF_OPTIMIZED = "optimized_pref";
    private static final String KEY_PREF_RESTRICTED = "restricted_pref";
    private static final String KEY_FOOTER_PREFERENCE = "app_usage_footer_preference";
    private static final String PACKAGE_NAME_NONE = "none";

    private static final String HEADER_SUMMARY_FORMAT = "%s\n(%s)";

    private static final int REQUEST_UNINSTALL = 0;
    private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;

    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();

    @VisibleForTesting
    LayoutPreference mHeaderPreference;
    @VisibleForTesting
    ApplicationsState mState;
    @VisibleForTesting
    ApplicationsState.AppEntry mAppEntry;
    @VisibleForTesting
    BatteryOptimizeUtils mBatteryOptimizeUtils;
    @VisibleForTesting
    FooterPreference mFooterPreference;
    @VisibleForTesting
    SelectorWithWidgetPreference mRestrictedPreference;
    @VisibleForTesting
    SelectorWithWidgetPreference mOptimizePreference;
    @VisibleForTesting
    SelectorWithWidgetPreference mUnrestrictedPreference;
    @VisibleForTesting
    @BatteryOptimizeUtils.OptimizationMode
    int mOptimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
    @VisibleForTesting
    BackupManager mBackupManager;
    @VisibleForTesting
    StringBuilder mLogStringBuilder;

    private AppButtonsPreferenceController mAppButtonsPreferenceController;

    // A wrapper class to carry LaunchBatteryDetailPage required arguments.
    private static final class LaunchBatteryDetailPageArgs {
        private String mUsagePercent;
        private String mPackageName;
        private String mAppLabel;
        private String mSlotInformation;
        private int mUid;
        private int mIconId;
        private int mConsumedPower;
        private long mForegroundTimeMs;
        private long mBackgroundTimeMs;
        private long mScreenOnTimeMs;
        private boolean mIsUserEntry;
    }

    /** Launches battery details page for an individual battery consumer. */
    public static void startBatteryDetailPage(
            Activity caller, InstrumentedPreferenceFragment fragment,
            BatteryDiffEntry diffEntry, String usagePercent, String slotInformation) {
        startBatteryDetailPage(
                caller, fragment.getMetricsCategory(), diffEntry, usagePercent, slotInformation,
                /*showTimeInformation=*/ true);
    }

    /** Launches battery details page for an individual battery consumer fragment. */
    public static void startBatteryDetailPage(
            Context context, int sourceMetricsCategory,
            BatteryDiffEntry diffEntry, String usagePercent, String slotInformation,
            boolean showTimeInformation) {
        final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
        final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
        // configure the launch argument.
        launchArgs.mUsagePercent = usagePercent;
        launchArgs.mPackageName = diffEntry.getPackageName();
        launchArgs.mAppLabel = diffEntry.getAppLabel();
        launchArgs.mSlotInformation = slotInformation;
        launchArgs.mUid = (int) histEntry.mUid;
        launchArgs.mIconId = diffEntry.getAppIconId();
        launchArgs.mConsumedPower = (int) diffEntry.mConsumePower;
        if (showTimeInformation) {
            launchArgs.mForegroundTimeMs = diffEntry.mForegroundUsageTimeInMs;
            launchArgs.mBackgroundTimeMs = diffEntry.mBackgroundUsageTimeInMs;
            launchArgs.mScreenOnTimeMs = diffEntry.mScreenOnTimeInMs;
        }
        launchArgs.mIsUserEntry = histEntry.isUserEntry();
        startBatteryDetailPage(context, sourceMetricsCategory, launchArgs);
    }

    /** Launches battery details page for an individual battery consumer. */
    public static void startBatteryDetailPage(Activity caller,
            InstrumentedPreferenceFragment fragment, BatteryEntry entry, String usagePercent) {
        final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
        // configure the launch argument.
        launchArgs.mUsagePercent = usagePercent;
        launchArgs.mPackageName = entry.getDefaultPackageName();
        launchArgs.mAppLabel = entry.getLabel();
        launchArgs.mUid = entry.getUid();
        launchArgs.mIconId = entry.mIconId;
        launchArgs.mConsumedPower = (int) entry.getConsumedPower();
        launchArgs.mIsUserEntry = entry.isUserEntry();
        startBatteryDetailPage(caller, fragment.getMetricsCategory(), launchArgs);
    }

    private static void startBatteryDetailPage(
            Context context, int sourceMetricsCategory, LaunchBatteryDetailPageArgs launchArgs) {
        final Bundle args = new Bundle();
        if (launchArgs.mPackageName == null) {
            // populate data for system app
            args.putString(EXTRA_LABEL, launchArgs.mAppLabel);
            args.putInt(EXTRA_ICON_ID, launchArgs.mIconId);
            args.putString(EXTRA_PACKAGE_NAME, null);
        } else {
            // populate data for normal app
            args.putString(EXTRA_PACKAGE_NAME, launchArgs.mPackageName);
        }

        args.putInt(EXTRA_UID, launchArgs.mUid);
        args.putLong(EXTRA_BACKGROUND_TIME, launchArgs.mBackgroundTimeMs);
        args.putLong(EXTRA_FOREGROUND_TIME, launchArgs.mForegroundTimeMs);
        args.putLong(EXTRA_SCREEN_ON_TIME, launchArgs.mScreenOnTimeMs);
        args.putString(EXTRA_SLOT_TIME, launchArgs.mSlotInformation);
        args.putString(EXTRA_POWER_USAGE_PERCENT, launchArgs.mUsagePercent);
        args.putInt(EXTRA_POWER_USAGE_AMOUNT, launchArgs.mConsumedPower);
        final int userId = launchArgs.mIsUserEntry ? ActivityManager.getCurrentUser()
            : UserHandle.getUserId(launchArgs.mUid);

        new SubSettingLauncher(context)
                .setDestination(AdvancedPowerUsageDetail.class.getName())
                .setTitleRes(R.string.battery_details_title)
                .setArguments(args)
                .setSourceMetricsCategory(sourceMetricsCategory)
                .setUserHandle(new UserHandle(userId))
                .launch();
    }

    /**
     * Start packageName's battery detail page.
     */
    public static void startBatteryDetailPage(
            Activity caller, Instrumentable instrumentable, String packageName,
            UserHandle userHandle) {
        final Bundle args = new Bundle(3);
        final PackageManager packageManager = caller.getPackageManager();
        args.putString(EXTRA_PACKAGE_NAME, packageName);
        args.putString(EXTRA_POWER_USAGE_PERCENT, Utils.formatPercentage(0));
        try {
            args.putInt(EXTRA_UID, packageManager.getPackageUid(packageName, 0 /* no flag */));
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Cannot find package: " + packageName, e);
        }

        new SubSettingLauncher(caller)
                .setDestination(AdvancedPowerUsageDetail.class.getName())
                .setTitleRes(R.string.battery_details_title)
                .setArguments(args)
                .setSourceMetricsCategory(instrumentable.getMetricsCategory())
                .setUserHandle(userHandle)
                .launch();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        mState = ApplicationsState.getInstance(getActivity().getApplication());
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME);
        onCreateForTriState(packageName);
        mHeaderPreference = findPreference(KEY_PREF_HEADER);

        if (packageName != null) {
            mAppEntry = mState.getEntry(packageName, UserHandle.myUserId());
        }
    }

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

        initHeader();
        mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode();
        initPreferenceForTriState(getContext());
        mExecutor.execute(() -> {
            String packageName =
                    getLoggingPackageName(getContext(), mBatteryOptimizeUtils.getPackageName());
            FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
                    .action(
                            getContext(),
                            SettingsEnums.OPEN_APP_BATTERY_USAGE,
                            packageName);
        });
        mLogStringBuilder = new StringBuilder("onResume mode = ").append(mOptimizationMode);
    }

    @Override
    public void onPause() {
        super.onPause();

        final int selectedPreference = getSelectedPreference();

        notifyBackupManager();
        mLogStringBuilder.append(", onPause mode = ").append(selectedPreference);
        logMetricCategory(selectedPreference);

        BatteryHistoricalLogUtil.writeLog(
                getContext().getApplicationContext(),
                Action.LEAVE,
                BatteryHistoricalLogUtil.getPackageNameWithUserId(
                        mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()),
                mLogStringBuilder.toString());
        Log.d(TAG, "Leave with mode: " + selectedPreference);
    }

    @VisibleForTesting
    void notifyBackupManager() {
        if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) {
            final BackupManager backupManager = mBackupManager != null
                    ? mBackupManager : new BackupManager(getContext());
            backupManager.dataChanged();
        }
    }

    @VisibleForTesting
    void initHeader() {
        final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header);
        final Activity context = getActivity();
        final Bundle bundle = getArguments();
        EntityHeaderController controller = EntityHeaderController
                .newInstance(context, this, appSnippet)
                .setRecyclerView(getListView(), getSettingsLifecycle())
                .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
                        EntityHeaderController.ActionType.ACTION_NONE);

        if (mAppEntry == null) {
            controller.setLabel(bundle.getString(EXTRA_LABEL));

            final int iconId = bundle.getInt(EXTRA_ICON_ID, 0);
            if (iconId == 0) {
                controller.setIcon(context.getPackageManager().getDefaultActivityIcon());
            } else {
                controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
            }
        } else {
            mState.ensureIcon(mAppEntry);
            controller.setLabel(mAppEntry);
            controller.setIcon(mAppEntry);
            controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info));
        }

        controller.setSummary(getHeaderSummary(bundle));
        controller.done(context, true /* rebindActions */);
    }

    @VisibleForTesting
    void initPreferenceForTriState(Context context) {
        final String stateString;
        final String footerString;

        if (mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()) {
            // Present optimized only string when the package name is invalid.
            stateString = context.getString(R.string.manager_battery_usage_optimized_only);
            footerString = context.getString(
                    R.string.manager_battery_usage_footer_limited, stateString);
        } else if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) {
            // Present unrestricted only string when the package is system or default active app.
            stateString = context.getString(R.string.manager_battery_usage_unrestricted_only);
            footerString = context.getString(
                    R.string.manager_battery_usage_footer_limited, stateString);
        } else {
            // Present default string to normal app.
            footerString = context.getString(R.string.manager_battery_usage_footer);
        }
        mFooterPreference.setTitle(footerString);
        final Intent helpIntent = HelpUtils.getHelpIntent(context, context.getString(
                R.string.help_url_app_usage_settings), /*backupContext=*/ "");
        if (helpIntent != null) {
            mFooterPreference.setLearnMoreAction(v ->
                    startActivityForResult(helpIntent, /*requestCode=*/ 0));
            mFooterPreference.setLearnMoreText(
                    context.getString(R.string.manager_battery_usage_link_a11y));
        }
    }

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL;
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.power_usage_detail;
    }

    @Override
    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        final Bundle bundle = getArguments();
        final int uid = bundle.getInt(EXTRA_UID, 0);
        final String packageName = bundle.getString(EXTRA_PACKAGE_NAME);

        mAppButtonsPreferenceController = new AppButtonsPreferenceController(
                (SettingsActivity) getActivity(), this, getSettingsLifecycle(),
                packageName, mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);
        controllers.add(mAppButtonsPreferenceController);
        controllers.add(new UnrestrictedPreferenceController(context, uid, packageName));
        controllers.add(new OptimizedPreferenceController(context, uid, packageName));
        controllers.add(new RestrictedPreferenceController(context, uid, packageName));

        return controllers;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (mAppButtonsPreferenceController != null) {
            mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    public void handleDialogClick(int id) {
        if (mAppButtonsPreferenceController != null) {
            mAppButtonsPreferenceController.handleDialogClick(id);
        }
    }

    @Override
    public void onRadioButtonClicked(SelectorWithWidgetPreference selected) {
        final String selectedKey = selected.getKey();
        updatePreferenceState(mUnrestrictedPreference, selectedKey);
        updatePreferenceState(mOptimizePreference, selectedKey);
        updatePreferenceState(mRestrictedPreference, selectedKey);
        mBatteryOptimizeUtils.setAppUsageState(getSelectedPreference(), Action.APPLY);
    }

    private void updatePreferenceState(SelectorWithWidgetPreference preference,
            String selectedKey) {
        preference.setChecked(selectedKey.equals(preference.getKey()));
    }

    private void logMetricCategory(int selectedKey) {
        if (selectedKey == mOptimizationMode) {
            return;
        }

        int metricCategory = 0;
        switch (selectedKey) {
            case BatteryOptimizeUtils.MODE_UNRESTRICTED:
                metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_UNRESTRICTED;
                break;
            case BatteryOptimizeUtils.MODE_OPTIMIZED:
                metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_OPTIMIZED;
                break;
            case BatteryOptimizeUtils.MODE_RESTRICTED:
                metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_RESTRICTED;
                break;
        }
        if (metricCategory == 0) {
            return;
        }
        int finalMetricCategory = metricCategory;
        mExecutor.execute(() -> {
            String packageName =
                    getLoggingPackageName(getContext(), mBatteryOptimizeUtils.getPackageName());
            FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
                    .action(
                            /* attribution */ SettingsEnums.OPEN_APP_BATTERY_USAGE,
                            /* action */ finalMetricCategory,
                            /* pageId */ SettingsEnums.OPEN_APP_BATTERY_USAGE,
                            packageName,
                            getArguments().getInt(EXTRA_POWER_USAGE_AMOUNT));
        });
    }

    private void onCreateForTriState(String packageName) {
        mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED);
        mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED);
        mRestrictedPreference = findPreference(KEY_PREF_RESTRICTED);
        mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE);
        mUnrestrictedPreference.setOnClickListener(this);
        mOptimizePreference.setOnClickListener(this);
        mRestrictedPreference.setOnClickListener(this);

        mBatteryOptimizeUtils = new BatteryOptimizeUtils(
                getContext(), getArguments().getInt(EXTRA_UID), packageName);
    }

    private int getSelectedPreference() {
        if (mRestrictedPreference.isChecked()) {
            return BatteryOptimizeUtils.MODE_RESTRICTED;
        } else if (mUnrestrictedPreference.isChecked()) {
            return BatteryOptimizeUtils.MODE_UNRESTRICTED;
        } else if (mOptimizePreference.isChecked()) {
            return BatteryOptimizeUtils.MODE_OPTIMIZED;
        } else {
            return BatteryOptimizeUtils.MODE_UNKNOWN;
        }
    }

    private CharSequence getHeaderSummary(Bundle bundle) {
        final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
        final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
        final long screenOnTimeInMs = bundle.getLong(EXTRA_SCREEN_ON_TIME);
        final String slotTime = bundle.getString(EXTRA_SLOT_TIME, null);
        final String usageSummary = BatteryUtils.buildBatteryUsageTimeSummary(getContext(),
                /* isSystem= */ false, foregroundTimeMs, backgroundTimeMs, screenOnTimeInMs);

        if (usageSummary.isEmpty()) {
            return getText(R.string.battery_usage_without_time);
        } else {
            CharSequence slotSummary = slotTime == null
                    ? getText(R.string.battery_usage_since_last_full_charge) : slotTime;
            return String.format("%s\n(%s)", usageSummary, slotSummary);
        }
    }

    private static String getLoggingPackageName(Context context, String originalPackingName) {
        return BatteryUtils.isAppInstalledFromGooglePlayStore(context, originalPackingName)
                ? originalPackingName : PACKAGE_NAME_NONE;
    }
}
