• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.View;
28 import android.view.ViewGroup;
29 import android.widget.LinearLayout;
30 
31 import androidx.annotation.OptIn;
32 import androidx.collection.MutableIntObjectMap;
33 
34 import com.android.internal.statusbar.StatusBarIcon;
35 import com.android.internal.statusbar.StatusBarIcon.Shape;
36 import com.android.systemui.Flags;
37 import com.android.systemui.demomode.DemoModeCommandReceiver;
38 import com.android.systemui.kairos.ExperimentalKairosApi;
39 import com.android.systemui.kairos.KairosNetwork;
40 import com.android.systemui.modes.shared.ModesUiIcons;
41 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
42 import com.android.systemui.statusbar.StatusBarIconView;
43 import com.android.systemui.statusbar.StatusIconDisplayable;
44 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
45 import com.android.systemui.statusbar.phone.DemoStatusIcons;
46 import com.android.systemui.statusbar.phone.StatusBarIconHolder;
47 import com.android.systemui.statusbar.phone.StatusBarIconHolder.BindableIconHolder;
48 import com.android.systemui.statusbar.phone.StatusBarLocation;
49 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
50 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapterKairos;
51 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
52 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
53 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
54 import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView;
55 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
56 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
57 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
58 import com.android.systemui.util.Assert;
59 
60 import dagger.Lazy;
61 
62 import kotlin.Pair;
63 
64 import kotlinx.coroutines.CoroutineScope;
65 import kotlinx.coroutines.Job;
66 
67 import java.util.ArrayList;
68 import java.util.HashMap;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.concurrent.CancellationException;
72 
73 /**
74  * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
75  */
76 @OptIn(markerClass = ExperimentalKairosApi.class)
77 public class IconManager implements DemoModeCommandReceiver {
78     protected final ViewGroup mGroup;
79     private final MobileContextProvider mMobileContextProvider;
80     private final LocationBasedWifiViewModel mWifiViewModel;
81     private final MobileIconsViewModel mMobileIconsViewModel;
82 
83     private final Lazy<MobileUiAdapterKairos> mMobileUiAdapterKairos;
84     private final KairosNetwork mKairosNetwork;
85     private final CoroutineScope mAppScope;
86     private final MutableIntObjectMap<Job> mBindingJobs = new MutableIntObjectMap<>();
87 
88     /**
89      * Stores the list of bindable icons that have been added, keyed on slot name. This ensures
90      * we don't accidentally add the same bindable icon twice.
91      */
92     private final Map<String, BindableIconHolder> mBindableIcons = new HashMap<>();
93     protected final Context mContext;
94     protected int mIconSize;
95     // Whether or not these icons show up in dumpsys
96     protected boolean mShouldLog = false;
97     private StatusBarIconController mController;
98     private final StatusBarLocation mLocation;
99 
100     // Enables SystemUI demo mode to take effect in this group
101     protected boolean mDemoable = true;
102     private boolean mIsInDemoMode;
103     protected DemoStatusIcons mDemoStatusIcons;
104 
105     protected ArrayList<String> mBlockList = new ArrayList<>();
106 
IconManager( ViewGroup group, StatusBarLocation location, WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, Lazy<MobileUiAdapterKairos> mobileUiAdapterKairos, MobileContextProvider mobileContextProvider, KairosNetwork kairosNetwork, CoroutineScope appScope )107     public IconManager(
108             ViewGroup group,
109             StatusBarLocation location,
110             WifiUiAdapter wifiUiAdapter,
111             MobileUiAdapter mobileUiAdapter,
112             Lazy<MobileUiAdapterKairos> mobileUiAdapterKairos,
113             MobileContextProvider mobileContextProvider,
114             KairosNetwork kairosNetwork,
115             CoroutineScope appScope
116     ) {
117         mGroup = group;
118         mMobileContextProvider = mobileContextProvider;
119         mContext = group.getContext();
120         mLocation = location;
121         mKairosNetwork = kairosNetwork;
122         mAppScope = appScope;
123 
124         reloadDimens();
125 
126         // This starts the flow for the new pipeline, and will notify us of changes via
127         // {@link #setNewMobileIconIds}
128         mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
129         MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
130 
131 
132         mMobileUiAdapterKairos = mobileUiAdapterKairos;
133 
134         mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
135     }
136 
isDemoable()137     public boolean isDemoable() {
138         return mDemoable;
139     }
140 
setController(StatusBarIconController controller)141     void setController(StatusBarIconController controller) {
142         mController = controller;
143     }
144 
145     /** Sets the list of slots that should be blocked from showing in the status bar. */
setBlockList(@ullable List<String> blockList)146     public void setBlockList(@Nullable List<String> blockList) {
147         Assert.isMainThread();
148         mBlockList.clear();
149         mBlockList.addAll(blockList);
150         if (mController != null) {
151             mController.refreshIconGroup(this);
152         }
153     }
154 
155     /** Sets whether this manager's changes should be dumped in a bug report. */
setShouldLog(boolean should)156     public void setShouldLog(boolean should) {
157         mShouldLog = should;
158     }
159 
160     /** Returns true if this manager's changes should be dumped in a bug report. */
shouldLog()161     public boolean shouldLog() {
162         return mShouldLog;
163     }
164 
onIconAdded(int index, String slot, boolean blocked, StatusBarIconHolder holder)165     protected void onIconAdded(int index, String slot, boolean blocked,
166             StatusBarIconHolder holder) {
167         addHolder(index, slot, blocked, holder);
168     }
169 
addHolder(int index, String slot, boolean blocked, StatusBarIconHolder holder)170     protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
171             StatusBarIconHolder holder) {
172         // This is a little hacky, and probably regrettable, but just set `blocked` on any icon
173         // that is in our blocked list, then we'll never see it
174         if (mBlockList.contains(slot)) {
175             blocked = true;
176         }
177         return switch (holder.getType()) {
178             case TYPE_ICON -> addIcon(index, slot, blocked, holder.getIcon());
179             case TYPE_WIFI_NEW -> addNewWifiIcon(index, slot);
180             case TYPE_MOBILE_NEW -> addNewMobileIcon(index, slot, holder.getTag());
181             case TYPE_BINDABLE ->
182                 // Safe cast, since only BindableIconHolders can set this tag on themselves
183                     addBindableIcon((BindableIconHolder) holder, index);
184             default -> null;
185         };
186     }
187 
addIcon(int index, String slot, boolean blocked, StatusBarIcon icon)188     protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
189             StatusBarIcon icon) {
190         StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
191         view.set(icon);
192         mGroup.addView(view, index, onCreateLayoutParams(icon.shape));
193         return view;
194     }
195 
196     /**
197      * ModernStatusBarViews can be created and bound, and thus do not need to update their
198      * drawable by sending multiple calls to setIcon. Instead, by using a bindable
199      * icon view, we can simply create the icon when requested and allow the
200      * ViewBinder to control its visual state.
201      */
addBindableIcon(BindableIconHolder holder, int index)202     protected StatusIconDisplayable addBindableIcon(BindableIconHolder holder,
203             int index) {
204         mBindableIcons.put(holder.getSlot(), holder);
205         ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
206         mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
207         if (mIsInDemoMode) {
208             mDemoStatusIcons.addBindableIcon(holder);
209         }
210         return view;
211     }
212 
addNewWifiIcon(int index, String slot)213     protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
214         ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
215         mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
216 
217         if (mIsInDemoMode) {
218             mDemoStatusIcons.addModernWifiView(mWifiViewModel);
219         }
220 
221         return view;
222     }
223 
224 
addNewMobileIcon( int index, String slot, int subId )225     protected StatusIconDisplayable addNewMobileIcon(
226             int index,
227             String slot,
228             int subId
229     ) {
230         BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
231         mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
232 
233         if (mIsInDemoMode) {
234             Context mobileContext = mMobileContextProvider
235                     .getMobileContextForSub(subId, mContext);
236             mDemoStatusIcons.addModernMobileView(
237                     mobileContext,
238                     mMobileIconsViewModel.getLogger(),
239                     subId);
240         }
241 
242         return view;
243     }
244 
onCreateStatusBarIconView(String slot, boolean blocked)245     private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
246         return new StatusBarIconView(mContext, slot, null, blocked);
247     }
248 
onCreateModernStatusBarWifiView(String slot)249     private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
250         return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
251     }
252 
onCreateModernStatusBarMobileView( String slot, int subId)253     private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
254             String slot, int subId) {
255         Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
256         if (Flags.statusBarMobileIconKairos()) {
257             Pair<ModernStatusBarMobileView, Job> viewAndJob =
258                     ModernStatusBarMobileView.constructAndBind(
259                             mobileContext,
260                             mMobileUiAdapterKairos.get().getMobileIconsViewModel().getLogger(),
261                             slot,
262                             mMobileUiAdapterKairos.get().getMobileIconsViewModel()
263                                     .viewModelForSub(subId, mLocation),
264                             mAppScope,
265                             subId,
266                             mLocation,
267                             mKairosNetwork
268                     );
269             mBindingJobs.put(subId, viewAndJob.getSecond());
270             return viewAndJob.getFirst();
271         } else {
272             return ModernStatusBarMobileView
273                     .constructAndBind(
274                             mobileContext,
275                             mMobileIconsViewModel.getLogger(),
276                             slot,
277                             mMobileIconsViewModel.viewModelForSub(subId, mLocation)
278                     );
279         }
280     }
281 
onCreateLayoutParams(Shape shape)282     protected LinearLayout.LayoutParams onCreateLayoutParams(Shape shape) {
283         int width = ModesUiIcons.isEnabled() && shape == StatusBarIcon.Shape.FIXED_SPACE
284                 ? mIconSize
285                 : ViewGroup.LayoutParams.WRAP_CONTENT;
286 
287         return new LinearLayout.LayoutParams(width, mIconSize);
288     }
289 
destroy()290     protected void destroy() {
291         mGroup.removeAllViews();
292     }
293 
reloadDimens()294     protected void reloadDimens() {
295         mIconSize = mContext.getResources().getDimensionPixelSize(
296                 com.android.internal.R.dimen.status_bar_icon_size_sp);
297     }
298 
onRemoveIcon(int viewIndex)299     protected void onRemoveIcon(int viewIndex) {
300         if (mIsInDemoMode) {
301             mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
302         }
303         if (Flags.statusBarMobileIconKairos()) {
304             View view = mGroup.getChildAt(viewIndex);
305             if (view instanceof ModernStatusBarMobileView) {
306                 Job bindingJob = mBindingJobs.remove(((ModernStatusBarMobileView) view).getSubId());
307                 if (bindingJob != null) {
308                     bindingJob.cancel(new CancellationException());
309                 }
310             }
311         }
312         mGroup.removeViewAt(viewIndex);
313     }
314 
315     /** Called once an icon has been set. */
onSetIcon(int viewIndex, StatusBarIcon icon)316     public void onSetIcon(int viewIndex, StatusBarIcon icon) {
317         StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
318         if (ModesUiIcons.isEnabled()) {
319             ViewGroup.LayoutParams current = view.getLayoutParams();
320             ViewGroup.LayoutParams desired = onCreateLayoutParams(icon.shape);
321             if (desired.width != current.width || desired.height != current.height) {
322                 view.setLayoutParams(desired);
323             }
324         }
325         view.set(icon);
326     }
327 
328     /** Called once an icon holder has been set. */
onSetIconHolder(int viewIndex, StatusBarIconHolder holder)329     public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
330         switch (holder.getType()) {
331             case TYPE_ICON:
332                 onSetIcon(viewIndex, holder.getIcon());
333                 return;
334             case TYPE_MOBILE_NEW:
335             case TYPE_WIFI_NEW:
336             case TYPE_BINDABLE:
337                 // Nothing, the new icons update themselves
338                 return;
339             default:
340                 break;
341         }
342     }
343 
344     @Override
dispatchDemoCommand(String command, Bundle args)345     public void dispatchDemoCommand(String command, Bundle args) {
346         if (!mDemoable) {
347             return;
348         }
349 
350         mDemoStatusIcons.dispatchDemoCommand(command, args);
351     }
352 
353     @Override
onDemoModeStarted()354     public void onDemoModeStarted() {
355         mIsInDemoMode = true;
356         if (mDemoStatusIcons == null) {
357             mDemoStatusIcons = createDemoStatusIcons();
358             mDemoStatusIcons.addModernWifiView(mWifiViewModel);
359             for (BindableIconHolder holder : mBindableIcons.values()) {
360                 mDemoStatusIcons.addBindableIcon(holder);
361             }
362         }
363         mDemoStatusIcons.onDemoModeStarted();
364     }
365 
366     @Override
onDemoModeFinished()367     public void onDemoModeFinished() {
368         if (mDemoStatusIcons != null) {
369             mDemoStatusIcons.onDemoModeFinished();
370             exitDemoMode();
371             mIsInDemoMode = false;
372         }
373     }
374 
exitDemoMode()375     protected void exitDemoMode() {
376         mDemoStatusIcons.remove();
377         mDemoStatusIcons = null;
378     }
379 
createDemoStatusIcons()380     protected DemoStatusIcons createDemoStatusIcons() {
381         return new DemoStatusIcons(
382                 (LinearLayout) mGroup,
383                 mMobileIconsViewModel,
384                 mLocation,
385                 mIconSize,
386                 mMobileUiAdapterKairos,
387                 mKairosNetwork,
388                 mAppScope
389         );
390     }
391 }
392