1 /*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.devicediagnostics
17
18 import android.content.Context
19 import android.content.Intent
20 import android.icu.number.NumberFormatter
21 import android.icu.util.MeasureUnit
22 import android.os.BatteryManager
23 import android.os.Build
24 import android.os.Bundle
25 import android.util.AttributeSet
26 import androidx.fragment.app.commit
27 import androidx.preference.Preference
28 import androidx.preference.PreferenceFragmentCompat
29 import com.android.devicediagnostics.Protos.BatteryInfo
30 import com.android.devicediagnostics.evaluated.getBatteryInfo
31 import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity
32 import com.android.settingslib.widget.UsageProgressBarPreference
33 import kotlin.math.max
34
legacyHealthToStringIdnull35 fun legacyHealthToStringId(value: Int): Int {
36 return when (value) {
37 BatteryManager.BATTERY_HEALTH_COLD -> R.string.battery_health_cold
38 BatteryManager.BATTERY_HEALTH_DEAD -> R.string.battery_health_dead
39 BatteryManager.BATTERY_HEALTH_GOOD -> R.string.battery_health_good
40 BatteryManager.BATTERY_HEALTH_OVERHEAT -> R.string.battery_health_overheat
41 BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE -> R.string.battery_health_over_voltage
42 else -> R.string.unknown
43 }
44 }
45
46 class BatteryInfoFragment(private val diagnostics: BatteryInfo, private val launchLevel: Int) :
47 PreferenceFragmentCompat() {
onCreatePreferencesnull48 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
49 setPreferencesFromResource(R.xml.battery_preferences, rootKey)
50
51 val locale = getDefaultLocale(activity!!)
52 val formatter = NumberFormatter.withLocale(locale)
53 findPreference<BatteryHealthPreference>("health")!!.also {
54 it.diagnostics = diagnostics
55 it.setPercent(max(0, diagnostics.stateOfHealth.toLong()), 100)
56 if (diagnostics.hasStateOfHealth()) {
57 val percentString =
58 formatter.unit(MeasureUnit.PERCENT).format(diagnostics.stateOfHealth).toString()
59 it.setBottomSummary(getString(R.string.battery_health_summary_fmt, percentString))
60 } else {
61 it.setBottomSummary(getString(R.string.battery_health_unavailable))
62 it.isVisible = launchLevel >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
63 }
64 }
65
66 findPreference<Preference>("legacy_health")!!.also {
67 if (diagnostics.hasStateOfHealth()) {
68 it.isVisible = false
69 } else {
70 it.summary = getString(legacyHealthToStringId(diagnostics.legacyHealth))
71 it.isVisible = true
72 }
73 }
74
75 findPreference<Preference>("manufacture_date")!!.also {
76 if (diagnostics.hasManufactureTimestamp()) {
77 it.summary = localizeDateTimestamp(locale, diagnostics.manufactureTimestamp)
78 } else {
79 it.summary = getString(R.string.unknown)
80 it.isVisible = launchLevel >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
81 }
82 }
83
84 findPreference<Preference>("first_usage_date")!!.also {
85 if (diagnostics.hasFirstUsageTimestamp()) {
86 it.summary = localizeDateTimestamp(locale, diagnostics.firstUsageTimestamp)
87 } else {
88 it.summary = getString(R.string.unknown)
89 it.isVisible = launchLevel >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
90 }
91 }
92
93 findPreference<Preference>("cycle_count")!!.also {
94 if (diagnostics.hasCycleCount()) {
95 it.summary = formatter.format(diagnostics.cycleCount).toString()
96 } else {
97 it.summary = getString(R.string.unknown)
98 it.isVisible = launchLevel >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
99 }
100 }
101
102 findPreference<Preference>("serial_number")!!.also {
103 if (diagnostics.hasSerial()) {
104 it.summary = diagnostics.serial
105 } else {
106 it.summary = getString(R.string.unknown)
107 it.isVisible = launchLevel > Build.VERSION_CODES.VANILLA_ICE_CREAM
108 }
109 }
110
111 findPreference<Preference>("part_status")!!.also {
112 if (diagnostics.hasPartStatus()) {
113 it.summary = diagnostics.partStatus
114 } else {
115 it.summary = getString(R.string.unknown)
116 it.isVisible = launchLevel > Build.VERSION_CODES.VANILLA_ICE_CREAM
117 }
118 }
119 }
120 }
121
122 class BatteryHealthPreference(context: Context, attrs: AttributeSet) :
123 UsageProgressBarPreference(context, attrs) {
124
125 var diagnostics: BatteryInfo = BatteryInfo.getDefaultInstance()
126
getSummarynull127 override fun getSummary(): CharSequence? {
128 if (!diagnostics.hasStateOfHealth()) {
129 return context.getString(R.string.unavailable)
130 }
131 return diagnostics.stateOfHealth.toString() + "%"
132 }
133 }
134
135 class BatteryActivity : CollapsingToolbarBaseActivity() {
onCreatenull136 override fun onCreate(savedInstanceState: Bundle?) {
137 super.onCreate(savedInstanceState)
138 setContentView(R.layout.activity_one_fragment)
139 setTitle(R.string.battery_title)
140
141 if (savedInstanceState == null) {
142 val diagnostics = readBatteryInfo()
143 val launchLevel = readLaunchLevel()
144 supportFragmentManager.commit {
145 setReorderingAllowed(true)
146 add(R.id.fragment_container_view, BatteryInfoFragment(diagnostics, launchLevel))
147 }
148 }
149 }
150
151 // Get either current battery info or battery info from the intent.
readBatteryInfonull152 private fun readBatteryInfo(): BatteryInfo {
153 if (intent.extras != null) {
154 val array = intent.extras!!.getByteArray(INTENT_BATTERY_INFO)
155 return BatteryInfo.parseFrom(array)
156 }
157 return getBatteryInfo(this)
158 }
159
readLaunchLevelnull160 private fun readLaunchLevel(): Int {
161 if (intent.extras != null) {
162 return intent.extras!!.getInt(INTENT_LAUNCH_LEVEL)
163 }
164 return ApplicationInterface.app.getLaunchLevel()
165 }
166
167 companion object Static {
168 const val INTENT_BATTERY_INFO = "battery_info"
169 const val INTENT_LAUNCH_LEVEL = "vsr_level"
170
createIntentnull171 fun createIntent(from: Context, diagnostics: BatteryInfo, launchLevel: Int): Intent {
172 return Intent(from, BatteryActivity::class.java)
173 .putExtra(INTENT_BATTERY_INFO, diagnostics.toByteArray())
174 .putExtra(INTENT_LAUNCH_LEVEL, launchLevel)
175 }
176 }
177 }
178