Final (?) touches to people/apps/sound circles

* Support comparing icon sets with a custom equivalence (needed because AppEntry doesn't implement equals(), and somewhat tricky because CircularIconPreference itself is not generic).
* Use final icons for allowed sounds.
* Use final appearance for +n icon.

Test: atest com.android.settings.notification.modes
Bug: 346551087
Flag: android.app.modes_ui
Change-Id: Iceadf4a182e607111afb72ff36bca872a62ae040
This commit is contained in:
Matías Hernández
2024-07-22 17:03:25 +02:00
parent d8b9fe8f01
commit 44c7272733
11 changed files with 262 additions and 26 deletions

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2024 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
android:fillColor="@android:color/white"
android:pathData="M480,880Q405,880 339.5,851.5Q274,823 225.5,774.5Q177,726 148.5,660.5Q120,595 120,520Q120,445 148.5,379.5Q177,314 225.5,265.5Q274,217 339.5,188.5Q405,160 480,160Q555,160 620.5,188.5Q686,217 734.5,265.5Q783,314 811.5,379.5Q840,445 840,520Q840,595 811.5,660.5Q783,726 734.5,774.5Q686,823 620.5,851.5Q555,880 480,880ZM480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520ZM592,688L648,632L520,504L520,320L440,320L440,536L592,688ZM224,94L280,150L110,320L54,264L224,94ZM736,94L906,264L850,320L680,150L736,94ZM480,800Q597,800 678.5,718.5Q760,637 760,520Q760,403 678.5,321.5Q597,240 480,240Q363,240 281.5,321.5Q200,403 200,520Q200,637 281.5,718.5Q363,800 480,800Z" />
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2024 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
android:fillColor="@android:color/white"
android:pathData="M580,720Q538,720 509,691Q480,662 480,620Q480,578 509,549Q538,520 580,520Q622,520 651,549Q680,578 680,620Q680,662 651,691Q622,720 580,720ZM200,880Q167,880 143.5,856.5Q120,833 120,800L120,240Q120,207 143.5,183.5Q167,160 200,160L240,160L240,80L320,80L320,160L640,160L640,80L720,80L720,160L760,160Q793,160 816.5,183.5Q840,207 840,240L840,800Q840,833 816.5,856.5Q793,880 760,880L200,880ZM200,800L760,800Q760,800 760,800Q760,800 760,800L760,400L200,400L200,800Q200,800 200,800Q200,800 200,800ZM200,320L760,320L760,240Q760,240 760,240Q760,240 760,240L200,240Q200,240 200,240Q200,240 200,240L200,320ZM200,320L200,240Q200,240 200,240Q200,240 200,240L200,240Q200,240 200,240Q200,240 200,240L200,320Z" />
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2024 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
android:fillColor="@android:color/white"
android:pathData="M400,840Q334,840 287,793Q240,746 240,680Q240,614 287,567Q334,520 400,520Q423,520 442.5,525.5Q462,531 480,542L480,120L720,120L720,280L560,280L560,680Q560,746 513,793Q466,840 400,840Z" />
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2024 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
android:fillColor="@android:color/white"
android:pathData="M260,320L300,320L300,280Q300,263 288.5,251.5Q277,240 260,240Q243,240 231.5,251.5Q220,263 220,280Q220,297 231.5,308.5Q243,320 260,320ZM440,320Q457,320 468.5,308.5Q480,297 480,280Q480,263 468.5,251.5Q457,240 440,240Q423,240 411.5,251.5Q400,263 400,280L400,320L440,320ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620ZM419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,400L260,400Q210,400 175,365Q140,330 140,280Q140,230 175,195Q210,160 260,160Q271,160 280.5,162Q290,164 300,167L300,120Q300,103 311.5,91.5Q323,80 340,80Q357,80 369,91.5Q381,103 381,120L381,176Q395,168 409.5,164Q424,160 440,160Q490,160 525,195Q560,230 560,280Q560,330 525,365Q490,400 440,400L381,400L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z" />
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2024 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
android:fillColor="@android:color/white"
android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620Z" />
</vector>

View File

@@ -22,7 +22,4 @@
android:width="@dimen/zen_mode_circular_icon_diameter"
android:height="@dimen/zen_mode_circular_icon_diameter" />
<solid android:color="?androidprv:attr/materialColorSecondaryContainer" />
<!-- TODO: b/346551087 - Include border (or not) according to final design
<stroke android:width="1dp" android:color="?androidprv:attr/materialColorOnSecondaryContainer" />
-->
</shape>

View File

@@ -20,8 +20,10 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.common.base.Equivalence;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
@@ -29,6 +31,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -67,8 +70,25 @@ class CircularIconSet<T> {
return MoreObjects.toStringHelper(this).add("items", mItems).toString();
}
boolean hasSameItemsAs(CircularIconSet<?> other) {
return other != null && this.mItems.equals(other.mItems);
@SuppressWarnings("unchecked")
<OtherT> boolean hasSameItemsAs(CircularIconSet<OtherT> other,
@Nullable Equivalence<OtherT> equivalence) {
if (other == null) {
return false;
}
if (other == this) {
return true;
}
if (equivalence == null) {
return mItems.equals(other.mItems);
}
// Check that types match before applying equivalence (statically unsafe). :(
Optional<Class<?>> thisItemClass = this.mItems.stream().findFirst().map(T::getClass);
Optional<Class<?>> otherItemClass = other.mItems.stream().findFirst().map(OtherT::getClass);
if (!thisItemClass.equals(otherItemClass)) {
return false;
}
return equivalence.pairwise().equivalent((Iterable<OtherT>) this.mItems, other.mItems);
}
int size() {

View File

@@ -41,6 +41,7 @@ import com.android.settings.R;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.Utils;
import com.google.common.base.Equivalence;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -105,8 +106,12 @@ public class CircularIconsPreference extends RestrictedPreference {
}
}
void displayIcons(CircularIconSet<?> iconSet) {
if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet)) {
<T> void displayIcons(CircularIconSet<T> iconSet) {
displayIcons(iconSet, null);
}
<T> void displayIcons(CircularIconSet<T> iconSet, @Nullable Equivalence<T> itemEquivalence) {
if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) {
return;
}
mIconSet = iconSet;
@@ -189,7 +194,6 @@ public class CircularIconsPreference extends RestrictedPreference {
}
// ... plus 0/1 TextViews at the end.
if (extraItems > 0 && !(getLastChild(mIconContainer) instanceof TextView)) {
// TODO: b/346551087 - Check TODO in preference_circular_icons_plus_item_background
TextView plusView = (TextView) inflater.inflate(
R.layout.preference_circular_icons_plus_item, mIconContainer, false);
mIconContainer.addView(plusView);

View File

@@ -40,6 +40,7 @@ import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.google.common.base.Equivalence;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
@@ -47,6 +48,7 @@ import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
/**
* Preference with a link and summary about what apps can break through the mode
@@ -137,7 +139,8 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
mPreference.displayIcons(new CircularIconSet<>(apps,
app -> Utils.getBadgedIcon(mContext, app.info)));
app -> Utils.getBadgedIcon(mContext, app.info)),
APP_ENTRY_EQUIVALENCE);
}
@VisibleForTesting
@@ -158,6 +161,19 @@ class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceContr
.toList());
}
private static final Equivalence<AppEntry> APP_ENTRY_EQUIVALENCE = new Equivalence<>() {
@Override
protected boolean doEquivalent(@NonNull AppEntry a, @NonNull AppEntry b) {
return a.info.uid == b.info.uid
&& Objects.equals(a.info.packageName, b.info.packageName);
}
@Override
protected int doHash(@NonNull AppEntry entry) {
return Objects.hash(entry.info.uid, entry.info.packageName);
}
};
@VisibleForTesting
final ApplicationsState.Callbacks mAppSessionCallbacks =
new ApplicationsState.Callbacks() {

View File

@@ -29,6 +29,7 @@ import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenMode;
import com.google.common.collect.ImmutableList;
@@ -41,19 +42,13 @@ import java.util.Map;
*/
class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
// TODO: b/346551087 - Use proper icons
private static final ImmutableMap</* @PriorityCategory */ Integer, /* @DrawableRes */ Integer>
PRIORITIES_TO_ICONS = ImmutableMap.of(
PRIORITY_CATEGORY_ALARMS,
com.android.internal.R.drawable.ic_audio_alarm,
PRIORITY_CATEGORY_MEDIA,
com.android.settings.R.drawable.ic_media_stream,
PRIORITY_CATEGORY_SYSTEM,
com.android.settings.R.drawable.ic_settings_keyboards,
PRIORITY_CATEGORY_REMINDERS,
com.android.internal.R.drawable.ic_popup_reminder,
PRIORITY_CATEGORY_EVENTS,
com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
PRIORITY_CATEGORY_ALARMS, R.drawable.ic_zen_mode_sound_alarms,
PRIORITY_CATEGORY_MEDIA, R.drawable.ic_zen_mode_sound_media,
PRIORITY_CATEGORY_SYSTEM, R.drawable.ic_zen_mode_sound_system,
PRIORITY_CATEGORY_REMINDERS, R.drawable.ic_zen_mode_sound_reminders,
PRIORITY_CATEGORY_EVENTS, R.drawable.ic_zen_mode_sound_events);
private final ZenModeSummaryHelper mSummaryHelper;

View File

@@ -27,6 +27,7 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import com.google.common.base.Equivalence;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@@ -54,33 +55,111 @@ public class CircularIconSetTest {
}
@Test
public void equals_sameItems_true() {
public void hasSameItemsAs_sameItems_true() {
CircularIconSet<Integer> items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
num -> new ColorDrawable(Color.BLUE));
CircularIconSet<Integer> items2 = new CircularIconSet<>(ImmutableList.of(1, 2),
num -> new ColorDrawable(Color.GREEN));
assertThat(items1.hasSameItemsAs(items2)).isTrue();
assertThat(items1.hasSameItemsAs(items2, null)).isTrue();
}
@Test
public void equals_differentTypes_false() {
public void hasSameItemsAs_differentTypes_false() {
CircularIconSet<Integer> items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
num -> new ColorDrawable(Color.BLUE));
CircularIconSet<String> items2 = new CircularIconSet<>(ImmutableList.of("a", "b"),
str -> new ColorDrawable(Color.GREEN));
assertThat(items1.hasSameItemsAs(items2)).isFalse();
assertThat(items1.hasSameItemsAs(items2, null)).isFalse();
}
@Test
public void equals_differentItems_false() {
public void hasSameItemsAs_differentItems_false() {
CircularIconSet<String> items1 = new CircularIconSet<>(ImmutableList.of("a", "b"),
str -> new ColorDrawable(Color.GREEN));
CircularIconSet<String> items2 = new CircularIconSet<>(ImmutableList.of("a", "b", "c"),
str -> new ColorDrawable(Color.GREEN));
assertThat(items1.hasSameItemsAs(items2)).isFalse();
assertThat(items1.hasSameItemsAs(items2, null)).isFalse();
}
private static class WrapperWithoutEquals<T> {
private final T mValue;
private WrapperWithoutEquals(T value) {
mValue = value;
}
}
@Test
public void hasSameItemsAs_withEquivalence_trueIfEquivalentItems() {
CircularIconSet<WrapperWithoutEquals<Integer>> items1 = new CircularIconSet<>(
ImmutableList.of(
new WrapperWithoutEquals<>(1),
new WrapperWithoutEquals<>(2)),
unused -> new ColorDrawable(Color.BLACK));
CircularIconSet<WrapperWithoutEquals<Integer>> items2 = new CircularIconSet<>(
ImmutableList.of(
new WrapperWithoutEquals<>(1),
new WrapperWithoutEquals<>(2)),
unused -> new ColorDrawable(Color.BLACK));
CircularIconSet<WrapperWithoutEquals<Integer>> items3 = new CircularIconSet<>(
ImmutableList.of(
new WrapperWithoutEquals<>(2),
new WrapperWithoutEquals<>(3)),
unused -> new ColorDrawable(Color.BLACK));
// Needs special equivalence, equals is not enough.
assertThat(items1.hasSameItemsAs(items2, null)).isFalse();
Equivalence<WrapperWithoutEquals<Integer>> equivalence = new Equivalence<>() {
@Override
protected boolean doEquivalent(WrapperWithoutEquals<Integer> a,
WrapperWithoutEquals<Integer> b) {
return a.mValue.equals(b.mValue);
}
@Override
protected int doHash(WrapperWithoutEquals<Integer> t) {
return t.mValue;
}
};
assertThat(items1.hasSameItemsAs(items2, equivalence)).isTrue();
assertThat(items1.hasSameItemsAs(items3, equivalence)).isFalse();
}
@Test
public void hasSameItemsAs_withEquivalenceButDifferentTypes_falseAndNoClassCastExceptions() {
CircularIconSet<Integer> items1 = new CircularIconSet<>(ImmutableList.of(1, 2),
num -> new ColorDrawable(Color.BLUE));
CircularIconSet<String> items2 = new CircularIconSet<>(ImmutableList.of("one", "two"),
num -> new ColorDrawable(Color.GREEN));
Equivalence<String> stringEquivalence = new Equivalence<String>() {
@Override
protected boolean doEquivalent(String a, String b) {
return a.equals(b);
}
@Override
protected int doHash(String t) {
return t.hashCode();
}
};
Equivalence<Integer> integerEquivalence = new Equivalence<Integer>() {
@Override
protected boolean doEquivalent(Integer a, Integer b) {
return a.equals(b);
}
@Override
protected int doHash(Integer t) {
return t.hashCode();
}
};
assertThat(items1.hasSameItemsAs(items2, stringEquivalence)).isFalse();
assertThat(items2.hasSameItemsAs(items1, integerEquivalence)).isFalse();
}
@Test