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

Commit 46454f6f authored by jackqdyulei's avatar jackqdyulei Committed by android-build-merger
Browse files

Merge "Add battery indicator to bluetooth icon" into oc-mr1-dev

am: c3321c81

Change-Id: I7934edf2cb6a96edc37877897d0f10565a8d6af5
parents a94ef756 c3321c81
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.SettingLibRobolectricTestRunner;
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(SettingLibRobolectricTestRunner.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);
    }
}