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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;

import androidx.test.runner.AndroidJUnit4;

import com.android.phone.slice.SlicePurchaseController;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.net.URL;
import java.util.Locale;

@RunWith(AndroidJUnit4.class)
public class SlicePurchaseBroadcastReceiverTest {
    private static final int PHONE_ID = 0;
    private static final String CARRIER = "Some Carrier";
    private static final String EXTRA = "EXTRA";

    @Mock Intent mIntent;
    @Mock Intent mDataIntent;
    @Mock PendingIntent mPendingIntent;
    @Mock PendingIntent mCanceledIntent;
    @Mock PendingIntent mContentIntent1;
    @Mock PendingIntent mContentIntent2;
    @Mock PendingIntent mNotificationShownIntent;
    @Mock PendingIntent mNotificationsDisabledIntent;
    @Mock Context mContext;
    @Mock Resources mResources;
    @Mock Configuration mConfiguration;
    @Mock NotificationManager mNotificationManager;
    @Mock ApplicationInfo mApplicationInfo;
    @Mock PackageManager mPackageManager;

    private SlicePurchaseBroadcastReceiver mSlicePurchaseBroadcastReceiver;
    private Resources mSpiedResources;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mSpiedResources = spy(Resources.getSystem());

        doReturn("").when(mResources).getString(anyInt());
        doReturn(mNotificationManager).when(mContext)
                .getSystemService(eq(NotificationManager.class));
        doReturn(true).when(mNotificationManager).areNotificationsEnabled();
        doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
        doReturn(mPackageManager).when(mContext).getPackageManager();
        doReturn(mSpiedResources).when(mContext).getResources();

