<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>
protected final Paint mTextPaint;
protected final Paint mBoltPaint;
protected final Paint mPlusPaint;
+ protected float mButtonHeightFraction;
private int mLevel = -1;
private boolean mCharging;
private final int mIntrinsicWidth;
private final int mIntrinsicHeight;
- private float mButtonHeightFraction;
private float mSubpixelSmoothingLeft;
private float mSubpixelSmoothingRight;
private float mTextHeight, mWarningTextHeight;
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);
// 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);
public int getCriticalLevel() {
return mCriticalLevel;
}
+
+ protected float getAspectRatio() {
+ return ASPECT_RATIO;
+ }
+
+ protected float getRadiusRatio() {
+ return RADIUS_RATIO;
+ }
}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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));
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}