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 android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
18 import static android.app.StatusBarManager.DISABLE_NONE;
19 
20 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
21 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
22 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
23 
24 import android.content.Context;
25 import android.os.Bundle;
26 import android.text.TextUtils;
27 import android.util.ArraySet;
28 import android.view.Gravity;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.ImageView;
32 import android.widget.LinearLayout;
33 import android.widget.LinearLayout.LayoutParams;
34 
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.internal.statusbar.StatusBarIcon;
38 import com.android.systemui.DemoMode;
39 import com.android.systemui.Dependency;
40 import com.android.systemui.R;
41 import com.android.systemui.plugins.DarkIconDispatcher;
42 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
43 import com.android.systemui.statusbar.CommandQueue;
44 import com.android.systemui.statusbar.StatusBarIconView;
45 import com.android.systemui.statusbar.StatusBarMobileView;
46 import com.android.systemui.statusbar.StatusBarWifiView;
47 import com.android.systemui.statusbar.StatusIconDisplayable;
48 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
49 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
50 import com.android.systemui.util.Utils.DisableStateTracker;
51 
52 import java.util.List;
53 
54 public interface StatusBarIconController {
55 
56     /**
57      * When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
58      * in that slot and not added as a sub slot.
59      */
60     public static final int TAG_PRIMARY = 0;
61 
addIconGroup(IconManager iconManager)62     public void addIconGroup(IconManager iconManager);
removeIconGroup(IconManager iconManager)63     public void removeIconGroup(IconManager iconManager);
setExternalIcon(String slot)64     public void setExternalIcon(String slot);
setIcon(String slot, int resourceId, CharSequence contentDescription)65     public void setIcon(String slot, int resourceId, CharSequence contentDescription);
setIcon(String slot, StatusBarIcon icon)66     public void setIcon(String slot, StatusBarIcon icon);
setSignalIcon(String slot, WifiIconState state)67     public void setSignalIcon(String slot, WifiIconState state);
setMobileIcons(String slot, List<MobileIconState> states)68     public void setMobileIcons(String slot, List<MobileIconState> states);
setIconVisibility(String slot, boolean b)69     public void setIconVisibility(String slot, boolean b);
70 
71     /**
72      * Sets the live region mode for the icon
73      * @see android.view.View#setAccessibilityLiveRegion(int)
74      * @param slot Icon slot to set region for
75      * @param accessibilityLiveRegion live region mode for the icon
76      */
setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion)77     void setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion);
78 
79     /**
80      * If you don't know what to pass for `tag`, either remove all icons for slot, or use
81      * TAG_PRIMARY to refer to the first icon at a given slot.
82      */
removeIcon(String slot, int tag)83     public void removeIcon(String slot, int tag);
removeAllIconsForSlot(String slot)84     public void removeAllIconsForSlot(String slot);
85 
86     public static final String ICON_BLACKLIST = "icon_blacklist";
87 
88     /** Reads the default blacklist from config value unless blacklistStr is provided. */
getIconBlacklist(Context context, String blackListStr)89     static ArraySet<String> getIconBlacklist(Context context, String blackListStr) {
90         ArraySet<String> ret = new ArraySet<>();
91         String[] blacklist = blackListStr == null
92             ? context.getResources().getStringArray(R.array.config_statusBarIconBlackList)
93             : blackListStr.split(",");
94         for (String slot : blacklist) {
95             if (!TextUtils.isEmpty(slot)) {
96                 ret.add(slot);
97             }
98         }
99         return ret;
100     }
101 
102     /**
103      * Version of ViewGroup that observes state from the DarkIconDispatcher.
104      */
105     public static class DarkIconManager extends IconManager {
106         private final DarkIconDispatcher mDarkIconDispatcher;
107         private int mIconHPadding;
108 
DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue)109         public DarkIconManager(LinearLayout linearLayout, CommandQueue commandQueue) {
110             super(linearLayout, commandQueue);
111             mIconHPadding = mContext.getResources().getDimensionPixelSize(
112                     R.dimen.status_bar_icon_padding);
113             mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
114         }
115 
116         @Override
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)117         protected void onIconAdded(int index, String slot, boolean blocked,
118                 StatusBarIconHolder holder) {
119             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
120             mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
121         }
122 
123         @Override
onCreateLayoutParams()124         protected LayoutParams onCreateLayoutParams() {
125             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
126                     ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
127             lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
128             return lp;
129         }
130 
131         @Override
destroy()132         protected void destroy() {
133             for (int i = 0; i < mGroup.getChildCount(); i++) {
134                 mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(i));
135             }
136             mGroup.removeAllViews();
137         }
138 
139         @Override
onRemoveIcon(int viewIndex)140         protected void onRemoveIcon(int viewIndex) {
141             mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(viewIndex));
142             super.onRemoveIcon(viewIndex);
143         }
144 
145         @Override
onSetIcon(int viewIndex, StatusBarIcon icon)146         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
147             super.onSetIcon(viewIndex, icon);
148             mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex));
149         }
150 
151         @Override
createDemoStatusIcons()152         protected DemoStatusIcons createDemoStatusIcons() {
153             DemoStatusIcons icons = super.createDemoStatusIcons();
154             mDarkIconDispatcher.addDarkReceiver(icons);
155 
156             return icons;
157         }
158 
159         @Override
exitDemoMode()160         protected void exitDemoMode() {
161             mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons);
162             super.exitDemoMode();
163         }
164     }
165 
166     public static class TintedIconManager extends IconManager {
167         private int mColor;
168 
TintedIconManager(ViewGroup group, CommandQueue commandQueue)169         public TintedIconManager(ViewGroup group, CommandQueue commandQueue) {
170             super(group, commandQueue);
171         }
172 
173         @Override
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)174         protected void onIconAdded(int index, String slot, boolean blocked,
175                 StatusBarIconHolder holder) {
176             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
177             view.setStaticDrawableColor(mColor);
178             view.setDecorColor(mColor);
179         }
180 
setTint(int color)181         public void setTint(int color) {
182             mColor = color;
183             for (int i = 0; i < mGroup.getChildCount(); i++) {
184                 View child = mGroup.getChildAt(i);
185                 if (child instanceof StatusIconDisplayable) {
186                     StatusIconDisplayable icon = (StatusIconDisplayable) child;
187                     icon.setStaticDrawableColor(mColor);
188                     icon.setDecorColor(mColor);
189                 }
190             }
191         }
192 
193         @Override
createDemoStatusIcons()194         protected DemoStatusIcons createDemoStatusIcons() {
195             DemoStatusIcons icons = super.createDemoStatusIcons();
196             icons.setColor(mColor);
197             return icons;
198         }
199     }
200 
201     /**
202      * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
203      */
204     public static class IconManager implements DemoMode {
205         protected final ViewGroup mGroup;
206         protected final Context mContext;
207         protected final int mIconSize;
208         // Whether or not these icons show up in dumpsys
209         protected boolean mShouldLog = false;
210 
211         // Enables SystemUI demo mode to take effect in this group
212         protected boolean mDemoable = true;
213         private boolean mIsInDemoMode;
214         protected DemoStatusIcons mDemoStatusIcons;
215 
IconManager(ViewGroup group, CommandQueue commandQueue)216         public IconManager(ViewGroup group, CommandQueue commandQueue) {
217             mGroup = group;
218             mContext = group.getContext();
219             mIconSize = mContext.getResources().getDimensionPixelSize(
220                     com.android.internal.R.dimen.status_bar_icon_size);
221 
222             DisableStateTracker tracker =
223                     new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS, commandQueue);
224             mGroup.addOnAttachStateChangeListener(tracker);
225             if (mGroup.isAttachedToWindow()) {
226                 // In case we miss the first onAttachedToWindow event
227                 tracker.onViewAttachedToWindow(mGroup);
228             }
229         }
230 
isDemoable()231         public boolean isDemoable() {
232             return mDemoable;
233         }
234 
setIsDemoable(boolean demoable)235         public void setIsDemoable(boolean demoable) {
236             mDemoable = demoable;
237         }
238 
setShouldLog(boolean should)239         public void setShouldLog(boolean should) {
240             mShouldLog = should;
241         }
242 
shouldLog()243         public boolean shouldLog() {
244             return mShouldLog;
245         }
246 
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)247         protected void onIconAdded(int index, String slot, boolean blocked,
248                 StatusBarIconHolder holder) {
249             addHolder(index, slot, blocked, holder);
250         }
251 
addHolder(int index, String slot, boolean blocked, StatusBarIconHolder holder)252         protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
253                 StatusBarIconHolder holder) {
254             switch (holder.getType()) {
255                 case TYPE_ICON:
256                     return addIcon(index, slot, blocked, holder.getIcon());
257 
258                 case TYPE_WIFI:
259                     return addSignalIcon(index, slot, holder.getWifiState());
260 
261                 case TYPE_MOBILE:
262                     return addMobileIcon(index, slot, holder.getMobileState());
263             }
264 
265             return null;
266         }
267 
268         @VisibleForTesting
addIcon(int index, String slot, boolean blocked, StatusBarIcon icon)269         protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
270                 StatusBarIcon icon) {
271             StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
272             view.set(icon);
273             mGroup.addView(view, index, onCreateLayoutParams());
274             return view;
275         }
276 
277         @VisibleForTesting
addSignalIcon(int index, String slot, WifiIconState state)278         protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
279             StatusBarWifiView view = onCreateStatusBarWifiView(slot);
280             view.applyWifiState(state);
281             mGroup.addView(view, index, onCreateLayoutParams());
282 
283             if (mIsInDemoMode) {
284                 mDemoStatusIcons.addDemoWifiView(state);
285             }
286             return view;
287         }
288 
289         @VisibleForTesting
addMobileIcon(int index, String slot, MobileIconState state)290         protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
291             StatusBarMobileView view = onCreateStatusBarMobileView(slot);
292             view.applyMobileState(state);
293             mGroup.addView(view, index, onCreateLayoutParams());
294 
295             if (mIsInDemoMode) {
296                 mDemoStatusIcons.addMobileView(state);
297             }
298             return view;
299         }
300 
onCreateStatusBarIconView(String slot, boolean blocked)301         private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
302             return new StatusBarIconView(mContext, slot, null, blocked);
303         }
304 
onCreateStatusBarWifiView(String slot)305         private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
306             StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
307             return view;
308         }
309 
onCreateStatusBarMobileView(String slot)310         private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
311             StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
312             return view;
313         }
314 
onCreateLayoutParams()315         protected LinearLayout.LayoutParams onCreateLayoutParams() {
316             return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
317         }
318 
destroy()319         protected void destroy() {
320             mGroup.removeAllViews();
321         }
322 
onIconExternal(int viewIndex, int height)323         protected void onIconExternal(int viewIndex, int height) {
324             ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex);
325             imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
326             imageView.setAdjustViewBounds(true);
327             setHeightAndCenter(imageView, height);
328         }
329 
onDensityOrFontScaleChanged()330         protected void onDensityOrFontScaleChanged() {
331             for (int i = 0; i < mGroup.getChildCount(); i++) {
332                 View child = mGroup.getChildAt(i);
333                 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
334                         ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
335                 child.setLayoutParams(lp);
336             }
337         }
338 
setHeightAndCenter(ImageView imageView, int height)339         private void setHeightAndCenter(ImageView imageView, int height) {
340             ViewGroup.LayoutParams params = imageView.getLayoutParams();
341             params.height = height;
342             if (params instanceof LinearLayout.LayoutParams) {
343                 ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
344             }
345             imageView.setLayoutParams(params);
346         }
347 
onRemoveIcon(int viewIndex)348         protected void onRemoveIcon(int viewIndex) {
349             if (mIsInDemoMode) {
350                 mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
351             }
352             mGroup.removeViewAt(viewIndex);
353         }
354 
onSetIcon(int viewIndex, StatusBarIcon icon)355         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
356             StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
357             view.set(icon);
358         }
359 
onSetIconHolder(int viewIndex, StatusBarIconHolder holder)360         public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
361             switch (holder.getType()) {
362                 case TYPE_ICON:
363                     onSetIcon(viewIndex, holder.getIcon());
364                     return;
365                 case TYPE_WIFI:
366                     onSetSignalIcon(viewIndex, holder.getWifiState());
367                     return;
368 
369                 case TYPE_MOBILE:
370                     onSetMobileIcon(viewIndex, holder.getMobileState());
371                 default:
372                     break;
373             }
374         }
375 
onSetSignalIcon(int viewIndex, WifiIconState state)376         public void onSetSignalIcon(int viewIndex, WifiIconState state) {
377             StatusBarWifiView wifiView = (StatusBarWifiView) mGroup.getChildAt(viewIndex);
378             if (wifiView != null) {
379                 wifiView.applyWifiState(state);
380             }
381 
382             if (mIsInDemoMode) {
383                 mDemoStatusIcons.updateWifiState(state);
384             }
385         }
386 
onSetMobileIcon(int viewIndex, MobileIconState state)387         public void onSetMobileIcon(int viewIndex, MobileIconState state) {
388             StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
389             if (view != null) {
390                 view.applyMobileState(state);
391             }
392 
393             if (mIsInDemoMode) {
394                 mDemoStatusIcons.updateMobileState(state);
395             }
396         }
397 
398         @Override
dispatchDemoCommand(String command, Bundle args)399         public void dispatchDemoCommand(String command, Bundle args) {
400             if (!mDemoable) {
401                 return;
402             }
403 
404             if (command.equals(COMMAND_EXIT)) {
405                 if (mDemoStatusIcons != null) {
406                     mDemoStatusIcons.dispatchDemoCommand(command, args);
407                     exitDemoMode();
408                 }
409                 mIsInDemoMode = false;
410             } else {
411                 if (mDemoStatusIcons == null) {
412                     mIsInDemoMode = true;
413                     mDemoStatusIcons = createDemoStatusIcons();
414                 }
415                 mDemoStatusIcons.dispatchDemoCommand(command, args);
416             }
417         }
418 
exitDemoMode()419         protected void exitDemoMode() {
420             mDemoStatusIcons.remove();
421             mDemoStatusIcons = null;
422         }
423 
createDemoStatusIcons()424         protected DemoStatusIcons createDemoStatusIcons() {
425             return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
426         }
427     }
428 }
429