• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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