diff --git a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt index 0908f9eaa85..6ca2776e06d 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt +++ b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt @@ -49,7 +49,7 @@ class FirmwareVersionScreen : PreferenceScreenCreator, PreferenceSummaryProvider preferenceHierarchy(this) { +PreferenceWidget("os_firmware_version", R.string.firmware_version) +PreferenceWidget("security_key", R.string.security_patch) - +PreferenceWidget("module_version", R.string.module_version) + +MainlineModuleVersionPreference() +BasebandVersionPreference() +KernelVersionPreference() +SimpleBuildNumberPreference() diff --git a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreference.kt b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreference.kt new file mode 100644 index 00000000000..fae1a94d8ed --- /dev/null +++ b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreference.kt @@ -0,0 +1,122 @@ +/* + * 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.settings.deviceinfo.firmwareversion + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.text.format.DateFormat +import android.util.Log +import androidx.preference.Preference +import com.android.settings.R +import com.android.settings.flags.Flags +import com.android.settings.utils.getLocale +import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceSummaryProvider +import com.android.settingslib.preference.PreferenceBinding +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.TimeZone + +// LINT.IfChange +class MainlineModuleVersionPreference : + PreferenceMetadata, + PreferenceSummaryProvider, + PreferenceAvailabilityProvider, + PreferenceBinding { + + private var moduleVersion: String? = null + + override val key: String + get() = "module_version" + + override val title: Int + get() = R.string.module_version + + override fun getSummary(context: Context): CharSequence? { + val version = getModuleVersion(context) + if (version.isEmpty()) return null + + val locale = context.getLocale() + fun parseDate(pattern: String): Date? { + val simpleDateFormat = SimpleDateFormat(pattern, locale) + simpleDateFormat.timeZone = TimeZone.getDefault() + return try { + simpleDateFormat.parse(version) + } catch (e: ParseException) { + null + } + } + + val date = parseDate("yyyy-MM-dd") ?: parseDate("yyyy-MM") + return if (date == null) { + Log.w(TAG, "Cannot parse mainline versionName ($version) as date") + version + } else { + DateFormat.format(DateFormat.getBestDateTimePattern(locale, "dMMMMyyyy"), date) + } + } + + override fun intent(context: Context): Intent? { + val packageManager = context.packageManager + val intentPackage = + if (Flags.mainlineModuleExplicitIntent()) { + context.getString(R.string.config_mainline_module_update_package) + } else { + null + } + fun String.resolveIntent() = + Intent(this).let { + if (intentPackage != null) it.setPackage(intentPackage) + if (packageManager.resolveActivity(it, 0) != null) it else null + } + + return MODULE_UPDATE_ACTION_V2.resolveIntent() ?: MODULE_UPDATE_ACTION.resolveIntent() + } + + override fun isAvailable(context: Context) = getModuleVersion(context).isNotEmpty() + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + preference.isSelectable = preference.intent != null + preference.isCopyingEnabled = true + } + + private fun getModuleVersion(context: Context): String = + moduleVersion ?: context.getVersion().also { moduleVersion = it } + + private fun Context.getVersion(): String { + val moduleProvider = + getString(com.android.internal.R.string.config_defaultModuleMetadataProvider) + if (moduleProvider.isEmpty()) return "" + return try { + packageManager.getPackageInfo(moduleProvider, 0)?.versionName ?: "" + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Failed to get mainline version.", e) + "" + } + } + + companion object { + private const val TAG = "MainlineModulePreference" + const val MODULE_UPDATE_ACTION = "android.settings.MODULE_UPDATE_SETTINGS" + const val MODULE_UPDATE_ACTION_V2 = "android.settings.MODULE_UPDATE_VERSIONS" + } +} +// LINT.ThenChange(MainlineModuleVersionPreferenceController.java) diff --git a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java index 4c02feb044f..b65497f6808 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java +++ b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java @@ -39,6 +39,7 @@ import java.util.Locale; import java.util.Optional; import java.util.TimeZone; +// LINT.IfChange public class MainlineModuleVersionPreferenceController extends BasePreferenceController { @VisibleForTesting @@ -141,3 +142,4 @@ public class MainlineModuleVersionPreferenceController extends BasePreferenceCon return Optional.empty(); } } +// LINT.ThenChange(MainlineModuleVersionPreference.kt) diff --git a/src/com/android/settings/utils/ContextUtils.kt b/src/com/android/settings/utils/ContextUtils.kt new file mode 100644 index 00000000000..76b13906ad8 --- /dev/null +++ b/src/com/android/settings/utils/ContextUtils.kt @@ -0,0 +1,27 @@ +/* + * 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.settings.utils + +import android.content.Context +import java.util.Locale + +/** Returns the locale of context. */ +fun Context.getLocale(): Locale { + val configuration = resources.configuration ?: return Locale.getDefault() + val locales = configuration.locales + return if (locales.isEmpty) configuration.locale else locales.get(0) +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceControllerTest.java index 7cd6734477a..f6e4d2a0573 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceControllerTest.java @@ -47,6 +47,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +// LINT.IfChange @RunWith(RobolectricTestRunner.class) public class MainlineModuleVersionPreferenceControllerTest { @@ -222,3 +223,4 @@ public class MainlineModuleVersionPreferenceControllerTest { when(mPackageManager.getPackageInfo(eq(provider), anyInt())).thenReturn(info); } } +// LINT.ThenChange(MainlineModuleVersionPreferenceTest.kt) diff --git a/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceTest.kt b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceTest.kt new file mode 100644 index 00000000000..8fc1a2bc58d --- /dev/null +++ b/tests/robotests/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceTest.kt @@ -0,0 +1,185 @@ +/* + * 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.settings.deviceinfo.firmwareversion + +import android.content.ContextWrapper +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.content.res.Resources +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.preference.Preference +import androidx.test.core.app.ApplicationProvider +import com.android.settings.R +import com.android.settings.deviceinfo.firmwareversion.MainlineModuleVersionPreference.Companion.MODULE_UPDATE_ACTION +import com.android.settings.deviceinfo.firmwareversion.MainlineModuleVersionPreference.Companion.MODULE_UPDATE_ACTION_V2 +import com.android.settings.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.kotlin.KStubbing +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.robolectric.RobolectricTestRunner + +// LINT.IfChange +@RunWith(RobolectricTestRunner::class) +class MainlineModuleVersionPreferenceTest { + @get:Rule val setFlagsRule = SetFlagsRule() + + private lateinit var mockPackageManager: PackageManager + private lateinit var mockResources: Resources + + private val context = + object : ContextWrapper(ApplicationProvider.getApplicationContext()) { + override fun getPackageManager(): PackageManager = mockPackageManager + + override fun getResources(): Resources = mockResources + } + + private val mainlineModuleVersionPreference = MainlineModuleVersionPreference() + + @Test + fun isAvailable_noMainlineModuleProvider_unavailable() { + createMocks("", null) + assertThat(mainlineModuleVersionPreference.isAvailable(context)).isFalse() + } + + @Test + fun isAvailable_noMainlineModulePackageInfo_unavailable() { + createMocks("test.provider", PackageManager.NameNotFoundException()) + assertThat(mainlineModuleVersionPreference.isAvailable(context)).isFalse() + } + + @Test + fun isAvailable_hasMainlineModulePackageInfo_available() { + createMocks("test.provider", "test version 123") + assertThat(mainlineModuleVersionPreference.isAvailable(context)).isTrue() + } + + @Test + fun getSummary_versionIsNull() { + createMocks("test.provider", PackageInfo()) + assertThat(mainlineModuleVersionPreference.getSummary(context)).isNull() + } + + @Test + fun getSummary_versionIsEmpty() { + createMocks("test.provider", "") + assertThat(mainlineModuleVersionPreference.getSummary(context)).isNull() + } + + @Test + fun getSummary_versionIsNotDate() { + createMocks("test.provider", "a") + assertThat(mainlineModuleVersionPreference.getSummary(context)).isEqualTo("a") + } + + @Test + fun getSummary_versionIsMonth() { + createMocks("test.provider", "2019-05") + assertThat(mainlineModuleVersionPreference.getSummary(context)).isEqualTo("May 1, 2019") + } + + @Test + fun getSummary_versionIsDate() { + createMocks("test.provider", "2019-05-13") + assertThat(mainlineModuleVersionPreference.getSummary(context)).isEqualTo("May 13, 2019") + } + + @Test + @EnableFlags(Flags.FLAG_MAINLINE_MODULE_EXPLICIT_INTENT) + fun intentV2_preferenceShouldBeSelectable() { + intent_preferenceShouldBeSelectable(MODULE_UPDATE_ACTION_V2, MODULE_PACKAGE) + } + + @Test + @DisableFlags(Flags.FLAG_MAINLINE_MODULE_EXPLICIT_INTENT) + fun intent_preferenceShouldBeSelectable() { + intent_preferenceShouldBeSelectable(MODULE_UPDATE_ACTION, null) + } + + private fun intent_preferenceShouldBeSelectable(action: String, intentPackage: String?) { + createMocks("test.provider", "test version 123") { + on { resolveActivity(any(), anyInt()) } doAnswer + { + when { + (it.arguments[0] as Intent).action == action -> ResolveInfo() + else -> null + } + } + } + + val preference = Preference(context) + mainlineModuleVersionPreference.bind(preference, mainlineModuleVersionPreference) + + val intent = preference.intent!! + assertThat(intent.action).isEqualTo(action) + assertThat(preference.isSelectable).isTrue() + assertThat(intent.`package`).isEqualTo(intentPackage) + } + + @Test + fun intent_null() { + createMocks("test.provider", "test version 123") + + val preference = Preference(context) + mainlineModuleVersionPreference.bind(preference, mainlineModuleVersionPreference) + + assertThat(preference.intent).isNull() + assertThat(preference.isSelectable).isFalse() + } + + private fun createMocks( + pkg: String, + pkgInfo: Any?, + stubbing: KStubbing.() -> Unit = {}, + ) { + mockResources = mock { + on { getString(R.string.config_mainline_module_update_package) } doReturn MODULE_PACKAGE + on { + getString(com.android.internal.R.string.config_defaultModuleMetadataProvider) + } doReturn pkg + } + + mockPackageManager = mock { + when (pkgInfo) { + is PackageInfo -> on { getPackageInfo(eq(pkg), anyInt()) } doReturn pkgInfo + is String -> + on { getPackageInfo(eq(pkg), anyInt()) } doReturn + PackageInfo().apply { versionName = pkgInfo } + is Exception -> on { getPackageInfo(eq(pkg), anyInt()) } doThrow pkgInfo + else -> {} + } + stubbing.invoke(this) + } + } + + companion object { + const val MODULE_PACKAGE = "com.android.vending" + } +} +// LINT.ThenChange(MainlineModuleVersionPreferenceControllerTest.java)