1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.phone.ui;
18 
19 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_BINDABLE;
20 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
21 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
22 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
23 
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.os.Bundle;
27 import android.view.ViewGroup;
28 import android.widget.LinearLayout;
29 
30 import androidx.annotation.VisibleForTesting;
31 
32 import com.android.internal.statusbar.StatusBarIcon;
33 import com.android.systemui.demomode.DemoModeCommandReceiver;
34 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
35 import com.android.systemui.statusbar.StatusBarIconView;
36 import com.android.systemui.statusbar.StatusIconDisplayable;
37 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
38 import com.android.systemui.statusbar.phone.DemoStatusIcons;
39 import com.android.systemui.statusbar.phone.StatusBarIconHolder;
40 import com.android.systemui.statusbar.phone.StatusBarIconHolder.BindableIconHolder;
41 import com.android.systemui.statusbar.phone.StatusBarLocation;
42 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
43 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
44 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
45 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
46 import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView;
47 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
48 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
49 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
50 import com.android.systemui.util.Assert;
51 
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 
57 /**
58  * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
59  */
60 public class IconManager implements DemoModeCommandReceiver {
61     protected final ViewGroup mGroup;
62     private final MobileContextProvider mMobileContextProvider;
63     private final LocationBasedWifiViewModel mWifiViewModel;
64     private final MobileIconsViewModel mMobileIconsViewModel;
65 
66     /**
67      * Stores the list of bindable icons that have been added, keyed on slot name. This ensures
68      * we don't accidentally add the same bindable icon twice.
69      */
70     private final Map<String, BindableIconHolder> mBindableIcons = new HashMap<>();
71     protected final Context mContext;
72     protected int mIconSize;
73     // Whether or not these icons show up in dumpsys
74     protected boolean mShouldLog = false;
75     private StatusBarIconController mController;
76     private final StatusBarLocation mLocation;
77 
78     // Enables SystemUI demo mode to take effect in this group
79     protected boolean mDemoable = true;
80     private boolean mIsInDemoMode;
81     protected DemoStatusIcons mDemoStatusIcons;
82 
83     protected ArrayList<String> mBlockList = new ArrayList<>();
84 
IconManager( ViewGroup group, StatusBarLocation location, WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider )85     public IconManager(
86             ViewGroup group,
87             StatusBarLocation location,
88             WifiUiAdapter wifiUiAdapter,
89             MobileUiAdapter mobileUiAdapter,
90             MobileContextProvider mobileContextProvider
91     ) {
92         mGroup = group;
93         mMobileContextProvider = mobileContextProvider;
94         mContext = group.getContext();
95         mLocation = location;
96 
97         reloadDimens();
98 
99         // This starts the flow for the new pipeline, and will notify us of changes via
100         // {@link #setNewMobileIconIds}
101         mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
102         MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
103 
104         mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
105     }
106 
isDemoable()107     public boolean isDemoable() {
108         return mDemoable;
109     }
110 
setController(StatusBarIconController controller)111     void setController(StatusBarIconController controller) {
112         mController = controller;
113     }
114 
115     /** Sets the list of slots that should be blocked from showing in the status bar. */
setBlockList(@ullable List<String> blockList)116     public void setBlockList(@Nullable List<String> blockList) {
117         Assert.isMainThread();
118         mBlockList.clear();
119         mBlockList.addAll(blockList);
120         if (mController != null) {
121             mController.refreshIconGroup(this);
122         }
123     }
124 
125     /** Sets whether this manager's changes should be dumped in a bug report. */
setShouldLog(boolean should)126     public void setShouldLog(boolean should) {
127         mShouldLog = should;
128     }
129 
130     /** Returns true if this manager's changes should be dumped in a bug report. */
shouldLog()131     public boolean shouldLog() {
132         return mShouldLog;
133     }
134 
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)135     protected void onIconAdded(int index, String slot, boolean blocked,
136             StatusBarIconHolder holder) {
137         addHolder(index, slot, blocked, holder);
138     }
139 
addHolder(int index, String slot, boolean blocked, StatusBarIconHolder holder)140     protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
141             StatusBarIconHolder holder) {
142         // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
143         // that is in our blocked list, then we'll never see it
144         if (mBlockList.contains(slot)) {
145             blocked = true;
146         }
147         return switch (holder.getType()) {
148             case TYPE_ICON -> addIcon(index, slot, blocked, holder.getIcon());
149             case TYPE_WIFI_NEW -> addNewWifiIcon(index, slot);
150             case TYPE_MOBILE_NEW -> addNewMobileIcon(index, slot, holder.getTag());
151             case TYPE_BINDABLE ->
152                 // Safe cast, since only BindableIconHolders can set this tag on themselves
153                 addBindableIcon((BindableIconHolder) holder, index);
154             default -> null;
155         };
156     }
157 
158     @VisibleForTesting
addIcon(int index, String slot, boolean blocked, StatusBarIcon icon)159     protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
160             StatusBarIcon icon) {
161         StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
162         view.set(icon);
163         mGroup.addView(view, index, onCreateLayoutParams());
164         return view;
165     }
166 
167     /**
168      * ModernStatusBarViews can be created and bound, and thus do not need to update their
169      * drawable by sending multiple calls to setIcon. Instead, by using a bindable
170      * icon view, we can simply create the icon when requested and allow the
171      * ViewBinder to control its visual state.
172      */
addBindableIcon(BindableIconHolder holder, int index)173     protected StatusIconDisplayable addBindableIcon(BindableIconHolder holder,
174             int index) {
175         mBindableIcons.put(holder.getSlot(), holder);
176         ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
177         mGroup.addView(view, index, onCreateLayoutParams());
178         if (mIsInDemoMode) {
179             mDemoStatusIcons.addBindableIcon(holder);
180         }
181         return view;
182     }
183 
addNewWifiIcon(int index, String slot)184     protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
185         ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
186         mGroup.addView(view, index, onCreateLayoutParams());
187 
188         if (mIsInDemoMode) {
189             mDemoStatusIcons.addModernWifiView(mWifiViewModel);
190         }
191 
192         return view;
193     }
194 
195 
addNewMobileIcon( int index, String slot, int subId )196     protected StatusIconDisplayable addNewMobileIcon(
197             int index,
198             String slot,
199             int subId
200     ) {
201         BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
202         mGroup.addView(view, index, onCreateLayoutParams());
203 
204         if (mIsInDemoMode) {
205             Context mobileContext = mMobileContextProvider
206                     .getMobileContextForSub(subId, mContext);
207             mDemoStatusIcons.addModernMobileView(
208                     mobileContext,
209                     mMobileIconsViewModel.getLogger(),
210                     subId);
211         }
212 
213         return view;
214     }
215 
onCreateStatusBarIconView(String slot, boolean blocked)216     private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
217         return new StatusBarIconView(mContext, slot, null, blocked);
218     }
219 
onCreateModernStatusBarWifiView(String slot)220     private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
221         return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
222     }
223 
onCreateModernStatusBarMobileView( String slot, int subId)224     private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
225             String slot, int subId) {
226         Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
227         return ModernStatusBarMobileView
228                 .constructAndBind(
229                         mobileContext,
230                         mMobileIconsViewModel.getLogger(),
231                         slot,
232                         mMobileIconsViewModel.viewModelForSub(subId, mLocation)
233                 );
234     }
235 
onCreateLayoutParams()236     protected LinearLayout.LayoutParams onCreateLayoutParams() {
237         return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
238     }
239 
destroy()240     protected void destroy() {
241         mGroup.removeAllViews();
242     }
243 
reloadDimens()244     protected void reloadDimens() {
245         mIconSize = mContext.getResources().getDimensionPixelSize(
246                 com.android.internal.R.dimen.status_bar_icon_size_sp);
247     }
248 
onRemoveIcon(int viewIndex)249     protected void onRemoveIcon(int viewIndex) {
250         if (mIsInDemoMode) {
251             mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
252         }
253         mGroup.removeViewAt(viewIndex);
254     }
255 
256     /** Called once an icon has been set. */
onSetIcon(int viewIndex, StatusBarIcon icon)257     public void onSetIcon(int viewIndex, StatusBarIcon icon) {
258         StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
259         view.set(icon);
260     }
261 
262     /** Called once an icon holder has been set. */
onSetIconHolder(int viewIndex, StatusBarIconHolder holder)263     public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
264         switch (holder.getType()) {
265             case TYPE_ICON:
266                 onSetIcon(viewIndex, holder.getIcon());
267                 return;
268             case TYPE_MOBILE_NEW:
269             case TYPE_WIFI_NEW:
270             case TYPE_BINDABLE:
271                 // Nothing, the new icons update themselves
272                 return;
273             default:
274                 break;
275         }
276     }
277 
278     @Override
dispatchDemoCommand(String command, Bundle args)279     public void dispatchDemoCommand(String command, Bundle args) {
280         if (!mDemoable) {
281             return;
282         }
283 
284         mDemoStatusIcons.dispatchDemoCommand(command, args);
285     }
286 
287     @Override
onDemoModeStarted()288     public void onDemoModeStarted() {
289         mIsInDemoMode = true;
290         if (mDemoStatusIcons == null) {
291             mDemoStatusIcons = createDemoStatusIcons();
292             mDemoStatusIcons.addModernWifiView(mWifiViewModel);
293             for (BindableIconHolder holder : mBindableIcons.values()) {
294                 mDemoStatusIcons.addBindableIcon(holder);
295             }
296         }
297         mDemoStatusIcons.onDemoModeStarted();
298     }
299 
300     @Override
onDemoModeFinished()301     public void onDemoModeFinished() {
302         if (mDemoStatusIcons != null) {
303             mDemoStatusIcons.onDemoModeFinished();
304             exitDemoMode();
305             mIsInDemoMode = false;
306         }
307     }
308 
exitDemoMode()309     protected void exitDemoMode() {
310         mDemoStatusIcons.remove();
311         mDemoStatusIcons = null;
312     }
313 
createDemoStatusIcons()314     protected DemoStatusIcons createDemoStatusIcons() {
315         return new DemoStatusIcons(
316                 (LinearLayout) mGroup,
317                 mMobileIconsViewModel,
318                 mLocation,
319                 mIconSize
320         );
321     }
322 }
323