Home | History | Annotate | Download | only in systemui
      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 RectF mBoltFrame = new RectF();
     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 float bl = mFrame.left + mFrame.width() / 4.5f;
    323             final float bt = mFrame.top + mFrame.height() / 6f;
    324             final float br = mFrame.right - mFrame.width() / 7f;
    325             final float bb = 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