1 /* 2 * Copyright (C) 2013 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.systemui; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.res.Resources; 24 import android.content.res.TypedArray; 25 import android.graphics.Canvas; 26 import android.graphics.Paint; 27 import android.graphics.Path; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffXfermode; 30 import android.graphics.Rect; 31 import android.graphics.RectF; 32 import android.graphics.Typeface; 33 import android.os.BatteryManager; 34 import android.os.Bundle; 35 import android.provider.Settings; 36 import android.util.AttributeSet; 37 import android.view.View; 38 39 public class BatteryMeterView extends View implements DemoMode { 40 public static final String TAG = BatteryMeterView.class.getSimpleName(); 41 public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST"; 42 43 public static final boolean ENABLE_PERCENT = true; 44 public static final boolean SINGLE_DIGIT_PERCENT = false; 45 public static final boolean SHOW_100_PERCENT = false; 46 47 public static final int FULL = 96; 48 public static final int EMPTY = 4; 49 50 public static final float SUBPIXEL = 0.4f; // inset rects for softer edges 51 52 int[] mColors; 53 54 boolean mShowPercent = true; 55 Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint; 56 int mButtonHeight; 57 private float mTextHeight, mWarningTextHeight; 58 59 private int mHeight; 60 private int mWidth; 61 private String mWarningString; 62 private final int mChargeColor; 63 private final float[] mBoltPoints; 64 private final Path mBoltPath = new Path(); 65 66 private final RectF mFrame = new RectF(); 67 private final RectF mButtonFrame = new RectF(); 68 private final RectF mClipFrame = new RectF(); 69 private final Rect mBoltFrame = new Rect(); 70 71 private class BatteryTracker extends BroadcastReceiver { 72 public static final int UNKNOWN_LEVEL = -1; 73 74 // current battery status 75 int level = UNKNOWN_LEVEL; 76 String percentStr; 77 int plugType; 78 boolean plugged; 79 int health; 80 int status; 81 String technology; 82 int voltage; 83 int temperature; 84 boolean testmode = false; 85 86 @Override 87 public void onReceive(Context context, Intent intent) { 88 final String action = intent.getAction(); 89 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { 90 if (testmode && ! intent.getBooleanExtra("testmode", false)) return; 91 92 level = (int)(100f 93 * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) 94 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)); 95 96 plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); 97 plugged = plugType != 0; 98 health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, 99 BatteryManager.BATTERY_HEALTH_UNKNOWN); 100 status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 101 BatteryManager.BATTERY_STATUS_UNKNOWN); 102 technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY); 103 voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0); 104 temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0); 105 106 setContentDescription( 107 context.getString(R.string.accessibility_battery_level, level)); 108 postInvalidate(); 109 } else if (action.equals(ACTION_LEVEL_TEST)) { 110 testmode = true; 111 post(new Runnable() { 112 int curLevel = 0; 113 int incr = 1; 114 int saveLevel = level; 115 int savePlugged = plugType; 116 Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED); 117 @Override 118 public void run() { 119 if (curLevel < 0) { 120 testmode = false; 121 dummy.putExtra("level", saveLevel); 122 dummy.putExtra("plugged", savePlugged); 123 dummy.putExtra("testmode", false); 124 } else { 125 dummy.putExtra("level", curLevel); 126 dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC : 0); 127 dummy.putExtra("testmode", true); 128 } 129 getContext().sendBroadcast(dummy); 130 131 if (!testmode) return; 132 133 curLevel += incr; 134 if (curLevel == 100) { 135 incr *= -1; 136 } 137 postDelayed(this, 200); 138 } 139 }); 140 } 141 } 142 } 143 144 BatteryTracker mTracker = new BatteryTracker(); 145 146 @Override 147 public void onAttachedToWindow() { 148 super.onAttachedToWindow(); 149 150 IntentFilter filter = new IntentFilter(); 151 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 152 filter.addAction(ACTION_LEVEL_TEST); 153 final Intent sticky = getContext().registerReceiver(mTracker, filter); 154 if (sticky != null) { 155 // preload the battery level 156 mTracker.onReceive(getContext(), sticky); 157 } 158 } 159 160 @Override 161 public void onDetachedFromWindow() { 162 super.onDetachedFromWindow(); 163 164 getContext().unregisterReceiver(mTracker); 165 } 166 167 public BatteryMeterView(Context context) { 168 this(context, null, 0); 169 } 170 171 public BatteryMeterView(Context context, AttributeSet attrs) { 172 this(context, attrs, 0); 173 } 174 175 public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) { 176 super(context, attrs, defStyle); 177 178 final Resources res = context.getResources(); 179 TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels); 180 TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values); 181 182 final int N = levels.length(); 183 mColors = new int[2*N]; 184 for (int i=0; i<N; i++) { 185 mColors[2*i] = levels.getInt(i, 0); 186 mColors[2*i+1] = colors.getColor(i, 0); 187 } 188 levels.recycle(); 189 colors.recycle(); 190 mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt( 191 context.getContentResolver(), "status_bar_show_battery_percent", 0); 192 193 mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol); 194 195 mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 196 mFramePaint.setColor(res.getColor(R.color.batterymeter_frame_color)); 197 mFramePaint.setDither(true); 198 mFramePaint.setStrokeWidth(0); 199 mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE); 200 mFramePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); 201 202 mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 203 mBatteryPaint.setDither(true); 204 mBatteryPaint.setStrokeWidth(0); 205 mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE); 206 207 mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 208 mTextPaint.setColor(0xFFFFFFFF); 209 Typeface font = Typeface.create("sans-serif-condensed", Typeface.NORMAL); 210 mTextPaint.setTypeface(font); 211 mTextPaint.setTextAlign(Paint.Align.CENTER); 212 213 mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 214 mWarningTextPaint.setColor(mColors[1]); 215 font = Typeface.create("sans-serif", Typeface.BOLD); 216 mWarningTextPaint.setTypeface(font); 217 mWarningTextPaint.setTextAlign(Paint.Align.CENTER); 218 219 mChargeColor = getResources().getColor(R.color.batterymeter_charge_color); 220 221 mBoltPaint = new Paint(); 222 mBoltPaint.setAntiAlias(true); 223 mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color)); 224 mBoltPoints = loadBoltPoints(res); 225 setLayerType(View.LAYER_TYPE_SOFTWARE, null); 226 } 227 228 private static float[] loadBoltPoints(Resources res) { 229 final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points); 230 int maxX = 0, maxY = 0; 231 for (int i = 0; i < pts.length; i += 2) { 232 maxX = Math.max(maxX, pts[i]); 233 maxY = Math.max(maxY, pts[i + 1]); 234 } 235 final float[] ptsF = new float[pts.length]; 236 for (int i = 0; i < pts.length; i += 2) { 237 ptsF[i] = (float)pts[i] / maxX; 238 ptsF[i + 1] = (float)pts[i + 1] / maxY; 239 } 240 return ptsF; 241 } 242 243 @Override 244 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 245 mHeight = h; 246 mWidth = w; 247 mWarningTextPaint.setTextSize(h * 0.75f); 248 mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent; 249 } 250 251 private int getColorForLevel(int percent) { 252 int thresh, color = 0; 253 for (int i=0; i<mColors.length; i+=2) { 254 thresh = mColors[i]; 255 color = mColors[i+1]; 256 if (percent <= thresh) return color; 257 } 258 return color; 259 } 260 261 @Override 262 public void draw(Canvas c) { 263 BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker; 264 final int level = tracker.level; 265 266 if (level == BatteryTracker.UNKNOWN_LEVEL) return; 267 268 float drawFrac = (float) level / 100f; 269 final int pt = getPaddingTop(); 270 final int pl = getPaddingLeft(); 271 final int pr = getPaddingRight(); 272 final int pb = getPaddingBottom(); 273 int height = mHeight - pt - pb; 274 int width = mWidth - pl - pr; 275 276 mButtonHeight = (int) (height * 0.12f); 277 278 mFrame.set(0, 0, width, height); 279 mFrame.offset(pl, pt); 280 281 mButtonFrame.set( 282 mFrame.left + width * 0.25f, 283 mFrame.top, 284 mFrame.right - width * 0.25f, 285 mFrame.top + mButtonHeight + 5 /*cover frame border of intersecting area*/); 286 287 mButtonFrame.top += SUBPIXEL; 288 mButtonFrame.left += SUBPIXEL; 289 mButtonFrame.right -= SUBPIXEL; 290 291 mFrame.top += mButtonHeight; 292 mFrame.left += SUBPIXEL; 293 mFrame.top += SUBPIXEL; 294 mFrame.right -= SUBPIXEL; 295 mFrame.bottom -= SUBPIXEL; 296 297 // first, draw the battery shape 298 c.drawRect(mFrame, mFramePaint); 299 300 // fill 'er up 301 final int color = tracker.plugged ? mChargeColor : getColorForLevel(level); 302 mBatteryPaint.setColor(color); 303 304 if (level >= FULL) { 305 drawFrac = 1f; 306 } else if (level <= EMPTY) { 307 drawFrac = 0f; 308 } 309 310 c.drawRect(mButtonFrame, drawFrac == 1f ? mBatteryPaint : mFramePaint); 311 312 mClipFrame.set(mFrame); 313 mClipFrame.top += (mFrame.height() * (1f - drawFrac)); 314 315 c.save(Canvas.CLIP_SAVE_FLAG); 316 c.clipRect(mClipFrame); 317 c.drawRect(mFrame, mBatteryPaint); 318 c.restore(); 319 320 if (tracker.plugged) { 321 // draw the bolt 322 final int bl = (int)(mFrame.left + mFrame.width() / 4.5f); 323 final int bt = (int)(mFrame.top + mFrame.height() / 6f); 324 final int br = (int)(mFrame.right - mFrame.width() / 7f); 325 final int bb = (int)(mFrame.bottom - mFrame.height() / 10f); 326 if (mBoltFrame.left != bl || mBoltFrame.top != bt 327 || mBoltFrame.right != br || mBoltFrame.bottom != bb) { 328 mBoltFrame.set(bl, bt, br, bb); 329 mBoltPath.reset(); 330 mBoltPath.moveTo( 331 mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), 332 mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); 333 for (int i = 2; i < mBoltPoints.length; i += 2) { 334 mBoltPath.lineTo( 335 mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(), 336 mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height()); 337 } 338 mBoltPath.lineTo( 339 mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), 340 mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); 341 } 342 c.drawPath(mBoltPath, mBoltPaint); 343 } else if (level <= EMPTY) { 344 final float x = mWidth * 0.5f; 345 final float y = (mHeight + mWarningTextHeight) * 0.48f; 346 c.drawText(mWarningString, x, y, mWarningTextPaint); 347 } else if (mShowPercent && !(tracker.level == 100 && !SHOW_100_PERCENT)) { 348 mTextPaint.setTextSize(height * 349 (SINGLE_DIGIT_PERCENT ? 0.75f 350 : (tracker.level == 100 ? 0.38f : 0.5f))); 351 mTextHeight = -mTextPaint.getFontMetrics().ascent; 352 353 final String str = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level); 354 final float x = mWidth * 0.5f; 355 final float y = (mHeight + mTextHeight) * 0.47f; 356 c.drawText(str, 357 x, 358 y, 359 mTextPaint); 360 } 361 } 362 363 private boolean mDemoMode; 364 private BatteryTracker mDemoTracker = new BatteryTracker(); 365 366 @Override 367 public void dispatchDemoCommand(String command, Bundle args) { 368 if (!mDemoMode && command.equals(COMMAND_ENTER)) { 369 mDemoMode = true; 370 mDemoTracker.level = mTracker.level; 371 mDemoTracker.plugged = mTracker.plugged; 372 } else if (mDemoMode && command.equals(COMMAND_EXIT)) { 373 mDemoMode = false; 374 postInvalidate(); 375 } else if (mDemoMode && command.equals(COMMAND_BATTERY)) { 376 String level = args.getString("level"); 377 String plugged = args.getString("plugged"); 378 if (level != null) { 379 mDemoTracker.level = Math.min(Math.max(Integer.parseInt(level), 0), 100); 380 } 381 if (plugged != null) { 382 mDemoTracker.plugged = Boolean.parseBoolean(plugged); 383 } 384 postInvalidate(); 385 } 386 } 387 } 388