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_NEW;
19 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
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.View;
27 import android.view.ViewGroup;
28 import android.widget.LinearLayout;
29 import android.widget.LinearLayout.LayoutParams;
30 
31 import androidx.annotation.VisibleForTesting;
32 
33 import com.android.internal.statusbar.StatusBarIcon;
34 import com.android.systemui.R;
35 import com.android.systemui.dagger.SysUISingleton;
36 import com.android.systemui.demomode.DemoModeCommandReceiver;
37 import com.android.systemui.plugins.DarkIconDispatcher;
38 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
39 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
40 import com.android.systemui.statusbar.StatusBarIconView;
41 import com.android.systemui.statusbar.StatusIconDisplayable;
42 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
43 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
44 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
45 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
46 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
47 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
48 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
49 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
50 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
51 import com.android.systemui.util.Assert;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 import javax.inject.Inject;
57 
58 public interface StatusBarIconController {
59 
60     /**
61      * When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
62      * in that slot and not added as a sub slot.
63      */
64     int TAG_PRIMARY = 0;
65 
66     /** */
addIconGroup(IconManager iconManager)67     void addIconGroup(IconManager iconManager);
68     /** */
removeIconGroup(IconManager iconManager)69     void removeIconGroup(IconManager iconManager);
70 
71     /** Refresh the state of an IconManager by recreating the views */
refreshIconGroup(IconManager iconManager)72     void refreshIconGroup(IconManager iconManager);
73 
74     /**
75      * Adds or updates an icon that comes from an active tile service.
76      *
77      * If the icon is null, the icon will be removed.
78      */
setIconFromTile(String slot, @Nullable StatusBarIcon icon)79     void setIconFromTile(String slot, @Nullable StatusBarIcon icon);
80 
81     /** Removes an icon that had come from an active tile service. */
removeIconForTile(String slot)82     void removeIconForTile(String slot);
83 
84     /**
85      * Adds or updates an icon for the given slot for **internal system icons**.
86      *
87      * TODO(b/265307726): Re-name this to `setInternalIcon`.
88      */
setIcon(String slot, int resourceId, CharSequence contentDescription)89     void setIcon(String slot, int resourceId, CharSequence contentDescription);
90 
91     /**
92      * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
93      * set up (inflated and added to the view hierarchy).
94      */
setNewWifiIcon()95     void setNewWifiIcon();
96 
97     /**
98      * Notify this class that there is a new set of mobile icons to display, keyed off of this list
99      * of subIds. The icons will be added and bound to the mobile data pipeline via
100      * {@link com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder}.
101      */
setNewMobileIconSubIds(List<Integer> subIds)102     void setNewMobileIconSubIds(List<Integer> subIds);
103     /**
104      * Display the no calling & SMS icons.
105      */
setCallStrengthIcons(String slot, List<CallIndicatorIconState> states)106     void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
107 
108     /**
109      * Display the no calling & SMS icons.
110      */
setNoCallingIcons(String slot, List<CallIndicatorIconState> states)111     void setNoCallingIcons(String slot, List<CallIndicatorIconState> states);
112 
setIconVisibility(String slot, boolean b)113     public void setIconVisibility(String slot, boolean b);
114 
115     /**
116      * Sets the live region mode for the icon
117      *
118      * @param slot                    Icon slot to set region for
119      * @param accessibilityLiveRegion live region mode for the icon
120      * @see android.view.View#setAccessibilityLiveRegion(int)
121      */
setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion)122     void setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion);
123 
124     /**
125      * If you don't know what to pass for `tag`, either remove all icons for slot, or use
126      * TAG_PRIMARY to refer to the first icon at a given slot.
127      */
removeIcon(String slot, int tag)128     void removeIcon(String slot, int tag);
129 
130     /**
131      * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`.
132      */
removeAllIconsForSlot(String slot)133     void removeAllIconsForSlot(String slot);
134 
135     // TODO: See if we can rename this tunable name.
136     String ICON_HIDE_LIST = "icon_blacklist";
137 
138     /** Reads the default hide list from config value unless hideListStr is provided. */
getIconHideList(Context context, String hideListStr)139     static ArraySet<String> getIconHideList(Context context, String hideListStr) {
140         ArraySet<String> ret = new ArraySet<>();
141         String[] hideList = hideListStr == null
142                 ? context.getResources().getStringArray(R.array.config_statusBarIconsToExclude)
143                 : hideListStr.split(",");
144         for (String slot : hideList) {
145             if (!TextUtils.isEmpty(slot)) {
146                 ret.add(slot);
147             }
148         }
149         return ret;
150     }
151 
152     /**
153      * Version of ViewGroup that observes state from the DarkIconDispatcher.
154      */
155     class DarkIconManager extends IconManager {
156         private final DarkIconDispatcher mDarkIconDispatcher;
157         private final int mIconHorizontalMargin;
158 
DarkIconManager( LinearLayout linearLayout, StatusBarLocation location, WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider, DarkIconDispatcher darkIconDispatcher)159         public DarkIconManager(
160                 LinearLayout linearLayout,
161                 StatusBarLocation location,
162                 WifiUiAdapter wifiUiAdapter,
163                 MobileUiAdapter mobileUiAdapter,
164                 MobileContextProvider mobileContextProvider,
165                 DarkIconDispatcher darkIconDispatcher) {
166             super(linearLayout,
167                     location,
168                     wifiUiAdapter,
169                     mobileUiAdapter,
170                     mobileContextProvider);
171             mIconHorizontalMargin = mContext.getResources().getDimensionPixelSize(
172                     R.dimen.status_bar_icon_horizontal_margin);
173             mDarkIconDispatcher = darkIconDispatcher;
174         }
175 
176         @Override
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)177         protected void onIconAdded(int index, String slot, boolean blocked,
178                 StatusBarIconHolder holder) {
179             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
180             mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
181         }
182 
183         @Override
onCreateLayoutParams()184         protected LayoutParams onCreateLayoutParams() {
185             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
186                     ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
187             lp.setMargins(mIconHorizontalMargin, 0, mIconHorizontalMargin, 0);
188             return lp;
189         }
190 
191         @Override
destroy()192         protected void destroy() {
193             for (int i = 0; i < mGroup.getChildCount(); i++) {
194                 mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(i));
195             }
196             mGroup.removeAllViews();
197         }
198 
199         @Override
onRemoveIcon(int viewIndex)200         protected void onRemoveIcon(int viewIndex) {
201             mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(viewIndex));
202             super.onRemoveIcon(viewIndex);
203         }
204 
205         @Override
onSetIcon(int viewIndex, StatusBarIcon icon)206         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
207             super.onSetIcon(viewIndex, icon);
208             mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex));
209         }
210 
211         @Override
createDemoStatusIcons()212         protected DemoStatusIcons createDemoStatusIcons() {
213             DemoStatusIcons icons = super.createDemoStatusIcons();
214             mDarkIconDispatcher.addDarkReceiver(icons);
215 
216             return icons;
217         }
218 
219         @Override
exitDemoMode()220         protected void exitDemoMode() {
221             mDarkIconDispatcher.removeDarkReceiver(mDemoStatusIcons);
222             super.exitDemoMode();
223         }
224 
225         @SysUISingleton
226         public static class Factory {
227             private final WifiUiAdapter mWifiUiAdapter;
228             private final MobileContextProvider mMobileContextProvider;
229             private final MobileUiAdapter mMobileUiAdapter;
230             private final DarkIconDispatcher mDarkIconDispatcher;
231 
232             @Inject
Factory( WifiUiAdapter wifiUiAdapter, MobileContextProvider mobileContextProvider, MobileUiAdapter mobileUiAdapter, DarkIconDispatcher darkIconDispatcher)233             public Factory(
234                     WifiUiAdapter wifiUiAdapter,
235                     MobileContextProvider mobileContextProvider,
236                     MobileUiAdapter mobileUiAdapter,
237                     DarkIconDispatcher darkIconDispatcher) {
238                 mWifiUiAdapter = wifiUiAdapter;
239                 mMobileContextProvider = mobileContextProvider;
240                 mMobileUiAdapter = mobileUiAdapter;
241                 mDarkIconDispatcher = darkIconDispatcher;
242             }
243 
create(LinearLayout group, StatusBarLocation location)244             public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
245                 return new DarkIconManager(
246                         group,
247                         location,
248                         mWifiUiAdapter,
249                         mMobileUiAdapter,
250                         mMobileContextProvider,
251                         mDarkIconDispatcher);
252             }
253         }
254     }
255 
256     /**
257      *
258      */
259     class TintedIconManager extends IconManager {
260         private int mColor;
261 
TintedIconManager( ViewGroup group, StatusBarLocation location, WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider )262         public TintedIconManager(
263                 ViewGroup group,
264                 StatusBarLocation location,
265                 WifiUiAdapter wifiUiAdapter,
266                 MobileUiAdapter mobileUiAdapter,
267                 MobileContextProvider mobileContextProvider
268         ) {
269             super(group,
270                     location,
271                     wifiUiAdapter,
272                     mobileUiAdapter,
273                     mobileContextProvider);
274         }
275 
276         @Override
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)277         protected void onIconAdded(int index, String slot, boolean blocked,
278                 StatusBarIconHolder holder) {
279             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
280             view.setStaticDrawableColor(mColor);
281             view.setDecorColor(mColor);
282         }
283 
setTint(int color)284         public void setTint(int color) {
285             mColor = color;
286             for (int i = 0; i < mGroup.getChildCount(); i++) {
287                 View child = mGroup.getChildAt(i);
288                 if (child instanceof StatusIconDisplayable) {
289                     StatusIconDisplayable icon = (StatusIconDisplayable) child;
290                     icon.setStaticDrawableColor(mColor);
291                     icon.setDecorColor(mColor);
292                 }
293             }
294         }
295 
296         @Override
createDemoStatusIcons()297         protected DemoStatusIcons createDemoStatusIcons() {
298             DemoStatusIcons icons = super.createDemoStatusIcons();
299             icons.setColor(mColor);
300             return icons;
301         }
302 
303         @SysUISingleton
304         public static class Factory {
305             private final WifiUiAdapter mWifiUiAdapter;
306             private final MobileContextProvider mMobileContextProvider;
307             private final MobileUiAdapter mMobileUiAdapter;
308 
309             @Inject
Factory( WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider )310             public Factory(
311                     WifiUiAdapter wifiUiAdapter,
312                     MobileUiAdapter mobileUiAdapter,
313                     MobileContextProvider mobileContextProvider
314             ) {
315                 mWifiUiAdapter = wifiUiAdapter;
316                 mMobileUiAdapter = mobileUiAdapter;
317                 mMobileContextProvider = mobileContextProvider;
318             }
319 
create(ViewGroup group, StatusBarLocation location)320             public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
321                 return new TintedIconManager(
322                         group,
323                         location,
324                         mWifiUiAdapter,
325                         mMobileUiAdapter,
326                         mMobileContextProvider);
327             }
328         }
329     }
330 
331     /**
332      * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
333      */
334     class IconManager implements DemoModeCommandReceiver {
335         protected final ViewGroup mGroup;
336         private final MobileContextProvider mMobileContextProvider;
337         private final LocationBasedWifiViewModel mWifiViewModel;
338         private final MobileIconsViewModel mMobileIconsViewModel;
339 
340         protected final Context mContext;
341         protected int mIconSize;
342         // Whether or not these icons show up in dumpsys
343         protected boolean mShouldLog = false;
344         private StatusBarIconController mController;
345         private final StatusBarLocation mLocation;
346 
347         // Enables SystemUI demo mode to take effect in this group
348         protected boolean mDemoable = true;
349         private boolean mIsInDemoMode;
350         protected DemoStatusIcons mDemoStatusIcons;
351 
352         protected ArrayList<String> mBlockList = new ArrayList<>();
353 
IconManager( ViewGroup group, StatusBarLocation location, WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider )354         public IconManager(
355                 ViewGroup group,
356                 StatusBarLocation location,
357                 WifiUiAdapter wifiUiAdapter,
358                 MobileUiAdapter mobileUiAdapter,
359                 MobileContextProvider mobileContextProvider
360         ) {
361             mGroup = group;
362             mMobileContextProvider = mobileContextProvider;
363             mContext = group.getContext();
364             mLocation = location;
365 
366             reloadDimens();
367 
368             // This starts the flow for the new pipeline, and will notify us of changes via
369             // {@link #setNewMobileIconIds}
370             mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
371             MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
372 
373             mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
374         }
375 
isDemoable()376         public boolean isDemoable() {
377             return mDemoable;
378         }
379 
setIsDemoable(boolean demoable)380         public void setIsDemoable(boolean demoable) {
381             mDemoable = demoable;
382         }
383 
setController(StatusBarIconController controller)384         void setController(StatusBarIconController controller) {
385             mController = controller;
386         }
387 
setBlockList(@ullable List<String> blockList)388         public void setBlockList(@Nullable List<String> blockList) {
389             Assert.isMainThread();
390             mBlockList.clear();
391             mBlockList.addAll(blockList);
392             if (mController != null) {
393                 mController.refreshIconGroup(this);
394             }
395         }
396 
setShouldLog(boolean should)397         public void setShouldLog(boolean should) {
398             mShouldLog = should;
399         }
400 
shouldLog()401         public boolean shouldLog() {
402             return mShouldLog;
403         }
404 
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)405         protected void onIconAdded(int index, String slot, boolean blocked,
406                 StatusBarIconHolder holder) {
407             addHolder(index, slot, blocked, holder);
408         }
409 
addHolder(int index, String slot, boolean blocked, StatusBarIconHolder holder)410         protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
411                 StatusBarIconHolder holder) {
412             // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
413             // that is in our blocked list, then we'll never see it
414             if (mBlockList.contains(slot)) {
415                 blocked = true;
416             }
417             switch (holder.getType()) {
418                 case TYPE_ICON:
419                     return addIcon(index, slot, blocked, holder.getIcon());
420 
421                 case TYPE_WIFI_NEW:
422                     return addNewWifiIcon(index, slot);
423 
424                 case TYPE_MOBILE_NEW:
425                     return addNewMobileIcon(index, slot, holder.getTag());
426             }
427 
428             return null;
429         }
430 
431         @VisibleForTesting
addIcon(int index, String slot, boolean blocked, StatusBarIcon icon)432         protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
433                 StatusBarIcon icon) {
434             StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
435             view.set(icon);
436             mGroup.addView(view, index, onCreateLayoutParams());
437             return view;
438         }
439 
addNewWifiIcon(int index, String slot)440         protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
441             ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
442             mGroup.addView(view, index, onCreateLayoutParams());
443 
444             if (mIsInDemoMode) {
445                 mDemoStatusIcons.addModernWifiView(mWifiViewModel);
446             }
447 
448             return view;
449         }
450 
451 
addNewMobileIcon( int index, String slot, int subId )452         protected StatusIconDisplayable addNewMobileIcon(
453                 int index,
454                 String slot,
455                 int subId
456         ) {
457             BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
458             mGroup.addView(view, index, onCreateLayoutParams());
459 
460             if (mIsInDemoMode) {
461                 Context mobileContext = mMobileContextProvider
462                         .getMobileContextForSub(subId, mContext);
463                 mDemoStatusIcons.addModernMobileView(
464                         mobileContext,
465                         mMobileIconsViewModel.getLogger(),
466                         subId);
467             }
468 
469             return view;
470         }
471 
onCreateStatusBarIconView(String slot, boolean blocked)472         private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
473             return new StatusBarIconView(mContext, slot, null, blocked);
474         }
475 
onCreateModernStatusBarWifiView(String slot)476         private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
477             return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
478         }
479 
onCreateModernStatusBarMobileView( String slot, int subId)480         private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
481                 String slot, int subId) {
482             Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
483             return ModernStatusBarMobileView
484                     .constructAndBind(
485                             mobileContext,
486                             mMobileIconsViewModel.getLogger(),
487                             slot,
488                             mMobileIconsViewModel.viewModelForSub(subId, mLocation)
489                         );
490         }
491 
onCreateLayoutParams()492         protected LinearLayout.LayoutParams onCreateLayoutParams() {
493             return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
494         }
495 
destroy()496         protected void destroy() {
497             mGroup.removeAllViews();
498         }
499 
reloadDimens()500         protected void reloadDimens() {
501             mIconSize = mContext.getResources().getDimensionPixelSize(
502                     com.android.internal.R.dimen.status_bar_icon_size_sp);
503         }
504 
onRemoveIcon(int viewIndex)505         protected void onRemoveIcon(int viewIndex) {
506             if (mIsInDemoMode) {
507                 mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
508             }
509             mGroup.removeViewAt(viewIndex);
510         }
511 
onSetIcon(int viewIndex, StatusBarIcon icon)512         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
513             StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
514             view.set(icon);
515         }
516 
onSetIconHolder(int viewIndex, StatusBarIconHolder holder)517         public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
518             switch (holder.getType()) {
519                 case TYPE_ICON:
520                     onSetIcon(viewIndex, holder.getIcon());
521                     return;
522                 case TYPE_MOBILE_NEW:
523                 case TYPE_WIFI_NEW:
524                     // Nothing, the new icons update themselves
525                     return;
526                 default:
527                     break;
528             }
529         }
530 
531         @Override
dispatchDemoCommand(String command, Bundle args)532         public void dispatchDemoCommand(String command, Bundle args) {
533             if (!mDemoable) {
534                 return;
535             }
536 
537             mDemoStatusIcons.dispatchDemoCommand(command, args);
538         }
539 
540         @Override
onDemoModeStarted()541         public void onDemoModeStarted() {
542             mIsInDemoMode = true;
543             if (mDemoStatusIcons == null) {
544                 mDemoStatusIcons = createDemoStatusIcons();
545                 mDemoStatusIcons.addModernWifiView(mWifiViewModel);
546             }
547             mDemoStatusIcons.onDemoModeStarted();
548         }
549 
550         @Override
onDemoModeFinished()551         public void onDemoModeFinished() {
552             if (mDemoStatusIcons != null) {
553                 mDemoStatusIcons.onDemoModeFinished();
554                 exitDemoMode();
555                 mIsInDemoMode = false;
556             }
557         }
558 
exitDemoMode()559         protected void exitDemoMode() {
560             mDemoStatusIcons.remove();
561             mDemoStatusIcons = null;
562         }
563 
createDemoStatusIcons()564         protected DemoStatusIcons createDemoStatusIcons() {
565             return new DemoStatusIcons(
566                     (LinearLayout) mGroup,
567                     mMobileIconsViewModel,
568                     mLocation,
569                     mIconSize
570             );
571         }
572     }
573 }
574