Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit df21a7ff authored by Sven Dawitz's avatar Sven Dawitz
Browse files

Battery percentage on status bar - done right!

This is by far the most requested feature! First try of implementing this
didnt go very well. Hope this solves the issue and gets merged ASAP.

- Added optional Percentage-Status-Bar-Battery
- Displaying text next to a mini battery icon
- no additional resources added!
- no color pickers were harmed in the process!
- mini battery icon build from a scaled slice, cutted from the 4th vertical
  line of pixels from the original battery icon of the matching charge level
- this way, themes will be obeyed, as long as those themes dont change the
  basic layout of the battery icon too much. in such cases (i.e. theme features
  some kind of circle mod) this option should be disabled anyways
- Added also an option to disable clock in status bar, so our Status-Bar-Tweaks
  submenu got more than one entry - i look forward for it to grow in cm7.1
- Changed as few original files as possible. mainly since status bar is most
  likely a place of changes by google. CmBatteryText and CmBatteryMiniIcon do
  most of the work. Original files needed a SettingsObserver of course.

The usual screenshots: http://kan.gd/sc

Needs review on MDPI and LDPI devices

Change-Id: I187710784909eca9d7271f021aa16ab94212f3e6
parent 44eb91e6
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -2106,6 +2106,26 @@ public final class Settings {
         */
        public static final String LOCK_MMS_IN_MEMORY = "lock_mms_in_memory";

        /**
         * Whether to show the CM battery percentage implementation instead
         * of the stock battery icon
         * 0: don't show / show stock icon instead
         * 1: show cm battery / dont show stock icon
         * default: 0
         * @hide
         */
        public static final String STATUS_BAR_CM_BATTERY = "status_bar_cm_battery";

        /**
         * Whether to show the clock in status bar
         * of the stock battery icon
         * 0: don't show the clock
         * 1: show the clock
         * default: 1
         * @hide
         */
        public static final String STATUS_BAR_CLOCK = "status_bar_clock";

        /**
         * Whether to wake the screen with the trackball. The value is boolean (1 or 0).
         * @hide
+17 −1
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
/* apps/common/assets/default/default/skins/StatusBar.xml
**
** Copyright 2006, The Android Open Source Project
** Patched by Sven Dawitz; Copyright (C) 2011 CyanogenMod Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
@@ -45,7 +46,22 @@
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:paddingRight="6dip"
            android:paddingRight="1dip"
            android:gravity="center_vertical"
            android:orientation="horizontal"/>    

        <com.android.systemui.statusbar.CmBatteryText
            android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingRight="2dip"
            android:gravity="center_vertical"
            android:orientation="horizontal"/>

        <com.android.systemui.statusbar.CmBatteryMiniIcon
            android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:orientation="horizontal"/>

+48 −13
Original line number Diff line number Diff line
/*
 * Copyright (C) 2006 The Android Open Source Project
 * Patched by Sven Dawitz; Copyright (C) 2011 CyanogenMod Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -16,31 +17,27 @@

package com.android.systemui.statusbar;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.database.ContentObserver;
import android.os.Handler;
import android.provider.Settings;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.format.DateFormat;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

import com.android.internal.R;

/**
@@ -59,6 +56,26 @@ public class Clock extends TextView {

    private static final int AM_PM_STYLE = AM_PM_STYLE_GONE;

    private boolean mShowClock;

    Handler mHandler;

    class SettingsObserver extends ContentObserver {
        SettingsObserver(Handler handler) {
            super(handler);
        }

        void observe() {
            ContentResolver resolver = mContext.getContentResolver();
            resolver.registerContentObserver(Settings.System.getUriFor(
                    Settings.System.STATUS_BAR_CLOCK), false, this);
        }

        @Override public void onChange(boolean selfChange) {
            updateSettings();
        }
    }

    public Clock(Context context) {
        this(context, null);
    }
@@ -69,6 +86,12 @@ public class Clock extends TextView {

    public Clock(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mHandler = new Handler();
        SettingsObserver settingsObserver = new SettingsObserver(mHandler);
        settingsObserver.observe();

        updateSettings();
    }

    @Override
@@ -204,5 +227,17 @@ public class Clock extends TextView {
        return result;

    }

    private void updateSettings(){
        ContentResolver resolver = mContext.getContentResolver();

        mShowClock = (Settings.System.getInt(resolver,
                Settings.System.STATUS_BAR_CLOCK, 1) == 1);

        if(mShowClock)
            setVisibility(View.VISIBLE);
        else
            setVisibility(View.GONE);
    }
}
+284 −0
Original line number Diff line number Diff line
/*
 * Created by Sven Dawitz; Copyright (C) 2011 CyanogenMod 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.statusbar;

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import java.lang.Runnable;

import com.android.internal.R;

/**
 * This widget displays the percentage of the battery as a mini icon
 */
public class CmBatteryMiniIcon extends ImageView {
    // the width of the mini bat icon
    static final int BATTERY_MINI_ICON_WIDTH_DIP = 4;

    // the margin to the right of this widget
    static final int BATTERY_MINI_ICON_MARGIN_RIGHT_DIP = 6;

    // duration of each frame in charging animation in millis
    static final int ANIM_FRAME_DURATION = (1000 / 3);

    // duration of each fake-timer call to update animation in millis
    static final int ANIM_TIMER_DURATION = (1000 / 4);

    // contains the current bat level, values: 0-100
    private int mBatteryLevel = 0;

    // contains current charger plugged state
    private boolean mBatteryPlugged = false;

    // recalculation of BATTERY_MINI_ICON_WIDTH_DIP to pixels
    private int mWidthPx;

    // recalculation of BATTERY_MINI_ICON_MARGIN_RIGHT_DIP to pixels
    private int mMarginRightPx;

    // weather to show this battery widget or not
    private boolean mShowCmBattery = false;

    // used for animation
    private long mLastMillis = 0;

    // used for animation
    private int mCurrentFrame;

    private boolean mAttached;

    private Matrix mMatrix = new Matrix();

    private Paint mPaint = new Paint();

    private Handler mHandler;

    private float mDensity;

    // tracks changes to settings, so status bar is auto updated the moment the
    // setting is toggled
    class SettingsObserver extends ContentObserver {
        SettingsObserver(Handler handler) {
            super(handler);
        }

        void observe() {
            ContentResolver resolver = mContext.getContentResolver();
            resolver.registerContentObserver(
                    Settings.System.getUriFor(Settings.System.STATUS_BAR_CM_BATTERY), false, this);
        }

        @Override
        public void onChange(boolean selfChange) {
            updateSettings();
        }
    }

    // provides a fake-timer using Handler to force onDraw() events when
    // animating
    final Runnable onFakeTimer = new Runnable() {
        public void run() {
            invalidate();
            mHandler.postDelayed(onFakeTimer, ANIM_TIMER_DURATION);
        }
    };

    public CmBatteryMiniIcon(Context context) {
        this(context, null);
    }

    public CmBatteryMiniIcon(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CmBatteryMiniIcon(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mHandler = new Handler();
        SettingsObserver settingsObserver = new SettingsObserver(mHandler);
        settingsObserver.observe();

        Resources r = getResources();

        mDensity = r.getDisplayMetrics().density;
        mWidthPx = (int) (BATTERY_MINI_ICON_WIDTH_DIP * mDensity);
        mMarginRightPx = (int) (BATTERY_MINI_ICON_MARGIN_RIGHT_DIP * mDensity);

        // set up the scaling matrix, so one-pixel-width becomes desired width
        mMatrix.setTranslate(0, 0);
        mMatrix.postScale(mWidthPx, 1);

        updateSettings();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (!mAttached) {
            mAttached = true;
            IntentFilter filter = new IntentFilter();

            filter.addAction(Intent.ACTION_BATTERY_CHANGED);

            getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAttached) {
            getContext().unregisterReceiver(mIntentReceiver);
            mAttached = false;
        }
    }

    /**
     * Handles changes ins battery level and charger connection
     */
    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
                // mIconId = intent.getIntExtra("icon-small", 0);
                mBatteryLevel = intent.getIntExtra("level", 0);
                mBatteryPlugged = intent.getIntExtra("plugged", 0) != 0;

                if (mBatteryPlugged && mBatteryLevel < 100)
                    mHandler.postDelayed(onFakeTimer, ANIM_TIMER_DURATION);
                else{
                    mHandler.removeCallbacks(onFakeTimer);
                    invalidate();
                }
            }
        }
    };

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(mWidthPx + mMarginRightPx, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (!mAttached)
            return;
        if (!mShowCmBattery)
            return;

        // set up animation when charger plugged in
        if (mBatteryPlugged && mBatteryLevel < 100) {
            if (mLastMillis == 0) {
                // just got plugged - setup animation
                mLastMillis = SystemClock.uptimeMillis();
                mCurrentFrame = 0;
            }
            long now = SystemClock.uptimeMillis();

            while (now - mLastMillis > ANIM_FRAME_DURATION) {
                mCurrentFrame = mCurrentFrame + 1;
                // count to eleven, so fully charged icon also got two frames
                if (mCurrentFrame > 11)
                    mCurrentFrame = 0;
                mLastMillis = mLastMillis + ANIM_FRAME_DURATION;
            }
        } else {
            // reset the animation for next charger connection
            mLastMillis = 0;
            mCurrentFrame = 10;
        }

        // get the original battery image
        int frame = (mBatteryPlugged ? mCurrentFrame : mBatteryLevel / 10);
        Bitmap bmBat = getBitmapFor(getBatResourceID(frame));
        // cut one slice of pixels from battery image
        Bitmap bmMiniBat = Bitmap.createBitmap(bmBat, 4, 0, 1, bmBat.getHeight());

        canvas.drawBitmap(bmMiniBat, mMatrix, mPaint);
    }

    private int getBatResourceID(int level) {
        switch (level) {
            case 0:
            case 1:
                return R.drawable.stat_sys_battery_10;
            case 2:
            case 3:
                return R.drawable.stat_sys_battery_20;
            case 4:
            case 5:
                return R.drawable.stat_sys_battery_40;
            case 6:
            case 7:
                return R.drawable.stat_sys_battery_60;
            case 8:
            case 9:
                return R.drawable.stat_sys_battery_80;
            case 10:
            default:
                return R.drawable.stat_sys_battery_100;
        }
    }

    /**
     * Converts resource id to actual Bitmap
     *
     * @param resId the resource id
     * @return resluting bitmap
     */
    private Bitmap getBitmapFor(int resId) {
        return BitmapFactory.decodeResource(getContext().getResources(), resId);
    }

    /**
     * Invoked by SettingsObserver, this method keeps track of just changed
     * settings. Also does the initial call from constructor
     */
    private void updateSettings() {
        ContentResolver resolver = mContext.getContentResolver();

        mShowCmBattery = (Settings.System
                .getInt(resolver, Settings.System.STATUS_BAR_CM_BATTERY, 0) == 1);

        if (mShowCmBattery)
            setVisibility(View.VISIBLE);
        else
            setVisibility(View.GONE);
    }
}
+140 −0
Original line number Diff line number Diff line
/*
 * Created by Sven Dawitz; Copyright (C) 2011 CyanogenMod 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.statusbar;

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.os.Handler;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

/**
 * This widget displays the percentage of the battery as a number
 */
