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

Commit 178afa5a authored by Lei Yu's avatar Lei Yu Committed by Android (Google) Code Review
Browse files

Merge "Add battery indicator to bluetooth icon"

parents aeb5b42c d94a9388
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -55,9 +55,17 @@
    <dimen name="battery_height">14.5dp</dimen>
    <dimen name="battery_width">9.5dp</dimen>

    <dimen name="bt_battery_padding">2dp</dimen>

    <!-- Margin on the right side of the system icon group on Keyguard. -->
    <fraction name="battery_button_height_fraction">10.5%</fraction>

    <!-- Ratio between height of button part and height of total -->
    <fraction name="bt_battery_button_height_fraction">7.5%</fraction>

    <!-- Ratio between width and height -->
    <fraction name="bt_battery_ratio_fraction">45%</fraction>

    <!-- Fraction value to smooth the edges of the battery icon. The path will be inset by this
         fraction of a pixel.-->
    <fraction name="battery_subpixel_smoothing_left">0%</fraction>
+11 −3
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ public class BatteryMeterDrawableBase extends Drawable {
    protected final Paint mTextPaint;
    protected final Paint mBoltPaint;
    protected final Paint mPlusPaint;
    protected float mButtonHeightFraction;

    private int mLevel = -1;
    private boolean mCharging;
@@ -66,7 +67,6 @@ public class BatteryMeterDrawableBase extends Drawable {
    private final int mIntrinsicWidth;
    private final int mIntrinsicHeight;

    private float mButtonHeightFraction;
    private float mSubpixelSmoothingLeft;
    private float mSubpixelSmoothingRight;
    private float mTextHeight, mWarningTextHeight;
@@ -298,7 +298,7 @@ public class BatteryMeterDrawableBase extends Drawable {

        float drawFrac = (float) level / 100f;
        final int height = mHeight;
        final int width = (int) (ASPECT_RATIO * mHeight);
        final int width = (int) (getAspectRatio() * mHeight);
        final int px = (mWidth - width) / 2;
        final int buttonHeight = Math.round(height * mButtonHeightFraction);

@@ -329,7 +329,7 @@ public class BatteryMeterDrawableBase extends Drawable {

        // define the battery shape
        mShapePath.reset();
        final float radius = RADIUS_RATIO * (mFrame.height() + buttonHeight);
        final float radius = getRadiusRatio() * (mFrame.height() + buttonHeight);
        mShapePath.setFillType(FillType.WINDING);
        mShapePath.addRoundRect(mFrame, radius, radius, Direction.CW);
        mShapePath.addRect(mButtonFrame, Direction.CW);
@@ -469,4 +469,12 @@ public class BatteryMeterDrawableBase extends Drawable {
    public int getCriticalLevel() {
        return mCriticalLevel;
    }

    protected float getAspectRatio() {
        return ASPECT_RATIO;
    }

    protected float getRadiusRatio() {
        return RADIUS_RATIO;
    }
}
+170 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.settingslib.graph;

import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.support.annotation.VisibleForTesting;
import android.view.Gravity;
import android.view.View;

import com.android.settingslib.R;
import com.android.settingslib.Utils;

/**
 * LayerDrawable contains the bluetooth device icon and battery gauge icon
 */
public class BluetoothDeviceLayerDrawable extends LayerDrawable {

    private BluetoothDeviceLayerDrawableState mState;

    private BluetoothDeviceLayerDrawable(@NonNull Drawable[] layers) {
        super(layers);
    }

    /**
     * Create the {@link LayerDrawable} that contains bluetooth device icon and battery icon.
     * This is a vertical layout drawable while bluetooth icon at top and battery icon at bottom.
     *
     * @param context      used to get the spec for icon
     * @param resId        represents the bluetooth device drawable
     * @param batteryLevel the battery level for bluetooth device
     */
    public static BluetoothDeviceLayerDrawable createLayerDrawable(Context context, int resId,
            int batteryLevel) {
        final Drawable deviceDrawable = context.getDrawable(resId);

        final BatteryMeterDrawable batteryDrawable = new BatteryMeterDrawable(context,
                R.color.meter_background_color, batteryLevel);
        final int pad = context.getResources()
                .getDimensionPixelSize(R.dimen.bt_battery_padding);
        batteryDrawable.setPadding(0, pad, 0, pad);

        final BluetoothDeviceLayerDrawable drawable = new BluetoothDeviceLayerDrawable(
                new Drawable[]{deviceDrawable,
                        rotateDrawable(context.getResources(), batteryDrawable)});
        // Set the bluetooth icon at the top
        drawable.setLayerGravity(0 /* index of deviceDrawable */, Gravity.TOP);
        // Set battery icon right below the bluetooth icon
        drawable.setLayerInset(1 /* index of batteryDrawable */, 0,
                deviceDrawable.getIntrinsicHeight(), 0, 0);

        drawable.setConstantState(context, resId, batteryLevel);

        return drawable;
    }

    /**
     * Rotate the {@code drawable} by 90 degree clockwise and return rotated {@link Drawable}
     */
    private static Drawable rotateDrawable(Resources res, Drawable drawable) {
        // Get the bitmap from drawable
        final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        // Create rotate matrix
        final Matrix matrix = new Matrix();
        matrix.postRotate(
                res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR
                        ? 90 : 270);

        // Create new bitmap with rotate matrix
        final Bitmap rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                bitmap.getHeight(), matrix, true);
        bitmap.recycle();

        return new BitmapDrawable(res, rotateBitmap);
    }

    public void setConstantState(Context context, int resId, int batteryLevel) {
        mState = new BluetoothDeviceLayerDrawableState(context, resId, batteryLevel);
    }

    @Override
    public ConstantState getConstantState() {
        return mState;
    }

    /**
     * Battery gauge icon with new spec.
     */
    @VisibleForTesting
    static class BatteryMeterDrawable extends BatteryMeterDrawableBase {
        private final float mAspectRatio;

        public BatteryMeterDrawable(Context context, int frameColor, int batteryLevel) {
            super(context, frameColor);
            final Resources resources = context.getResources();
            mButtonHeightFraction = resources.getFraction(
                    R.fraction.bt_battery_button_height_fraction, 1, 1);
            mAspectRatio = resources.getFraction(R.fraction.bt_battery_ratio_fraction, 1, 1);

            final int tintColor = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
            setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
            setBatteryLevel(batteryLevel);
        }

        @Override
        protected float getAspectRatio() {
            return mAspectRatio;
        }

        @Override
        protected float getRadiusRatio() {
            // Remove the round edge
            return 0;
        }
    }

    /**
     * {@link ConstantState} to restore the {@link BluetoothDeviceLayerDrawable}
     */
    private static class BluetoothDeviceLayerDrawableState extends ConstantState {
        Context context;
        int resId;
        int batteryLevel;

        public BluetoothDeviceLayerDrawableState(Context context, int resId,
                int batteryLevel) {
            this.context = context;
            this.resId = resId;
            this.batteryLevel = batteryLevel;
        }

        @Override
        public Drawable newDrawable() {
            return createLayerDrawable(context, resId, batteryLevel);
        }

        @Override
        public int getChangingConfigurations() {
            return 0;
        }
    }
}
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.settingslib.graph;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.VectorDrawable;

import com.android.settingslib.R;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.TestConfig;
import com.android.settingslib.testutils.shadow.SettingsLibShadowResources;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

@RunWith(SettingsLibRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
        shadows = SettingsLibShadowResources.class)
public class BluetoothDeviceLayerDrawableTest {
    private static final int RES_ID = R.drawable.ic_bt_cellphone;
    private static final int BATTERY_LEVEL = 15;
    private static final float TOLERANCE = 0.001f;

    private Context mContext;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;
    }

    @Test
    public void testCreateLayerDrawable_configCorrect() {
        BluetoothDeviceLayerDrawable drawable = BluetoothDeviceLayerDrawable.createLayerDrawable(
                mContext, RES_ID, BATTERY_LEVEL);

        assertThat(drawable.getDrawable(0)).isInstanceOf(VectorDrawable.class);
        assertThat(drawable.getDrawable(1)).isInstanceOf(BitmapDrawable.class);
        assertThat(drawable.getLayerInsetTop(1)).isEqualTo(
                drawable.getDrawable(0).getIntrinsicHeight());
    }

    @Test
    public void testBatteryMeterDrawable_configCorrect() {
        BluetoothDeviceLayerDrawable.BatteryMeterDrawable batteryDrawable =
                new BluetoothDeviceLayerDrawable.BatteryMeterDrawable(mContext,
                        R.color.meter_background_color, BATTERY_LEVEL);

        assertThat(batteryDrawable.getAspectRatio()).isWithin(TOLERANCE).of(0.45f);
        assertThat(batteryDrawable.getRadiusRatio()).isWithin(TOLERANCE).of(0f);
        assertThat(batteryDrawable.getBatteryLevel()).isEqualTo(BATTERY_LEVEL);
    }

    @Test
    public void testConstantState_returnTwinBluetoothLayerDrawable() {
        BluetoothDeviceLayerDrawable drawable = BluetoothDeviceLayerDrawable.createLayerDrawable(
                mContext, RES_ID, BATTERY_LEVEL);

        BluetoothDeviceLayerDrawable twinDrawable =
                (BluetoothDeviceLayerDrawable) drawable.getConstantState().newDrawable();

        assertThat(twinDrawable.getDrawable(0)).isEqualTo(drawable.getDrawable(0));
        assertThat(twinDrawable.getDrawable(1)).isEqualTo(drawable.getDrawable(1));
        assertThat(twinDrawable.getLayerInsetTop(1)).isEqualTo(
                drawable.getLayerInsetTop(1));
    }
}
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.settingslib.testutils.shadow;

import static org.robolectric.internal.Shadow.directlyOn;

import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.support.annotation.ArrayRes;

import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadows.ShadowResources;

/**
 * Shadow Resources to handle resource references that Robolectric shadows cannot
 * handle because they are too new or private.
 */
@Implements(Resources.class)
public class SettingsLibShadowResources extends ShadowResources {

    @RealObject
    public Resources realResources;

    @Implementation
    public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
        // The Robolectric has resource mismatch for these values, so we need to stub it here
        if (id == com.android.settingslib.R.array.batterymeter_bolt_points
                || id == com.android.settingslib.R.array.batterymeter_plus_points) {
            return new int[2];
        }
        return directlyOn(realResources, Resources.class).getIntArray(id);
    }
}