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

Commit 4c559306 authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "New PermissionsChangedFlow" into main

parents b20ce9ed f7bfbc34
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
        }
    }
}