Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f7bfbc34 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

New PermissionsChangedFlow

Also also clean up LiveDataTestUtil.

Bug: 321163306
Test: unit test
Change-Id: Id2d77f53569619654036e782285cab8931fd5bfc
parent 4846e39b
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 * 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.
@@ -14,37 +14,33 @@
 * limitations under the License.
 */

package com.android.settingslib.spa.testutils
package com.android.settingslib.spaprivileged.model.app

import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.android.settingslib.spaprivileged.framework.common.asUser
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn

fun <T> LiveData<T>.getOrAwaitValue(
    timeout: Long = 1,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {},
): T? {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = Observer<T> { newData ->
        data = newData
        latch.countDown()
    }
    this.observeForever(observer)

    afterObserve()
/**
 * Creates an instance of a cold Flow for permissions changed callback of given [app].
 *
 * An initial element will be always sent.
 */
fun Context.permissionsChangedFlow(app: ApplicationInfo) = callbackFlow {
    val userPackageManager = asUser(app.userHandle).packageManager

    try {
        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(timeout, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }
    } finally {
        this.removeObserver(observer)
    val onPermissionsChangedListener = PackageManager.OnPermissionsChangedListener { uid ->
        if (uid == app.uid) trySend(Unit)
    }
    userPackageManager.addOnPermissionsChangeListener(onPermissionsChangedListener)
    trySend(Unit)

    return data
    awaitClose {
        userPackageManager.removeOnPermissionsChangeListener(onPermissionsChangedListener)
    }
}.conflate().flowOn(Dispatchers.Default)
+81 −0
Original line number Diff line number Diff line
/*
 * 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.settingslib.spaprivileged.model.app

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.spa.testutils.toListWithTimeout
import com.android.settingslib.spaprivileged.framework.common.asUser
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy

@RunWith(AndroidJUnit4::class)
class PermissionsChangedFlowTest {

    private var onPermissionsChangedListener: PackageManager.OnPermissionsChangedListener? = null

    private val mockPackageManager = mock<PackageManager> {
        on { addOnPermissionsChangeListener(any()) } doAnswer {
            onPermissionsChangedListener =
                it.arguments[0] as PackageManager.OnPermissionsChangedListener
        }
    }

    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
        on { asUser(APP.userHandle) } doReturn mock
        on { packageManager } doReturn mockPackageManager
    }

    @Test
    fun permissionsChangedFlow_sendInitialValueTrue() = runBlocking {
        val flow = context.permissionsChangedFlow(APP)

        assertThat(flow.firstWithTimeoutOrNull()).isNotNull()
    }

    @Test
    fun permissionsChangedFlow_collectChanged_getTwo() = runBlocking {
        val listDeferred = async {
            context.permissionsChangedFlow(APP).toListWithTimeout()
        }
        delay(100)

        onPermissionsChangedListener?.onPermissionsChanged(APP.uid)

        assertThat(listDeferred.await()).hasSize(2)
    }

    private companion object {
        val APP = ApplicationInfo().apply {
            packageName = "package.name"
            uid = 10000
        }
    }
}