/* * Copyright (C) 2023 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.systemui.tv.privacy; import android.annotation.ColorRes; import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import androidx.annotation.NonNull; import com.android.systemui.privacy.PrivacyType; import com.android.systemui.tv.res.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * View that shows indicator icons for privacy items. */ public class PrivacyItemsChip extends FrameLayout { private static final String TAG = "PrivacyItemsChip"; private static final boolean DEBUG = false; /** * Configuration for a PrivacyItemsChip's appearance. */ public static class ChipConfig { public final List privacyTypes; @ColorRes public final int colorRes; public final boolean collapseToDot; /** * @param privacyTypes Privacy types to show icons for, in order. * @param colorRes Color resource for the chip's foreground color. * @param collapseToDot Whether to collapse the chip in to a dot, * or just collapse it into a smaller chip with icons still visible. */ public ChipConfig(@NonNull List privacyTypes, int colorRes, boolean collapseToDot) { this.privacyTypes = privacyTypes; this.colorRes = colorRes; this.collapseToDot = collapseToDot; } } @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"STATE_"}, value = { STATE_NOT_SHOWN, STATE_EXPANDED, STATE_COLLAPSED, }) public @interface State { } private static final int STATE_NOT_SHOWN = 0; private static final int STATE_EXPANDED = 1; private static final int STATE_COLLAPSED = 2; private final ChipConfig mConfig; private final int mIconSize; private final int mCollapsedIconSize; private final int mIconMarginHorizontal; private final PrivacyChipDrawable mChipBackgroundDrawable; private final List mIcons = new ArrayList<>(); @State private int mState = STATE_NOT_SHOWN; public PrivacyItemsChip(@NonNull Context context, @NonNull ChipConfig config) { super(context); mConfig = config; setVisibility(View.GONE); Resources res = context.getResources(); mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size); mCollapsedIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_collapsed_icon_size); mIconMarginHorizontal = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_margin_in_between); LayoutInflater.from(context).inflate(R.layout.ongoing_privacy_chip, this); LinearLayout iconsContainer = findViewById(R.id.icons_container); mChipBackgroundDrawable = new PrivacyChipDrawable( context, config.colorRes, config.collapseToDot); mChipBackgroundDrawable.setCallback(new Drawable.Callback() { @Override public void invalidateDrawable(@NonNull Drawable who) { invalidate(); } @Override public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { } @Override public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { } }); setBackground(mChipBackgroundDrawable); for (PrivacyType type : config.privacyTypes) { ImageView typeIconView = new ImageView(context); Drawable icon = type.getIcon(context); icon.mutate().setTint(context.getColor(R.color.privacy_icon_tint)); typeIconView.setImageDrawable(icon); typeIconView.setScaleType(ImageView.ScaleType.FIT_CENTER); mIcons.add(typeIconView); iconsContainer.addView(typeIconView, mIconSize, mIconSize); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) typeIconView.getLayoutParams(); lp.leftMargin = mIconMarginHorizontal; lp.rightMargin = mIconMarginHorizontal; typeIconView.setVisibility(View.GONE); } } /** * Sets the active privacy types, and expands the chip if there are active items and the chip is * currently collapsed, or hides the chip if there are no active items. * * @param types The set of active privacy types. Only types configured in {@link ChipConfig} * are shown. */ public void expandForTypes(Set types) { if (DEBUG) Log.d(TAG, "expandForTypes, state=" + stateToString(mState)); boolean hasActiveTypes = false; for (int i = 0; i < mConfig.privacyTypes.size(); i++) { PrivacyType type = mConfig.privacyTypes.get(i); ImageView icon = mIcons.get(i); boolean isTypeActive = types.contains(type); hasActiveTypes = hasActiveTypes || isTypeActive; icon.setVisibility(isTypeActive ? View.VISIBLE : View.GONE); // Set icon size to expanded size ViewGroup.LayoutParams lp = icon.getLayoutParams(); lp.width = mIconSize; lp.height = mIconSize; icon.requestLayout(); } if (hasActiveTypes) { if (DEBUG) Log.d(TAG, "Chip has active types, expanding"); if (mState == STATE_NOT_SHOWN) { mChipBackgroundDrawable.expand(/* animate= */ false); } else if (mState == STATE_COLLAPSED) { mChipBackgroundDrawable.expand(/* animate= */ true); } setVisibility(View.VISIBLE); setState(STATE_EXPANDED); } else { if (DEBUG) Log.d(TAG, "Chip has no active types, hiding"); setVisibility(View.GONE); setState(STATE_NOT_SHOWN); } } /** * Collapses this chip if currently expanded. */ public void collapse() { if (DEBUG) Log.d(TAG, "collapse"); if (mState != STATE_EXPANDED) { return; } setState(STATE_COLLAPSED); for (ImageView icon : mIcons) { if (mConfig.collapseToDot) { icon.setVisibility(View.GONE); } else { ViewGroup.LayoutParams lp = icon.getLayoutParams(); lp.width = mCollapsedIconSize; lp.height = mCollapsedIconSize; icon.requestLayout(); } } mChipBackgroundDrawable.collapse(); } public boolean isExpanded() { return mState == STATE_EXPANDED; } private void setState(@State int state) { if (mState != state) { if (DEBUG) Log.d(TAG, "State changed: " + stateToString(state)); mState = state; } } @Override protected void dispatchDraw(Canvas canvas) { mChipBackgroundDrawable.clipToForeground(canvas); super.dispatchDraw(canvas); } /** * Used in debug logs. */ private static String stateToString(@State int state) { switch (state) { case STATE_NOT_SHOWN: return "NOT_SHOWN"; case STATE_EXPANDED: return "EXPANDED"; case STATE_COLLAPSED: return "COLLAPSED"; default: return "INVALID"; } } }