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