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