From ed6a4d28295f130c699b2941591d1d1b2897d4c4 Mon Sep 17 00:00:00 2001 From: hughchen Date: Thu, 28 Mar 2019 11:20:48 +0800 Subject: [PATCH 1/3] Separate library from SettingsLib Separated library for following class: - AdaptiveIcon.java - AdaptiveIconShapeDrawable.java - AdaptiveOutlineDrawable.java - TileUtils.java - DashboardCategory.java - Tile.java Bug: 128570540 Test: RunSettingsLibRoboTests Change-Id: I616e68f6e5ac9afee805793e80374aabebc69ccb --- packages/SettingsLib/AdaptiveIcon/Android.bp | 13 ++ .../AdaptiveIcon/AndroidManifest.xml | 23 +++ .../AdaptiveIcon/res/values/colors.xml | 21 +++ .../AdaptiveIcon/res/values/dimens.xml | 24 +++ .../settingslib/widget/AdaptiveIcon.java | 130 ++++++++++++++++ .../widget/AdaptiveIconShapeDrawable.java | 59 ++++++++ .../widget/AdaptiveOutlineDrawable.java | 87 +++++++++++ packages/SettingsLib/Android.bp | 1 + packages/SettingsLib/Tile/Android.bp | 11 ++ packages/SettingsLib/Tile/AndroidManifest.xml | 23 +++ .../settingslib/drawer/DashboardCategory.java | 22 ++- .../com/android/settingslib/drawer/Tile.java | 29 ++-- .../android/settingslib/drawer/TileUtils.java | 8 +- .../settingslib/widget/AdaptiveIconTest.java | 139 ++++++++++++++++++ .../widget/AdaptiveOutlineDrawableTest.java | 44 ++++++ 15 files changed, 618 insertions(+), 16 deletions(-) create mode 100644 packages/SettingsLib/AdaptiveIcon/Android.bp create mode 100644 packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml create mode 100644 packages/SettingsLib/AdaptiveIcon/res/values/colors.xml create mode 100644 packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml create mode 100644 packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java create mode 100644 packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java create mode 100644 packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java create mode 100644 packages/SettingsLib/Tile/Android.bp create mode 100644 packages/SettingsLib/Tile/AndroidManifest.xml rename packages/SettingsLib/{ => Tile}/src/com/android/settingslib/drawer/DashboardCategory.java (93%) rename packages/SettingsLib/{ => Tile}/src/com/android/settingslib/drawer/Tile.java (95%) rename packages/SettingsLib/{ => Tile}/src/com/android/settingslib/drawer/TileUtils.java (98%) create mode 100644 packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java create mode 100644 packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp new file mode 100644 index 0000000000000..7f4442deecd6b --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "SettingsLibAdaptiveIcon", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.annotation_annotation", + "SettingsLibTile" + ], + + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml b/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml new file mode 100644 index 0000000000000..256b8f3ea4772 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml new file mode 100644 index 0000000000000..76d106a067dd2 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + + #1A73E8 + + #1f000000 + diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml new file mode 100644 index 0000000000000..7f5b58c48abbe --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml @@ -0,0 +1,24 @@ + + + + + + 6dp + + + 1dp + \ No newline at end of file diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java new file mode 100644 index 0000000000000..fc93650a6b85d --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 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.widget; + +import static androidx.annotation.VisibleForTesting.NONE; + +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.android.settingslib.drawer.Tile; + +/** + * Adaptive icon that can set background color + */ +public class AdaptiveIcon extends LayerDrawable { + + private static final String TAG = "AdaptiveHomepageIcon"; + + @VisibleForTesting(otherwise = NONE) + int mBackgroundColor = -1; + private AdaptiveConstantState mAdaptiveConstantState; + + public AdaptiveIcon(Context context, Drawable foreground) { + super(new Drawable[]{ + new AdaptiveIconShapeDrawable(context.getResources()), + foreground + }); + final int insetPx = context.getResources() + .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset); + setLayerInset(1 /* index */, insetPx, insetPx, insetPx, insetPx); + mAdaptiveConstantState = new AdaptiveConstantState(context, foreground); + } + + /** + * According {@code tile} metaData to set background color + */ + public void setBackgroundColor(Context context, Tile tile) { + final Bundle metaData = tile.getMetaData(); + try { + if (metaData != null) { + // Load from bg.argb first + int bgColor = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, + 0 /* default */); + // Not found, load from bg.hint + if (bgColor == 0) { + final int colorRes = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + 0 /* default */); + if (colorRes != 0) { + bgColor = context.getPackageManager() + .getResourcesForApplication(tile.getPackageName()) + .getColor(colorRes, null /* theme */); + } + } + // If found anything, use it. + if (bgColor != 0) { + setBackgroundColor(bgColor); + return; + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to set background color for " + tile.getPackageName()); + } + setBackgroundColor(context.getColor(R.color.homepage_generic_icon_background)); + } + + /** + * Set background color by {@code color} + */ + public void setBackgroundColor(int color) { + mBackgroundColor = color; + getDrawable(0).setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + Log.d(TAG, "Setting background color " + mBackgroundColor); + mAdaptiveConstantState.mColor = color; + } + + @Override + public ConstantState getConstantState() { + return mAdaptiveConstantState; + } + + @VisibleForTesting + static class AdaptiveConstantState extends ConstantState { + Context mContext; + Drawable mDrawable; + int mColor; + + AdaptiveConstantState(Context context, Drawable drawable) { + this.mContext = context; + this.mDrawable = drawable; + } + + @Override + public Drawable newDrawable() { + final AdaptiveIcon + icon = new AdaptiveIcon(mContext, mDrawable); + icon.setBackgroundColor(mColor); + + return icon; + } + + @Override + public int getChangingConfigurations() { + return 0; + } + } +} diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java new file mode 100644 index 0000000000000..4d7610cf97b68 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 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.widget; + +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; +import android.util.AttributeSet; +import android.util.PathParser; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Draws a filled {@link ShapeDrawable} using the path from {@link AdaptiveIconDrawable}. + */ +public class AdaptiveIconShapeDrawable extends ShapeDrawable { + public AdaptiveIconShapeDrawable() { + super(); + } + + public AdaptiveIconShapeDrawable(Resources resources) { + super(); + init(resources); + } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + init(r); + } + + private void init(Resources resources) { + final float pathSize = AdaptiveIconDrawable.MASK_SIZE; + final Path path = new Path(PathParser.createPathFromPathData( + resources.getString(com.android.internal.R.string.config_icon_mask))); + setShape(new PathShape(path, pathSize, pathSize)); + } +} diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java new file mode 100644 index 0000000000000..1c65bc248961d --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 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.widget; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.DrawableWrapper; +import android.util.PathParser; + +import androidx.annotation.VisibleForTesting; + +/** + * Adaptive outline drawable with white plain background color and black outline + */ +public class AdaptiveOutlineDrawable extends DrawableWrapper { + @VisibleForTesting + final Paint mOutlinePaint; + private Path mPath; + private final int mInsetPx; + private final Bitmap mBitmap; + + public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap) { + super(new AdaptiveIconShapeDrawable(resources)); + + getDrawable().setTint(Color.WHITE); + mPath = new Path(PathParser.createPathFromPathData( + resources.getString(com.android.internal.R.string.config_icon_mask))); + mOutlinePaint = new Paint(); + mOutlinePaint.setColor(resources.getColor(R.color.bt_outline_color, null)); + mOutlinePaint.setStyle(Paint.Style.STROKE); + mOutlinePaint.setStrokeWidth(resources.getDimension(R.dimen.adaptive_outline_stroke)); + mOutlinePaint.setAntiAlias(true); + + mInsetPx = resources + .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset); + mBitmap = bitmap; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + final Rect bounds = getBounds(); + final float pathSize = AdaptiveIconDrawable.MASK_SIZE; + + final float scaleX = (bounds.right - bounds.left) / pathSize; + final float scaleY = (bounds.bottom - bounds.top) / pathSize; + + final int count = canvas.save(); + canvas.scale(scaleX, scaleY); + // Draw outline + canvas.drawPath(mPath, mOutlinePaint); + canvas.restoreToCount(count); + + // Draw the foreground icon + canvas.drawBitmap(mBitmap, bounds.left + mInsetPx, bounds.top + mInsetPx, null); + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight() + 2 * mInsetPx; + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth() + 2 * mInsetPx; + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 730e9e134e3df..b532621cd6179 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -22,6 +22,7 @@ android_library { "SettingsLibEntityHeaderWidgets", "SettingsLibBarChartPreference", "SettingsLibProgressBar", + "SettingsLibAdaptiveIcon", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp new file mode 100644 index 0000000000000..bf16ef317fd8a --- /dev/null +++ b/packages/SettingsLib/Tile/Android.bp @@ -0,0 +1,11 @@ +android_library { + name: "SettingsLibTile", + + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.annotation_annotation", + ], + + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/Tile/AndroidManifest.xml b/packages/SettingsLib/Tile/AndroidManifest.xml new file mode 100644 index 0000000000000..b13532e2a5fd8 --- /dev/null +++ b/packages/SettingsLib/Tile/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DashboardCategory.java similarity index 93% rename from packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java rename to packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DashboardCategory.java index a3dda658bec72..7b062b1ac9a9a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DashboardCategory.java @@ -1,11 +1,11 @@ -/** - * Copyright (C) 2015 The Android Open Source Project +/* + * Copyright (C) 2019 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 + * 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, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.settingslib.drawer; import static java.lang.String.CASE_INSENSITIVE_ORDER; @@ -26,6 +25,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +/** + * The category for handle {@link Tile} + */ public class DashboardCategory implements Parcelable { /** @@ -67,18 +69,30 @@ public class DashboardCategory implements Parcelable { return result; } + /** + * Add tile + */ public synchronized void addTile(Tile tile) { mTiles.add(tile); } + /** + * Remove tile + */ public synchronized void removeTile(int n) { mTiles.remove(n); } + /** + * Get size of tile + */ public int getTilesCount() { return mTiles.size(); } + /** + * Get tile + */ public Tile getTile(int n) { return mTiles.get(n); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java similarity index 95% rename from packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java rename to packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index d28b00a7ed398..5108efbdc2169 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -1,11 +1,11 @@ -/** - * Copyright (C) 2015 The Android Open Source Project +/* + * Copyright (C) 2019 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 + * 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, @@ -84,8 +84,8 @@ public class Tile implements Parcelable { mActivityPackage = in.readString(); mActivityName = in.readString(); mIntent = new Intent().setClassName(mActivityPackage, mActivityName); - final int N = in.readInt(); - for (int i = 0; i < N; i++) { + final int number = in.readInt(); + for (int i = 0; i < number; i++) { userHandle.add(UserHandle.CREATOR.createFromParcel(in)); } mCategory = in.readString(); @@ -101,9 +101,9 @@ public class Tile implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(mActivityPackage); dest.writeString(mActivityName); - final int N = userHandle.size(); - dest.writeInt(N); - for (int i = 0; i < N; i++) { + final int size = userHandle.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { userHandle.get(i).writeToParcel(dest, flags); } dest.writeString(mCategory); @@ -151,6 +151,9 @@ public class Tile implements Parcelable { } } + /** + * Check whether title has order. + */ public boolean hasOrder() { return mMetaData.containsKey(META_DATA_KEY_ORDER) && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer; @@ -262,6 +265,9 @@ public class Tile implements Parcelable { } } + /** + * Check whether title has key. + */ public boolean hasKey() { return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT); } @@ -361,9 +367,12 @@ public class Tile implements Parcelable { } }; + /** + * Check whether title is only have primary profile + */ public boolean isPrimaryProfileOnly() { - String profile = mMetaData != null ? - mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; + String profile = mMetaData != null + ? mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; profile = (profile != null ? profile : PROFILE_ALL); return TextUtils.equals(profile, PROFILE_PRIMARY); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java similarity index 98% rename from packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java rename to packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java index 91892abdfb446..31925ab64ec3a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 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. @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.settingslib.drawer; @@ -39,6 +39,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Utils is a helper class that contains profile key, meta data, settings action + * and static methods for get icon or text from uri. + */ public class TileUtils { private static final boolean DEBUG_TIMING = false; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java new file mode 100644 index 0000000000000..ed6b9b0a135e3 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 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.widget; + +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.ShapeDrawable; +import android.os.Bundle; + +import com.android.settingslib.R; +import com.android.settingslib.drawer.CategoryKey; +import com.android.settingslib.drawer.Tile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AdaptiveIconTest { + + private Context mContext; + private ActivityInfo mActivityInfo; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mActivityInfo = new ActivityInfo(); + mActivityInfo.packageName = mContext.getPackageName(); + mActivityInfo.name = "class"; + mActivityInfo.metaData = new Bundle(); + } + + @Test + public void createIcon_shouldSetBackgroundAndInset() { + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + assertThat(icon.getNumberOfLayers()).isEqualTo(2); + assertThat(icon.getDrawable(0)).isInstanceOf(AdaptiveIconShapeDrawable.class); + } + + @Test + public void setBackgroundColor_shouldUpdateColorFilter() { + final AdaptiveIcon icon = + spy(new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK))); + final ShapeDrawable background = mock(ShapeDrawable.class); + when(icon.getDrawable(0)).thenReturn(background); + + icon.setBackgroundColor(Color.BLUE); + + verify(background).setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP); + } + + @Test + public void setBackgroundColor_externalTileWithBackgroundColorRawValue_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, 0xff0000); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + icon.setBackgroundColor(mContext, tile); + assertThat(icon.mBackgroundColor).isEqualTo(0xff0000); + } + + @Test + public void setBackgroundColor_tileWithoutBackgroundColor_shouldSetDefaultBackgroundColor() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + final AdaptiveIcon icon = new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + icon.setBackgroundColor(mContext, tile); + + assertThat(icon.mBackgroundColor) + .isEqualTo(mContext.getColor(R.color.homepage_generic_icon_background)); + } + + @Test + public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + R.color.bt_outline_color); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + icon.setBackgroundColor(mContext, tile); + + assertThat(icon.mBackgroundColor) + .isEqualTo(mContext.getColor(R.color.bt_outline_color)); + } + + @Test + public void getConstantState_returnCorrectState() { + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + icon.setBackgroundColor(Color.YELLOW); + + final AdaptiveIcon.AdaptiveConstantState state = + (AdaptiveIcon.AdaptiveConstantState) icon.getConstantState(); + + assertThat(state.mColor).isEqualTo(Color.YELLOW); + assertThat(state.mContext).isEqualTo(mContext); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java new file mode 100644 index 0000000000000..71d55bc3de2ab --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.res.Resources; +import android.graphics.Paint; + +import com.android.settingslib.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AdaptiveOutlineDrawableTest { + + @Test + public void constructor_initPaint() { + final Resources resources = RuntimeEnvironment.application.getResources(); + final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable(resources, null); + + assertThat(drawable.mOutlinePaint.getStyle()).isEqualTo(Paint.Style.STROKE); + assertThat(drawable.mOutlinePaint.getStrokeWidth()).isWithin(0.01f).of( + resources.getDimension(R.dimen.adaptive_outline_stroke)); + } + +} From 678f4fc1c7bd21c40fc6cfbd3b23910d87888d5d Mon Sep 17 00:00:00 2001 From: hughchen Date: Mon, 25 Mar 2019 17:36:36 +0800 Subject: [PATCH 2/3] Add method to get rainbow bt icon - Add method to get rainbow bt icon - Add fast pair icon with background shape Bug: 128570540 Test: RunSettingsLibRoboTests Change-Id: Idf97d3c1fd892d9b3188b61be47d190ac2075af9 --- packages/SettingsLib/res/values/arrays.xml | 22 +++++++ packages/SettingsLib/res/values/colors.xml | 16 +++++ packages/SettingsLib/res/values/dimens.xml | 3 +- .../settingslib/bluetooth/BluetoothUtils.java | 65 +++++++++++++++++++ .../bluetooth/BluetoothUtilsTest.java | 20 ++++++ 5 files changed, 125 insertions(+), 1 deletion(-) diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index ed3c11cd3ca3a..39c55fd1925cb 100644 --- a/packages/SettingsLib/res/values/arrays.xml +++ b/packages/SettingsLib/res/values/arrays.xml @@ -603,4 +603,26 @@ 33 + + + @color/bt_color_icon_1 + @color/bt_color_icon_2 + @color/bt_color_icon_3 + @color/bt_color_icon_4 + @color/bt_color_icon_5 + @color/bt_color_icon_6 + @color/bt_color_icon_7 + + + + + @color/bt_color_bg_1 + @color/bt_color_bg_2 + @color/bt_color_bg_3 + @color/bt_color_bg_4 + @color/bt_color_bg_5 + @color/bt_color_bg_6 + @color/bt_color_bg_7 + + diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml index 66bbb3a6c890f..4b91bbb8d8dc1 100644 --- a/packages/SettingsLib/res/values/colors.xml +++ b/packages/SettingsLib/res/values/colors.xml @@ -19,4 +19,20 @@ @*android:color/tertiary_device_default_settings #64000000 + + #48a50e0e + #480d652d + #48e37400 + #48b06000 + #489c166b + #48681da8 + #48007b83 + + #fad2cf + #ceead6 + #feefc3 + #fedfc8 + #fdcfe8 + #e9d2fd + #cbf0f8 diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index a9c5061f6d873..2cb9d4b14aa75 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -91,6 +91,7 @@ 0.9dp - + + 24dp diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index bb8c8a6768ed0..5759df725ffa4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -1,18 +1,30 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; import android.util.Pair; import androidx.annotation.DrawableRes; import com.android.settingslib.R; +import com.android.settingslib.widget.AdaptiveIcon; +import com.android.settingslib.widget.AdaptiveOutlineDrawable; +import java.io.IOException; import java.util.List; public class BluetoothUtils { + private static final String TAG = "BluetoothUtils"; + public static final boolean V = false; // verbose logging public static final boolean D = true; // regular logging @@ -112,4 +124,57 @@ public class BluetoothUtils { public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) { return context.getDrawable(resId); } + + /** + * Get colorful bluetooth icon with description + */ + public static Pair getBtRainbowDrawableWithDescription(Context context, + CachedBluetoothDevice cachedDevice) { + final Pair pair = BluetoothUtils.getBtClassDrawableWithDescription( + context, cachedDevice); + final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); + final boolean untetheredHeadset = bluetoothDevice != null + ? Boolean.parseBoolean(bluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)) + : false; + final int iconSize = context.getResources().getDimensionPixelSize( + R.dimen.bt_nearby_icon_size); + final Resources resources = context.getResources(); + + // Deal with untethered headset + if (untetheredHeadset) { + final String uriString = bluetoothDevice != null + ? bluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON) + : null; + final Uri iconUri = uriString != null ? Uri.parse(uriString) : null; + if (iconUri != null) { + try { + final Bitmap bitmap = MediaStore.Images.Media.getBitmap( + context.getContentResolver(), iconUri); + if (bitmap != null) { + final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, + iconSize, false); + bitmap.recycle(); + final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable( + resources, resizedBitmap); + return new Pair<>(drawable, pair.second); + } + } catch (IOException e) { + Log.e(TAG, "Failed to get drawable for: " + iconUri, e); + } + } + } + + // Deal with normal headset + final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors); + final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors); + + // get color index based on mac address + final int index = Math.abs(cachedDevice.getAddress().hashCode()) % iconBgColors.length; + pair.first.setColorFilter(iconFgColors[index], PorterDuff.Mode.SRC_ATOP); + final Drawable adaptiveIcon = new AdaptiveIcon(context, pair.first); + ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]); + + return new Pair<>(adaptiveIcon, pair.second); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index b713e08eb67e6..b228cf7c10c6d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -15,15 +15,20 @@ */ package com.android.settingslib.bluetooth; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.Pair; +import com.android.settingslib.widget.AdaptiveIcon; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +43,9 @@ public class BluetoothUtilsTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + private Context mContext; @Before @@ -66,4 +74,16 @@ public class BluetoothUtilsTest { verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_laptop); } + + @Test + public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn("false"); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn("1f:aa:bb"); + + assertThat(BluetoothUtils.getBtRainbowDrawableWithDescription( + RuntimeEnvironment.application, + mCachedBluetoothDevice).first).isInstanceOf(AdaptiveIcon.class); + } } \ No newline at end of file From 7e853a457c225b5b1197fd0f0f69aab0b9324ff4 Mon Sep 17 00:00:00 2001 From: hughchen Date: Thu, 14 Mar 2019 15:56:43 +0800 Subject: [PATCH 3/3] According Bluetooth device type to get icon - Change getIcon() type from int to Drawable - According Bluetooth device type to get icon e.g: fast pair device, hearing aid device, a2dp device Bug: 128570540 Test: make -j42 RunSettingsLibRoboTests Change-Id: I662e31958f6102c1c83b2de9112bf8967c8efbbc --- .../res/drawable/ic_media_device.xml | 33 +++++++++++++++++++ .../settingslib/bluetooth/BluetoothUtils.java | 19 ++++++++--- .../media/BluetoothMediaDevice.java | 10 ++++-- .../settingslib/media/InfoMediaDevice.java | 11 +++++-- .../settingslib/media/MediaDevice.java | 7 ++-- .../settingslib/media/PhoneMediaDevice.java | 7 ++-- 6 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 packages/SettingsLib/res/drawable/ic_media_device.xml diff --git a/packages/SettingsLib/res/drawable/ic_media_device.xml b/packages/SettingsLib/res/drawable/ic_media_device.xml new file mode 100644 index 0000000000000..5a6aeb4a8e237 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_media_device.xml @@ -0,0 +1,33 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 5759df725ffa4..867efb4082588 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -165,16 +165,27 @@ public class BluetoothUtils { } } + return new Pair<>(buildBtRainbowDrawable(context, + pair.first, cachedDevice.getAddress().hashCode()), pair.second); + } + + /** + * Build Bluetooth device icon with rainbow + */ + public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, + int hashCode) { + final Resources resources = context.getResources(); + // Deal with normal headset final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors); final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors); // get color index based on mac address - final int index = Math.abs(cachedDevice.getAddress().hashCode()) % iconBgColors.length; - pair.first.setColorFilter(iconFgColors[index], PorterDuff.Mode.SRC_ATOP); - final Drawable adaptiveIcon = new AdaptiveIcon(context, pair.first); + final int index = Math.abs(hashCode % iconBgColors.length); + drawable.setColorFilter(iconFgColors[index], PorterDuff.Mode.SRC_ATOP); + final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable); ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]); - return new Pair<>(adaptiveIcon, pair.second); + return adaptiveIcon; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 3092b9960c7ed..2711e31759577 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -18,8 +18,11 @@ package com.android.settingslib.media; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.Log; +import android.util.Pair; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; /** @@ -48,9 +51,10 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override - public int getIcon() { - //TODO(b/117129183): This is not final icon for bluetooth device, just for demo. - return com.android.internal.R.drawable.ic_bt_headphones_a2dp; + public Drawable getIcon() { + final Pair pair = BluetoothUtils + .getBtRainbowDrawableWithDescription(mContext, mCachedDevice); + return pair.first; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 95f3d3d0f7693..732e8dba3e44b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,10 +16,14 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.widget.Toast; import androidx.mediarouter.media.MediaRouter; +import com.android.settingslib.R; +import com.android.settingslib.bluetooth.BluetoothUtils; + /** * InfoMediaDevice extends MediaDevice to represents wifi device. */ @@ -46,9 +50,10 @@ public class InfoMediaDevice extends MediaDevice { } @Override - public int getIcon() { - //TODO(b/121083246): This is not final icon for cast device, just for demo. - return com.android.internal.R.drawable.ic_settings_print; + public Drawable getIcon() { + //TODO(b/120669861): Return remote device icon uri once api is ready. + return BluetoothUtils.buildBtRainbowDrawable(mContext, + mContext.getDrawable(R.drawable.ic_media_device), getId().hashCode()); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 9b9e80310c18d..53a852069478e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -16,6 +16,7 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.text.TextUtils; import androidx.annotation.IntDef; @@ -70,11 +71,11 @@ public abstract class MediaDevice implements Comparable { public abstract String getSummary(); /** - * Get resource id of MediaDevice. + * Get icon of MediaDevice. * - * @return resource id of MediaDevice. + * @return drawable of icon. */ - public abstract int getIcon(); + public abstract Drawable getIcon(); /** * Get unique ID that represent MediaDevice diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 8c3fcc077edcd..af91c34641945 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -16,10 +16,12 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.Log; import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -56,8 +58,9 @@ public class PhoneMediaDevice extends MediaDevice { } @Override - public int getIcon() { - return R.drawable.ic_smartphone; + public Drawable getIcon() { + return BluetoothUtils.buildBtRainbowDrawable(mContext, + mContext.getDrawable(R.drawable.ic_smartphone), getId().hashCode()); } @Override