Add more QS tiles

(only for paging prototype)

Change-Id: Ifb09e3262540a486c9ee7e83a904f96cc4e33fec
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
new file mode 100755
index 0000000..3eb1271
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.animation.ArgbEvaluator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.ContentObserver;
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+public class BatteryMeterDrawable extends Drawable implements DemoMode,
+        BatteryController.BatteryStateChangeCallback {
+
+    private static final float ASPECT_RATIO = 9.5f / 14.5f;
+    public static final String TAG = BatteryMeterDrawable.class.getSimpleName();
+    public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent";
+
+    private static final boolean SINGLE_DIGIT_PERCENT = false;
+
+    private static final int FULL = 96;
+
+    private static final float BOLT_LEVEL_THRESHOLD = 0.3f;  // opaque bolt below this fraction
+
+    private final int[] mColors;
+
+    private boolean mShowPercent;
+    private float mButtonHeightFraction;
+    private float mSubpixelSmoothingLeft;
+    private float mSubpixelSmoothingRight;
+    private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
+    private float mTextHeight, mWarningTextHeight;
+    private int mIconTint = Color.WHITE;
+
+    private int mHeight;
+    private int mWidth;
+    private String mWarningString;
+    private final int mCriticalLevel;
+    private int mChargeColor;
+    private final float[] mBoltPoints;
+    private final Path mBoltPath = new Path();
+
+    private final RectF mFrame = new RectF();
+    private final RectF mButtonFrame = new RectF();
+    private final RectF mBoltFrame = new RectF();
+
+    private final Path mShapePath = new Path();
+    private final Path mClipPath = new Path();
+    private final Path mTextPath = new Path();
+
+    private BatteryController mBatteryController;
+    private boolean mPowerSaveEnabled;
+
+    private int mDarkModeBackgroundColor;
+    private int mDarkModeFillColor;
+
+    private int mLightModeBackgroundColor;
+    private int mLightModeFillColor;
+
+    private final SettingObserver mSettingObserver = new SettingObserver();
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    private int mLevel = -1;
+    private boolean mPluggedIn;
+    private boolean mListening;
+
+    public BatteryMeterDrawable(Context context, Handler handler, int frameColor) {
+        mContext = context;
+        mHandler = handler;
+        final Resources res = context.getResources();
+        TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
+        TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
+
+        final int N = levels.length();
+        mColors = new int[2*N];
+        for (int i=0; i<N; i++) {
+            mColors[2*i] = levels.getInt(i, 0);
+            mColors[2*i+1] = colors.getColor(i, 0);
+        }
+        levels.recycle();
+        colors.recycle();
+        updateShowPercent();
+        mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol);
+        mCriticalLevel = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_criticalBatteryWarningLevel);
+        mButtonHeightFraction = context.getResources().getFraction(
+                R.fraction.battery_button_height_fraction, 1, 1);
+        mSubpixelSmoothingLeft = context.getResources().getFraction(
+                R.fraction.battery_subpixel_smoothing_left, 1, 1);
+        mSubpixelSmoothingRight = context.getResources().getFraction(
+                R.fraction.battery_subpixel_smoothing_right, 1, 1);
+
+        mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mFramePaint.setColor(frameColor);
+        mFramePaint.setDither(true);
+        mFramePaint.setStrokeWidth(0);
+        mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+        mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mBatteryPaint.setDither(true);
+        mBatteryPaint.setStrokeWidth(0);
+        mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
+        mTextPaint.setTypeface(font);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+        mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mWarningTextPaint.setColor(mColors[1]);
+        font = Typeface.create("sans-serif", Typeface.BOLD);
+        mWarningTextPaint.setTypeface(font);
+        mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
+
+        mChargeColor = context.getColor(R.color.batterymeter_charge_color);
+
+        mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mBoltPaint.setColor(context.getColor(R.color.batterymeter_bolt_color));
+        mBoltPoints = loadBoltPoints(res);
+
+        mDarkModeBackgroundColor =
+                context.getColor(R.color.dark_mode_icon_color_dual_tone_background);
+        mDarkModeFillColor = context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
+        mLightModeBackgroundColor =
+                context.getColor(R.color.light_mode_icon_color_dual_tone_background);
+        mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
+    }
+
+    public void startListening() {
+        mListening = true;
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
+        if (mDemoMode) return;
+        mBatteryController.addStateChangedCallback(this);
+    }
+
+    public void stopListening() {
+        mListening = false;
+        mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+        if (mDemoMode) return;
+        mBatteryController.removeStateChangedCallback(this);
+    }
+
+    private void postInvalidate() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                invalidateSelf();
+            }
+        });
+    }
+
+    public void setBatteryController(BatteryController batteryController) {
+        mBatteryController = batteryController;
+        mPowerSaveEnabled = mBatteryController.isPowerSave();
+    }
+
+    @Override
+    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+        mLevel = level;
+        mPluggedIn = pluggedIn;
+
+        postInvalidate();
+    }
+
+    @Override
+    public void onPowerSaveChanged() {
+        mPowerSaveEnabled = mBatteryController.isPowerSave();
+        invalidateSelf();
+    }
+
+    private static float[] loadBoltPoints(Resources res) {
+        final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points);
+        int maxX = 0, maxY = 0;
+        for (int i = 0; i < pts.length; i += 2) {
+            maxX = Math.max(maxX, pts[i]);
+            maxY = Math.max(maxY, pts[i + 1]);
+        }
+        final float[] ptsF = new float[pts.length];
+        for (int i = 0; i < pts.length; i += 2) {
+            ptsF[i] = (float)pts[i] / maxX;
+            ptsF[i + 1] = (float)pts[i + 1] / maxY;
+        }
+        return ptsF;
+    }
+
+    @Override
+    public void setBounds(int left, int top, int right, int bottom) {
+        super.setBounds(left, top, right, bottom);
+        mHeight = bottom - top;
+        mWidth = right - left;
+        mWarningTextPaint.setTextSize(mHeight * 0.75f);
+        mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent;
+    }
+
+    private void updateShowPercent() {
+        mShowPercent = 0 != Settings.System.getInt(mContext.getContentResolver(),
+                SHOW_PERCENT_SETTING, 0);
+    }
+
+    private int getColorForLevel(int percent) {
+
+        // If we are in power save mode, always use the normal color.
+        if (mPowerSaveEnabled) {
+            return mColors[mColors.length-1];
+        }
+        int thresh, color = 0;
+        for (int i=0; i<mColors.length; i+=2) {
+            thresh = mColors[i];
+            color = mColors[i+1];
+            if (percent <= thresh) {
+
+                // Respect tinting for "normal" level
+                if (i == mColors.length-2) {
+                    return mIconTint;
+                } else {
+                    return color;
+                }
+            }
+        }
+        return color;
+    }
+
+    public void setDarkIntensity(float darkIntensity) {
+        int backgroundColor = getBackgroundColor(darkIntensity);
+        int fillColor = getFillColor(darkIntensity);
+        mIconTint = fillColor;
+        mFramePaint.setColor(backgroundColor);
+        mBoltPaint.setColor(fillColor);
+        mChargeColor = fillColor;
+        invalidateSelf();
+    }
+
+    private int getBackgroundColor(float darkIntensity) {
+        return getColorForDarkIntensity(
+                darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor);
+    }
+
+    private int getFillColor(float darkIntensity) {
+        return getColorForDarkIntensity(
+                darkIntensity, mLightModeFillColor, mDarkModeFillColor);
+    }
+
+    private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
+        return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        final int level = mLevel;
+
+        if (level == -1) return;
+
+        float drawFrac = (float) level / 100f;
+        final int height = mHeight;
+        final int width = (int) (ASPECT_RATIO * mHeight);
+        int px = (mWidth - width) / 2;
+
+        final int buttonHeight = (int) (height * mButtonHeightFraction);
+
+        mFrame.set(0, 0, width, height);
+        mFrame.offset(px, 0);
+
+        // button-frame: area above the battery body
+        mButtonFrame.set(
+                mFrame.left + Math.round(width * 0.25f),
+                mFrame.top,
+                mFrame.right - Math.round(width * 0.25f),
+                mFrame.top + buttonHeight);
+
+        mButtonFrame.top += mSubpixelSmoothingLeft;
+        mButtonFrame.left += mSubpixelSmoothingLeft;
+        mButtonFrame.right -= mSubpixelSmoothingRight;
+
+        // frame: battery body area
+        mFrame.top += buttonHeight;
+        mFrame.left += mSubpixelSmoothingLeft;
+        mFrame.top += mSubpixelSmoothingLeft;
+        mFrame.right -= mSubpixelSmoothingRight;
+        mFrame.bottom -= mSubpixelSmoothingRight;
+
+        // set the battery charging color
+        mBatteryPaint.setColor(mPluggedIn ? mChargeColor : getColorForLevel(level));
+
+        if (level >= FULL) {
+            drawFrac = 1f;
+        } else if (level <= mCriticalLevel) {
+            drawFrac = 0f;
+        }
+
+        final float levelTop = drawFrac == 1f ? mButtonFrame.top
+                : (mFrame.top + (mFrame.height() * (1f - drawFrac)));
+
+        // define the battery shape
+        mShapePath.reset();
+        mShapePath.moveTo(mButtonFrame.left, mButtonFrame.top);
+        mShapePath.lineTo(mButtonFrame.right, mButtonFrame.top);
+        mShapePath.lineTo(mButtonFrame.right, mFrame.top);
+        mShapePath.lineTo(mFrame.right, mFrame.top);
+        mShapePath.lineTo(mFrame.right, mFrame.bottom);
+        mShapePath.lineTo(mFrame.left, mFrame.bottom);
+        mShapePath.lineTo(mFrame.left, mFrame.top);
+        mShapePath.lineTo(mButtonFrame.left, mFrame.top);
+        mShapePath.lineTo(mButtonFrame.left, mButtonFrame.top);
+
+        if (mPluggedIn) {
+            // define the bolt shape
+            final float bl = mFrame.left + mFrame.width() / 4.5f;
+            final float bt = mFrame.top + mFrame.height() / 6f;
+            final float br = mFrame.right - mFrame.width() / 7f;
+            final float bb = mFrame.bottom - mFrame.height() / 10f;
+            if (mBoltFrame.left != bl || mBoltFrame.top != bt
+                    || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
+                mBoltFrame.set(bl, bt, br, bb);
+                mBoltPath.reset();
+                mBoltPath.moveTo(
+                        mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+                        mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+                for (int i = 2; i < mBoltPoints.length; i += 2) {
+                    mBoltPath.lineTo(
+                            mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
+                            mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
+                }
+                mBoltPath.lineTo(
+                        mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
+                        mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+            }
+
+            float boltPct = (mBoltFrame.bottom - levelTop) / (mBoltFrame.bottom - mBoltFrame.top);
+            boltPct = Math.min(Math.max(boltPct, 0), 1);
+            if (boltPct <= BOLT_LEVEL_THRESHOLD) {
+                // draw the bolt if opaque
+                c.drawPath(mBoltPath, mBoltPaint);
+            } else {
+                // otherwise cut the bolt out of the overall shape
+                mShapePath.op(mBoltPath, Path.Op.DIFFERENCE);
+            }
+        }
+
+        // compute percentage text
+        boolean pctOpaque = false;
+        float pctX = 0, pctY = 0;
+        String pctText = null;
+        if (!mPluggedIn && level > mCriticalLevel && mShowPercent) {
+            mTextPaint.setColor(getColorForLevel(level));
+            mTextPaint.setTextSize(height *
+                    (SINGLE_DIGIT_PERCENT ? 0.75f
+                            : (mLevel == 100 ? 0.38f : 0.5f)));
+            mTextHeight = -mTextPaint.getFontMetrics().ascent;
+            pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
+            pctX = mWidth * 0.5f;
+            pctY = (mHeight + mTextHeight) * 0.47f;
+            pctOpaque = levelTop > pctY;
+            if (!pctOpaque) {
+                mTextPath.reset();
+                mTextPaint.getTextPath(pctText, 0, pctText.length(), pctX, pctY, mTextPath);
+                // cut the percentage text out of the overall shape
+                mShapePath.op(mTextPath, Path.Op.DIFFERENCE);
+            }
+        }
+
+        // draw the battery shape background
+        c.drawPath(mShapePath, mFramePaint);
+
+        // draw the battery shape, clipped to charging level
+        mFrame.top = levelTop;
+        mClipPath.reset();
+        mClipPath.addRect(mFrame,  Path.Direction.CCW);
+        mShapePath.op(mClipPath, Path.Op.INTERSECT);
+        c.drawPath(mShapePath, mBatteryPaint);
+
+        if (!mPluggedIn) {
+            if (level <= mCriticalLevel) {
+                // draw the warning text
+                final float x = mWidth * 0.5f;
+                final float y = (mHeight + mWarningTextHeight) * 0.48f;
+                c.drawText(mWarningString, x, y, mWarningTextPaint);
+            } else if (pctOpaque) {
+                // draw the percentage text
+                c.drawText(pctText, pctX, pctY, mTextPaint);
+            }
+        }
+    }
+
+    // Some stuff required by Drawable.
+    @Override
+    public void setAlpha(int alpha) {
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+    }
+
+    @Override
+    public int getOpacity() {
+        return 0;
+    }
+
+    private boolean mDemoMode;
+
+    @Override
+    public void dispatchDemoCommand(String command, Bundle args) {
+        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+            mBatteryController.removeStateChangedCallback(this);
+            mDemoMode = true;
+            if (mListening) {
+                mBatteryController.removeStateChangedCallback(this);
+            }
+        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+            mDemoMode = false;
+            postInvalidate();
+            if (mListening) {
+                mBatteryController.addStateChangedCallback(this);
+            }
+        } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
+           String level = args.getString("level");
+           String plugged = args.getString("plugged");
+           if (level != null) {
+               mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
+           }
+           if (plugged != null) {
+               mPluggedIn = Boolean.parseBoolean(plugged);
+           }
+           postInvalidate();
+        }
+    }
+
+    private final class SettingObserver extends ContentObserver {
+        public SettingObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            updateShowPercent();
+            postInvalidate();
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
old mode 100755
new mode 100644
index 95b58e5..6cb8da4
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -13,82 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.systemui;
 
-import android.animation.ArgbEvaluator;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.database.ContentObserver;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.BatteryManager;
-import android.os.Bundle;
 import android.os.Handler;
-import android.provider.Settings;
 import android.util.AttributeSet;
-import android.view.View;
-
+import android.widget.ImageView;
 import com.android.systemui.statusbar.policy.BatteryController;
 
-public class BatteryMeterView extends View implements DemoMode,
-        BatteryController.BatteryStateChangeCallback {
-    public static final String TAG = BatteryMeterView.class.getSimpleName();
-    public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
-    public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent";
+public class BatteryMeterView extends ImageView implements BatteryController.BatteryStateChangeCallback {
 
-    private static final boolean SINGLE_DIGIT_PERCENT = false;
-
-    private static final int FULL = 96;
-
-    private static final float BOLT_LEVEL_THRESHOLD = 0.3f;  // opaque bolt below this fraction
-
-    private final int[] mColors;
-
-    private boolean mShowPercent;
-    private float mButtonHeightFraction;
-    private float mSubpixelSmoothingLeft;
-    private float mSubpixelSmoothingRight;
-    private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
-    private float mTextHeight, mWarningTextHeight;
-    private int mIconTint = Color.WHITE;
-
-    private int mHeight;
-    private int mWidth;
-    private String mWarningString;
-    private final int mCriticalLevel;
-    private int mChargeColor;
-    private final float[] mBoltPoints;
-    private final Path mBoltPath = new Path();
-
-    private final RectF mFrame = new RectF();
-    private final RectF mButtonFrame = new RectF();
-    private final RectF mBoltFrame = new RectF();
-
-    private final Path mShapePath = new Path();
-    private final Path mClipPath = new Path();
-    private final Path mTextPath = new Path();
-
+    private final BatteryMeterDrawable mDrawable;
     private BatteryController mBatteryController;
-    private boolean mPowerSaveEnabled;
-
-    private int mDarkModeBackgroundColor;
-    private int mDarkModeFillColor;
-
-    private int mLightModeBackgroundColor;
-    private int mLightModeFillColor;
-
-    private BatteryTracker mTracker = new BatteryTracker();
-    private final SettingObserver mSettingObserver = new SettingObserver();
 
     public BatteryMeterView(Context context) {
         this(context, null, 0);
@@ -101,326 +38,14 @@
     public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        final Resources res = context.getResources();
         TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
                 defStyle, 0);
         final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
                 context.getColor(R.color.batterymeter_frame_color));
-        TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
-        TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
-
-        final int N = levels.length();
-        mColors = new int[2*N];
-        for (int i=0; i<N; i++) {
-            mColors[2*i] = levels.getInt(i, 0);
-            mColors[2*i+1] = colors.getColor(i, 0);
-        }
-        levels.recycle();
-        colors.recycle();
+        mDrawable = new BatteryMeterDrawable(context, new Handler(), frameColor);
         atts.recycle();
-        updateShowPercent();
-        mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol);
-        mCriticalLevel = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_criticalBatteryWarningLevel);
-        mButtonHeightFraction = context.getResources().getFraction(
-                R.fraction.battery_button_height_fraction, 1, 1);
-        mSubpixelSmoothingLeft = context.getResources().getFraction(
-                R.fraction.battery_subpixel_smoothing_left, 1, 1);
-        mSubpixelSmoothingRight = context.getResources().getFraction(
-                R.fraction.battery_subpixel_smoothing_right, 1, 1);
 
