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

Commit 9ecbc317 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge "Add build number" into main

parents f9e467f3 2c513981
Loading
Loading
Loading
Loading
+167 −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.systemui.development.data.repository

import android.content.pm.UserInfo
import android.os.Build
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.stub

@RunWith(AndroidJUnit4::class)
@SmallTest
class DevelopmentSettingRepositoryTest : SysuiTestCase() {

    private val kosmos = testKosmos()

    private val underTest = kosmos.developmentSettingRepository

    @Test
    fun nonAdminUser_unrestricted_neverDevelopmentEnabled() =
        with(kosmos) {
            testScope.runTest {
                val userInfo = nonAdminUserInfo
                val settingEnabled by
                    collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))

                setUserRestriction(userInfo.userHandle, restricted = false)

                assertThat(settingEnabled).isFalse()

                setSettingValue(false)
                assertThat(settingEnabled).isFalse()

                setSettingValue(true)
                assertThat(settingEnabled).isFalse()
            }
        }

    @Test
    fun nonAdminUser_restricted_neverDevelopmentEnabled() =
        with(kosmos) {
            testScope.runTest {
                val userInfo = nonAdminUserInfo
                val settingEnabled by
                    collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))

                setUserRestriction(userInfo.userHandle, restricted = true)

                assertThat(settingEnabled).isFalse()

                setSettingValue(false)
                assertThat(settingEnabled).isFalse()

                setSettingValue(true)
                assertThat(settingEnabled).isFalse()
            }
        }

    @Test
    fun adminUser_unrestricted_defaultValueOfSetting() =
        with(kosmos) {
            testScope.runTest {
                val userInfo = adminUserInfo
                val settingEnabled by
                    collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))

                setUserRestriction(userInfo.userHandle, restricted = false)

                val defaultValue = Build.TYPE == "eng"

                assertThat(settingEnabled).isEqualTo(defaultValue)
            }
        }

    @Test
    fun adminUser_unrestricted_enabledTracksSetting() =
        with(kosmos) {
            testScope.runTest {
                val userInfo = adminUserInfo
                val settingEnabled by
                    collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))

                setUserRestriction(userInfo.userHandle, restricted = false)

                setSettingValue(false)
                assertThat(settingEnabled).isFalse()

                setSettingValue(true)
                assertThat(settingEnabled).isTrue()
            }
        }

    @Test
    fun adminUser_restricted_neverDevelopmentEnabled() =
        with(kosmos) {
            testScope.runTest {
                val userInfo = adminUserInfo
                val settingEnabled by
                    collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))

                setUserRestriction(userInfo.userHandle, restricted = true)

                assertThat(settingEnabled).isFalse()

                setSettingValue(false)
                assertThat(settingEnabled).isFalse()

                setSettingValue(true)
                assertThat(settingEnabled).isFalse()
            }
        }

    private companion object {
        const val USER_RESTRICTION = UserManager.DISALLOW_DEBUGGING_FEATURES
        const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED

        val adminUserInfo =
            UserInfo(
                /* id= */ 10,
                /* name= */ "",
                /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
            )
        val nonAdminUserInfo =
            UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL)

        fun Kosmos.setUserRestriction(userHandle: UserHandle, restricted: Boolean) {
            userManager.stub {
                on { hasUserRestrictionForUser(eq(USER_RESTRICTION), eq(userHandle)) } doReturn
                    restricted
            }
        }

        fun Kosmos.setSettingValue(enabled: Boolean) {
            fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0)
        }
    }
}
+138 −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.systemui.development.domain.interactor

import android.content.ClipData
import android.content.ClipDescription
import android.content.clipboardManager
import android.content.pm.UserInfo
import android.content.res.mainResources
import android.os.Build
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.development.shared.model.BuildNumber
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.settings.fakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.verify

@RunWith(AndroidJUnit4::class)
@SmallTest
class BuildNumberInteractorTest : SysuiTestCase() {

    private val kosmos =
        testKosmos().apply {
            fakeUserRepository.setUserInfos(listOf(adminUserInfo, nonAdminUserInfo))
        }

    private val expectedBuildNumber =
        BuildNumber(
            kosmos.mainResources.getString(
                R.string.bugreport_status,
                Build.VERSION.RELEASE_OR_CODENAME,
                Build.ID,
            )
        )

    private val clipLabel =
        kosmos.mainResources.getString(
            com.android.systemui.res.R.string.build_number_clip_data_label
        )

    private val underTest = kosmos.buildNumberInteractor

    @Test
    fun nonAdminUser_settingEnabled_buildNumberNull() =
        with(kosmos) {
            testScope.runTest {
                val buildNumber by collectLastValue(underTest.buildNumber)

                fakeUserRepository.setSelectedUserInfo(nonAdminUserInfo)
                setSettingValue(true)

                assertThat(buildNumber).isNull()
            }
        }

    @Test
    fun adminUser_buildNumberCorrect_onlyWhenSettingEnabled() =
        with(kosmos) {
            testScope.runTest {
                val buildNumber by collectLastValue(underTest.buildNumber)

                fakeUserRepository.setSelectedUserInfo(adminUserInfo)

                setSettingValue(false)
                assertThat(buildNumber).isNull()

                setSettingValue(true)
                assertThat(buildNumber).isEqualTo(expectedBuildNumber)
            }
        }

