diff options
5 files changed, 324 insertions, 3 deletions
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 3322839ee213..1e35cbc78343 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -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> diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java index 426dc7c20a96..d1f91d917586 100755 --- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java +++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java @@ -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; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java new file mode 100644 index 000000000000..61790b96099f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java @@ -0,0 +1,170 @@ +/* + * 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; + } + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java new file mode 100644 index 000000000000..f6fe158691fa --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java @@ -0,0 +1,86 @@ +/* + * 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)); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java new file mode 100644 index 000000000000..a376dcdbbbd3 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java @@ -0,0 +1,49 @@ +/* + * 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); + } +} |