1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.statusbar.phone;
16 
17 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
18 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
19 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
20 
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.os.Bundle;
24 import android.text.TextUtils;
25 import android.util.ArraySet;
26 import android.view.Gravity;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 import android.widget.LinearLayout.LayoutParams;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.internal.statusbar.StatusBarIcon;
36 import com.android.systemui.Dependency;
37 import com.android.systemui.R;
38 import com.android.systemui.dagger.SysUISingleton;
39 import com.android.systemui.demomode.DemoModeCommandReceiver;
40 import com.android.systemui.flags.FeatureFlags;
41 import com.android.systemui.plugins.DarkIconDispatcher;
42 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
43 import com.android.systemui.statusbar.StatusBarIconView;
44 import com.android.systemui.statusbar.StatusBarMobileView;
45 import com.android.systemui.statusbar.StatusBarWifiView;
46 import com.android.systemui.statusbar.StatusIconDisplayable;
47 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
48 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
49 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 
54 import javax.inject.Inject;
55 
56 public interface StatusBarIconController {
57 
58     /**
59      * When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
60      * in that slot and not added as a sub slot.
61      */
62     int TAG_PRIMARY = 0;
63 
64     /** */
addIconGroup(IconManager iconManager)65     void addIconGroup(IconManager iconManager);
66     /** */
removeIconGroup(IconManager iconManager)67     void removeIconGroup(IconManager iconManager);
68     /** */
setExternalIcon(String slot)69     void setExternalIcon(String slot);
70     /** */
setIcon(String slot, int resourceId, CharSequence contentDescription)71     void setIcon(String slot, int resourceId, CharSequence contentDescription);
72     /** */
setIcon(String slot, StatusBarIcon icon)73     void setIcon(String slot, StatusBarIcon icon);
74     /** */
setSignalIcon(String slot, WifiIconState state)75     void setSignalIcon(String slot, WifiIconState state);
76     /** */
setMobileIcons(String slot, List<MobileIconState> states)77     void setMobileIcons(String slot, List<MobileIconState> states);
78     /**
79      * Display the no calling & SMS icons.
80      */
setCallStrengthIcons(String slot, List<CallIndicatorIconState> states)81     void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
82     /**
83      * Display the no calling & SMS icons.
84      */
setNoCallingIcons(String slot, List<CallIndicatorIconState> states)85     void setNoCallingIcons(String slot, List<CallIndicatorIconState> states);
setIconVisibility(String slot, boolean b)86     public void setIconVisibility(String slot, boolean b);
87 
88     /**
89      * Sets the live region mode for the icon
90      * @see android.view.View#setAccessibilityLiveRegion(int)
91      * @param slot Icon slot to set region for
92      * @param accessibilityLiveRegion live region mode for the icon
93      */
setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion)94     void setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion);
95 
96     /**
97      * If you don't know what to pass for `tag`, either remove all icons for slot, or use
98      * TAG_PRIMARY to refer to the first icon at a given slot.
99      */
removeIcon(String slot, int tag)100     void removeIcon(String slot, int tag);
101     /** */
removeAllIconsForSlot(String slot)102     void removeAllIconsForSlot(String slot);
103 
104     // TODO: See if we can rename this tunable name.
105     String ICON_HIDE_LIST = "icon_blacklist";
106 
107     /** Reads the default hide list from config value unless hideListStr is provided. */
getIconHideList(Context context, String hideListStr)108     static ArraySet<String> getIconHideList(Context context, String hideListStr) {
109         ArraySet<String> ret = new ArraySet<>();
110         String[] hideList = hideListStr == null
111             ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
112             : hideListStr.split(",");
113         for (String slot : hideList) {
114             if (!TextUtils.isEmpty(slot)) {
115                 ret.add(slot);
116             }
117         }
118         return ret;
119     }
120 
121     /**
122      * Version of ViewGroup that observes state from the DarkIconDispatcher.
123      */
124     class DarkIconManager extends IconManager {
125         private final DarkIconDispatcher mDarkIconDispatcher;
126         private int mIconHPadding;
127 
DarkIconManager(LinearLayout linearLayout, FeatureFlags featureFlags)128         public DarkIconManager(LinearLayout linearLayout, FeatureFlags featureFlags) {
129             super(linearLayout, featureFlags);
130             mIconHPadding = mContext.getResources().getDimensionPixelSize(
131                     R.dimen.status_bar_icon_padding);
132             mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
133         }
134 
135         @Override
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)136         protected void onIconAdded(int index, String slot, boolean blocked,
137                 StatusBarIconHolder holder) {
138             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
139             mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
140         }
141 
142         @Override
onCreateLayoutParams()143         protected LayoutParams onCreateLayoutParams() {
144             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
145                     ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
146             lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
147             return lp;
148         }
149 
150         @Override
destroy()151         protected void destroy() {
152             for (int i = 0; i < mGroup.getChildCount(); i++) {
153                 mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(i));
154             }
155             mGroup.removeAllViews();
156         }
157 
158         @Override
onRemoveIcon(int viewIndex)159         protected void onRemoveIcon(int viewIndex) {
160             mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(viewIndex));
161             super.onRemoveIcon(viewIndex);
162         }
163 
164         @Override
onSetIcon(int viewIndex, StatusBarIcon icon)165         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
166             super.onSetIcon(viewIndex, icon);
167             mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex));
168         }
169 
170         @Override
createDemoStatusIcons()171         protected DemoStatusIcons createDemoStatusIcons() {
172             DemoStatusIcons icons = super.createDemoStatusIcons();
173             mDarkIconDispatcher.addDarkReceiver(icons);
174 
175             return icons;
176         }
177 
178         @Override
exitDemoMode()179         protected void exitDemoMode() {
180             mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons);
181             super.exitDemoMode();
182         }
183     }
184 
185     /** */
186     class TintedIconManager extends IconManager {
187         private int mColor;
188 
TintedIconManager(ViewGroup group, FeatureFlags featureFlags)189         public TintedIconManager(ViewGroup group, FeatureFlags featureFlags) {
190             super(group, featureFlags);
191         }
192 
193         @Override
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)194         protected void onIconAdded(int index, String slot, boolean blocked,
195                 StatusBarIconHolder holder) {
196             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
197             view.setStaticDrawableColor(mColor);
198             view.setDecorColor(mColor);
199         }
200 
setTint(int color)201         public void setTint(int color) {
202             mColor = color;
203             for (int i = 0; i < mGroup.getChildCount(); i++) {
204                 View child = mGroup.getChildAt(i);
205                 if (child instanceof StatusIconDisplayable) {
206                     StatusIconDisplayable icon = (StatusIconDisplayable) child;
207                     icon.setStaticDrawableColor(mColor);
208                     icon.setDecorColor(mColor);
209                 }
210             }
211         }
212 
213         @Override
createDemoStatusIcons()214         protected DemoStatusIcons createDemoStatusIcons() {
215             DemoStatusIcons icons = super.createDemoStatusIcons();
216             icons.setColor(mColor);
217             return icons;
218         }
219 
220         @SysUISingleton
221         public static class Factory {
222             private final FeatureFlags mFeatureFlags;
223 
224             @Inject
Factory(FeatureFlags featureFlags)225             public Factory(FeatureFlags featureFlags) {
226                 mFeatureFlags = featureFlags;
227             }
228 
create(ViewGroup group)229             public TintedIconManager create(ViewGroup group) {
230                 return new TintedIconManager(group, mFeatureFlags);
231             }
232         }
233     }
234 
235     /**
236      * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
237      */
238     class IconManager implements DemoModeCommandReceiver {
239         private final FeatureFlags mFeatureFlags;
240         protected final ViewGroup mGroup;
241         protected final Context mContext;
242         protected final int mIconSize;
243         // Whether or not these icons show up in dumpsys
244         protected boolean mShouldLog = false;
245 
246         // Enables SystemUI demo mode to take effect in this group
247         protected boolean mDemoable = true;
248         private boolean mIsInDemoMode;
249         protected DemoStatusIcons mDemoStatusIcons;
250 
251         protected ArrayList<String> mBlockList = new ArrayList<>();
252 
IconManager(ViewGroup group, FeatureFlags featureFlags)253         public IconManager(ViewGroup group, FeatureFlags featureFlags) {
254             mFeatureFlags = featureFlags;
255             mGroup = group;
256             mContext = group.getContext();
257             mIconSize = mContext.getResources().getDimensionPixelSize(
258                     com.android.internal.R.dimen.status_bar_icon_size);
259         }
260 
isDemoable()261         public boolean isDemoable() {
262             return mDemoable;
263         }
264 
setIsDemoable(boolean demoable)265         public void setIsDemoable(boolean demoable) {
266             mDemoable = demoable;
267         }
268 
setBlockList(@ullable List<String> blockList)269         public void setBlockList(@Nullable List<String> blockList) {
270             mBlockList.clear();
271             if (blockList == null || blockList.isEmpty()) {
272                 return;
273             }
274 
275             mBlockList.addAll(blockList);
276         }
277 
setShouldLog(boolean should)278         public void setShouldLog(boolean should) {
279             mShouldLog = should;
280         }
281 
shouldLog()282         public boolean shouldLog() {
283             return mShouldLog;
284         }
285 
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)286         protected void onIconAdded(int index, String slot, boolean blocked,
287                 StatusBarIconHolder holder) {
288             addHolder(index, slot, blocked, holder);
289         }
290 
addHolder(int index, String slot, boolean blocked, StatusBarIconHolder holder)291         protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
292                 StatusBarIconHolder holder) {
293             // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
294             // that is in our blocked list, then we'll never see it
295             if (mBlockList.contains(slot)) {
296                 blocked = true;
297             }
298             switch (holder.getType()) {
299                 case TYPE_ICON:
300                     return addIcon(index, slot, blocked, holder.getIcon());
301 
302                 case TYPE_WIFI:
303                     return addSignalIcon(index, slot, holder.getWifiState());
304 
305                 case TYPE_MOBILE:
306                     return addMobileIcon(index, slot, holder.getMobileState());
307             }
308 
309             return null;
310         }
311 
312         @VisibleForTesting
addIcon(int index, String slot, boolean blocked, StatusBarIcon icon)313         protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
314                 StatusBarIcon icon) {
315             StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
316             view.set(icon);
317             mGroup.addView(view, index, onCreateLayoutParams());
318             return view;
319         }
320 
321         @VisibleForTesting
addSignalIcon(int index, String slot, WifiIconState state)322         protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
323             StatusBarWifiView view = onCreateStatusBarWifiView(slot);
324             view.applyWifiState(state);
325             mGroup.addView(view, index, onCreateLayoutParams());
326 
327             if (mIsInDemoMode) {
328                 mDemoStatusIcons.addDemoWifiView(state);
329             }
330             return view;
331         }
332 
333         @VisibleForTesting
addMobileIcon(int index, String slot, MobileIconState state)334         protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
335             StatusBarMobileView view = onCreateStatusBarMobileView(slot);
336             view.applyMobileState(state);
337             mGroup.addView(view, index, onCreateLayoutParams());
338 
339             if (mIsInDemoMode) {
340                 mDemoStatusIcons.addMobileView(state);
341             }
342             return view;
343         }
344 
onCreateStatusBarIconView(String slot, boolean blocked)345         private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
346             return new StatusBarIconView(mContext, slot, null, blocked);
347         }
348 
onCreateStatusBarWifiView(String slot)349         private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
350             StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
351             return view;
352         }
353 
onCreateStatusBarMobileView(String slot)354         private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
355             StatusBarMobileView view = StatusBarMobileView.fromContext(
356                             mContext, slot, mFeatureFlags.isCombinedStatusBarSignalIconsEnabled());
357             return view;
358         }
359 
onCreateLayoutParams()360         protected LinearLayout.LayoutParams onCreateLayoutParams() {
361             return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
362         }
363 
destroy()364         protected void destroy() {
365             mGroup.removeAllViews();
366         }
367 
onIconExternal(int viewIndex, int height)368         protected void onIconExternal(int viewIndex, int height) {
369             ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex);
370             imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
371             imageView.setAdjustViewBounds(true);
372             setHeightAndCenter(imageView, height);
373         }
374 
onDensityOrFontScaleChanged()375         protected void onDensityOrFontScaleChanged() {
376             for (int i = 0; i < mGroup.getChildCount(); i++) {
377                 View child = mGroup.getChildAt(i);
378                 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
379                         ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
380                 child.setLayoutParams(lp);
381             }
382         }
383 
setHeightAndCenter(ImageView imageView, int height)384         private void setHeightAndCenter(ImageView imageView, int height) {
385             ViewGroup.LayoutParams params = imageView.getLayoutParams();
386             params.height = height;
387             if (params instanceof LinearLayout.LayoutParams) {
388                 ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
389             }
390             imageView.setLayoutParams(params);
391         }
392 
onRemoveIcon(int viewIndex)393         protected void onRemoveIcon(int viewIndex) {
394             if (mIsInDemoMode) {
395                 mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
396             }
397             mGroup.removeViewAt(viewIndex);
398         }
399 
onSetIcon(int viewIndex, StatusBarIcon icon)400         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
401             StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
402             view.set(icon);
403         }
404 
onSetIconHolder(int viewIndex, StatusBarIconHolder holder)405         public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
406             switch (holder.getType()) {
407                 case TYPE_ICON:
408                     onSetIcon(viewIndex, holder.getIcon());
409                     return;
410                 case TYPE_WIFI:
411                     onSetSignalIcon(viewIndex, holder.getWifiState());
412                     return;
413 
414                 case TYPE_MOBILE:
415                     onSetMobileIcon(viewIndex, holder.getMobileState());
416                 default:
417                     break;
418             }
419         }
420 
onSetSignalIcon(int viewIndex, WifiIconState state)421         public void onSetSignalIcon(int viewIndex, WifiIconState state) {
422             StatusBarWifiView wifiView = (StatusBarWifiView) mGroup.getChildAt(viewIndex);
423             if (wifiView != null) {
424                 wifiView.applyWifiState(state);
425             }
426 
427             if (mIsInDemoMode) {
428                 mDemoStatusIcons.updateWifiState(state);
429             }
430         }
431 
onSetMobileIcon(int viewIndex, MobileIconState state)432         public void onSetMobileIcon(int viewIndex, MobileIconState state) {
433             StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
434             if (view != null) {
435                 view.applyMobileState(state);
436             }
437 
438             if (mIsInDemoMode) {
439                 mDemoStatusIcons.updateMobileState(state);
440             }
441         }
442 
443         @Override
dispatchDemoCommand(String command, Bundle args)444         public void dispatchDemoCommand(String command, Bundle args) {
445             if (!mDemoable) {
446                 return;
447             }
448 
449             mDemoStatusIcons.dispatchDemoCommand(command, args);
450         }
451 
452         @Override
onDemoModeStarted()453         public void onDemoModeStarted() {
454             mIsInDemoMode = true;
455             if (mDemoStatusIcons == null) {
456                 mDemoStatusIcons = createDemoStatusIcons();
457             }
458             mDemoStatusIcons.onDemoModeStarted();
459         }
460 
461         @Override
onDemoModeFinished()462         public void onDemoModeFinished() {
463             if (mDemoStatusIcons != null) {
464                 mDemoStatusIcons.onDemoModeFinished();
465                 exitDemoMode();
466                 mIsInDemoMode = false;
467             }
468         }
469 
exitDemoMode()470         protected void exitDemoMode() {
471             mDemoStatusIcons.remove();
472             mDemoStatusIcons = null;
473         }
474 
createDemoStatusIcons()475         protected DemoStatusIcons createDemoStatusIcons() {
476             return new DemoStatusIcons((LinearLayout) mGroup, mIconSize, mFeatureFlags);
477         }
478     }
479 }
480