blob: a8a834f1e8f498e555db1713d53b5740e45a1dac [file] [log] [blame]
/*
* Copyright (C) 2017 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.statusbar.phone;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
import com.android.systemui.util.Assert;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
public interface StatusBarIconController {
/**
* When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
* in that slot and not added as a sub slot.
*/
int TAG_PRIMARY = 0;
/** */
void addIconGroup(IconManager iconManager);
/** */
void removeIconGroup(IconManager iconManager);
/** Refresh the state of an IconManager by recreating the views */
void refreshIconGroup(IconManager iconManager);
/**
* Adds or updates an icon that comes from an active tile service.
*
* If the icon is null, the icon will be removed.
*/
void setIconFromTile(String slot, @Nullable StatusBarIcon icon);
/** Removes an icon that had come from an active tile service. */
void removeIconForTile(String slot);
/**
* Adds or updates an icon for the given slot for **internal system icons**.
*
* TODO(b/265307726): Re-name this to `setInternalIcon`.
*/
void setIcon(String slot, int resourceId, CharSequence contentDescription);
/** */
void setWifiIcon(String slot, WifiIconState state);
/**
* Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
* set up (inflated and added to the view hierarchy).
*
* This method completely replaces {@link #setWifiIcon} with the information from the new wifi
* data pipeline. Icons will automatically keep their state up to date, so we don't have to
* worry about funneling state objects through anymore.
*/
void setNewWifiIcon();
/** */
void setMobileIcons(String slot, List<MobileIconState> states);
/**
* This method completely replaces {@link #setMobileIcons} with the information from the new
* mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
* to worry about funneling MobileIconState objects through anymore.
*/
void setNewMobileIconSubIds(List<Integer> subIds);
/**
* Display the no calling & SMS icons.
*/
void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
/**
* Display the no calling & SMS icons.
*/
void setNoCallingIcons(String slot, List<CallIndicatorIconState> states);
public void setIconVisibility(String slot, boolean b);
/**
* Sets the live region mode for the icon
*
* @param slot Icon slot to set region for
* @param accessibilityLiveRegion live region mode for the icon
* @see android.view.View#setAccessibilityLiveRegion(int)
*/
void setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion);
/**
* If you don't know what to pass for `tag`, either remove all icons for slot, or use
* TAG_PRIMARY to refer to the first icon at a given slot.
*/
void removeIcon(String slot, int tag);
/**
* TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`.
*/
void removeAllIconsForSlot(String slot);
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
/** Reads the default hide list from config value unless hideListStr is provided. */
static ArraySet<String> getIconHideList(Context context, String hideListStr) {
ArraySet<String> ret = new ArraySet<>();
String[] hideList = hideListStr == null
? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
: hideListStr.split(",");
for (String slot : hideList) {
if (!TextUtils.isEmpty(slot)) {
ret.add(slot);
}
}
return ret;
}
/**
* Version of ViewGroup that observes state from the DarkIconDispatcher.
*/
class DarkIconManager extends IconManager {
private final DarkIconDispatcher mDarkIconDispatcher;
private int mIconHPadding;
public DarkIconManager(
LinearLayout linearLayout,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
mDarkIconDispatcher = darkIconDispatcher;
}
@Override
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
}
@Override
protected LayoutParams onCreateLayoutParams() {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
return lp;
}
@Override
protected void destroy() {
for (int i = 0; i < mGroup.getChildCount(); i++) {
mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(i));
}
mGroup.removeAllViews();
}
@Override
protected void onRemoveIcon(int viewIndex) {
mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(viewIndex));
super.onRemoveIcon(viewIndex);
}
@Override
public void onSetIcon(int viewIndex, StatusBarIcon icon) {
super.onSetIcon(viewIndex, icon);
mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex));
}
@Override
protected DemoStatusIcons createDemoStatusIcons() {
DemoStatusIcons icons = super.createDemoStatusIcons();
mDarkIconDispatcher.addDarkReceiver(icons);
return icons;
}
@Override
protected void exitDemoMode() {
mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons);
super.exitDemoMode();
}
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
WifiUiAdapter wifiUiAdapter,
MobileContextProvider mobileContextProvider,
MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiUiAdapter = wifiUiAdapter;
mMobileContextProvider = mobileContextProvider;
mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
}
public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
return new DarkIconManager(
group,
location,
mStatusBarPipelineFlags,
mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
}
}
}
/**
*
*/
class TintedIconManager extends IconManager {
private int mColor;
public TintedIconManager(
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
super(group,
location,
statusBarPipelineFlags,
wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
}
@Override
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
view.setStaticDrawableColor(mColor);
view.setDecorColor(mColor);
}
public void setTint(int color) {
mColor = color;
for (int i = 0; i < mGroup.getChildCount(); i++) {
View child = mGroup.getChildAt(i);
if (child instanceof StatusIconDisplayable) {
StatusIconDisplayable icon = (StatusIconDisplayable) child;
icon.setStaticDrawableColor(mColor);
icon.setDecorColor(mColor);
}
}
}
@Override
protected DemoStatusIcons createDemoStatusIcons() {
DemoStatusIcons icons = super.createDemoStatusIcons();
icons.setColor(mColor);
return icons;
}
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiUiAdapter = wifiUiAdapter;
mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
return new TintedIconManager(
group,
location,
mStatusBarPipelineFlags,
mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider);
}
}
}
/**
* Turns info from StatusBarIconController into ImageViews in a ViewGroup.
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final MobileContextProvider mMobileContextProvider;
private final LocationBasedWifiViewModel mWifiViewModel;
private final MobileIconsViewModel mMobileIconsViewModel;
protected final Context mContext;
protected final int mIconSize;
// Whether or not these icons show up in dumpsys
protected boolean mShouldLog = false;
private StatusBarIconController mController;
private final StatusBarLocation mLocation;
// Enables SystemUI demo mode to take effect in this group
protected boolean mDemoable = true;
private boolean mIsInDemoMode;
protected DemoStatusIcons mDemoStatusIcons;
protected ArrayList<String> mBlockList = new ArrayList<>();
public IconManager(
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mGroup = group;
mStatusBarPipelineFlags = statusBarPipelineFlags;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
mLocation = location;
if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else {
mMobileIconsViewModel = null;
}
if (statusBarPipelineFlags.runNewWifiIconBackend()) {
// This starts the flow for the new pipeline, and will notify us of changes if
// {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
} else {
mWifiViewModel = null;
}
}
public boolean isDemoable() {
return mDemoable;
}
public void setIsDemoable(boolean demoable) {
mDemoable = demoable;
}
void setController(StatusBarIconController controller) {
mController = controller;
}
public void setBlockList(@Nullable List<String> blockList) {
Assert.isMainThread();
mBlockList.clear();
mBlockList.addAll(blockList);
if (mController != null) {
mController.refreshIconGroup(this);
}
}
public void setShouldLog(boolean should) {
mShouldLog = should;
}
public boolean shouldLog() {
return mShouldLog;
}
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
addHolder(index, slot, blocked, holder);
}
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
// This is a little hacky, and probably regrettable, but just set `blocked` on any icon
// that is in our blocked list, then we'll never see it
if (mBlockList.contains(slot)) {
blocked = true;
}
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
case TYPE_WIFI:
return addWifiIcon(index, slot, holder.getWifiState());
case TYPE_WIFI_NEW:
return addNewWifiIcon(index, slot);
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
case TYPE_MOBILE_NEW:
return addNewMobileIcon(index, slot, holder.getTag());
}
return null;
}
@VisibleForTesting
protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
StatusBarIcon icon) {
StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
view.set(icon);
mGroup.addView(view, index, onCreateLayoutParams());
return view;
}
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
throw new IllegalStateException("Attempting to add a wifi icon while the new "
+ "icons are enabled is not supported");
}
final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
view.applyWifiState(state);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
mDemoStatusIcons.addDemoWifiView(state);
}
return view;
}
protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
throw new IllegalStateException("Attempting to add a wifi icon using the new"
+ "pipeline, but the enabled flag is false.");
}
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
}
return view;
}
@VisibleForTesting
protected StatusIconDisplayable addMobileIcon(
int index,
String slot,
MobileIconState state
) {
if (mStatusBarPipelineFlags.useNewMobileIcons()) {
throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ "icons are enabled is not supported");
}
// Use the `subId` field as a key to query for the correct context
StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
mobileView.applyMobileState(state);
mGroup.addView(mobileView, index, onCreateLayoutParams());
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
.getMobileContextForSub(state.subId, mContext);
mDemoStatusIcons.addMobileView(state, mobileContext);
}
return mobileView;
}
protected StatusIconDisplayable addNewMobileIcon(
int index,
String slot,
int subId
) {
if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
throw new IllegalStateException("Attempting to add a mobile icon using the new"
+ "pipeline, but the enabled flag is false.");
}
BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
.getMobileContextForSub(subId, mContext);
mDemoStatusIcons.addModernMobileView(
mobileContext,
mMobileIconsViewModel.getLogger(),
subId);
}
return view;
}
private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
return new StatusBarIconView(mContext, slot, null, blocked);
}
private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
return view;
}
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
StatusBarMobileView view = StatusBarMobileView
.fromContext(mobileContext, slot);
return view;
}
private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
String slot, int subId) {
Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
return ModernStatusBarMobileView
.constructAndBind(
mobileContext,
mMobileIconsViewModel.getLogger(),
slot,
mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
}
protected LinearLayout.LayoutParams onCreateLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
}
protected void destroy() {
mGroup.removeAllViews();
}
protected void onDensityOrFontScaleChanged() {
for (int i = 0; i < mGroup.getChildCount(); i++) {
View child = mGroup.getChildAt(i);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
child.setLayoutParams(lp);
}
}
private void setHeightAndCenter(ImageView imageView, int height) {
ViewGroup.LayoutParams params = imageView.getLayoutParams();
params.height = height;
if (params instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
}
imageView.setLayoutParams(params);
}
protected void onRemoveIcon(int viewIndex) {
if (mIsInDemoMode) {
mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
}
mGroup.removeViewAt(viewIndex);
}
public void onSetIcon(int viewIndex, StatusBarIcon icon) {
StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
view.set(icon);
}
public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
switch (holder.getType()) {
case TYPE_ICON:
onSetIcon(viewIndex, holder.getIcon());
return;
case TYPE_WIFI:
onSetWifiIcon(viewIndex, holder.getWifiState());
return;
case TYPE_MOBILE:
onSetMobileIcon(viewIndex, holder.getMobileState());
return;
case TYPE_MOBILE_NEW:
case TYPE_WIFI_NEW:
// Nothing, the new icons update themselves
return;
default:
break;
}
}
public void onSetWifiIcon(int viewIndex, WifiIconState state) {
View view = mGroup.getChildAt(viewIndex);
if (view instanceof StatusBarWifiView) {
((StatusBarWifiView) view).applyWifiState(state);
} else if (view instanceof ModernStatusBarWifiView) {
// ModernStatusBarWifiView will automatically apply state based on its callbacks, so
// we don't need to call applyWifiState.
} else {
throw new IllegalStateException("View at " + viewIndex + " must be of type "
+ "StatusBarWifiView or ModernStatusBarWifiView");
}
if (mIsInDemoMode) {
mDemoStatusIcons.updateWifiState(state);
}
}
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
View view = mGroup.getChildAt(viewIndex);
if (view instanceof StatusBarMobileView) {
((StatusBarMobileView) view).applyMobileState(state);
} else {
// ModernStatusBarMobileView automatically updates via the ViewModel
throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
+ "the new pipeline");
}
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
.getMobileContextForSub(state.subId, mContext);
mDemoStatusIcons.updateMobileState(state, mobileContext);
}
}
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoable) {
return;
}
mDemoStatusIcons.dispatchDemoCommand(command, args);
}
@Override
public void onDemoModeStarted() {
mIsInDemoMode = true;
if (mDemoStatusIcons == null) {
mDemoStatusIcons = createDemoStatusIcons();
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
mDemoStatusIcons.addModernWifiView(mWifiViewModel);
}
}
mDemoStatusIcons.onDemoModeStarted();
}
@Override
public void onDemoModeFinished() {
if (mDemoStatusIcons != null) {
mDemoStatusIcons.onDemoModeFinished();
exitDemoMode();
mIsInDemoMode = false;
}
}
protected void exitDemoMode() {
mDemoStatusIcons.remove();
mDemoStatusIcons = null;
}
protected DemoStatusIcons createDemoStatusIcons() {
return new DemoStatusIcons(
(LinearLayout) mGroup,
mMobileIconsViewModel,
mLocation,
mIconSize
);
}
}
}