        mSlicePurchaseBroadcastReceiver = spy(new SlicePurchaseBroadcastReceiver());
    }

    @Test
    public void testSendSlicePurchaseAppResponse() throws Exception {
        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
        verify(mPendingIntent, never()).send();

        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
                eq(EXTRA), eq(PendingIntent.class));
        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(mIntent, EXTRA);
        verify(mPendingIntent).send();
    }

    @Test
    public void testSendSlicePurchaseAppResponseWithData() throws Exception {
        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
                mContext, mIntent, EXTRA, mDataIntent);
        verify(mPendingIntent, never()).send(eq(mContext), eq(0), any(Intent.class));

        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
                eq(EXTRA), eq(PendingIntent.class));
        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(
                mContext, mIntent, EXTRA, mDataIntent);
        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
        verify(mPendingIntent).send(eq(mContext), eq(0), captor.capture());
        assertEquals(mDataIntent, captor.getValue());
    }

    @Test
    public void testIsIntentValid() {
        assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));

        // set up intent
        doReturn(PHONE_ID).when(mIntent).getIntExtra(
                eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
                eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
        doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
                eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
        doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
        assertFalse(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));

        // set up pending intent
        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
        doReturn(true).when(mPendingIntent).isBroadcast();
        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
                anyString(), eq(PendingIntent.class));
        assertTrue(SlicePurchaseBroadcastReceiver.isIntentValid(mIntent));
    }

    @Test
    public void testGetPurchaseUrl() {
        String[] invalidUrls = new String[] {
                null,
                "",
                "www.google.com",
                "htt://www.google.com",
                "http//www.google.com",
                "http:/www.google.com",
                "file:///android_asset/",
                "file:///android_asset/slice_store_test.html"
        };

        // test invalid URLs
        for (String url : invalidUrls) {
            URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, null, false);
            assertNull(purchaseUrl);
        }

        // test asset URL
        assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE,
                SlicePurchaseBroadcastReceiver.getPurchaseUrl(
                        SlicePurchaseController.SLICE_PURCHASE_TEST_FILE, null, false).toString());

        // test normal URL
        String validUrl = "http://www.google.com";
        assertEquals(validUrl,
                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, false).toString());

        // test normal URL with user data but no append
        String userData = "encryptedUserData=data";
        assertEquals(validUrl,
                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, false)
                        .toString());

        // test normal URL with user data and append
        assertEquals(validUrl + "?" + userData,
                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, true).toString());

        // test normal URL without user data and append
        assertEquals(validUrl,
                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, true).toString());
    }

    @Test
    public void testDisplayPerformanceBoostNotification() throws Exception {
        displayPerformanceBoostNotification();

        // verify performance boost notification was shown
        ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
        verify(mNotificationManager).notifyAsUser(
                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
                captor.capture(),
                eq(UserHandle.ALL));

        // verify notification fields
        Notification notification = captor.getValue();
        assertEquals(mContentIntent1, notification.contentIntent);
        assertEquals(mPendingIntent, notification.deleteIntent);
        assertEquals(2, notification.actions.length);
        assertEquals(mCanceledIntent, notification.actions[0].actionIntent);
        assertEquals(mContentIntent2, notification.actions[1].actionIntent);

        // verify SlicePurchaseController was notified
        verify(mNotificationShownIntent).send();
    }

    private void displayPerformanceBoostNotification() {
        // set up pending intents
        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mPendingIntent).getCreatorPackage();
        doReturn(true).when(mPendingIntent).isBroadcast();
        doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
                anyString(), eq(PendingIntent.class));
        createValidPendingIntent(mNotificationShownIntent,
                SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
        createValidPendingIntent(mNotificationsDisabledIntent,
                SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);

        // spy notification intents to prevent PendingIntent issues
        doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
                eq(mContext), eq(mIntent), eq(1));
        doReturn(mContentIntent2).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
                eq(mContext), eq(mIntent), eq(2));
        doReturn(mCanceledIntent).when(mSlicePurchaseBroadcastReceiver).createCanceledIntent(
                eq(mContext), eq(mIntent));

        // spy resources to prevent resource not found issues
        doReturn(mResources).when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));

        // send ACTION_START_SLICE_PURCHASE_APP
        doReturn(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP).when(mIntent).getAction();
        doReturn(PHONE_ID).when(mIntent).getIntExtra(
                eq(SlicePurchaseController.EXTRA_PHONE_ID), anyInt());
        doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mIntent).getIntExtra(
                eq(SlicePurchaseController.EXTRA_SUB_ID), anyInt());
        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
        doReturn(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).when(mIntent).getStringExtra(
                eq(SlicePurchaseController.EXTRA_PURCHASE_URL));
        doReturn(CARRIER).when(mIntent).getStringExtra(eq(SlicePurchaseController.EXTRA_CARRIER));
        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
    }

    private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) {
        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage();
        doReturn(true).when(intent).isBroadcast();
        doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class));
    }

    @Test
    public void testNotificationCanceled() {
        displayPerformanceBoostNotification();

        // send ACTION_NOTIFICATION_CANCELED
        doReturn("com.android.phone.slice.action.NOTIFICATION_CANCELED").when(mIntent).getAction();
        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);

        // verify notification was canceled
        verify(mNotificationManager).cancelAsUser(
                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
                eq(UserHandle.ALL));
    }

    @Test
    public void testNotificationTimeout() {
        displayPerformanceBoostNotification();

        // send ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT
        doReturn(SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT).when(mIntent)
                .getAction();
        doReturn(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY).when(mIntent).getIntExtra(
                eq(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY), anyInt());
        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);

        // verify notification was canceled
        verify(mNotificationManager).cancelAsUser(
                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
                eq(UserHandle.ALL));
    }

    @Test
    public void testLocaleChanged() throws Exception {
        // get previous locale
        doReturn(mConfiguration).when(mSpiedResources).getConfiguration();
        Locale before = getLocale();

        // display notification
        displayPerformanceBoostNotification();
        clearInvocations(mNotificationManager);
        clearInvocations(mNotificationShownIntent);

        // change current locale from previous value
        Locale newLocale = Locale.forLanguageTag("en-US");
        if (before.equals(newLocale)) {
            newLocale = Locale.forLanguageTag("ko-KR");
        }
        doReturn(newLocale).when(mSlicePurchaseBroadcastReceiver).getCurrentLocale();

        // send ACTION_LOCALE_CHANGED
        doReturn(Intent.ACTION_LOCALE_CHANGED).when(mIntent).getAction();
        mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);

        // verify notification was updated and SlicePurchaseController was not notified
        verify(mNotificationManager).cancelAsUser(
                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
                eq(UserHandle.ALL));
        verify(mNotificationManager).notifyAsUser(
                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
                any(Notification.class),
                eq(UserHandle.ALL));
        verify(mNotificationShownIntent, never()).send();

        // verify locale was changed successfully
        doCallRealMethod().when(mSlicePurchaseBroadcastReceiver).getResources(eq(mContext));
        assertEquals(newLocale, getLocale());
    }

    private Locale getLocale() {
        try {
            mSlicePurchaseBroadcastReceiver.getResources(mContext);
            fail("getLocale should not have completed successfully.");
        } catch (NullPointerException expected) { }
        ArgumentCaptor<Locale> captor = ArgumentCaptor.forClass(Locale.class);
        verify(mConfiguration).setLocale(captor.capture());
        clearInvocations(mConfiguration);
        return captor.getValue();
    }

    @Test
    public void testNotificationsDisabled() throws Exception {
        doReturn(false).when(mNotificationManager).areNotificationsEnabled();

        displayPerformanceBoostNotification();

        // verify notification was not shown
        verify(mNotificationManager, never()).notifyAsUser(
                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
                any(Notification.class),
                eq(UserHandle.ALL));
        verify(mNotificationShownIntent, never()).send();

        // verify SlicePurchaseController was notified that notifications are disabled
        verify(mNotificationsDisabledIntent).send();
    }
}