public class CmBatteryText extends TextView {
    private boolean mAttached;

    // weather to show this battery widget or not
    private boolean mShowCmBattery;

    Handler mHandler;

    // tracks changes to settings, so status bar is auto updated the moment the
    // setting is toggled
    class SettingsObserver extends ContentObserver {
        SettingsObserver(Handler handler) {
            super(handler);
        }

        void observe() {
            ContentResolver resolver = mContext.getContentResolver();
            resolver.registerContentObserver(
                    Settings.System.getUriFor(Settings.System.STATUS_BAR_CM_BATTERY), false, this);
        }

        @Override
        public void onChange(boolean selfChange) {
            updateSettings();
        }
    }

    public CmBatteryText(Context context) {
        this(context, null);
    }

    public CmBatteryText(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CmBatteryText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mHandler = new Handler();
        SettingsObserver settingsObserver = new SettingsObserver(mHandler);
        settingsObserver.observe();

        updateSettings();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (!mAttached) {
            mAttached = true;
            IntentFilter filter = new IntentFilter();

            filter.addAction(Intent.ACTION_BATTERY_CHANGED);

            getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAttached) {
            getContext().unregisterReceiver(mIntentReceiver);
            mAttached = false;
        }
    }

    /**
     * Handles changes ins battery level and charger connection
     */
    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
                updateCmBatteryText(intent);
            }
        }
    };

    /**
     * Sets the output text. Kind of onDraw of canvas based classes
     *
     * @param intent
     */
    final void updateCmBatteryText(Intent intent) {
        int level = intent.getIntExtra("level", 0);
        setText(Integer.toString(level));
    }

    /**
     * Invoked by SettingsObserver, this method keeps track of just changed
     * settings. Also does the initial call from constructor
     */
    private void updateSettings() {
        ContentResolver resolver = mContext.getContentResolver();

        mShowCmBattery = (Settings.System
                .getInt(resolver, Settings.System.STATUS_BAR_CM_BATTERY, 0) == 1);

        if (mShowCmBattery)
            setVisibility(View.VISIBLE);
        else
            setVisibility(View.GONE);
    }
}
Loading