-        mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mFramePaint.setColor(frameColor);
-        mFramePaint.setDither(true);
-        mFramePaint.setStrokeWidth(0);
-        mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE);
-
-        mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mBatteryPaint.setDither(true);
-        mBatteryPaint.setStrokeWidth(0);
-        mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-
-        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
-        mTextPaint.setTypeface(font);
-        mTextPaint.setTextAlign(Paint.Align.CENTER);
-
-        mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mWarningTextPaint.setColor(mColors[1]);
-        font = Typeface.create("sans-serif", Typeface.BOLD);
-        mWarningTextPaint.setTypeface(font);
-        mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
-
-        mChargeColor = context.getColor(R.color.batterymeter_charge_color);
-
-        mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mBoltPaint.setColor(context.getColor(R.color.batterymeter_bolt_color));
-        mBoltPoints = loadBoltPoints(res);
-
-        mDarkModeBackgroundColor =
-                context.getColor(R.color.dark_mode_icon_color_dual_tone_background);
-        mDarkModeFillColor = context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
-        mLightModeBackgroundColor =
-                context.getColor(R.color.light_mode_icon_color_dual_tone_background);
-        mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-        filter.addAction(ACTION_LEVEL_TEST);
-        final Intent sticky = getContext().registerReceiver(mTracker, filter);
-        if (sticky != null) {
-            // preload the battery level
-            mTracker.onReceive(getContext(), sticky);
-        }
-        mBatteryController.addStateChangedCallback(this);
-        getContext().getContentResolver().registerContentObserver(
-                Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        getContext().unregisterReceiver(mTracker);
-        mBatteryController.removeStateChangedCallback(this);
-        getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
-    }
-
-    public void setBatteryController(BatteryController batteryController) {
-        mBatteryController = batteryController;
-        mPowerSaveEnabled = mBatteryController.isPowerSave();
-    }
-
-    @Override
-    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-        // TODO: Use this callback instead of own broadcast receiver.
-    }
-
-    @Override
-    public void onPowerSaveChanged() {
-        mPowerSaveEnabled = mBatteryController.isPowerSave();
-        invalidate();
-    }
-
-    private static float[] loadBoltPoints(Resources res) {
-        final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points);
-        int maxX = 0, maxY = 0;
-        for (int i = 0; i < pts.length; i += 2) {
-            maxX = Math.max(maxX, pts[i]);
-            maxY = Math.max(maxY, pts[i + 1]);
-        }
-        final float[] ptsF = new float[pts.length];
-        for (int i = 0; i < pts.length; i += 2) {
-            ptsF[i] = (float)pts[i] / maxX;
-            ptsF[i + 1] = (float)pts[i + 1] / maxY;
-        }
-        return ptsF;
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        mHeight = h;
-        mWidth = w;
-        mWarningTextPaint.setTextSize(h * 0.75f);
-        mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent;
-    }
-
-    private void updateShowPercent() {
-        mShowPercent = 0 != Settings.System.getInt(getContext().getContentResolver(),
-                SHOW_PERCENT_SETTING, 0);
-    }
-
-    private int getColorForLevel(int percent) {
-
-        // If we are in power save mode, always use the normal color.
-        if (mPowerSaveEnabled) {
-            return mColors[mColors.length-1];
-        }
-        int thresh, color = 0;
-        for (int i=0; i<mColors.length; i+=2) {
-            thresh = mColors[i];
-            color = mColors[i+1];
-            if (percent <= thresh) {
-
-                // Respect tinting for "normal" level
-                if (i == mColors.length-2) {
-                    return mIconTint;
-                } else {
-                    return color;
-                }
-            }
-        }
-        return color;
-    }
-
-    public void setDarkIntensity(float darkIntensity) {
-        int backgroundColor = getBackgroundColor(darkIntensity);
-        int fillColor = getFillColor(darkIntensity);
-        mIconTint = fillColor;
-        mFramePaint.setColor(backgroundColor);
-        mBoltPaint.setColor(fillColor);
-        mChargeColor = fillColor;
-        invalidate();
-    }
-
-    private int getBackgroundColor(float darkIntensity) {
-        return getColorForDarkIntensity(
-                darkIntensity, mLightModeBackgroundColor, mDarkModeBackgroundColor);
-    }
-
-    private int getFillColor(float darkIntensity) {
-        return getColorForDarkIntensity(
-                darkIntensity, mLightModeFillColor, mDarkModeFillColor);
-    }
-
-    private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
-        return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
-    }
-
-    @Override
-    public void draw(Canvas c) {
-        BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
-        final int level = tracker.level;
-
-        if (level == BatteryTracker.UNKNOWN_LEVEL) return;
-
-        float drawFrac = (float) level / 100f;
-        final int pt = getPaddingTop();
-        final int pl = getPaddingLeft();
-        final int pr = getPaddingRight();
-        final int pb = getPaddingBottom();
-        final int height = mHeight - pt - pb;
-        final int width = mWidth - pl - pr;
-
-        final int buttonHeight = (int) (height * mButtonHeightFraction);
-
-        mFrame.set(0, 0, width, height);
-        mFrame.offset(pl, pt);
-
-        // button-frame: area above the battery body
-        mButtonFrame.set(
-                mFrame.left + Math.round(width * 0.25f),
-                mFrame.top,
-                mFrame.right - Math.round(width * 0.25f),
-                mFrame.top + buttonHeight);
-
-        mButtonFrame.top += mSubpixelSmoothingLeft;
-        mButtonFrame.left += mSubpixelSmoothingLeft;
-        mButtonFrame.right -= mSubpixelSmoothingRight;
-
-        // frame: battery body area
-        mFrame.top += buttonHeight;
-        mFrame.left += mSubpixelSmoothingLeft;
-        mFrame.top += mSubpixelSmoothingLeft;
-        mFrame.right -= mSubpixelSmoothingRight;
-        mFrame.bottom -= mSubpixelSmoothingRight;
-
-        // set the battery charging color
-        mBatteryPaint.setColor(tracker.plugged ? mChargeColor : getColorForLevel(level));
-
-        if (level >= FULL) {
-            drawFrac = 1f;
-        } else if (level <= mCriticalLevel) {
-            drawFrac = 0f;
-        }
-
-        final float levelTop = drawFrac == 1f ? mButtonFrame.top
-                : (mFrame.top + (mFrame.height() * (1f - drawFrac)));
-
-        // define the battery shape
-        mShapePath.reset();
-        mShapePath.moveTo(mButtonFrame.left, mButtonFrame.top);
-        mShapePath.lineTo(mButtonFrame.right, mButtonFrame.top);
-        mShapePath.lineTo(mButtonFrame.right, mFrame.top);
-        mShapePath.lineTo(mFrame.right, mFrame.top);
-        mShapePath.lineTo(mFrame.right, mFrame.bottom);
-        mShapePath.lineTo(mFrame.left, mFrame.bottom);
-        mShapePath.lineTo(mFrame.left, mFrame.top);
-        mShapePath.lineTo(mButtonFrame.left, mFrame.top);
-        mShapePath.lineTo(mButtonFrame.left, mButtonFrame.top);
-
-        if (tracker.plugged) {
-            // define the bolt shape
-            final float bl = mFrame.left + mFrame.width() / 4.5f;
-            final float bt = mFrame.top + mFrame.height() / 6f;
-            final float br = mFrame.right - mFrame.width() / 7f;
-            final float bb = mFrame.bottom - mFrame.height() / 10f;
-            if (mBoltFrame.left != bl || mBoltFrame.top != bt
-                    || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
-                mBoltFrame.set(bl, bt, br, bb);
-                mBoltPath.reset();
-                mBoltPath.moveTo(
-                        mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
-                        mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
-                for (int i = 2; i < mBoltPoints.length; i += 2) {
-                    mBoltPath.lineTo(
-                            mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
-                            mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
-                }
-                mBoltPath.lineTo(
-                        mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
-                        mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
-            }
-
-            float boltPct = (mBoltFrame.bottom - levelTop) / (mBoltFrame.bottom - mBoltFrame.top);
-            boltPct = Math.min(Math.max(boltPct, 0), 1);
-            if (boltPct <= BOLT_LEVEL_THRESHOLD) {
-                // draw the bolt if opaque
-                c.drawPath(mBoltPath, mBoltPaint);
-            } else {
-                // otherwise cut the bolt out of the overall shape
-                mShapePath.op(mBoltPath, Path.Op.DIFFERENCE);
-            }
-        }
-
-        // compute percentage text
-        boolean pctOpaque = false;
-        float pctX = 0, pctY = 0;
-        String pctText = null;
-        if (!tracker.plugged && level > mCriticalLevel && mShowPercent) {
-            mTextPaint.setColor(getColorForLevel(level));
-            mTextPaint.setTextSize(height *
-                    (SINGLE_DIGIT_PERCENT ? 0.75f
-                            : (tracker.level == 100 ? 0.38f : 0.5f)));
-            mTextHeight = -mTextPaint.getFontMetrics().ascent;
-            pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
-            pctX = mWidth * 0.5f;
-            pctY = (mHeight + mTextHeight) * 0.47f;
-            pctOpaque = levelTop > pctY;
-            if (!pctOpaque) {
-                mTextPath.reset();
-                mTextPaint.getTextPath(pctText, 0, pctText.length(), pctX, pctY, mTextPath);
-                // cut the percentage text out of the overall shape
-                mShapePath.op(mTextPath, Path.Op.DIFFERENCE);
-            }
-        }
-
-        // draw the battery shape background
-        c.drawPath(mShapePath, mFramePaint);
-
-        // draw the battery shape, clipped to charging level
-        mFrame.top = levelTop;
-        mClipPath.reset();
-        mClipPath.addRect(mFrame,  Path.Direction.CCW);
-        mShapePath.op(mClipPath, Path.Op.INTERSECT);
-        c.drawPath(mShapePath, mBatteryPaint);
-
-        if (!tracker.plugged) {
-            if (level <= mCriticalLevel) {
-                // draw the warning text
-                final float x = mWidth * 0.5f;
-                final float y = (mHeight + mWarningTextHeight) * 0.48f;
-                c.drawText(mWarningString, x, y, mWarningTextPaint);
-            } else if (pctOpaque) {
-                // draw the percentage text
-                c.drawText(pctText, pctX, pctY, mTextPaint);
-            }
-        }
+        setImageDrawable(mDrawable);
     }
 
     @Override
@@ -428,116 +53,37 @@
         return false;
     }
 
-    private boolean mDemoMode;
-    private BatteryTracker mDemoTracker = new BatteryTracker();
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mBatteryController.addStateChangedCallback(this);
+        mDrawable.startListening();
+    }
 
     @Override
-    public void dispatchDemoCommand(String command, Bundle args) {
-        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
-            mDemoMode = true;
-            mDemoTracker.level = mTracker.level;
-            mDemoTracker.plugged = mTracker.plugged;
-        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
-            mDemoMode = false;
-            postInvalidate();
-        } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
-           String level = args.getString("level");
-           String plugged = args.getString("plugged");
-           if (level != null) {
-               mDemoTracker.level = Math.min(Math.max(Integer.parseInt(level), 0), 100);
-           }
-           if (plugged != null) {
-               mDemoTracker.plugged = Boolean.parseBoolean(plugged);
-           }
-           postInvalidate();
-        }
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mBatteryController.removeStateChangedCallback(this);
+        mDrawable.stopListening();
     }
 
