/*
 * Copyright (C) 2022 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.internal.app;

import android.annotation.StyleRes;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.Html;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.android.internal.R;

/**
 * Dialog responsible for obtaining user consent per-use log access
 */
public class LogAccessDialogActivity extends Activity implements
        View.OnClickListener {
    private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
    public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";


    private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
    private static final int MSG_DISMISS_DIALOG = 0;

    private String mPackageName;
    private int mUid;
    private ILogAccessDialogCallback mCallback;

    private String mAlertTitle;
    private String mAlertBody;
    private String mAlertLearnMore;
    private AlertDialog.Builder mAlertDialog;
    private AlertDialog mAlert;
    private View mAlertView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // retrieve Intent extra information
        if (!readIntentInfo(getIntent())) {
            Slog.e(TAG, "Invalid Intent extras, finishing");
            finish();
            return;
        }

        // retrieve the title string from passed intent extra
        try {
            mAlertTitle = getTitleString(this, mPackageName, mUid);
        } catch (NameNotFoundException e) {
            Slog.e(TAG, "Unable to fetch label of package " + mPackageName, e);
            declineLogAccess();
            finish();
            return;
        }

        mAlertBody = getResources().getString(R.string.log_access_confirmation_body);
        mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);

        // create View
        boolean isDarkTheme = (getResources().getConfiguration().uiMode
                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
        int themeId = isDarkTheme ? android.R.style.Theme_DeviceDefault_Dialog_Alert :
                android.R.style.Theme_DeviceDefault_Light_Dialog_Alert;
        mAlertView = createView(themeId);

        // create AlertDialog
        mAlertDialog = new AlertDialog.Builder(this, themeId);
        mAlertDialog.setView(mAlertView);
        mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
        mAlertDialog.setOnDismissListener(dialog -> finish());

        // show Alert
        mAlert = mAlertDialog.create();
        mAlert.getWindow().setHideOverlayWindows(true);
        mAlert.show();

        // set Alert Timeout
        mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (!isChangingConfigurations() && mAlert != null && mAlert.isShowing()) {
            mAlert.dismiss();
        }
        mAlert = null;
    }

    private boolean readIntentInfo(Intent intent) {
        if (intent == null) {
            Slog.e(TAG, "Intent is null");
            return false;
        }

        mCallback = ILogAccessDialogCallback.Stub.asInterface(
                intent.getExtras().getBinder(EXTRA_CALLBACK));
        if (mCallback == null) {
            Slog.e(TAG, "Missing callback");
            return false;
        }

        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
        if (mPackageName == null || mPackageName.length() == 0) {
            Slog.e(TAG, "Missing package name extra");
            return false;
        }

        if (!intent.hasExtra(Intent.EXTRA_UID)) {
            Slog.e(TAG, "Missing EXTRA_UID");
            return false;
        }

        mUid = intent.getIntExtra(Intent.EXTRA_UID, 0);

        return true;
    }

    private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case MSG_DISMISS_DIALOG:
                    if (mAlert != null) {
                        mAlert.dismiss();
                        mAlert = null;
                        declineLogAccess();
                    }
                    break;

                default:
                    break;
            }
        }
    };

    private String getTitleString(Context context, String callingPackage, int uid)
            throws NameNotFoundException {
        PackageManager pm = context.getPackageManager();

        CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage,
                PackageManager.MATCH_DIRECT_BOOT_AUTO,
                UserHandle.getUserId(uid)).loadLabel(pm);

        String titleString = context.getString(
                com.android.internal.R.string.log_access_confirmation_title, appLabel);

        return titleString;
    }

    private Spannable styleFont(String text) {
        Spannable s = (Spannable) Html.fromHtml(text);
        for (URLSpan span : s.getSpans(0, s.length(), URLSpan.class)) {
            TypefaceSpan typefaceSpan = new TypefaceSpan("google-sans");
            s.setSpan(typefaceSpan, s.getSpanStart(span), s.getSpanEnd(span), 0);
        }
        return s;
    }

    /**
     * Returns the dialog view.
     * If we cannot retrieve the package name, it returns null and we decline the full device log
     * access
     */
    private View createView(@StyleRes int themeId) {
        Context themedContext = new ContextThemeWrapper(this, themeId);
        final View view = LayoutInflater.from(themedContext).inflate(
                R.layout.log_access_user_consent_dialog_permission, null /*root*/);

        if (view == null) {
            throw new InflateException();
        }

        ((TextView) view.findViewById(R.id.log_access_dialog_title))
            .setText(mAlertTitle);

        if (!TextUtils.isEmpty(mAlertLearnMore)) {
            Spannable mSpannableLearnMore = styleFont(mAlertLearnMore);

            ((TextView) view.findViewById(R.id.log_access_dialog_body))
                    .setText(TextUtils.concat(mAlertBody, "\n\n", mSpannableLearnMore));

            ((TextView) view.findViewById(R.id.log_access_dialog_body))
                    .setMovementMethod(LinkMovementMethod.getInstance());
        } else {
            ((TextView) view.findViewById(R.id.log_access_dialog_body))
                    .setText(mAlertBody);
        }

        Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button);
        button_allow.setOnClickListener(this);

        Button button_deny = (Button) view.findViewById(R.id.log_access_dialog_deny_button);
        button_deny.setOnClickListener(this);

        return view;

    }

    @Override
    public void onClick(View view) {
        try {
            switch (view.getId()) {
                case R.id.log_access_dialog_allow_button:
                    mCallback.approveAccessForClient(mUid, mPackageName);
                    finish();
                    break;
                case R.id.log_access_dialog_deny_button:
                    declineLogAccess();
                    finish();
                    break;
            }
        } catch (RemoteException e) {
            finish();
        }
    }

    private void declineLogAccess() {
        try {
            mCallback.declineAccessForClient(mUid, mPackageName);
        } catch (RemoteException e) {
            finish();
        }
    }
}
