1 /* 2 * Copyright (C) 2018 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 * http://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 17 package com.android.settings.fuelgauge; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.os.BatteryManager; 22 import android.util.Log; 23 24 import androidx.annotation.VisibleForTesting; 25 import androidx.preference.Preference; 26 import androidx.preference.PreferenceScreen; 27 28 import com.android.settings.R; 29 import com.android.settings.core.BasePreferenceController; 30 import com.android.settings.overlay.FeatureFactory; 31 import com.android.settingslib.Utils; 32 import com.android.settingslib.core.lifecycle.LifecycleObserver; 33 import com.android.settingslib.core.lifecycle.events.OnStart; 34 import com.android.settingslib.core.lifecycle.events.OnStop; 35 import com.android.settingslib.utils.ThreadUtils; 36 37 public class TopLevelBatteryPreferenceController extends BasePreferenceController 38 implements LifecycleObserver, OnStart, OnStop, BatteryPreferenceController { 39 40 private static final String TAG = "TopLvBatteryPrefControl"; 41 42 @VisibleForTesting Preference mPreference; 43 @VisibleForTesting protected boolean mIsBatteryPresent = true; 44 45 private final BatteryBroadcastReceiver mBatteryBroadcastReceiver; 46 47 private BatteryInfo mBatteryInfo; 48 private BatteryStatusFeatureProvider mBatteryStatusFeatureProvider; 49 private String mBatteryStatusLabel; 50 TopLevelBatteryPreferenceController(Context context, String preferenceKey)51 public TopLevelBatteryPreferenceController(Context context, String preferenceKey) { 52 super(context, preferenceKey); 53 mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext); 54 mBatteryBroadcastReceiver.setBatteryChangedListener( 55 type -> { 56 Log.d(TAG, "onBatteryChanged: type=" + type); 57 if (type == BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT) { 58 mIsBatteryPresent = false; 59 } 60 BatteryInfo.getBatteryInfo( 61 mContext, 62 info -> { 63 Log.d(TAG, "getBatteryInfo: " + info); 64 mBatteryInfo = info; 65 updateState(mPreference); 66 // Update the preference summary text to the latest state. 67 setSummaryAsync(info); 68 }, 69 true /* shortString */); 70 }); 71 72 mBatteryStatusFeatureProvider = 73 FeatureFactory.getFeatureFactory().getBatteryStatusFeatureProvider(); 74 } 75 76 @Override getAvailabilityStatus()77 public int getAvailabilityStatus() { 78 return mContext.getResources().getBoolean(R.bool.config_show_top_level_battery) 79 ? AVAILABLE 80 : UNSUPPORTED_ON_DEVICE; 81 } 82 83 @Override displayPreference(PreferenceScreen screen)84 public void displayPreference(PreferenceScreen screen) { 85 super.displayPreference(screen); 86 mPreference = screen.findPreference(getPreferenceKey()); 87 } 88 89 @Override onStart()90 public void onStart() { 91 mBatteryBroadcastReceiver.register(); 92 } 93 94 @Override onStop()95 public void onStop() { 96 mBatteryBroadcastReceiver.unRegister(); 97 } 98 99 @Override getSummary()100 public CharSequence getSummary() { 101 return getSummary(true /* batteryStatusUpdate */); 102 } 103 getSummary(boolean batteryStatusUpdate)104 private CharSequence getSummary(boolean batteryStatusUpdate) { 105 // Display help message if battery is not present. 106 if (!mIsBatteryPresent) { 107 return mContext.getText(R.string.battery_missing_message); 108 } 109 return getDashboardLabel(mContext, mBatteryInfo, batteryStatusUpdate); 110 } 111 getDashboardLabel( Context context, BatteryInfo info, boolean batteryStatusUpdate)112 protected CharSequence getDashboardLabel( 113 Context context, BatteryInfo info, boolean batteryStatusUpdate) { 114 if (info == null || context == null) { 115 return null; 116 } 117 Log.d( 118 TAG, 119 "getDashboardLabel: " 120 + mBatteryStatusLabel 121 + " batteryStatusUpdate=" 122 + batteryStatusUpdate); 123 124 if (batteryStatusUpdate) { 125 setSummaryAsync(info); 126 } 127 return mBatteryStatusLabel == null ? generateLabel(info) : mBatteryStatusLabel; 128 } 129 setSummaryAsync(BatteryInfo info)130 private void setSummaryAsync(BatteryInfo info) { 131 ThreadUtils.postOnBackgroundThread( 132 () -> { 133 // Return false if built-in status should be used, will use 134 // updateBatteryStatus() 135 // method to inject the customized battery status label. 136 final boolean triggerBatteryStatusUpdate = 137 mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info); 138 ThreadUtils.postOnMainThread( 139 () -> { 140 if (!triggerBatteryStatusUpdate) { 141 mBatteryStatusLabel = null; // will generateLabel() 142 } 143 mPreference.setSummary( 144 mBatteryStatusLabel == null 145 ? generateLabel(info) 146 : mBatteryStatusLabel); 147 }); 148 }); 149 } 150 generateLabel(BatteryInfo info)151 private CharSequence generateLabel(BatteryInfo info) { 152 if (Utils.containsIncompatibleChargers(mContext, TAG)) { 153 return mContext.getString( 154 com.android.settingslib.R.string.power_incompatible_charging_settings_home_page, 155 info.batteryPercentString); 156 } 157 if (BatteryUtils.isBatteryDefenderOn(info)) { 158 return mContext.getString( 159 com.android.settingslib.R.string.power_charging_on_hold_settings_home_page, 160 info.batteryPercentString); 161 } 162 final BatterySettingsFeatureProvider featureProvider = 163 FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider(); 164 if (info.chargeLabel != null && featureProvider.isChargingOptimizationMode(mContext)) { 165 return info.chargeLabel; 166 } 167 if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { 168 // Present status only if no remaining time or status anomalous 169 return info.statusLabel; 170 } else if (!info.discharging && info.chargeLabel != null) { 171 return info.chargeLabel; 172 } else if (info.remainingLabel == null) { 173 return info.batteryPercentString; 174 } else { 175 return mContext.getString( 176 com.android.settingslib.R.string.power_remaining_settings_home_page, 177 info.batteryPercentString, 178 info.remainingLabel); 179 } 180 } 181 182 /** Callback which receives text for the label. */ 183 @Override updateBatteryStatus(String label, BatteryInfo info)184 public void updateBatteryStatus(String label, BatteryInfo info) { 185 mBatteryStatusLabel = label; // Null if adaptive charging is not active 186 if (mPreference == null) { 187 return; 188 } 189 // Do not triggerBatteryStatusUpdate() here to cause infinite loop 190 final CharSequence summary = getSummary(false /* batteryStatusUpdate */); 191 if (summary != null) { 192 mPreference.setSummary(summary); 193 } 194 Log.d(TAG, "updateBatteryStatus: " + label + " summary: " + summary); 195 } 196 197 @VisibleForTesting convertClassPathToComponentName(String classPath)198 protected static ComponentName convertClassPathToComponentName(String classPath) { 199 if (classPath == null || classPath.isEmpty()) { 200 return null; 201 } 202 String[] split = classPath.split("\\."); 203 int classNameIndex = split.length - 1; 204 if (classNameIndex < 0) { 205 return null; 206 } 207 int lastPkgIndex = classPath.length() - split[classNameIndex].length() - 1; 208 String pkgName = lastPkgIndex > 0 ? classPath.substring(0, lastPkgIndex) : ""; 209 return new ComponentName(pkgName, split[classNameIndex]); 210 } 211 } 212