-    private final class BatteryTracker extends BroadcastReceiver {
-        public static final int UNKNOWN_LEVEL = -1;
-
-        // current battery status
-        int level = UNKNOWN_LEVEL;
-        String percentStr;
-        int plugType;
-        boolean plugged;
-        int health;
-        int status;
-        String technology;
-        int voltage;
-        int temperature;
-        boolean testmode = false;
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-                if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
-
-                level = (int)(100f
-                        * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
-                        / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
-
-                plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
-                plugged = plugType != 0;
-                health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
-                        BatteryManager.BATTERY_HEALTH_UNKNOWN);
-                status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
-                        BatteryManager.BATTERY_STATUS_UNKNOWN);
-                technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
-                voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
-                temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
-
-                setContentDescription(
-                        context.getString(R.string.accessibility_battery_level, level));
-                postInvalidate();
-            } else if (action.equals(ACTION_LEVEL_TEST)) {
-                testmode = true;
-                post(new Runnable() {
-                    int curLevel = 0;
-                    int incr = 1;
-                    int saveLevel = level;
-                    int savePlugged = plugType;
-                    Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
-                    @Override
-                    public void run() {
-                        if (curLevel < 0) {
-                            testmode = false;
-                            dummy.putExtra("level", saveLevel);
-                            dummy.putExtra("plugged", savePlugged);
-                            dummy.putExtra("testmode", false);
-                        } else {
-                            dummy.putExtra("level", curLevel);
-                            dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
-                                    : 0);
-                            dummy.putExtra("testmode", true);
-                        }
-                        getContext().sendBroadcast(dummy);
-
-                        if (!testmode) return;
-
-                        curLevel += incr;
-                        if (curLevel == 100) {
-                            incr *= -1;
-                        }
-                        postDelayed(this, 200);
-                    }
-                });
-            }
-        }
+    @Override
+    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+        setContentDescription(
+                getContext().getString(R.string.accessibility_battery_level, level));
     }
 
