Files
app_Settings/src/com/android/settings/notification/modes/CircularIconsPreference.java
Matías Hernández 0ebc865c5e Show icons for apps bypassing DND
(Several things pending, such as the +n icon and the correct pending icons, but it's a start).

Test: atest com.android.settings.notification.modes
Bug: 346551087
Flag: android.app.modes_ui
Change-Id: Ifd2ab6a8bb447739dc8ffe400c3960779d477fd6
2024-07-18 11:59:06 +02:00

209 lines
7.7 KiB
Java

/*
* 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.
*/
package com.android.settings.notification.modes;
import static com.google.common.base.Preconditions.checkNotNull;
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.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.RestrictedPreference;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.concurrent.Executor;
public class CircularIconsPreference extends RestrictedPreference {
private Executor mUiExecutor;
@Nullable private LinearLayout mIconContainer;
@Nullable private CircularIconSet<?> mPendingIconSet;
@Nullable private ListenableFuture<?> mPendingLoadIconsFuture;
public CircularIconsPreference(Context context) {
super(context);
init(context);
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public CircularIconsPreference(Context context, Executor uiExecutor) {
this(context);
mUiExecutor = uiExecutor;
}
public CircularIconsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
mUiExecutor = context.getMainExecutor();
setLayoutResource(R.layout.preference_circular_icons);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
mIconContainer = checkNotNull((LinearLayout) holder.findViewById(R.id.circles_container));
displayIconsIfPending();
}
private void displayIconsIfPending() {
CircularIconSet<?> pendingIconSet = mPendingIconSet;
if (pendingIconSet != null) {
mPendingIconSet = null;
displayIcons(pendingIconSet);
}
}
void displayIcons(CircularIconSet<?> iconSet) {
if (mIconContainer == null) {
// Too soon, wait for bind.
mPendingIconSet = iconSet;
return;
}
mIconContainer.setVisibility(iconSet.size() != 0 ? View.VISIBLE : View.GONE);
if (iconSet.size() == 0) {
return;
}
if (mIconContainer.getMeasuredWidth() == 0) {
// Too soon, wait for first measure to know width.
mPendingIconSet = iconSet;
ViewTreeObserver vto = mIconContainer.getViewTreeObserver();
vto.addOnGlobalLayoutListener(() ->
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
vto.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)
+ res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
int numIconsThatFit = availableSpace / iconHorizontalSpace;
List<ListenableFuture<Drawable>> iconFutures;
int extraItems = 0;
if (iconSet.size() > numIconsThatFit) {
// Reserve one space for the (+xx) circle.
int numIconsToShow = numIconsThatFit - 1;
if (numIconsToShow < 0) {
numIconsToShow = 0;
}
iconFutures = iconSet.getIcons(numIconsToShow);
extraItems = iconSet.size() - numIconsToShow;
} else {
// Fit exactly or with remaining space.
iconFutures = iconSet.getIcons();
}
displayIconsWhenReady(iconFutures, extraItems);
}
private void displayIconsWhenReady(List<ListenableFuture<Drawable>> iconFutures,
int extraItems) {
checkState(mIconContainer != null);
if (mPendingLoadIconsFuture != null) {
mPendingLoadIconsFuture.cancel(true);
}
int numCircles = iconFutures.size() + (extraItems > 0 ? 1 : 0);
if (mIconContainer.getChildCount() > numCircles) {
mIconContainer.removeViews(numCircles, mIconContainer.getChildCount() - numCircles);
}
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);
}
// Set up placeholders and extra items indicator.
for (int i = 0; i < iconFutures.size(); 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));
}
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));
}
// Display icons when all are ready (more consistent than randomly loading).
mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
FutureUtil.whenDone(
Futures.allAsList(iconFutures),
icons -> {
checkState(mIconContainer != null);
for (int i = 0; i < icons.size(); i++) {
((ImageView) mIconContainer.getChildAt(i)).setImageDrawable(icons.get(i));
}
},
mUiExecutor);
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
ImmutableList<ImageView> getIconViews() {
if (mIconContainer == null) {
return ImmutableList.of();
}
ImmutableList.Builder<ImageView> imageViews = new ImmutableList.Builder<>();
for (int i = 0; i < mIconContainer.getChildCount(); i++) {
imageViews.add((ImageView) mIconContainer.getChildAt(i));
}
return imageViews.build();
}
}