    @Test
    fun copyToClipboard() =
        with(kosmos) {
            testScope.runTest {
                fakeUserRepository.setSelectedUserInfo(adminUserInfo)

                underTest.copyBuildNumber()
                runCurrent()

                val argumentCaptor = argumentCaptor<ClipData>()

                verify(clipboardManager).setPrimaryClip(argumentCaptor.capture())

                with(argumentCaptor.firstValue) {
                    assertThat(description.label).isEqualTo(clipLabel)
                    assertThat(description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN))
                        .isTrue()
                    assertThat(itemCount).isEqualTo(1)
                    assertThat(getItemAt(0).text).isEqualTo(expectedBuildNumber.value)
                }
            }
        }

    private companion object {
        const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED

        val adminUserInfo =
            UserInfo(
                /* id= */ 10,
                /* name= */ "",
                /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
            )
        val nonAdminUserInfo =
            UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL)

        fun Kosmos.setSettingValue(enabled: Boolean) {
            fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0)
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -3214,6 +3214,9 @@
    <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]-->
    <string name="build_number_copy_toast">Build number copied to clipboard.</string>

    <!-- Text for accessibility action for copying content to clipboard [CHAR LIMIT=NONE]-->
    <string name="copy_to_clipboard_a11y_action">copy to clipboard.</string>

     <!-- Status for conversation without interaction data [CHAR LIMIT=120] -->
    <string name="basic_status">Open conversation</string>
    <!--Title text for Conversation widget set up screen [CHAR LIMIT=180] -->
+79 −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.systemui.development.data.repository

import android.content.pm.UserInfo
import android.os.Build
import android.os.UserManager
import android.provider.Settings
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext

@SysUISingleton
class DevelopmentSettingRepository
@Inject
constructor(
    private val globalSettings: GlobalSettings,
    private val userManager: UserManager,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
    private val settingFlow = globalSettings.observerFlow(SETTING)

    /**
     * Indicates whether development settings is enabled for this user. The conditions are:
     * * Setting is enabled (defaults to true in eng builds)
     * * User is an admin
     * * User is not restricted from Debugging features.
     */
    fun isDevelopmentSettingEnabled(userInfo: UserInfo): Flow<Boolean> {
        return settingFlow
            .emitOnStart()
            .map { checkDevelopmentSettingEnabled(userInfo) }
            .flowOn(backgroundDispatcher)
    }

    private suspend fun checkDevelopmentSettingEnabled(userInfo: UserInfo): Boolean {
        val hasUserRestriction =
            withContext(backgroundDispatcher) {
                userManager.hasUserRestrictionForUser(
                    UserManager.DISALLOW_DEBUGGING_FEATURES,
                    userInfo.userHandle,
                )
            }
        val isSettingEnabled =
            withContext(backgroundDispatcher) {
                globalSettings.getInt(SETTING, DEFAULT_ENABLED) != 0
            }
        val isAdmin = userInfo.isAdmin
        return isAdmin && !hasUserRestriction && isSettingEnabled
    }

    private companion object {
        val DEFAULT_ENABLED = if (Build.TYPE == "eng") 1 else 0

        const val SETTING = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED
    }
}
+89 −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.systemui.development.domain.interactor

import android.content.ClipData
import android.content.ClipboardManager
import android.content.res.Resources
import android.os.Build
import android.os.UserHandle
import com.android.internal.R as InternalR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.development.data.repository.DevelopmentSettingRepository
import com.android.systemui.development.shared.model.BuildNumber
import com.android.systemui.res.R as SystemUIR
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.utils.UserScopedService
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext

@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class BuildNumberInteractor
@Inject
constructor(
    repository: DevelopmentSettingRepository,
    @Main resources: Resources,
    private val userRepository: UserRepository,
    private val clipboardManagerProvider: UserScopedService<ClipboardManager>,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {

    /**
     * Build number, or `null` if Development Settings is not enabled for the current user.
     *
     * @see DevelopmentSettingRepository.isDevelopmentSettingEnabled
     */
    val buildNumber: Flow<BuildNumber?> =
        userRepository.selectedUserInfo
            .flatMapConcat { userInfo -> repository.isDevelopmentSettingEnabled(userInfo) }
            .map { enabled -> buildText.takeIf { enabled } }

    private val buildText =
        BuildNumber(
            resources.getString(
                InternalR.string.bugreport_status,
                Build.VERSION.RELEASE_OR_CODENAME,
                Build.ID,
            )
        )

    private val clipLabel = resources.getString(SystemUIR.string.build_number_clip_data_label)

    private val currentUserHandle: UserHandle
        get() = userRepository.getSelectedUserInfo().userHandle

    /**
     * Copy to the clipboard the build number for the current user.
     *
     * This can be performed regardless of the current user having Development Settings enabled
     */
    suspend fun copyBuildNumber() {
        withContext(backgroundDispatcher) {
            clipboardManagerProvider
                .forUser(currentUserHandle)
                .setPrimaryClip(ClipData.newPlainText(clipLabel, buildText.value))
        }
    }
}
Loading