-    private final class SettingObserver extends ContentObserver {
-        public SettingObserver() {
-            super(new Handler());
-        }
+    @Override
+    public void onPowerSaveChanged() {
 
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            super.onChange(selfChange, uri);
-            updateShowPercent();
-            postInvalidate();
-        }
     }
 
+    public void setBatteryController(BatteryController mBatteryController) {
+        this.mBatteryController = mBatteryController;
+        mDrawable.setBatteryController(mBatteryController);
+    }
+
+    public void setDarkIntensity(float f) {
+        mDrawable.setDarkIntensity(f);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 3b3593b..e562682 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -30,16 +30,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.qs.QSTile.State;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.Listenable;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.*;
 
 import java.util.Collection;
 import java.util.Objects;
@@ -349,6 +340,9 @@
         CastController getCastController();
         FlashlightController getFlashlightController();
         KeyguardMonitor getKeyguardMonitor();
+        UserSwitcherController getUserSwitcherController();
+        UserInfoController getUserInfoController();
+        BatteryController getBatteryController();
 
         public interface Callback {
             void onTilesChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 08cdc1e..e575923 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -267,7 +267,7 @@
         final int w = MeasureSpec.getSize(widthMeasureSpec);
         final int h = MeasureSpec.getSize(heightMeasureSpec);
         final int iconSpec = exactly(mIconSizePx);
-        mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST), iconSpec);
+        mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), iconSpec);
         switch (mType) {
             case QS_TYPE_QUICK:
                 mCircle.measure(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
index 84b05d0..f676ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
@@ -44,7 +44,8 @@
                 host.getRotationLockController(), host.getNetworkController(),
                 host.getZenModeController(), host.getHotspotController(), host.getCastController(),
                 host.getFlashlightController(), host.getUserSwitcherController(),
-                host.getKeyguardMonitor(), new BlankSecurityController());
+                host.getUserInfoController(), host.getKeyguardMonitor(),
+                new BlankSecurityController(), host.getBatteryController());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
new file mode 100644
index 0000000..8f9655d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.BatteryMeterDrawable;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import java.text.NumberFormat;
+
+public class BatteryTile extends QSTile<QSTile.State> implements BatteryController.BatteryStateChangeCallback {
+
+    private final BatteryMeterDrawable mDrawable;
+    private final BatteryController mBatteryController;
+
+    private int mLevel;
+
+    public BatteryTile(Host host) {
+        super(host);
+        mBatteryController = host.getBatteryController();
+        mDrawable = new BatteryMeterDrawable(host.getContext(), new Handler(),
+                host.getContext().getColor(R.color.batterymeter_frame_color));
+        mDrawable.setBatteryController(mBatteryController);
+    }
+
+    @Override
+    protected State newTileState() {
+        return new QSTile.State();
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_BATTERY_TILE;
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (listening) {
+            mDrawable.startListening();
+            mBatteryController.addStateChangedCallback(this);
+        } else {
+            mDrawable.stopListening();
+            mBatteryController.removeStateChangedCallback(this);
+        }
+    }
+
+    @Override
+    protected void handleClick() {
+        mHost.startActivityDismissingKeyguard(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY));
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        int level = (arg != null) ? (Integer) arg : mLevel;
+        String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
+
+        state.visible = true;
+        state.icon = new Icon() {
+            @Override
+            public Drawable getDrawable(Context context) {
+                return mDrawable;
+            }
+        };
+        state.label = percentage;
+    }
+
+    @Override
+    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+        mLevel = level;
+        refreshState((Integer) level);
+    }
+
+    @Override
+    public void onPowerSaveChanged() {
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
new file mode 100644
index 0000000..3675f02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileView;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.R;
+
+public class QLockTile extends QSTile<QSTile.State> implements KeyguardMonitor.Callback {
+
+    private final KeyguardMonitor mKeyguard;
+
+    public QLockTile(Host host) {
+        super(host);
+        mKeyguard = host.getKeyguardMonitor();
+    }
+
+    @Override
+    public int getTileType() {
+        return QSTileView.QS_TYPE_QUICK;
+    }
+
+    @Override
+    protected State newTileState() {
+        return new State();
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (listening) {
+            mKeyguard.addCallback(this);
+        } else {
+            mKeyguard.removeCallback(this);
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_LOCK_TILE;
+    }
+
+    @Override
+    public void onKeyguardChanged() {
+        refreshState();
+    }
+
+    @Override
+    protected void handleClick() {
+        if (mKeyguard.isShowing()) {
+            mKeyguard.unlock();
+        } else {
+            mKeyguard.lock();
+        }
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        // TOD: Content description.
+        state.visible = true;
+        if (mKeyguard.isShowing()) {
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_lock);
+        } else {
+            state.icon = ResourceIcon.get(R.drawable.ic_qs_lock_open);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
new file mode 100644
index 0000000..3c5ab8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.Pair;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+
+public class UserTile extends QSTile<QSTile.State> implements UserInfoController.OnUserInfoChangedListener {
+
+    private final UserSwitcherController mUserSwitcherController;
+    private final UserInfoController mUserInfoController;
+    private Pair<String, Drawable> mLastUpdate;
+
+    public UserTile(Host host) {
+        super(host);
+        mUserSwitcherController = host.getUserSwitcherController();
+        mUserInfoController = host.getUserInfoController();
+    }
+
+    @Override
+    protected State newTileState() {
+        return new QSTile.State();
+    }
+
+    @Override
+    protected void handleClick() {
+        showDetail(true);
+    }
+
+    @Override
+    public DetailAdapter getDetailAdapter() {
+        return mUserSwitcherController.userDetailAdapter;
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_USER_TILE;
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+        if (listening) {
+            mUserInfoController.addListener(this);
+        } else {
+            mUserInfoController.remListener(this);
+        }
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        final Pair<String, Drawable> p = arg != null ? (Pair<String, Drawable>) arg : mLastUpdate;
+        state.visible = p != null;
+        if (!state.visible) return;
+        state.label = p.first;
+        // TODO: Better content description.
+        state.contentDescription = p.first;
+        state.icon = new Icon() {
+            @Override
+            public Drawable getDrawable(Context context) {
+                return p.second;
+            }
+        };
+    }
+
+    @Override
+    public void onUserInfoChanged(String name, Drawable picture) {
+        mLastUpdate = new Pair<>(name, picture);
+        refreshState(mLastUpdate);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 98f5444..876a934 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -904,8 +904,8 @@
                     mBluetoothController, mLocationController, mRotationLockController,
                     mNetworkController, mZenModeController, mHotspotController,
                     mCastController, mFlashlightController,
-                    mUserSwitcherController, mKeyguardMonitor,
-                    mSecurityController);
+                    mUserSwitcherController, mUserInfoController, mKeyguardMonitor,
+                    mSecurityController, mBatteryController);
             mQSPanel.setHost(qsh);
             mQSPanel.setTiles(qsh.getTiles());
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
@@ -3001,6 +3001,10 @@
         dismissKeyguardThenExecute(action, null /* cancelRunnable */, afterKeyguardGone);
     }
 
+    public void dismissKeyguard() {
+        mStatusBarKeyguardViewManager.dismiss();
+    }
+
     private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone) {
         if (mStatusBarKeyguardViewManager.isShowing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 385c5d5..6ec9494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -26,34 +26,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.tiles.AirplaneModeTile;
-import com.android.systemui.qs.tiles.BluetoothTile;
-import com.android.systemui.qs.tiles.CastTile;
-import com.android.systemui.qs.tiles.CellularTile;
-import com.android.systemui.qs.tiles.ColorInversionTile;
-import com.android.systemui.qs.tiles.DndTile;
-import com.android.systemui.qs.tiles.FlashlightTile;
-import com.android.systemui.qs.tiles.HotspotTile;
-import com.android.systemui.qs.tiles.IntentTile;
-import com.android.systemui.qs.tiles.LocationTile;
-import com.android.systemui.qs.tiles.QAirplaneTile;
-import com.android.systemui.qs.tiles.QBluetoothTile;
-import com.android.systemui.qs.tiles.QFlashlightTile;
-import com.android.systemui.qs.tiles.QRotationLockTile;
-import com.android.systemui.qs.tiles.QWifiTile;
-import com.android.systemui.qs.tiles.RotationLockTile;
-import com.android.systemui.qs.tiles.WifiTile;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.qs.tiles.*;
+import com.android.systemui.statusbar.policy.*;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -85,8 +59,10 @@
     private final Looper mLooper;
     private final FlashlightController mFlashlight;
     private final UserSwitcherController mUserSwitcherController;
+    private final UserInfoController mUserInfoController;
     private final KeyguardMonitor mKeyguard;
     private final SecurityController mSecurity;
+    private final BatteryController mBattery;
 
     private Callback mCallback;
 
@@ -95,8 +71,8 @@
             RotationLockController rotation, NetworkController network,
             ZenModeController zen, HotspotController hotspot,
             CastController cast, FlashlightController flashlight,
-            UserSwitcherController userSwitcher, KeyguardMonitor keyguard,
-            SecurityController security) {
+            UserSwitcherController userSwitcher, UserInfoController userInfo, KeyguardMonitor keyguard,
+            SecurityController security, BatteryController battery) {
         mContext = context;
         mStatusBar = statusBar;
         mBluetooth = bluetooth;
@@ -108,8 +84,10 @@
         mCast = cast;
         mFlashlight = flashlight;
         mUserSwitcherController = userSwitcher;
+        mUserInfoController = userInfo;
         mKeyguard = keyguard;
         mSecurity = security;
+        mBattery = battery;
 
         final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
                 Process.THREAD_PRIORITY_BACKGROUND);
@@ -203,10 +181,21 @@
         return mKeyguard;
     }
 
+    @Override
     public UserSwitcherController getUserSwitcherController() {
         return mUserSwitcherController;
     }
 
+    @Override
+    public UserInfoController getUserInfoController() {
+        return mUserInfoController;
+    }
+
+    @Override
+    public BatteryController getBatteryController() {
+        return mBattery;
+    }
+
     public SecurityController getSecurityController() {
         return mSecurity;
     }
@@ -259,6 +248,8 @@
         else if (tileSpec.equals("location")) return new LocationTile(this);
         else if (tileSpec.equals("cast")) return new CastTile(this);
         else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
+        else if (tileSpec.equals("user")) return new UserTile(this);
+        else if (tileSpec.equals("battery")) return new BatteryTile(this);
         // Detail only versions of wifi and bluetooth.
         else if (tileSpec.equals("dwifi")) return new WifiTile(this, true);
         else if (tileSpec.equals("dbt")) return new BluetoothTile(this, true);
@@ -268,6 +259,7 @@
         else if (tileSpec.equals("qairplane")) return new QAirplaneTile(this);
         else if (tileSpec.equals("qrotation")) return new QRotationLockTile(this);
         else if (tileSpec.equals("qflashlight")) return new QFlashlightTile(this);
+        else if (tileSpec.equals("qlock")) return new QLockTile(this);
         // Intent tiles.
         else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
         else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index d1b69ab..5071df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.util.Log;
 
@@ -30,24 +31,31 @@
 
 public class BatteryController extends BroadcastReceiver {
     private static final String TAG = "BatteryController";
+
+    public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
     private final PowerManager mPowerManager;
+    private final Handler mHandler;
 
     private int mLevel;
     private boolean mPluggedIn;
     private boolean mCharging;
     private boolean mCharged;
     private boolean mPowerSave;
+    private boolean mTestmode = false;
 
     public BatteryController(Context context) {
+        mHandler = new Handler();
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
+        filter.addAction(ACTION_LEVEL_TEST);
         context.registerReceiver(this, filter);
 
         updatePowerSave();
@@ -71,9 +79,10 @@
         mChangeCallbacks.remove(cb);
     }
 
-    public void onReceive(Context context, Intent intent) {
+    public void onReceive(final Context context, Intent intent) {
         final String action = intent.getAction();
         if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+            if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
             mLevel = (int)(100f
                     * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
                     / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
@@ -89,6 +98,38 @@
             updatePowerSave();
         } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
             setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
+        } else if (action.equals(ACTION_LEVEL_TEST)) {
+            mTestmode = true;
+            mHandler.post(new Runnable() {
+                int curLevel = 0;
+                int incr = 1;
+                int saveLevel = mLevel;
+                boolean savePlugged = mPluggedIn;
+                Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
+                @Override
+                public void run() {
+                    if (curLevel < 0) {
+                        mTestmode = false;
+                        dummy.putExtra("level", saveLevel);
+                        dummy.putExtra("plugged", savePlugged);
+                        dummy.putExtra("testmode", false);
+                    } else {
+                        dummy.putExtra("level", curLevel);
+                        dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
+                                : 0);
+                        dummy.putExtra("testmode", true);
+                    }
+                    context.sendBroadcast(dummy);
+
+                    if (!mTestmode) return;
+
+                    curLevel += incr;
+                    if (curLevel == 100) {
+                        incr *= -1;
+                    }
+                    mHandler.postDelayed(this, 200);
+                }
+            });
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index d907b00..cec0c0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -18,7 +18,8 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
-
+import android.os.RemoteException;
+import android.view.WindowManagerGlobal;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.settings.CurrentUserTracker;
@@ -83,6 +84,20 @@
         return mCanSkipBouncer;
     }
 
+    public void unlock() {
+        try {
+            WindowManagerGlobal.getWindowManagerService().dismissKeyguard();
+        } catch (RemoteException e) {
+        }
+    }
+
+    public void lock() {
+        try {
+            WindowManagerGlobal.getWindowManagerService().lockNow(null /* options */);
+        } catch (RemoteException e) {
+        }
+    }
+
     public void notifyKeyguardState(boolean showing, boolean secure) {
         if (mShowing == showing && mSecure == secure) return;
         mShowing = showing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index a8d4f13..6931d1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -71,6 +71,11 @@
 
     public void addListener(OnUserInfoChangedListener callback) {
         mCallbacks.add(callback);
+        callback.onUserInfoChanged(mUserName, mUserDrawable);
+    }
+
+    public void remListener(OnUserInfoChangedListener callback) {
+        mCallbacks.remove(callback);
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java
index 4387b33..04a51f0 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QSPagingSwitch.java
@@ -9,8 +9,8 @@
 public class QSPagingSwitch extends TunerSwitch {
 
     public static final String QS_PAGE_TILES =
-            "dwifi,dbt,inversion,dnd,cell,airplane,rotation,flashlight,location,"
-             + "hotspot,qwifi,qbt,qrotation,qflashlight,qairplane,cast";
+            "dwifi,dbt,dnd,cell,battery,user,rotation,flashlight,location,"
+             + "hotspot,qwifi,qbt,qlock,qflashlight,qairplane,inversion,cast";
 
     public QSPagingSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index 772f866..703ee661 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -206,8 +206,8 @@
     private static class CustomHost extends QSTileHost {
 
         public CustomHost(Context context) {
-            super(context, null, null, null, null, null, null, null, null, null,
-                    null, null, new BlankSecurityController());
+            super(context, null, null, null, null, null, null, null, null, null, null,
+                    null, null, new BlankSecurityController(), null);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 920f875..9a78b6c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -39,7 +39,7 @@
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.tuner.TunerService.Tunable;
 
-import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING;
+import static com.android.systemui.BatteryMeterDrawable.SHOW_PERCENT_SETTING;
 
 public class TunerFragment extends PreferenceFragment {
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 50234b2..71559389 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -33,7 +33,7 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 
-import com.android.systemui.BatteryMeterView;
+import com.android.systemui.BatteryMeterDrawable;
 import com.android.systemui.DemoMode;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
@@ -135,7 +135,7 @@
     public void clearAll() {
         // A couple special cases.
         Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
-        Settings.System.putString(mContentResolver, BatteryMeterView.SHOW_PERCENT_SETTING, null);
+        Settings.System.putString(mContentResolver, BatteryMeterDrawable.SHOW_PERCENT_SETTING, null);
         Intent intent = new Intent(DemoMode.ACTION_DEMO);
         intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
         mContext.sendBroadcast(intent);