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 Original line 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");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -14,37 +14,33 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


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


import androidx.lifecycle.LiveData
import android.content.Context
import androidx.lifecycle.Observer
import android.content.pm.ApplicationInfo
import java.util.concurrent.CountDownLatch
import android.content.pm.PackageManager
import java.util.concurrent.TimeUnit
import com.android.settingslib.spaprivileged.framework.common.asUser
import java.util.concurrent.TimeoutException
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,
 * Creates an instance of a cold Flow for permissions changed callback of given [app].
    timeUnit: TimeUnit = TimeUnit.SECONDS,
 *
    afterObserve: () -> Unit = {},
 * An initial element will be always sent.
): T? {
 */
    var data: T? = null
fun Context.permissionsChangedFlow(app: ApplicationInfo) = callbackFlow {
    val latch = CountDownLatch(1)
    val userPackageManager = asUser(app.userHandle).packageManager
    val observer = Observer<T> { newData ->
        data = newData
        latch.countDown()
    }
    this.observeForever(observer)

    afterObserve()


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


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