1 /* 2 * Copyright (C) 2021 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.car.statusicon; 18 19 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; 20 import static android.widget.ListPopupWindow.WRAP_CONTENT; 21 22 import android.annotation.ColorInt; 23 import android.annotation.DimenRes; 24 import android.annotation.LayoutRes; 25 import android.app.PendingIntent; 26 import android.car.app.CarActivityManager; 27 import android.car.drivingstate.CarUxRestrictions; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.view.Gravity; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.view.ViewTreeObserver; 37 import android.view.WindowManager; 38 import android.widget.ImageView; 39 import android.widget.PopupWindow; 40 import android.widget.Toast; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 import androidx.annotation.VisibleForTesting; 45 46 import com.android.car.qc.QCItem; 47 import com.android.car.qc.view.QCView; 48 import com.android.car.ui.FocusParkingView; 49 import com.android.car.ui.utils.CarUxRestrictionsUtil; 50 import com.android.systemui.R; 51 import com.android.systemui.broadcast.BroadcastDispatcher; 52 import com.android.systemui.car.CarServiceProvider; 53 import com.android.systemui.car.qc.QCFooterButton; 54 import com.android.systemui.car.qc.QCFooterButtonView; 55 import com.android.systemui.car.qc.QCHeaderReadOnlyIconsContainer; 56 import com.android.systemui.car.qc.SystemUIQCView; 57 import com.android.systemui.car.qc.SystemUIQCViewController; 58 import com.android.systemui.car.statusicon.ui.QCPanelReadOnlyIconsController; 59 import com.android.systemui.car.users.CarSystemUIUserUtil; 60 import com.android.systemui.settings.UserTracker; 61 import com.android.systemui.statusbar.policy.ConfigurationController; 62 63 import java.util.ArrayList; 64 65 import javax.inject.Provider; 66 67 /** 68 * A controller for a panel view associated with a status icon. 69 */ 70 public class StatusIconPanelController { 71 private static final int DEFAULT_POPUP_WINDOW_ANCHOR_GRAVITY = Gravity.TOP | Gravity.START; 72 73 private final Context mContext; 74 private final UserTracker mUserTracker; 75 private final CarServiceProvider mCarServiceProvider; 76 private final BroadcastDispatcher mBroadcastDispatcher; 77 private final ConfigurationController mConfigurationController; 78 private final Provider<SystemUIQCViewController> mQCViewControllerProvider; 79 @Nullable 80 private final QCPanelReadOnlyIconsController mQCPanelReadOnlyIconsController; 81 private final String mIdentifier; 82 private final String mIconTag; 83 private final @ColorInt int mIconHighlightedColor; 84 private final @ColorInt int mIconNotHighlightedColor; 85 private final int mYOffsetPixel; 86 private final boolean mIsDisabledWhileDriving; 87 private final ArrayList<SystemUIQCViewController> mQCViewControllers = new ArrayList<>(); 88 89 private PopupWindow mPanel; 90 private @LayoutRes int mPanelLayoutRes; 91 private @DimenRes int mPanelWidthRes; 92 private ViewGroup mPanelContent; 93 private OnQcViewsFoundListener mOnQcViewsFoundListener; 94 private View mAnchorView; 95 private ImageView mStatusIconView; 96 private CarUxRestrictionsUtil mCarUxRestrictionsUtil; 97 private CarActivityManager mCarActivityManager; 98 private float mDimValue = -1.0f; 99 private View.OnClickListener mOnClickListener; 100 private boolean mIsPanelDestroyed; 101 102 private final ConfigurationController.ConfigurationListener mConfigurationListener = 103 new ConfigurationController.ConfigurationListener() { 104 @Override 105 public void onLayoutDirectionChanged(boolean isLayoutRtl) { 106 recreatePanel(); 107 } 108 }; 109 110 private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener 111 mUxRestrictionsChangedListener = 112 new CarUxRestrictionsUtil.OnUxRestrictionsChangedListener() { 113 @Override 114 public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) { 115 if (mIsDisabledWhileDriving 116 && carUxRestrictions.isRequiresDistractionOptimization() 117 && isPanelShowing()) { 118 mPanel.dismiss(); 119 } 120 } 121 }; 122 123 private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceOnConnectedListener = 124 car -> { 125 mCarActivityManager = car.getCarManager(CarActivityManager.class); 126 }; 127 128 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 129 @Override 130 public void onReceive(Context context, Intent intent) { 131 String action = intent.getAction(); 132 boolean isIntentFromSelf = 133 intent.getIdentifier() != null && intent.getIdentifier().equals(mIdentifier); 134 135 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && !isIntentFromSelf 136 && isPanelShowing()) { 137 mPanel.dismiss(); 138 } 139 } 140 }; 141 142 private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() { 143 @Override 144 public void onUserChanged(int newUser, Context userContext) { 145 mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); 146 mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, 147 new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), /* executor= */ null, 148 mUserTracker.getUserHandle()); 149 } 150 }; 151 152 private final ViewTreeObserver.OnGlobalFocusChangeListener mFocusChangeListener = 153 (oldFocus, newFocus) -> { 154 if (isPanelShowing() && oldFocus != null && newFocus instanceof FocusParkingView) { 155 // When nudging out of the panel, RotaryService will focus on the 156 // FocusParkingView to clear the focus highlight. When this occurs, dismiss the 157 // panel. 158 mPanel.dismiss(); 159 } 160 }; 161 162 private final QCView.QCActionListener mQCActionListener = (item, action) -> { 163 if (!isPanelShowing()) { 164 return; 165 } 166 if (action instanceof PendingIntent) { 167 if (((PendingIntent) action).isActivity()) { 168 mPanel.dismiss(); 169 } 170 } else if (action instanceof QCItem.ActionHandler) { 171 if (((QCItem.ActionHandler) action).isActivity()) { 172 mPanel.dismiss(); 173 } 174 } 175 }; 176 StatusIconPanelController( Context context, UserTracker userTracker, CarServiceProvider carServiceProvider, BroadcastDispatcher broadcastDispatcher, ConfigurationController configurationController, Provider<SystemUIQCViewController> qcViewControllerProvider)177 public StatusIconPanelController( 178 Context context, 179 UserTracker userTracker, 180 CarServiceProvider carServiceProvider, 181 BroadcastDispatcher broadcastDispatcher, 182 ConfigurationController configurationController, 183 Provider<SystemUIQCViewController> qcViewControllerProvider) { 184 this(context, userTracker, carServiceProvider, broadcastDispatcher, configurationController, 185 qcViewControllerProvider, /* isDisabledWhileDriving= */ false); 186 } 187 StatusIconPanelController( Context context, UserTracker userTracker, CarServiceProvider carServiceProvider, BroadcastDispatcher broadcastDispatcher, ConfigurationController configurationController, Provider<SystemUIQCViewController> qcViewControllerProvider, boolean isDisabledWhileDriving)188 public StatusIconPanelController( 189 Context context, 190 UserTracker userTracker, 191 CarServiceProvider carServiceProvider, 192 BroadcastDispatcher broadcastDispatcher, 193 ConfigurationController configurationController, 194 Provider<SystemUIQCViewController> qcViewControllerProvider, 195 boolean isDisabledWhileDriving) { 196 this(context, userTracker, carServiceProvider, broadcastDispatcher, configurationController, 197 qcViewControllerProvider, isDisabledWhileDriving, 198 /* qcPanelReadOnlyIconsController= */ null); 199 } 200 StatusIconPanelController( Context context, UserTracker userTracker, CarServiceProvider carServiceProvider, BroadcastDispatcher broadcastDispatcher, ConfigurationController configurationController, Provider<SystemUIQCViewController> qcViewControllerProvider, boolean isDisabledWhileDriving, QCPanelReadOnlyIconsController qcPanelReadOnlyIconsController)201 public StatusIconPanelController( 202 Context context, 203 UserTracker userTracker, 204 CarServiceProvider carServiceProvider, 205 BroadcastDispatcher broadcastDispatcher, 206 ConfigurationController configurationController, 207 Provider<SystemUIQCViewController> qcViewControllerProvider, 208 boolean isDisabledWhileDriving, 209 QCPanelReadOnlyIconsController qcPanelReadOnlyIconsController) { 210 mContext = context; 211 mUserTracker = userTracker; 212 mCarServiceProvider = carServiceProvider; 213 mBroadcastDispatcher = broadcastDispatcher; 214 mConfigurationController = configurationController; 215 mQCViewControllerProvider = qcViewControllerProvider; 216 mQCPanelReadOnlyIconsController = qcPanelReadOnlyIconsController; 217 mIdentifier = Integer.toString(System.identityHashCode(this)); 218 219 mIconTag = mContext.getResources().getString(R.string.qc_icon_tag); 220 mIconHighlightedColor = mContext.getColor(R.color.status_icon_highlighted_color); 221 mIconNotHighlightedColor = mContext.getColor(R.color.status_icon_not_highlighted_color); 222 223 int panelMarginTop = mContext.getResources().getDimensionPixelSize( 224 R.dimen.car_status_icon_panel_margin_top); 225 int topSystemBarHeight = mContext.getResources().getDimensionPixelSize( 226 R.dimen.car_top_system_bar_height); 227 // TODO(b/202563671): remove mYOffsetPixel when the PopupWindow API is updated. 228 mYOffsetPixel = panelMarginTop - topSystemBarHeight; 229 230 mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, 231 new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), /* executor= */ null, 232 mUserTracker.getUserHandle()); 233 mUserTracker.addCallback(mUserTrackerCallback, mContext.getMainExecutor()); 234 mConfigurationController.addCallback(mConfigurationListener); 235 236 mIsDisabledWhileDriving = isDisabledWhileDriving; 237 if (mIsDisabledWhileDriving) { 238 mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(mContext); 239 mCarUxRestrictionsUtil.register(mUxRestrictionsChangedListener); 240 } 241 } 242 243 /** 244 * @return default Y offset in pixels that cancels out the superfluous inset automatically 245 * applied to the panel 246 */ getDefaultYOffset()247 public int getDefaultYOffset() { 248 return mYOffsetPixel; 249 } 250 setOnQcViewsFoundListener(OnQcViewsFoundListener onQcViewsFoundListener)251 public void setOnQcViewsFoundListener(OnQcViewsFoundListener onQcViewsFoundListener) { 252 mOnQcViewsFoundListener = onQcViewsFoundListener; 253 } 254 255 /** 256 * A listener that can be used to attach controllers quick control panels using 257 * {@link SystemUIQCView#getLocalQCProvider()} 258 */ 259 public interface OnQcViewsFoundListener { 260 /** 261 * This method is call up when {@link SystemUIQCView}s are found 262 */ qcViewsFound(ArrayList<SystemUIQCView> qcViews)263 void qcViewsFound(ArrayList<SystemUIQCView> qcViews); 264 } 265 266 /** 267 * Attaches a panel to a root view that toggles the panel visibility when clicked. 268 * 269 * Variant of {@link #attachPanel(View, int, int, int, int, int, boolean)} with 270 * xOffset={@code 0}, yOffset={@link #mYOffsetPixel} & 271 * gravity={@link #DEFAULT_POPUP_WINDOW_ANCHOR_GRAVITY} & 272 * showAsDropDown={@code true}. 273 */ attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes)274 public void attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes) { 275 attachPanel(view, layoutRes, widthRes, DEFAULT_POPUP_WINDOW_ANCHOR_GRAVITY); 276 } 277 278 /** 279 * Attaches a panel to a root view that toggles the panel visibility when clicked. 280 * 281 * Variant of {@link #attachPanel(View, int, int, int, int, int, boolean)} with 282 * xOffset={@code 0} & yOffset={@link #mYOffsetPixel} & 283 * showAsDropDown={@code true}. 284 */ attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, int gravity)285 public void attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, 286 int gravity) { 287 attachPanel(view, layoutRes, widthRes, /* xOffset= */ 0, mYOffsetPixel, 288 gravity); 289 } 290 291 /** 292 * Attaches a panel to a root view that toggles the panel visibility when clicked. 293 * 294 * Variant of {@link #attachPanel(View, int, int, int, int, int, boolean)} with 295 * gravity={@link #DEFAULT_POPUP_WINDOW_ANCHOR_GRAVITY} & 296 * showAsDropDown={@code true}. 297 */ attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, int xOffset, int yOffset)298 public void attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, 299 int xOffset, int yOffset) { 300 attachPanel(view, layoutRes, widthRes, xOffset, yOffset, 301 DEFAULT_POPUP_WINDOW_ANCHOR_GRAVITY); 302 } 303 304 /** 305 * Attaches a panel to a root view that toggles the panel visibility when clicked. 306 * 307 * Variant of {@link #attachPanel(View, int, int, int, int, int, boolean)} with 308 * showAsDropDown={@code true}. 309 */ attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, int xOffset, int yOffset, int gravity)310 public void attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, 311 int xOffset, int yOffset, int gravity) { 312 attachPanel(view, layoutRes, widthRes, xOffset, yOffset, gravity, 313 /* showAsDropDown= */ true); 314 } 315 316 /** 317 * Attaches a panel to a root view that toggles the panel visibility when clicked. 318 */ attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, int xOffset, int yOffset, int gravity, boolean showAsDropDown)319 public void attachPanel(View view, @LayoutRes int layoutRes, @DimenRes int widthRes, 320 int xOffset, int yOffset, int gravity, boolean showAsDropDown) { 321 if (mIsPanelDestroyed) { 322 throw new IllegalStateException("Attempting to attach destroyed panel"); 323 } 324 325 mCarServiceProvider.addListener(mCarServiceOnConnectedListener); 326 327 if (mAnchorView == null) { 328 mAnchorView = view; 329 } 330 mPanelLayoutRes = layoutRes; 331 mPanelWidthRes = widthRes; 332 // Pre-create panel to improve perceived UI performance 333 createPanel(); 334 335 mOnClickListener = v -> { 336 if (mIsDisabledWhileDriving && mCarUxRestrictionsUtil.getCurrentRestrictions() 337 .isRequiresDistractionOptimization()) { 338 dismissAllSystemDialogs(); 339 Toast.makeText(mContext, R.string.car_ui_restricted_while_driving, 340 Toast.LENGTH_LONG).show(); 341 return; 342 } 343 344 if (mPanel == null && !createPanel()) { 345 return; 346 } 347 348 if (mPanel.isShowing()) { 349 mPanel.dismiss(); 350 return; 351 } 352 353 // Dismiss all currently open system dialogs before opening this panel. 354 dismissAllSystemDialogs(); 355 356 mQCViewControllers.forEach(controller -> controller.listen(true)); 357 358 registerFocusListener(true); 359 360 if (CarSystemUIUserUtil.isMUMDSystemUI() 361 && mPanelLayoutRes == R.layout.qc_profile_switcher) { 362 // TODO(b/269490856): consider removal of UserPicker carve-outs 363 if (mCarActivityManager != null) { 364 mCarActivityManager.startUserPickerOnDisplay(mContext.getDisplayId()); 365 } 366 } else { 367 if (showAsDropDown) { 368 // TODO(b/202563671): remove yOffsetPixel when the PopupWindow API is updated. 369 mPanel.showAsDropDown(mAnchorView, xOffset, yOffset, gravity); 370 } else { 371 int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 372 int animationStyle = verticalGravity == Gravity.BOTTOM 373 ? com.android.internal.R.style.Animation_DropDownUp 374 : com.android.internal.R.style.Animation_DropDownDown; 375 mPanel.setAnimationStyle(animationStyle); 376 mPanel.showAtLocation(mAnchorView, gravity, xOffset, yOffset); 377 } 378 mAnchorView.setSelected(true); 379 highlightStatusIcon(true); 380 setAnimatedStatusIconHighlightedStatus(true); 381 dimBehind(mPanel); 382 } 383 }; 384 385 mAnchorView.setOnClickListener(mOnClickListener); 386 } 387 388 /** 389 * Cleanup listeners and reset panel. This controller instance should not be used after this 390 * method is called. 391 */ destroyPanel()392 public void destroyPanel() { 393 reset(); 394 if (mCarUxRestrictionsUtil != null) { 395 mCarUxRestrictionsUtil.unregister(mUxRestrictionsChangedListener); 396 } 397 mCarServiceProvider.removeListener(mCarServiceOnConnectedListener); 398 mConfigurationController.removeCallback(mConfigurationListener); 399 mUserTracker.removeCallback(mUserTrackerCallback); 400 mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); 401 mPanelLayoutRes = 0; 402 mIsPanelDestroyed = true; 403 } 404 405 @VisibleForTesting getPanel()406 protected PopupWindow getPanel() { 407 return mPanel; 408 } 409 410 @VisibleForTesting getBroadcastReceiver()411 protected BroadcastReceiver getBroadcastReceiver() { 412 return mBroadcastReceiver; 413 } 414 415 @VisibleForTesting getIdentifier()416 protected String getIdentifier() { 417 return mIdentifier; 418 } 419 420 @VisibleForTesting 421 @ColorInt getIconHighlightedColor()422 protected int getIconHighlightedColor() { 423 return mIconHighlightedColor; 424 } 425 426 @VisibleForTesting 427 @ColorInt getIconNotHighlightedColor()428 protected int getIconNotHighlightedColor() { 429 return mIconNotHighlightedColor; 430 } 431 432 @VisibleForTesting getOnClickListener()433 protected View.OnClickListener getOnClickListener() { 434 return mOnClickListener; 435 } 436 437 @VisibleForTesting getConfigurationListener()438 protected ConfigurationController.ConfigurationListener getConfigurationListener() { 439 return mConfigurationListener; 440 } 441 442 @VisibleForTesting getUserTrackerCallback()443 protected UserTracker.Callback getUserTrackerCallback() { 444 return mUserTrackerCallback; 445 } 446 447 @VisibleForTesting getFocusChangeListener()448 protected ViewTreeObserver.OnGlobalFocusChangeListener getFocusChangeListener() { 449 return mFocusChangeListener; 450 } 451 452 @VisibleForTesting getQCActionListener()453 protected QCView.QCActionListener getQCActionListener() { 454 return mQCActionListener; 455 } 456 457 /** 458 * Create the PopupWindow panel and assign to {@link mPanel}. 459 * @return true if the panel was created, false otherwise 460 */ createPanel()461 boolean createPanel() { 462 if (mPanelWidthRes == 0 || mPanelLayoutRes == 0) { 463 return false; 464 } 465 466 int panelWidth = mContext.getResources().getDimensionPixelSize(mPanelWidthRes); 467 468 mPanelContent = (ViewGroup) LayoutInflater.from(mContext).inflate(mPanelLayoutRes, 469 /* root= */ null); 470 mPanelContent.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); 471 findQcHeaderViews(mPanelContent); 472 findQcViews(mPanelContent); 473 findQcFooterViews(mPanelContent); 474 mPanel = new PopupWindow(mPanelContent, panelWidth, WRAP_CONTENT); 475 mPanel.setBackgroundDrawable( 476 mContext.getResources().getDrawable(R.drawable.status_icon_panel_bg, 477 mContext.getTheme())); 478 mPanel.setWindowLayoutType(TYPE_SYSTEM_DIALOG); 479 mPanel.setFocusable(true); 480 mPanel.setOutsideTouchable(false); 481 mPanel.setOnDismissListener(() -> { 482 setAnimatedStatusIconHighlightedStatus(false); 483 mAnchorView.setSelected(false); 484 highlightStatusIcon(false); 485 registerFocusListener(false); 486 mQCViewControllers.forEach(controller -> controller.listen(false)); 487 }); 488 489 return true; 490 } 491 dimBehind(PopupWindow popupWindow)492 private void dimBehind(PopupWindow popupWindow) { 493 View container = popupWindow.getContentView().getRootView(); 494 WindowManager wm = mContext.getSystemService(WindowManager.class); 495 496 if (wm == null) return; 497 498 if (mDimValue < 0) { 499 mDimValue = mContext.getResources().getFloat(R.dimen.car_status_icon_panel_dim); 500 } 501 502 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) container.getLayoutParams(); 503 lp.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; 504 lp.dimAmount = mDimValue; 505 wm.updateViewLayout(container, lp); 506 } 507 dismissAllSystemDialogs()508 private void dismissAllSystemDialogs() { 509 Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 510 intent.setIdentifier(mIdentifier); 511 mContext.sendBroadcastAsUser(intent, mUserTracker.getUserHandle()); 512 } 513 registerFocusListener(boolean register)514 private void registerFocusListener(boolean register) { 515 if (mPanelContent == null) { 516 return; 517 } 518 if (register) { 519 mPanelContent.getViewTreeObserver().addOnGlobalFocusChangeListener( 520 mFocusChangeListener); 521 } else { 522 mPanelContent.getViewTreeObserver().removeOnGlobalFocusChangeListener( 523 mFocusChangeListener); 524 } 525 } 526 reset()527 private void reset() { 528 if (mPanel == null) return; 529 530 mPanel.dismiss(); 531 mPanel = null; 532 mPanelContent = null; 533 mQCViewControllers.forEach(SystemUIQCViewController::destroy); 534 mQCViewControllers.clear(); 535 } 536 recreatePanel()537 private void recreatePanel() { 538 reset(); 539 createPanel(); 540 } 541 findQcHeaderViews(ViewGroup rootView)542 private void findQcHeaderViews(ViewGroup rootView) { 543 for (int i = 0; i < rootView.getChildCount(); i++) { 544 View v = rootView.getChildAt(i); 545 if (v instanceof QCHeaderReadOnlyIconsContainer) { 546 if (mQCPanelReadOnlyIconsController != null) { 547 mQCPanelReadOnlyIconsController.addIconViews( 548 (QCHeaderReadOnlyIconsContainer) v, /* shouldAttachPanel= */ false); 549 } 550 } else if (v instanceof ViewGroup) { 551 this.findQcHeaderViews((ViewGroup) v); 552 } 553 } 554 } 555 findQcViews(ViewGroup rootView)556 private void findQcViews(ViewGroup rootView) { 557 for (int i = 0; i < rootView.getChildCount(); i++) { 558 View v = rootView.getChildAt(i); 559 if (v instanceof SystemUIQCView) { 560 SystemUIQCView qcv = (SystemUIQCView) v; 561 SystemUIQCViewController controller = mQCViewControllerProvider.get(); 562 controller.attachView(qcv); 563 mQCViewControllers.add(controller); 564 qcv.setActionListener(mQCActionListener); 565 } else if (v instanceof ViewGroup) { 566 this.findQcViews((ViewGroup) v); 567 } 568 } 569 } 570 findQcFooterViews(ViewGroup rootView)571 private void findQcFooterViews(ViewGroup rootView) { 572 for (int i = 0; i < rootView.getChildCount(); i++) { 573 View v = rootView.getChildAt(i); 574 if (v instanceof QCFooterButton) { 575 ((QCFooterButton) v).setUserTracker(mUserTracker); 576 } else if (v instanceof QCFooterButtonView) { 577 ((QCFooterButtonView) v).setUserTracker(mUserTracker); 578 ((QCFooterButtonView) v).setBroadcastDispatcher(mBroadcastDispatcher); 579 } else if (v instanceof ViewGroup) { 580 this.findQcFooterViews((ViewGroup) v); 581 } 582 } 583 } 584 setAnimatedStatusIconHighlightedStatus(boolean isHighlighted)585 private void setAnimatedStatusIconHighlightedStatus(boolean isHighlighted) { 586 if (mAnchorView instanceof AnimatedStatusIcon) { 587 ((AnimatedStatusIcon) mAnchorView).setIconHighlighted(isHighlighted); 588 } 589 } 590 highlightStatusIcon(boolean isHighlighted)591 private void highlightStatusIcon(boolean isHighlighted) { 592 if (mStatusIconView == null) { 593 mStatusIconView = mAnchorView.findViewWithTag(mIconTag); 594 } 595 596 if (mStatusIconView != null) { 597 mStatusIconView.setColorFilter( 598 isHighlighted ? mIconHighlightedColor : mIconNotHighlightedColor); 599 } 600 } 601 isPanelShowing()602 private boolean isPanelShowing() { 603 return mPanel != null && mPanel.isShowing(); 604 } 605 } 606