Snap for 12116631 from 2004df98f1 to 24Q4-release
Change-Id: I25146143976f7c437163219c9bcf5732f63e53f0
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="oval">
|
||||
<size
|
||||
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>
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/zen_mode_circular_icon_size"
|
||||
android:layout_height="@dimen/zen_mode_circular_icon_size"
|
||||
android:layout_width="@dimen/zen_mode_circular_icon_diameter"
|
||||
android:layout_height="@dimen/zen_mode_circular_icon_diameter"
|
||||
android:layout_marginTop="@dimen/zen_mode_circular_icon_margin_vertical"
|
||||
android:layout_marginBottom="@dimen/zen_mode_circular_icon_margin_vertical"
|
||||
android:layout_marginEnd="@dimen/zen_mode_circular_icon_margin_between" />
|
||||
32
res/layout/preference_circular_icons_plus_item.xml
Normal file
32
res/layout/preference_circular_icons_plus_item.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:layout_width="@dimen/zen_mode_circular_icon_diameter"
|
||||
android:layout_height="@dimen/zen_mode_circular_icon_diameter"
|
||||
android:layout_marginTop="@dimen/zen_mode_circular_icon_margin_vertical"
|
||||
android:layout_marginBottom="@dimen/zen_mode_circular_icon_margin_vertical"
|
||||
android:gravity="center"
|
||||
android:padding="4dp"
|
||||
android:drawablePadding="0dp"
|
||||
android:background="@drawable/preference_circular_icons_plus_item_background"
|
||||
android:textColor="?androidprv:attr/materialColorOnSecondaryContainer"
|
||||
android:maxLines="1"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:autoSizeMinTextSize="6sp" />
|
||||
@@ -509,7 +509,8 @@
|
||||
<dimen name="zen_mode_icon_list_item_circle_diameter">56dp</dimen>
|
||||
<dimen name="zen_mode_icon_list_item_icon_size">32dp</dimen>
|
||||
<!-- For the items in the CircularIconsPreference (contacts, apps, sound channels). -->
|
||||
<dimen name="zen_mode_circular_icon_size">32dp</dimen>
|
||||
<dimen name="zen_mode_circular_icon_diameter">32dp</dimen>
|
||||
<dimen name="zen_mode_circular_icon_inner_icon_size">20dp</dimen>
|
||||
<dimen name="zen_mode_circular_icon_margin_between">4dp</dimen>
|
||||
<dimen name="zen_mode_circular_icon_margin_vertical">8dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -8871,9 +8871,10 @@
|
||||
<string name="notif_listener_more_settings_desc">More settings are available inside this app</string>
|
||||
|
||||
<!-- Title for Polite Notifications setting [CHAR LIMIT=45]-->
|
||||
<string name="notification_polite_title">Adaptive Notifications</string>
|
||||
<string name="notification_polite_main_control_title">Use adaptive notifications</string>
|
||||
<string name="notification_polite_description">When you get many notifications within a short time, your phone will lower volume and minimize pop-ups on screen for up to two minutes. Calls, alarms, and priority conversations still vibrate, make a sound, or show up on the screen, and all notifications are easy to find when you pull down from the top of the screen.</string>
|
||||
<string name="notification_polite_title">Notification cooldown</string>
|
||||
<string name="notification_polite_main_control_title">Use notification cooldown</string>
|
||||
<string name="notification_polite_description">When you receive many notifications within a short time, your device will lower it\u2019s volume and minimize alerts for up to 2 minutes. Calls, alarms, and priority conversations aren\u2019t affected.
|
||||
\n\nNotifications received during the cooldown can be found by pulling down from the top of the screen.</string>
|
||||
<string name="notification_polite_work">Apply to work profiles</string>
|
||||
<string name="notification_polite_work_summary">Apply to work profile apps</string>
|
||||
|
||||
@@ -9365,6 +9366,8 @@
|
||||
<string name="zen_mode_apps_work_app"><xliff:g id="app_label" example="Chrome">%s</xliff:g> (Work)</string>
|
||||
<!-- Text displayed (for a brief time) while the list of bypassing apps is being fetched. Will be replaced by a zen_mode_apps_subtext. [CHAR_LIMIT=60] -->
|
||||
<string name="zen_mode_apps_calculating">Calculating\u2026</string>
|
||||
<!-- Priority Modes: Format for a string displayed when there are more items (e.g. apps, contacts) that can be shown. For example, we show (A)(B)(C)(+5), where this string represents the "+5" value. Needs to be as compact as possible, since it will be drawn in a really small area. [CHAR_LIMIT=4] -->
|
||||
<string name="zen_mode_plus_n_items">+<xliff:g id="number" example="42">%d</xliff:g></string>
|
||||
|
||||
<!-- [CHAR LIMIT=100] Zen mode settings: Allow apps to bypass DND -->
|
||||
<string name="zen_mode_bypassing_apps">Allow apps to override</string>
|
||||
|
||||
@@ -955,11 +955,16 @@
|
||||
<item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
|
||||
</style>
|
||||
|
||||
<style name="ScreenLockPasswordHintTextFontStyle">
|
||||
<style name="ScreenLockPasswordHintTextFontStyleError">
|
||||
<item name="android:textColor">?android:attr/colorError</item>
|
||||
<item name="android:fontFamily">google-sans-text</item>
|
||||
</style>
|
||||
|
||||
<style name="ScreenLockPasswordHintTextFontStyle">
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
<item name="android:fontFamily">google-sans-text</item>
|
||||
</style>
|
||||
|
||||
<style name="PrivateSpaceSetupTextFontStyle" parent="@android:style/TextAppearance.DeviceDefault">
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
<item name="android:fontFamily">google-sans-text</item>
|
||||
|
||||
@@ -154,7 +154,7 @@ public class AccessibilitySettings extends DashboardFragment implements
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
final AccessibilitySettingsContentObserver mSettingsContentObserver;
|
||||
AccessibilitySettingsContentObserver mSettingsContentObserver;
|
||||
|
||||
private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
|
||||
new ArrayMap<>();
|
||||
@@ -168,9 +168,14 @@ public class AccessibilitySettings extends DashboardFragment implements
|
||||
private boolean mIsForeground = true;
|
||||
|
||||
public AccessibilitySettings() {
|
||||
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
|
||||
}
|
||||
|
||||
private void initializeSettingsContentObserver() {
|
||||
// Observe changes to anything that the shortcut can toggle, so we can reflect updates
|
||||
final Collection<AccessibilityShortcutController.FrameworkFeatureInfo> features =
|
||||
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values();
|
||||
AccessibilityShortcutController
|
||||
.getFrameworkShortcutFeaturesMap().values();
|
||||
final List<String> shortcutFeatureKeys = new ArrayList<>(features.size());
|
||||
for (AccessibilityShortcutController.FrameworkFeatureInfo feature : features) {
|
||||
final String key = feature.getSettingKey();
|
||||
@@ -188,7 +193,6 @@ public class AccessibilitySettings extends DashboardFragment implements
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_STICKY_KEYS);
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SLOW_KEYS);
|
||||
shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS);
|
||||
mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler);
|
||||
mSettingsContentObserver.registerKeysToObserverCallback(shortcutFeatureKeys,
|
||||
key -> onContentChanged());
|
||||
}
|
||||
@@ -213,6 +217,7 @@ public class AccessibilitySettings extends DashboardFragment implements
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
initializeSettingsContentObserver();
|
||||
initializeAllPreferences();
|
||||
updateAllPreferences();
|
||||
mNeedPreferencesUpdate = false;
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
@@ -61,6 +62,15 @@ class CircularIconSet<T> {
|
||||
mCachedIcons = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("items", mItems).toString();
|
||||
}
|
||||
|
||||
boolean hasSameItemsAs(CircularIconSet<?> other) {
|
||||
return other != null && this.mItems.equals(other.mItems);
|
||||
}
|
||||
|
||||
int size() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@@ -21,15 +21,17 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.OvalShape;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -37,11 +39,12 @@ import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -50,8 +53,9 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
private Executor mUiExecutor;
|
||||
@Nullable private LinearLayout mIconContainer;
|
||||
|
||||
@Nullable private CircularIconSet<?> mPendingIconSet;
|
||||
@Nullable private ListenableFuture<?> mPendingLoadIconsFuture;
|
||||
@Nullable private CircularIconSet<?> mIconSet;
|
||||
@Nullable private CircularIconSet<?> mPendingDisplayIconSet;
|
||||
@Nullable private ListenableFuture<List<Drawable>> mPendingLoadIconsFuture;
|
||||
|
||||
public CircularIconsPreference(Context context) {
|
||||
super(context);
|
||||
@@ -94,17 +98,25 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
}
|
||||
|
||||
private void displayIconsIfPending() {
|
||||
CircularIconSet<?> pendingIconSet = mPendingIconSet;
|
||||
CircularIconSet<?> pendingIconSet = mPendingDisplayIconSet;
|
||||
if (pendingIconSet != null) {
|
||||
mPendingIconSet = null;
|
||||
displayIcons(pendingIconSet);
|
||||
mPendingDisplayIconSet = null;
|
||||
displayIconsInternal(pendingIconSet);
|
||||
}
|
||||
}
|
||||
|
||||
void displayIcons(CircularIconSet<?> iconSet) {
|
||||
if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet)) {
|
||||
return;
|
||||
}
|
||||
mIconSet = iconSet;
|
||||
displayIconsInternal(iconSet);
|
||||
}
|
||||
|
||||
void displayIconsInternal(CircularIconSet<?> iconSet) {
|
||||
if (mIconContainer == null) {
|
||||
// Too soon, wait for bind.
|
||||
mPendingIconSet = iconSet;
|
||||
mPendingDisplayIconSet = iconSet;
|
||||
return;
|
||||
}
|
||||
mIconContainer.setVisibility(iconSet.size() != 0 ? View.VISIBLE : View.GONE);
|
||||
@@ -113,30 +125,31 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
}
|
||||
if (mIconContainer.getMeasuredWidth() == 0) {
|
||||
// Too soon, wait for first measure to know width.
|
||||
mPendingIconSet = iconSet;
|
||||
ViewTreeObserver vto = mIconContainer.getViewTreeObserver();
|
||||
vto.addOnGlobalLayoutListener(() ->
|
||||
mPendingDisplayIconSet = iconSet;
|
||||
mIconContainer.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
vto.removeOnGlobalLayoutListener(this);
|
||||
checkNotNull(mIconContainer).getViewTreeObserver()
|
||||
.removeOnGlobalLayoutListener(this);
|
||||
displayIconsIfPending();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
mIconContainer.setVisibility(View.VISIBLE);
|
||||
Resources res = getContext().getResources();
|
||||
int availableSpace = mIconContainer.getMeasuredWidth();
|
||||
int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_size)
|
||||
int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
|
||||
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
|
||||
int numIconsThatFit = availableSpace / iconHorizontalSpace;
|
||||
|
||||
List<ListenableFuture<Drawable>> iconFutures;
|
||||
int extraItems = 0;
|
||||
int extraItems;
|
||||
if (iconSet.size() > numIconsThatFit) {
|
||||
// Reserve one space for the (+xx) circle.
|
||||
// Reserve one space for the (+xx) textview.
|
||||
int numIconsToShow = numIconsThatFit - 1;
|
||||
if (numIconsToShow < 0) {
|
||||
numIconsToShow = 0;
|
||||
@@ -146,6 +159,7 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
} else {
|
||||
// Fit exactly or with remaining space.
|
||||
iconFutures = iconSet.getIcons();
|
||||
extraItems = 0;
|
||||
}
|
||||
|
||||
displayIconsWhenReady(iconFutures, extraItems);
|
||||
@@ -158,33 +172,45 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
mPendingLoadIconsFuture.cancel(true);
|
||||
}
|
||||
|
||||
int numCircles = iconFutures.size() + (extraItems > 0 ? 1 : 0);
|
||||
if (mIconContainer.getChildCount() > numCircles) {
|
||||
mIconContainer.removeViews(numCircles, mIconContainer.getChildCount() - numCircles);
|
||||
// Rearrange child views until we have <numImages> ImageViews...
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
int numImages = iconFutures.size();
|
||||
int numImageViews = getChildCount(mIconContainer, ImageView.class);
|
||||
if (numImages > numImageViews) {
|
||||
for (int i = 0; i < numImages - numImageViews; i++) {
|
||||
ImageView imageView = (ImageView) inflater.inflate(
|
||||
R.layout.preference_circular_icons_item, mIconContainer, false);
|
||||
mIconContainer.addView(imageView, 0);
|
||||
}
|
||||
} else if (numImageViews > numImages) {
|
||||
for (int i = 0; i < numImageViews - numImages; i++) {
|
||||
mIconContainer.removeViewAt(0);
|
||||
}
|
||||
}
|
||||
for (int i = mIconContainer.getChildCount(); i < numCircles; i++) {
|
||||
ImageView imageView = (ImageView) LayoutInflater.from(getContext()).inflate(
|
||||
R.layout.preference_circular_icons_item, mIconContainer, false);
|
||||
mIconContainer.addView(imageView);
|
||||
// ... 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);
|
||||
} else if (extraItems == 0 && (getLastChild(mIconContainer) instanceof TextView)) {
|
||||
mIconContainer.removeViewAt(mIconContainer.getChildCount() - 1);
|
||||
}
|
||||
|
||||
// Set up placeholders and extra items indicator.
|
||||
for (int i = 0; i < iconFutures.size(); i++) {
|
||||
for (int i = 0; i < numImages; i++) {
|
||||
ImageView imageView = (ImageView) mIconContainer.getChildAt(i);
|
||||
// TODO: b/346551087 - proper color and shape, should be a gray circle.
|
||||
imageView.setImageDrawable(new ColorDrawable(Color.RED));
|
||||
imageView.setImageDrawable(getPlaceholderImage(getContext()));
|
||||
}
|
||||
if (extraItems > 0) {
|
||||
ImageView imageView = (ImageView) mIconContainer.getChildAt(
|
||||
mIconContainer.getChildCount() - 1);
|
||||
// TODO: b/346551087 - proper color and shape and number.
|
||||
imageView.setImageDrawable(new ColorDrawable(Color.BLUE));
|
||||
TextView textView = (TextView) checkNotNull(getLastChild(mIconContainer));
|
||||
textView.setText(getContext().getString(R.string.zen_mode_plus_n_items, extraItems));
|
||||
}
|
||||
|
||||
// Display icons when all are ready (more consistent than randomly loading).
|
||||
mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
|
||||
FutureUtil.whenDone(
|
||||
Futures.allAsList(iconFutures),
|
||||
mPendingLoadIconsFuture,
|
||||
icons -> {
|
||||
checkState(mIconContainer != null);
|
||||
for (int i = 0; i < icons.size(); i++) {
|
||||
@@ -194,15 +220,54 @@ public class CircularIconsPreference extends RestrictedPreference {
|
||||
mUiExecutor);
|
||||
}
|
||||
|
||||
private static Drawable getPlaceholderImage(Context context) {
|
||||
ShapeDrawable placeholder = new ShapeDrawable(new OvalShape());
|
||||
placeholder.setTintList(Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer));
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
private static int getChildCount(ViewGroup parent, Class<? extends View> childClass) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < parent.getChildCount(); i++) {
|
||||
if (childClass.isInstance(parent.getChildAt(i))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static View getLastChild(ViewGroup parent) {
|
||||
if (parent.getChildCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
return parent.getChildAt(parent.getChildCount() - 1);
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
ImmutableList<ImageView> getIconViews() {
|
||||
List<Drawable> getIcons() {
|
||||
if (mIconContainer == null) {
|
||||
return ImmutableList.of();
|
||||
return List.of();
|
||||
}
|
||||
ImmutableList.Builder<ImageView> imageViews = new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < mIconContainer.getChildCount(); i++) {
|
||||
imageViews.add((ImageView) mIconContainer.getChildAt(i));
|
||||
ArrayList<Drawable> drawables = new ArrayList<>();
|
||||
for (int i = 0; i < getChildCount(mIconContainer, ImageView.class); i++) {
|
||||
drawables.add(((ImageView) mIconContainer.getChildAt(i)).getDrawable());
|
||||
}
|
||||
return drawables;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@Nullable
|
||||
String getPlusText() {
|
||||
if (mIconContainer == null) {
|
||||
return null;
|
||||
}
|
||||
View lastChild = getLastChild(mIconContainer);
|
||||
if (lastChild instanceof TextView tv) {
|
||||
return tv.getText() != null ? tv.getText().toString() : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return imageViews.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class IconUtil {
|
||||
* Returns a variant of the supplied {@code icon} to be used as the header in the icon picker.
|
||||
* The inner icon is 48x48dp and it's contained into a circle of diameter 90dp.
|
||||
*/
|
||||
static Drawable makeBigIconCircle(@NonNull Context context, Drawable icon) {
|
||||
static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) {
|
||||
return composeIconCircle(
|
||||
Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer),
|
||||
@@ -73,7 +73,7 @@ class IconUtil {
|
||||
* The inner icon is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up
|
||||
* so that selection and pressed states are represented in the color.
|
||||
*/
|
||||
static Drawable makeSmallIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
|
||||
static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) {
|
||||
return composeIconCircle(
|
||||
context.getColorStateList(R.color.modes_icon_picker_item_background),
|
||||
context.getResources().getDimensionPixelSize(
|
||||
@@ -84,6 +84,24 @@ class IconUtil {
|
||||
R.dimen.zen_mode_icon_list_item_icon_size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a variant of the supplied icon to be used in a {@link CircularIconsPreference}. The
|
||||
* inner icon is 20x20 dp and it's contained in a circle of diameter 32dp, and is tinted
|
||||
* with the "material secondary container" color combination.
|
||||
*/
|
||||
static Drawable makeSoundIcon(@NonNull Context context, @DrawableRes int iconResId) {
|
||||
return composeIconCircle(
|
||||
Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer),
|
||||
context.getResources().getDimensionPixelSize(
|
||||
R.dimen.zen_mode_circular_icon_diameter),
|
||||
checkNotNull(context.getDrawable(iconResId)),
|
||||
Utils.getColorAttr(context,
|
||||
com.android.internal.R.attr.materialColorOnSecondaryContainer),
|
||||
context.getResources().getDimensionPixelSize(
|
||||
R.dimen.zen_mode_circular_icon_inner_icon_size));
|
||||
}
|
||||
|
||||
private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
|
||||
Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
|
||||
ShapeDrawable background = new ShapeDrawable(new OvalShape());
|
||||
@@ -93,11 +111,11 @@ class IconUtil {
|
||||
|
||||
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
|
||||
|
||||
layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
|
||||
layerDrawable.setLayerSize(0, circleDiameterPx, circleDiameterPx);
|
||||
layerDrawable.setLayerGravity(1, Gravity.CENTER);
|
||||
layerDrawable.setLayerSize(1, iconSizePx, iconSizePx);
|
||||
|
||||
layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
|
||||
return layerDrawable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenc
|
||||
|
||||
FutureUtil.whenDone(
|
||||
zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
|
||||
icon -> mHeaderController.setIcon(IconUtil.makeBigIconCircle(mContext, icon))
|
||||
icon -> mHeaderController.setIcon(IconUtil.makeIconPickerHeader(mContext, icon))
|
||||
.done(/* rebindActions= */ false),
|
||||
mContext.getMainExecutor());
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
|
||||
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
|
||||
IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
|
||||
Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
|
||||
info -> IconUtil.makeSmallIconCircle(mContext, info.resId()));
|
||||
info -> IconUtil.makeIconPickerItem(mContext, info.resId()));
|
||||
holder.bindIcon(iconInfo, iconDrawable);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,19 +17,44 @@
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
|
||||
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
|
||||
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
|
||||
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
|
||||
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
|
||||
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
|
||||
|
||||
import android.content.Context;
|
||||
import android.service.notification.ZenPolicy;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Preference with a link and summary about what other sounds can break through the mode
|
||||
*/
|
||||
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);
|
||||
|
||||
private final ZenModeSummaryHelper mSummaryHelper;
|
||||
|
||||
public ZenModeOtherLinkPreferenceController(Context context, String key,
|
||||
@@ -51,7 +76,17 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
|
||||
zenMode.getId(), 0).toIntent());
|
||||
|
||||
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
|
||||
// TODO: b/346551087 - Show media icons
|
||||
((CircularIconsPreference) preference).displayIcons(CircularIconSet.EMPTY);
|
||||
((CircularIconsPreference) preference).displayIcons(getSoundIcons(zenMode.getPolicy()));
|
||||
}
|
||||
|
||||
private CircularIconSet<Integer> getSoundIcons(ZenPolicy policy) {
|
||||
ImmutableList.Builder<Integer> icons = new ImmutableList.Builder<>();
|
||||
for (Map.Entry<Integer, Integer> entry : PRIORITIES_TO_ICONS.entrySet()) {
|
||||
if (policy.isCategoryAllowed(entry.getKey(), false)) {
|
||||
icons.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
return new CircularIconSet<>(icons.build(),
|
||||
iconResId -> IconUtil.makeSoundIcon(mContext, iconResId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ import com.android.settings.R;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -85,14 +87,18 @@ class ZenModeSummaryHelper {
|
||||
PRIORITY_CATEGORY_REPEAT_CALLERS,
|
||||
};
|
||||
|
||||
static final ImmutableList</* @PriorityCategory */ Integer> OTHER_SOUND_CATEGORIES =
|
||||
ImmutableList.of(
|
||||
PRIORITY_CATEGORY_ALARMS,
|
||||
PRIORITY_CATEGORY_MEDIA,
|
||||
PRIORITY_CATEGORY_SYSTEM,
|
||||
PRIORITY_CATEGORY_REMINDERS,
|
||||
PRIORITY_CATEGORY_EVENTS);
|
||||
|
||||
String getOtherSoundCategoriesSummary(ZenMode zenMode) {
|
||||
List<String> enabledCategories = getEnabledCategories(
|
||||
zenMode.getPolicy(),
|
||||
category -> PRIORITY_CATEGORY_ALARMS == category
|
||||
|| PRIORITY_CATEGORY_MEDIA == category
|
||||
|| PRIORITY_CATEGORY_SYSTEM == category
|
||||
|| PRIORITY_CATEGORY_REMINDERS == category
|
||||
|| PRIORITY_CATEGORY_EVENTS == category,
|
||||
OTHER_SOUND_CATEGORIES::contains,
|
||||
true);
|
||||
int numCategories = enabledCategories.size();
|
||||
MessageFormat msgFormat = new MessageFormat(
|
||||
|
||||
@@ -271,6 +271,8 @@ public class ChooseLockPassword extends SettingsActivity {
|
||||
|
||||
private static final int CONFIRM_EXISTING_REQUEST = 58;
|
||||
static final int RESULT_FINISHED = RESULT_FIRST_USER;
|
||||
private boolean mIsErrorTooShort = true;
|
||||
|
||||
/** Used to store the profile type for which pin/password is being set */
|
||||
protected enum ProfileType {
|
||||
None,
|
||||
@@ -672,6 +674,11 @@ public class ChooseLockPassword extends SettingsActivity {
|
||||
view.addView(mPasswordRestrictionView);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
View getPasswordRequirementsView() {
|
||||
return mPasswordRestrictionView;
|
||||
}
|
||||
|
||||
private void createHintMessageView(ViewGroup view) {
|
||||
if (mPasswordRestrictionView != null) {
|
||||
return;
|
||||
@@ -855,6 +862,7 @@ public class ChooseLockPassword extends SettingsActivity {
|
||||
*/
|
||||
String[] convertErrorCodeToMessages() {
|
||||
List<String> messages = new ArrayList<>();
|
||||
mIsErrorTooShort = false;
|
||||
for (PasswordValidationError error : mValidationErrors) {
|
||||
switch (error.errorCode) {
|
||||
case CONTAINS_INVALID_CHARACTERS:
|
||||
@@ -889,6 +897,7 @@ public class ChooseLockPassword extends SettingsActivity {
|
||||
R.string.lockpassword_password_requires_nonnumerical));
|
||||
break;
|
||||
case TOO_SHORT:
|
||||
mIsErrorTooShort = true;
|
||||
String message = StringUtil.getIcuPluralsString(getContext(),
|
||||
error.requirement,
|
||||
mIsAlphaMode
|
||||
@@ -951,12 +960,13 @@ public class ChooseLockPassword extends SettingsActivity {
|
||||
? LockscreenCredential.createPassword(mPasswordEntry.getText())
|
||||
: LockscreenCredential.createPin(mPasswordEntry.getText());
|
||||
final int length = password.size();
|
||||
|
||||
if (mUiStage == Stage.Introduction) {
|
||||
mPasswordRestrictionView.setVisibility(View.VISIBLE);
|
||||
final boolean passwordCompliant = validatePassword(password);
|
||||
String[] messages = convertErrorCodeToMessages();
|
||||
// Update the fulfillment of requirements.
|
||||
mPasswordRequirementAdapter.setRequirements(messages);
|
||||
mPasswordRequirementAdapter.setRequirements(messages, mIsErrorTooShort);
|
||||
// set the visibility of pin_auto_confirm option accordingly
|
||||
setAutoPinConfirmOption(passwordCompliant, length);
|
||||
// Enable/Disable the next button accordingly.
|
||||
|
||||
@@ -36,6 +36,7 @@ public class PasswordRequirementAdapter extends
|
||||
|
||||
private String[] mRequirements;
|
||||
private Context mContext;
|
||||
private boolean mIsTooShortError = true;
|
||||
|
||||
public PasswordRequirementAdapter(Context context) {
|
||||
mContext = context;
|
||||
@@ -54,8 +55,9 @@ public class PasswordRequirementAdapter extends
|
||||
return mRequirements.length;
|
||||
}
|
||||
|
||||
public void setRequirements(String[] requirements) {
|
||||
public void setRequirements(String[] requirements, boolean isPasswordShort) {
|
||||
mRequirements = requirements;
|
||||
mIsTooShortError = isPasswordShort;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@@ -74,7 +76,12 @@ public class PasswordRequirementAdapter extends
|
||||
final int fontSize = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.password_requirement_font_size);
|
||||
holder.mDescriptionText.setText(mRequirements[position]);
|
||||
holder.mDescriptionText.setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyle);
|
||||
if (mIsTooShortError) {
|
||||
holder.mDescriptionText.setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyle);
|
||||
} else {
|
||||
holder.mDescriptionText.
|
||||
setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyleError);
|
||||
}
|
||||
holder.mDescriptionText.setTextSize(fontSize / mContext.getResources()
|
||||
.getDisplayMetrics().scaledDensity);
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ public class SmartForwardingFragment extends PreferenceFragmentCompat
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.smart_forwarding_switch, rootKey);
|
||||
|
||||
String title = getResources().getString(R.string.smart_forwarding_title);
|
||||
getActivity().getActionBar().setTitle(title);
|
||||
getActivity().setTitle(R.string.smart_forwarding_title);
|
||||
|
||||
TwoStatePreference smartForwardingSwitch = findPreference(KEY_SMART_FORWARDING_SWITCH);
|
||||
if (turnOffSwitch) {
|
||||
|
||||
@@ -53,6 +53,36 @@ public class CircularIconSetTest {
|
||||
when(mDrawableLoader.apply(anyInt())).thenReturn(new ColorDrawable(Color.BLACK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equals_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();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equals_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();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equals_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();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIcons_loadsAllIcons() {
|
||||
CircularIconSet<Integer> set = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
|
||||
@@ -19,16 +19,16 @@ package com.android.settings.notification.modes;
|
||||
import static android.view.View.MeasureSpec.makeMeasureSpec;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
@@ -41,11 +41,11 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@@ -68,20 +68,30 @@ public class CircularIconsPreferenceTest {
|
||||
// Tests should call bindAndMeasureViewHolder() so that icons can be added.
|
||||
|
||||
Resources res = mContext.getResources();
|
||||
mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_size)
|
||||
mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
|
||||
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
|
||||
}
|
||||
|
||||
private void bindAndMeasureViewHolder(int viewWidth) {
|
||||
bindViewHolder();
|
||||
measureViewHolder(viewWidth);
|
||||
}
|
||||
|
||||
private void bindViewHolder() {
|
||||
View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
|
||||
null);
|
||||
mIconContainer = checkNotNull(preferenceView.findViewById(R.id.circles_container));
|
||||
mIconContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
|
||||
PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
|
||||
mPreference.onBindViewHolder(holder);
|
||||
}
|
||||
|
||||
private void measureViewHolder(int viewWidth) {
|
||||
checkState(mIconContainer != null, "Call bindViewHolder() first!");
|
||||
mIconContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
|
||||
mIconContainer.getViewTreeObserver().dispatchOnGlobalLayout();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_loadsIcons() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
|
||||
@@ -90,13 +100,10 @@ public class CircularIconsPreferenceTest {
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
mPreference.displayIcons(iconSet);
|
||||
|
||||
assertThat(mPreference.getIconViews()).hasSize(2);
|
||||
assertThat(mPreference.getIconViews().get(0).getDrawable())
|
||||
.isInstanceOf(ColorDrawable.class);
|
||||
assertThat(((ColorDrawable) mPreference.getIconViews().get(0).getDrawable()).getColor())
|
||||
.isEqualTo(1);
|
||||
assertThat(((ColorDrawable) mPreference.getIconViews().get(1).getDrawable()).getColor())
|
||||
.isEqualTo(2);
|
||||
assertThat(mPreference.getIcons()).hasSize(2);
|
||||
assertThat(((ColorDrawable) mPreference.getIcons().get(0)).getColor()).isEqualTo(1);
|
||||
assertThat(((ColorDrawable) mPreference.getIcons().get(1)).getColor()).isEqualTo(2);
|
||||
assertThat(mPreference.getPlusText()).isNull();
|
||||
assertThat(mIconContainer.getVisibility()).isEqualTo(View.VISIBLE);
|
||||
}
|
||||
|
||||
@@ -111,74 +118,81 @@ public class CircularIconsPreferenceTest {
|
||||
assertThat(mIconContainer.getVisibility()).isEqualTo(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void displayIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
|
||||
int width = 300;
|
||||
int fittingIcons = width / mOneIconWidth;
|
||||
int fittingCircles = width / mOneIconWidth;
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(
|
||||
IntStream.range(0, fittingIcons).boxed().toList(),
|
||||
IntStream.range(0, fittingCircles).boxed().toList(),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(width);
|
||||
mPreference.displayIcons(iconSet);
|
||||
|
||||
List<Drawable> displayedDrawables = mPreference.getIconViews().stream()
|
||||
.map(ImageView::getDrawable).toList();
|
||||
assertThat(displayedDrawables).hasSize(fittingIcons);
|
||||
assertThat(displayedDrawables).containsExactlyElementsIn(
|
||||
assertThat(mPreference.getIcons()).hasSize(fittingCircles);
|
||||
assertThat(mPreference.getIcons()).containsExactlyElementsIn(
|
||||
Futures.allAsList(iconSet.getIcons()).get()).inOrder();
|
||||
assertThat(mPreference.getPlusText()).isNull();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
|
||||
int width = 300;
|
||||
int fittingIcons = width / mOneIconWidth;
|
||||
int fittingCircles = width / mOneIconWidth;
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(
|
||||
IntStream.range(0, fittingIcons + 5).boxed().toList(),
|
||||
IntStream.range(0, fittingCircles + 5).boxed().toList(),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(width);
|
||||
mPreference.displayIcons(iconSet);
|
||||
|
||||
List<Drawable> displayedDrawables = mPreference.getIconViews().stream()
|
||||
.map(ImageView::getDrawable).toList();
|
||||
assertThat(displayedDrawables).hasSize(fittingIcons);
|
||||
// N-1 are actual icons, Nth icon is (+xx).
|
||||
assertThat(displayedDrawables.stream().limit(fittingIcons - 1).toList())
|
||||
.containsExactlyElementsIn(
|
||||
Futures.allAsList(iconSet.getIcons(fittingIcons - 1)).get())
|
||||
// N-1 icons, plus (+6) text.
|
||||
assertThat(mPreference.getIcons()).hasSize(fittingCircles - 1);
|
||||
assertThat(mPreference.getIcons()).containsExactlyElementsIn(
|
||||
Futures.allAsList(iconSet.getIcons(fittingCircles - 1)).get())
|
||||
.inOrder();
|
||||
// TODO: b/346551087 - Correctly verify the plus-6 icon, once we generate it properly.
|
||||
assertThat(((ColorDrawable) displayedDrawables.get(
|
||||
displayedDrawables.size() - 1)).getColor()).isEqualTo(Color.BLUE);
|
||||
assertThat(mPreference.getPlusText()).isEqualTo("+6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_teenyTinySpace_showsPlusIcon_noCrash() {
|
||||
int width = 1;
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
|
||||
ColorDrawable::new);
|
||||
|
||||
bindAndMeasureViewHolder(width);
|
||||
bindAndMeasureViewHolder(1);
|
||||
mPreference.displayIcons(iconSet);
|
||||
|
||||
assertThat(mPreference.getIconViews()).hasSize(1);
|
||||
// TODO: b/346551087 - Correctly verify the plus-2 icon, once we generate it properly.
|
||||
assertThat(((ColorDrawable) mPreference.getIconViews().get(0).getDrawable()).getColor())
|
||||
.isEqualTo(Color.BLUE);
|
||||
assertThat(mPreference.getIcons()).isEmpty();
|
||||
assertThat(mPreference.getPlusText()).isEqualTo("+2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_beforeBind_loadsIconsOnBind() {
|
||||
public void displayIcons_beforeBind_loadsIconsOnBindAndMeasure() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new);
|
||||
|
||||
mPreference.displayIcons(iconSet);
|
||||
assertThat(mPreference.getIconViews()).isEmpty();
|
||||
assertThat(mPreference.getIcons()).isEmpty(); // Hold...
|
||||
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
assertThat(mPreference.getIconViews()).hasSize(3);
|
||||
bindViewHolder();
|
||||
assertThat(mPreference.getIcons()).isEmpty(); // Hooooold...
|
||||
|
||||
measureViewHolder(VIEW_WIDTH);
|
||||
assertThat(mPreference.getIcons()).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_beforeMeasure_loadsIconsOnMeasure() {
|
||||
CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new);
|
||||
bindViewHolder();
|
||||
|
||||
mPreference.displayIcons(iconSet);
|
||||
assertThat(mPreference.getIcons()).isEmpty();
|
||||
|
||||
measureViewHolder(VIEW_WIDTH);
|
||||
assertThat(mPreference.getIcons()).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -192,10 +206,24 @@ public class CircularIconsPreferenceTest {
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
|
||||
mPreference.displayIcons(threeIcons);
|
||||
assertThat(mPreference.getIconViews()).hasSize(3);
|
||||
assertThat(mPreference.getIcons()).hasSize(3);
|
||||
mPreference.displayIcons(twoIcons);
|
||||
assertThat(mPreference.getIconViews()).hasSize(2);
|
||||
assertThat(mPreference.getIcons()).hasSize(2);
|
||||
mPreference.displayIcons(fourIcons);
|
||||
assertThat(mPreference.getIconViews()).hasSize(4);
|
||||
assertThat(mPreference.getIcons()).hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayIcons_sameSet_doesNotReloadIcons() {
|
||||
CircularIconSet<Integer> one = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new);
|
||||
CircularIconSet<Integer> same = Mockito.spy(new CircularIconSet<>(ImmutableList.of(1, 2, 3),
|
||||
ColorDrawable::new));
|
||||
when(same.getIcons()).thenThrow(new RuntimeException("Shouldn't be called!"));
|
||||
|
||||
bindAndMeasureViewHolder(VIEW_WIDTH);
|
||||
|
||||
mPreference.displayIcons(one);
|
||||
mPreference.displayIcons(same); // if no exception, wasn't called.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ public final class ZenModeAppsLinkPreferenceControllerTest {
|
||||
appEntries.add(createAppEntry("test2", mContext.getUserId()));
|
||||
mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
|
||||
|
||||
assertThat(mPreference.getIconViews()).hasSize(2);
|
||||
assertThat(mPreference.getIcons()).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -25,8 +25,10 @@ import android.app.Flags;
|
||||
import android.content.Context;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.service.notification.ZenPolicy;
|
||||
|
||||
import com.android.settingslib.notification.modes.TestModeBuilder;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -60,13 +62,40 @@ public final class ZenModeOtherLinkPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void testHasSummary() {
|
||||
public void updateState_loadsSummary() {
|
||||
CircularIconsPreference pref = mock(CircularIconsPreference.class);
|
||||
|
||||
mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
|
||||
|
||||
verify(pref).setSummary(any());
|
||||
verify(pref).displayIcons(eq(CircularIconSet.EMPTY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateState_loadsIcons() {
|
||||
CircularIconsPreference pref = mock(CircularIconsPreference.class);
|
||||
ZenMode mode = new TestModeBuilder()
|
||||
.setZenPolicy(new ZenPolicy.Builder()
|
||||
.disallowAllSounds()
|
||||
.allowMedia(true)
|
||||
.allowSystem(true)
|
||||
.allowReminders(true)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
mController.updateState(pref, mode);
|
||||
|
||||
verify(pref).displayIcons(argThat(iconSet -> iconSet.size() == 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateState_loadsAllIcons() {
|
||||
CircularIconsPreference pref = mock(CircularIconsPreference.class);
|
||||
ZenMode mode = new TestModeBuilder()
|
||||
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
|
||||
.build();
|
||||
|
||||
mController.updateState(pref, mode);
|
||||
|
||||
verify(pref).displayIcons(argThat(iconSet ->
|
||||
iconSet.size() == ZenModeSummaryHelper.OTHER_SOUND_CATEGORIES.size()));
|
||||
}
|
||||
}
|
||||
@@ -36,19 +36,25 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static org.robolectric.RuntimeEnvironment.application;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.DevicePolicyManager.PasswordComplexity;
|
||||
import android.app.admin.PasswordMetrics;
|
||||
import android.app.admin.PasswordPolicy;
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment;
|
||||
import com.android.settings.password.ChooseLockPassword.IntentBuilder;
|
||||
import com.android.settings.testutils.shadow.SettingsShadowResources;
|
||||
@@ -515,6 +521,52 @@ public class ChooseLockPasswordTest {
|
||||
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultMessage_shouldBeInTextColorPrimary() {
|
||||
final ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
|
||||
|
||||
final ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
|
||||
final ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
|
||||
final RecyclerView view = (RecyclerView) fragment.getPasswordRequirementsView();
|
||||
@ColorInt final int textColorPrimary = Utils.getColorAttrDefaultColor(passwordActivity,
|
||||
android.R.attr.textColorPrimary);
|
||||
|
||||
passwordEntry.setText("");
|
||||
fragment.updateUi();
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
TextView textView = (TextView)view.getLayoutManager().findViewByPosition(0);
|
||||
|
||||
assertThat(textView.getCurrentTextColor()).isEqualTo(textColorPrimary);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMessage_shouldBeColorError() {
|
||||
final ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
|
||||
|
||||
final ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
|
||||
final ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
|
||||
final RecyclerView view = (RecyclerView) fragment.getPasswordRequirementsView();
|
||||
@ColorInt final int textColorPrimary = Utils.getColorAttrDefaultColor(passwordActivity,
|
||||
android.R.attr.textColorPrimary);
|
||||
@ColorInt final int colorError = Utils.getColorAttrDefaultColor(passwordActivity,
|
||||
android.R.attr.colorError);
|
||||
|
||||
passwordEntry.setText("");
|
||||
fragment.updateUi();
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
TextView textView = (TextView)view.getLayoutManager().findViewByPosition(0);
|
||||
|
||||
assertThat(textView.getCurrentTextColor()).isEqualTo(textColorPrimary);
|
||||
|
||||
// Password must be fewer than 17 digits, so this should give an error.
|
||||
passwordEntry.setText("a".repeat(17));
|
||||
fragment.updateUi();
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
textView = (TextView)view.getLayoutManager().findViewByPosition(0);
|
||||
|
||||
assertThat(textView.getCurrentTextColor()).isEqualTo(colorError);
|
||||
}
|
||||
|
||||
private ChooseLockPassword setupActivityWithPinTypeAndDefaultPolicy() {
|
||||
PasswordPolicy policy = new PasswordPolicy();
|
||||
policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
|
||||
@@ -543,7 +595,7 @@ public class ChooseLockPasswordTest {
|
||||
.setForFingerprint(addFingerprintExtra)
|
||||
.build());
|
||||
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
|
||||
return Shadows.shadowOf(((GlifLayout) fragment.getView()).getIcon());
|
||||
return shadowOf(((GlifLayout) fragment.getView()).getIcon());
|
||||
}
|
||||
|
||||
private void assertPasswordValidationResult(PasswordMetrics minMetrics,
|
||||
|
||||
Reference in New Issue
Block a user