1 /* 2 * Copyright (C) 2015 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 package com.android.systemui.qs.tiles; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.graphics.drawable.Drawable; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.text.SpannableStringBuilder; 26 import android.text.Spanned; 27 import android.text.style.RelativeSizeSpan; 28 import android.util.TypedValue; 29 import android.view.LayoutInflater; 30 import android.view.View; 31 import android.view.View.OnAttachStateChangeListener; 32 import android.view.View.OnClickListener; 33 import android.view.ViewGroup; 34 import android.widget.Button; 35 import android.widget.Checkable; 36 import android.widget.ImageView; 37 import android.widget.TextView; 38 import com.android.internal.logging.MetricsProto.MetricsEvent; 39 import com.android.settingslib.BatteryInfo; 40 import com.android.settingslib.graph.UsageView; 41 import com.android.systemui.BatteryMeterDrawable; 42 import com.android.systemui.R; 43 import com.android.systemui.qs.QSTile; 44 import com.android.systemui.statusbar.policy.BatteryController; 45 46 import java.text.NumberFormat; 47 48 public class BatteryTile extends QSTile<QSTile.State> implements BatteryController.BatteryStateChangeCallback { 49 50 private final BatteryController mBatteryController; 51 private final BatteryDetail mBatteryDetail = new BatteryDetail(); 52 53 private int mLevel; 54 private boolean mPowerSave; 55 private boolean mCharging; 56 private boolean mDetailShown; 57 private boolean mPluggedIn; 58 59 public BatteryTile(Host host) { 60 super(host); 61 mBatteryController = host.getBatteryController(); 62 } 63 64 @Override 65 public State newTileState() { 66 return new QSTile.State(); 67 } 68 69 @Override 70 public DetailAdapter getDetailAdapter() { 71 return mBatteryDetail; 72 } 73 74 @Override 75 public int getMetricsCategory() { 76 return MetricsEvent.QS_BATTERY_TILE; 77 } 78 79 @Override 80 public void setListening(boolean listening) { 81 if (listening) { 82 mBatteryController.addStateChangedCallback(this); 83 } else { 84 mBatteryController.removeStateChangedCallback(this); 85 } 86 } 87 88 @Override 89 public void setDetailListening(boolean listening) { 90 super.setDetailListening(listening); 91 if (!listening) { 92 mBatteryDetail.mCurrentView = null; 93 } 94 } 95 96 @Override 97 public Intent getLongClickIntent() { 98 return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); 99 } 100 101 @Override 102 protected void handleClick() { 103 showDetail(true); 104 } 105 106 @Override 107 public CharSequence getTileLabel() { 108 return mContext.getString(R.string.battery); 109 } 110 111 @Override 112 protected void handleUpdateState(State state, Object arg) { 113 int level = (arg != null) ? (Integer) arg : mLevel; 114 String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0); 115 116 state.icon = new Icon() { 117 @Override 118 public Drawable getDrawable(Context context) { 119 BatteryMeterDrawable drawable = 120 new BatteryMeterDrawable(context, new Handler(Looper.getMainLooper()), 121 context.getColor(R.color.batterymeter_frame_color)); 122 drawable.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); 123 drawable.onPowerSaveChanged(mPowerSave); 124 return drawable; 125 } 126 127 @Override 128 public int getPadding() { 129 return mHost.getContext().getResources().getDimensionPixelSize( 130 R.dimen.qs_battery_padding); 131 } 132 }; 133 state.label = percentage; 134 state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_battery, 135 percentage) + "," + 136 (mPowerSave ? mContext.getString(R.string.battery_saver_notification_title) 137 : mCharging ? mContext.getString(R.string.expanded_header_battery_charging) 138 : "") 139 + "," + mContext.getString(R.string.accessibility_battery_details); 140 state.minimalAccessibilityClassName = state.expandedAccessibilityClassName 141 = Button.class.getName(); 142 } 143 144 @Override 145 public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { 146 mLevel = level; 147 mPluggedIn = pluggedIn; 148 mCharging = charging; 149 refreshState((Integer) level); 150 if (mDetailShown) { 151 mBatteryDetail.postBindView(); 152 } 153 } 154 155 @Override 156 public void onPowerSaveChanged(boolean isPowerSave) { 157 mPowerSave = isPowerSave; 158 refreshState(null); 159 if (mDetailShown) { 160 mBatteryDetail.postBindView(); 161 } 162 } 163 164 private final class BatteryDetail implements DetailAdapter, OnClickListener, 165 OnAttachStateChangeListener { 166 private final BatteryMeterDrawable mDrawable = new BatteryMeterDrawable(mHost.getContext(), 167 new Handler(), mHost.getContext().getColor(R.color.batterymeter_frame_color)); 168 private View mCurrentView; 169 170 @Override 171 public CharSequence getTitle() { 172 return mContext.getString(R.string.battery_panel_title, mLevel); 173 } 174 175 @Override 176 public Boolean getToggleState() { 177 return null; 178 } 179 180 @Override 181 public View createDetailView(Context context, View convertView, ViewGroup parent) { 182 if (convertView == null) { 183 convertView = LayoutInflater.from(mContext).inflate(R.layout.battery_detail, parent, 184 false); 185 } 186 mCurrentView = convertView; 187 mCurrentView.addOnAttachStateChangeListener(this); 188 bindView(); 189 return convertView; 190 } 191 192 private void postBindView() { 193 if (mCurrentView == null) return; 194 mCurrentView.post(new Runnable() { 195 @Override 196 public void run() { 197 bindView(); 198 } 199 }); 200 } 201 202 private void bindView() { 203 if (mCurrentView == null) { 204 return; 205 } 206 mDrawable.onBatteryLevelChanged(100, false, false); 207 mDrawable.onPowerSaveChanged(true); 208 mDrawable.disableShowPercent(); 209 ((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable); 210 Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle); 211 checkbox.setChecked(mPowerSave); 212 BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() { 213 @Override 214 public void onBatteryInfoLoaded(BatteryInfo info) { 215 if (mCurrentView != null) { 216 bindBatteryInfo(info); 217 } 218 } 219 }); 220 final TextView batterySaverTitle = 221 (TextView) mCurrentView.findViewById(android.R.id.title); 222 final TextView batterySaverSummary = 223 (TextView) mCurrentView.findViewById(android.R.id.summary); 224 if (mCharging) { 225 mCurrentView.findViewById(R.id.switch_container).setAlpha(.7f); 226 batterySaverTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); 227 batterySaverTitle.setText(R.string.battery_detail_charging_summary); 228 mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.GONE); 229 mCurrentView.findViewById(R.id.switch_container).setClickable(false); 230 } else { 231 mCurrentView.findViewById(R.id.switch_container).setAlpha(1); 232 batterySaverTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); 233 batterySaverTitle.setText(R.string.battery_detail_switch_title); 234 batterySaverSummary.setText(R.string.battery_detail_switch_summary); 235 mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.VISIBLE); 236 mCurrentView.findViewById(R.id.switch_container).setClickable(true); 237 mCurrentView.findViewById(R.id.switch_container).setOnClickListener(this); 238 } 239 } 240 241 private void bindBatteryInfo(BatteryInfo info) { 242 SpannableStringBuilder builder = new SpannableStringBuilder(); 243 builder.append(info.batteryPercentString, new RelativeSizeSpan(2.6f), 244 Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 245 if (info.remainingLabel != null) { 246 if (mContext.getResources().getBoolean(R.bool.quick_settings_wide)) { 247 builder.append(' '); 248 } else { 249 builder.append('\n'); 250 } 251 builder.append(info.remainingLabel); 252 } 253 ((TextView) mCurrentView.findViewById(R.id.charge_and_estimation)).setText(builder); 254 255 info.bindHistory((UsageView) mCurrentView.findViewById(R.id.battery_usage)); 256 } 257 258 @Override 259 public void onClick(View v) { 260 mBatteryController.setPowerSaveMode(!mPowerSave); 261 } 262 263 @Override 264 public Intent getSettingsIntent() { 265 return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); 266 } 267 268 @Override 269 public void setToggleState(boolean state) { 270 // No toggle state. 271 } 272 273 @Override 274 public int getMetricsCategory() { 275 return MetricsEvent.QS_BATTERY_DETAIL; 276 } 277 278 @Override 279 public void onViewAttachedToWindow(View v) { 280 if (!mDetailShown) { 281 mDetailShown = true; 282 v.getContext().registerReceiver(mReceiver, 283 new IntentFilter(Intent.ACTION_TIME_TICK)); 284 } 285 } 286 287 @Override 288 public void onViewDetachedFromWindow(View v) { 289 if (mDetailShown) { 290 mDetailShown = false; 291 v.getContext().unregisterReceiver(mReceiver); 292 } 293 } 294 295 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 296 @Override 297 public void onReceive(Context context, Intent intent) { 298 postBindView(); 299 } 300 }; 301 } 302 } 303