/*
 * Copyright (C) 2024 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.launcher3.pm

import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo.FLAG_INSTALLED
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Process.myUserHandle
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.PROMISE_ICON_IDS
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.IntArray
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class InstallSessionHelperTest {

    private val launcherModelHelper = LauncherModelHelper()
    private val sandboxContext = spy(launcherModelHelper.sandboxContext)
    private val packageManager = sandboxContext.packageManager
    private val expectedAppPackage = "expectedAppPackage"
    private val expectedInstallerPackage = sandboxContext.packageName
    private val mockPackageInstaller: PackageInstaller = mock()

    private lateinit var installSessionHelper: InstallSessionHelper
    private lateinit var launcherApps: LauncherApps

    @Before
    fun setup() {
        whenever(packageManager.packageInstaller).thenReturn(mockPackageInstaller)
        launcherApps = sandboxContext.spyService(LauncherApps::class.java)
        installSessionHelper = InstallSessionHelper(sandboxContext)
    }

    @Test
    fun `getActiveSessions fetches verified install sessions from LauncherApps`() {
        // Given
        val expectedVerifiedSession1 =
            PackageInstaller.SessionInfo().apply {
                sessionId = 0
                installerPackageName = expectedInstallerPackage
                appPackageName = expectedAppPackage
                userId = myUserHandle().identifier
            }
        val expectedVerifiedSession2 =
            PackageInstaller.SessionInfo().apply {
                sessionId = 1
                installerPackageName = expectedInstallerPackage
                appPackageName = "app2"
                userId = myUserHandle().identifier
            }
        val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
        whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
        // When
        val actualSessions = installSessionHelper.getActiveSessions()
        // Then
        assertThat(actualSessions.values.toList()).isEqualTo(expectedSessions)
    }

    @Test
    fun `getActiveSessionInfo fetches verified install sessions for given user and pkg`() {
        // Given
        val expectedVerifiedSession =
            PackageInstaller.SessionInfo().apply {
                installerPackageName = expectedInstallerPackage
                appPackageName = expectedAppPackage
                userId = myUserHandle().identifier
            }
        whenever(launcherApps.allPackageInstallerSessions)
            .thenReturn(listOf(expectedVerifiedSession))
        // When
        val actualSession =
            installSessionHelper.getActiveSessionInfo(myUserHandle(), expectedAppPackage)
        // Then
        assertThat(actualSession).isEqualTo(expectedVerifiedSession)
    }

    @Test
    fun `getVerifiedSessionInfo verifies and returns session for given id`() {
        // Given
        val expectedSession =
            PackageInstaller.SessionInfo().apply {
                sessionId = 1
                installerPackageName = expectedInstallerPackage
                appPackageName = expectedAppPackage
                userId = myUserHandle().identifier
            }
        whenever(mockPackageInstaller.getSessionInfo(1)).thenReturn(expectedSession)
        // When
        val actualSession = installSessionHelper.getVerifiedSessionInfo(1)
        // Then
        assertThat(actualSession).isEqualTo(expectedSession)
    }

    @Test
    fun `isTrustedPackage returns true if LauncherApps finds ApplicationInfo`() {
        // Given
        val expectedApplicationInfo =
            ApplicationInfo().apply { flags = FLAG_SYSTEM or FLAG_INSTALLED }
        doReturn(expectedApplicationInfo)
            .whenever(launcherApps)
            .getApplicationInfo(eq(expectedAppPackage), any(), eq(UserHandle(0)))
        // When
        val actualResult = installSessionHelper.isTrustedPackage(expectedAppPackage, UserHandle(0))
        // Then
        assertThat(actualResult).isTrue()
    }

    @Test
    fun `getAllVerifiedSessions verifies and returns all active install sessions`() {
        // Given
        val expectedVerifiedSession1 =
            PackageInstaller.SessionInfo().apply {
                sessionId = 0
                installerPackageName = expectedInstallerPackage
                appPackageName = expectedAppPackage
                userId = myUserHandle().identifier
            }
        val expectedVerifiedSession2 =
            PackageInstaller.SessionInfo().apply {
                sessionId = 1
                installerPackageName = expectedInstallerPackage
                appPackageName = "app2"
                userId = myUserHandle().identifier
            }
        val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
        whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
        // When
        val actualSessions = installSessionHelper.allVerifiedSessions
        // Then
        assertThat(actualSessions).isEqualTo(expectedSessions)
    }

    @Test
    fun `promiseIconAddedForId returns true if there is a promiseIcon with the session id`() {
        // Given
        val expectedIdString = IntArray().apply { add(1) }.toConcatString()
        LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
        val expectedSession =
            PackageInstaller.SessionInfo().apply {
                sessionId = 1
                installerPackageName = expectedInstallerPackage
                appPackageName = "app2"
                userId = myUserHandle().identifier
            }
        whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
        // When
        var actualResult = false
        TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
            actualResult = installSessionHelper.promiseIconAddedForId(1)
        }
        // Then
        assertThat(actualResult).isTrue()
    }

    @Test
    fun `removePromiseIconId removes promiseIconId for given Session id`() {
        // Given
        val expectedIdString = IntArray().apply { add(1) }.toConcatString()
        LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
        val expectedSession =
            PackageInstaller.SessionInfo().apply {
                sessionId = 1
                installerPackageName = expectedInstallerPackage
                appPackageName = "app2"
                userId = myUserHandle().identifier
            }
        whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
        // When
        var actualResult = true
        TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
            installSessionHelper.removePromiseIconId(1)
            actualResult = installSessionHelper.promiseIconAddedForId(1)
        }
        // Then
        assertThat(actualResult).isFalse()
    }

    @Test
    fun `tryQueuePromiseAppIcon will update promise icon ids from eligible sessions`() {
        // Given
        val expectedIdString = IntArray().apply { add(1) }.toConcatString()
        LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
        val expectedSession =
            PackageInstaller.SessionInfo().apply {
                sessionId = 1
                installerPackageName = expectedInstallerPackage
                appPackageName = "appPackage"
                userId = myUserHandle().identifier
                appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
                appLabel = "appLabel"
                installReason = PackageManager.INSTALL_REASON_USER
            }
        whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
        // When
        var wasPromiseIconAdded = false
        var actualPromiseIconIds = ""
        TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
            installSessionHelper.removePromiseIconId(1)
            installSessionHelper.tryQueuePromiseAppIcon(expectedSession)
            wasPromiseIconAdded = installSessionHelper.promiseIconAddedForId(1)
            actualPromiseIconIds = LauncherPrefs.get(sandboxContext).get(PROMISE_ICON_IDS)
        }
        // Then
        assertThat(wasPromiseIconAdded).isTrue()
        assertThat(actualPromiseIconIds).isEqualTo(expectedIdString)
    }

    @Test
    fun `verifySessionInfo is true if can verify given SessionInfo`() {
        // Given
        val expectedIdString = IntArray().apply { add(1) }.toConcatString()
        LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString))
        val expectedSession =
            PackageInstaller.SessionInfo().apply {
                sessionId = 1
                installerPackageName = expectedInstallerPackage
                appPackageName = "appPackage"
                userId = myUserHandle().identifier
                appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
                appLabel = "appLabel"
                installReason = PackageManager.INSTALL_REASON_USER
            }
        whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
        // When
        var actualResult = false
        TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
            actualResult = installSessionHelper.verifySessionInfo(expectedSession)
        }
        // Then
        assertThat(actualResult).isTrue()
    }
}
