Quick Setting Area QSPanel and Click Event Process Analysis of Android 6.0 System UI

Keywords: Android Java xml network

The shortcut menu options after the System UI drop-down are also part of the System UI; they are loaded with the PhoneStatusBar load;

Firstly, from the aspect of layout:

The layout of the shortcut settings area is provided by PhoneStatusBar. Java makeStatusBarView() is loaded uniformly.

  1. mStatusBarWindow = (StatusBarWindowView) View.inflate(context,  
  2.                 R.layout.super_status_bar, null);  
Load the frameworks/base/packages/System UI/res/layout/super_status_bar.xml layout

  1. <com.android.systemui.statusbar.phone.PanelHolder  
  2.         android:id="@+id/panel_holder"  
  3.         android:layout_width="match_parent"  
  4.         android:layout_height="match_parent"  
  5.         android:background="@color/transparent" >  
  6.         <include layout="@layout/status_bar_expanded"  
  7.             android:layout_width="match_parent"  
  8.             android:layout_height="match_parent"  
  9.             android:visibility="gone" />  
  10.     </com.android.systemui.statusbar.phone.PanelHolder>  
This layout goes back to include another layout, status_bar_expanded.xml, frameworks/base/packages/SystemUI/res/layout/status_bar_expanded.xml;

  1. <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer  
  2.         android:layout_width="match_parent"  
  3.         android:layout_height="match_parent"  
  4.         android:layout_gravity="@integer/notification_panel_layout_gravity"  
  5.         android:id="@+id/notification_container_parent"  
  6.         android:clipToPadding="false"  
  7.         android:clipChildren="false">  
  8.   
  9.         <com.android.systemui.statusbar.phone.ObservableScrollView  
  10.             android:id="@+id/scroll_view"  
  11.             android:layout_width="@dimen/notification_panel_width"  
  12.             android:layout_height="match_parent"  
  13.             android:layout_gravity="@integer/notification_panel_layout_gravity"  
  14.             android:scrollbars="none"  
  15.             android:overScrollMode="never"  
  16.             android:fillViewport="true">  
  17.             <LinearLayout  
  18.                 android:layout_width="match_parent"  
  19.                 android:layout_height="wrap_content"  
  20.                 android:orientation="vertical">  
  21.                 <include  
  22.                     <span style="color:#ff0000;">layout="@layout/qs_panel"</span>  
  23.                     android:layout_marginTop="@dimen/status_bar_header_height_expanded"  
  24.                     android:layout_width="match_parent"  
  25.                     android:layout_height="wrap_content"  
  26.                     android:layout_marginLeft="@dimen/notification_side_padding"  
  27.                     android:layout_marginRight="@dimen/notification_side_padding"/>  
  28.   
  29.                 <!-- A view to reserve space for the collapsed stack -->  
  30.                 <!-- Layout height: notification_min_height + bottom_stack_peek_amount -->  
  31.                 <View  
  32.                     android:id="@+id/reserve_notification_space"  
  33.                     android:layout_height="@dimen/min_stack_height"  
  34.                     android:layout_width="match_parent"  
  35.                     android:layout_marginTop="@dimen/notifications_top_padding" />  
  36.   
  37.                 <View  
  38.                     android:layout_height="@dimen/notification_side_padding"  
  39.                     android:layout_width="match_parent" />  
  40.             </LinearLayout>  
  41.         </com.android.systemui.statusbar.phone.ObservableScrollView>  
  42.   
  43.         <com.android.systemui.statusbar.stack.NotificationStackScrollLayout  
  44.             android:id="@+id/notification_stack_scroller"  
  45.             android:layout_width="@dimen/notification_panel_width"  
  46.             android:layout_height="match_parent"  
  47.             android:layout_gravity="@integer/notification_panel_layout_gravity"  
  48.             android:layout_marginBottom="@dimen/close_handle_underlap"  
  49.             android:importantForAccessibility="no" />  
  50.   
  51.         <ViewStub  
  52.             android:id="@+id/keyguard_user_switcher"  
  53.             android:layout="@layout/keyguard_user_switcher"  
  54.             android:layout_height="match_parent"  
  55.             android:layout_width="match_parent" />  
  56.   
  57.         <include  
  58.             layout="@layout/keyguard_status_bar"  
  59.             android:visibility="invisible" />  
  60.   
  61.     </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>  
Similarly, in this layout file, we will include a qs_panel.xml layout, frameworks/base/packages/SystemUI/res/layout/qs_panel.xml.

  1. <com.android.systemui.qs.QSContainer  
  2.         xmlns:android="http://schemas.android.com/apk/res/android"  
  3.         android:id="@+id/quick_settings_container"  
  4.         android:layout_width="match_parent"  
  5.         android:layout_height="wrap_content"  
  6.         android:background="@drawable/qs_background_primary"  
  7.         android:paddingTop="8dp"  
  8.         android:paddingBottom="8dp"  
  9.         android:elevation="2dp">  
  10.   
  11.     <com.android.systemui.qs.QSPanel  
  12.             android:id="@+id/<span style="color:#ff0000;">quick_settings_panel</span>"  
  13.             android:background="#0000"  
  14.             android:layout_width="match_parent"  
  15.             android:layout_height="wrap_content" />  
  16. </com.android.systemui.qs.QSContainer>  

Knowing by name, this ID is quick_settings_panel, which is the ID of the zone control on the System UI we are looking for.


Code control:

The same begins with PhoneStatusBar.java's makeStatusBarView() method.

  1. // Set up the quick settings tile panel  
  2. mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);  
  3. if (mQSPanel != null) {  
  4.     final QSTileHost qsh = new QSTileHost(mContext, this,  
  5.             mBluetoothController, mLocationController, mRotationLockController,  
  6.             mNetworkController, mZenModeController, mHotspotController,  
  7.             mCastController, mFlashlightController,  
  8.             mUserSwitcherController, mKeyguardMonitor,  
  9.             mSecurityController);  
  10.     mQSPanel.setHost(qsh);  
  11.     mQSPanel.setTiles(qsh.getTiles());  
  12.     mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);  
  13.     mQSPanel.setBrightnessMirror(mBrightnessMirrorController);  
  14.     mHeader.setQSPanel(mQSPanel);  
  15.     qsh.setCallback(new QSTileHost.Callback() {  
  16.         @Override  
  17.         public void onTilesChanged() {  
  18.             mQSPanel.setTiles(qsh.getTiles());  
  19.         }  
  20.     });  

Step by step analysis, first instantiate a QSPanel object, and then create a QSTileHost object, whose parameters are to set up controllers for various shortcuts, such as Bluetooth, screen rotation, positioning, flash, etc.

Analyzing the construction method of QSTileHost.java:

  1. public QSTileHost(Context context, PhoneStatusBar statusBar,  
  2.             BluetoothController bluetooth, LocationController location,  
  3.             RotationLockController rotation, NetworkController network,  
  4.             ZenModeController zen, HotspotController hotspot,  
  5.             CastController cast, FlashlightController flashlight,  
  6.             UserSwitcherController userSwitcher, UserInfoController userInfo,  
  7.             KeyguardMonitor keyguard, SecurityController security,  
  8.             BatteryController battery, StatusBarIconController iconController,  
  9.             NextAlarmController nextAlarmController) {  
  10.         mContext = context;  
  11.         mStatusBar = statusBar;  
  12.         mBluetooth = bluetooth;  
  13.         mLocation = location;  
  14.         mRotation = rotation;  
  15.         mNetwork = network;  
  16.         mZen = zen;  
  17.         mHotspot = hotspot;  
  18.         mCast = cast;  
  19.         mFlashlight = flashlight;  
  20.         mUserSwitcherController = userSwitcher;  
  21.         mUserInfoController = userInfo;  
  22.         mKeyguard = keyguard;  
  23.         mSecurity = security;  
  24.         mBattery = battery;  
  25.         mIconController = iconController;  
  26.         mNextAlarmController = nextAlarmController;  
  27.         mProfileController = new ManagedProfileController(this);  
  28.   
  29.         final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),  
  30.                 Process.THREAD_PRIORITY_BACKGROUND);  
  31.         ht.start();  
  32.         mLooper = ht.getLooper();  
  33.   
  34.         mServices = new TileServices(this, mLooper);  
  35.   
  36.         TunerService.get(mContext).addTunable(this, TILES_SETTING);  
  37.     }  
1. Instantiate the controllers and create a sub-thread handler to get the Looper object
2. Use TunerService to query the value of key as TILES_SETTING in Settings, that is, query the shortcut menu item, and the query result is returned by onTuningChanged() method.

frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java

  1. private void addTunable(Tunable tunable, String key) {  
  2.         if (!mTunableLookup.containsKey(key)) {  
  3.             mTunableLookup.put(key, new ArraySet<Tunable>());  
  4.         }  
  5.         mTunableLookup.get(key).add(tunable);  
  6.         Uri uri = Settings.Secure.getUriFor(key);  
  7.         if (!mListeningUris.containsKey(uri)) {  
  8.             mListeningUris.put(uri, key);  
  9.             mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);  
  10.         }  
  11.         // Send the first state.  
  12.         String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);  
  13.         tunable.onTuningChanged(key, value);  
  14.     }  

OnTuning Changed () method of frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java

  1. @Override  
  2.     public void onTuningChanged(String key, String newValue) {  
  3.         if (!TILES_SETTING.equals(key)) {  
  4.             return;  
  5.         }  
  6.         if (DEBUG) Log.d(TAG, "Recreating tiles");  
  7.         final List<String> tileSpecs = loadTileSpecs(newValue);  
  8.         if (tileSpecs.equals(mTileSpecs)) return;  
  9.         for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {  
  10.             if (!tileSpecs.contains(tile.getKey())) {  
  11.                 if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());  
  12.                 tile.getValue().destroy();  
  13.             }  
  14.         }  
  15.         final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();  
  16.         for (String tileSpec : tileSpecs) {  
  17.             if (mTiles.containsKey(tileSpec)) {  
  18.                 newTiles.put(tileSpec, mTiles.get(tileSpec));  
  19.             } else {  
  20.                 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);  
  21.                 try {  
  22.                     newTiles.put(tileSpec, createTile(tileSpec));  
  23.                 } catch (Throwable t) {  
  24.                     Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);  
  25.                 }  
  26.             }  
  27.         }  
  28.         mTileSpecs.clear();  
  29.         mTileSpecs.addAll(tileSpecs);  
  30.         mTiles.clear();  
  31.         mTiles.putAll(newTiles);  
  32.         if (mCallback != null) {  
  33.             mCallback.onTilesChanged();  
  34.         }  
  35.     }  

The result of query is judged and processed by calling loadTileSpecs().

  1. protected List<String> loadTileSpecs(Context context, String tileList) {  
  2.         final Resources res = context.getResources();  
  3.         final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);  
  4.         if (tileList == null) {  
  5.             tileList = res.getString(R.string.quick_settings_tiles);  
  6.             if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);  
  7.         } else {  
  8.             if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);  
  9.         }  
  10.         final ArrayList<String> tiles = new ArrayList<String>();  
  11.         boolean addedDefault = false;  
  12.         for (String tile : tileList.split(",")) {  
  13.             tile = tile.trim();  
  14.             if (tile.isEmpty()) continue;  
  15.             if (tile.equals("default")) {  
  16.                 if (!addedDefault) {  
  17.                     tiles.addAll(Arrays.asList(defaultTileList.split(",")));  
  18.                     addedDefault = true;  
  19.                 }  
  20.             } else {  
  21.                 tiles.add(tile);  
  22.             }  
  23.         }  
  24.         return tiles;  
  25.     }  
If the returned result tileList is not null, use "," to split the result, and judge each tileList. If it is not "default", save the tile.
If the returned result tileList is null, the tileList is assigned "default", and the quick_settings_tiles_default string in config.xml is read and saved separately.
Returns the set of quick settings tile s that are displayed as required.

Then continue to call the createTile() method in QSTileHost.java's onTuningChanged() method to create each tile object with quick settings.

  1. public QSTile<?> createTile(String tileSpec) {  
  2.         if (tileSpec.equals("wifi")) return new WifiTile(this);  
  3.         else if (tileSpec.equals("bt")) return new BluetoothTile(this);  
  4.         else if (tileSpec.equals("cell")) return new CellularTile(this);  
  5.         else if (tileSpec.equals("dnd")) return new DndTile(this);  
  6.         else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);  
  7.         else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);  
  8.         else if (tileSpec.equals("work")) return new WorkModeTile(this);  
  9.         else if (tileSpec.equals("rotation")) return new RotationLockTile(this);  
  10.         else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);  
  11.         else if (tileSpec.equals("location")) return new LocationTile(this);  
  12.         else if (tileSpec.equals("cast")) return new CastTile(this);  
  13.         else if (tileSpec.equals("hotspot")) return new HotspotTile(this);  
  14.         else if (tileSpec.equals("user")) return new UserTile(this);  
  15.         else if (tileSpec.equals("battery")) return new BatteryTile(this);  
  16.         else if (tileSpec.equals("saver")) return new DataSaverTile(this);  
  17.         else if (tileSpec.equals("night")) return new NightDisplayTile(this);  
  18.         // Intent tiles.  
  19.         else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);  
  20.         else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);  
  21.         else {  
  22.             Log.w(TAG, "Bad tile spec: " + tileSpec);  
  23.             return null;  
  24.         }  
  25.     }  
Save it in the mTiles collection of member variables, and finally call back onTilesChanged() method to notify PhoneStatusBar.java to display updates to the shortcut options.

  1. if (mCallback != null) {  
  2.             mCallback.onTilesChanged();  
  3.         }  

At this point, the analysis of QSTileHost.java's construction method is completed, and then it goes back to PhoneStatusBar.java's makeStatusBarView() method at the call point to continue the analysis:

  1. mQSPanel.setHost(qsh);  
  2. mQSPanel.setTiles(qsh.getTiles());  
Set QSPanel.setHost() and QSPanel.setTiles(), where the setTiles() method removes all TileRecord records and removes all tileView s;

  1. public void setTiles(Collection<QSTile<?>> tiles) {  
  2.         for (TileRecord record : mRecords) {  
  3.             removeView(record.tileView);  
  4.         }  
  5.         mRecords.clear();  
  6.         for (QSTile<?> tile : tiles) {  
  7.             addTile(tile);  
  8.         }  
  9.         if (isShowingDetail()) {  
  10.             mDetail.bringToFront();  
  11.         }  
  12.     }  

Then it calls addTile() to create TileRecord object and assigns the corresponding callback and click event (click, double-click, long-click) interface to bind, then saves it to the ArrayList < TileRecord > mRecords collection, and then goes to addView();

  1. private void addTile(final QSTile<?> tile) {  
  2.         final TileRecord r = new TileRecord();  
  3.         r.tile = tile;  
  4.         r.tileView = tile.createTileView(mContext);  
  5.         r.tileView.setVisibility(View.GONE);  
  6.         final QSTile.Callback callback = new QSTile.Callback() {  
  7.             @Override  
  8.             public void onStateChanged(QSTile.State state) {  
  9.                 int visibility = state.visible ? VISIBLE : GONE;  
  10.                 if (state.visible && !mGridContentVisible) {  
  11.   
  12.                     // We don't want to show it if the content is hidden,  
  13.                     // then we just set it to invisible, to ensure that it gets visible again  
  14.                     visibility = INVISIBLE;  
  15.                 }  
  16.                 setTileVisibility(r.tileView, visibility);  
  17.                 r.tileView.onStateChanged(state);  
  18.             }  
  19.             @Override  
  20.             public void onShowDetail(boolean show) {  
  21.                 QSPanel.this.showDetail(show, r);  
  22.             }  
  23.             @Override  
  24.             public void onToggleStateChanged(boolean state) {  
  25.                 if (mDetailRecord == r) {  
  26.                     fireToggleStateChanged(state);  
  27.                 }  
  28.             }  
  29.             @Override  
  30.             public void onScanStateChanged(boolean state) {  
  31.                 r.scanState = state;  
  32.                 if (mDetailRecord == r) {  
  33.                     fireScanStateChanged(r.scanState);  
  34.                 }  
  35.             }  
  36.   
  37.             @Override  
  38.             public void onAnnouncementRequested(CharSequence announcement) {  
  39.                 announceForAccessibility(announcement);  
  40.             }  
  41.         };  
  42.         r.tile.setCallback(callback);  
  43.         final View.OnClickListener click = new View.OnClickListener() {  
  44.             @Override  
  45.             public void onClick(View v) {  
  46.                 r.tile.click();  
  47.             }  
  48.         };  
  49.         final View.OnClickListener clickSecondary = new View.OnClickListener() {  
  50.             @Override  
  51.             public void onClick(View v) {  
  52.                 r.tile.secondaryClick();  
  53.             }  
  54.         };  
  55.         final View.OnLongClickListener longClick = new View.OnLongClickListener() {  
  56.             @Override  
  57.             public boolean onLongClick(View v) {  
  58.                 r.tile.longClick();  
  59.                 return true;  
  60.             }  
  61.         };  
  62.         r.tileView.init(click, clickSecondary, longClick);  
  63.         r.tile.setListening(mListening);  
  64.         callback.onStateChanged(r.tile.getState());  
  65.         r.tile.refreshState();  
  66.         mRecords.add(r);  
  67.   
  68.         addView(r.tileView);  
  69.     }  
Go back to PhoneStatusBar.java's makeStatusBarView() method at the call point and continue to analyze:
  1. qsh.setCallback(new QSTileHost.Callback() {  
  2.                 @Override  
  3.                 public void onTilesChanged() {  
  4.                     mQSPanel.setTiles(qsh.getTiles());  
  5.                 }  
  6.             });  

Set onTilesChanged() callback listener for QSTileHost objects;

So far, the general process analysis of loading and displaying the fast area has been completed.



Next, analyze the logical process of clicking on a shortcut icon to execute. Take Bluetooth Tile as an example, that is, the process analysis after clicking on Bluetooth shortcut icon is as follows:

1. Since callback monitoring such as click events has been set for each BluetoothTile when loading the icon to display the shortcut area, refer to the addTile() method of QSPanel.java for the specific code logic. After clicking, View.OnClickListener click in the addTile() method of QSPanel.java is processed first and r.tile.click() is called.

  1. final View.OnClickListener click = new View.OnClickListener() {  
  2.             @Override  
  3.             public void onClick(View v) {  
  4.                 r.tile.click();  
  5.             }  
  6.         };  
That is to call the click() method of QSTile.java, in which a message from msg to H.CLICK is sent to handler.
  1. public void click() {  
  2.         mHandler.sendEmptyMessage(H.CLICK);  
  3.     }  

In this handler, the abstract method handleClick() is called to handle the click event.

  1. if (msg.what == CLICK) {  
  2.                     name = "handleClick";  
  3.                     mAnnounceNextStateChange = true;  
  4.                     handleClick();  
  5.                 }  
Because handleClick() is an abstract method, it is implemented in a subclass of QSTile.java. Because the Bluetooth settings are clicked, the implementation logic can be found in BluetoothTile.java.

In this method, Bluetooth Controller is called to control the Bluetooth state.

  1. protected void handleClick() {  
  2.         final boolean isEnabled = (Boolean)mState.value;  
  3.         mController.setBluetoothEnabled(!isEnabled);  
  4.     }  
This mController is obtained from QSTileHost.java. Because Bluetooth Controller. Java is an interface, its implementation is in Bluetooth Controller Impl. java. This method will implement Bluetooth's open/close state setting by calling Bluetooth standard interface.

  1. @Override  
  2.     public void setBluetoothEnabled(boolean enabled) {  
  3.         if (mLocalBluetoothManager != null) {  
  4.             mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);  
  5.         }  
  6.     }  
At this point, when the Bluetooth status setting is successful, the onBluetoothStateChanged() method in Bluetooth Controller Impl. Java is called back, which sends a message from msg to H.MSG_STATE_CHANGED to handler.

  1. @Override  
  2.     public void onBluetoothStateChanged(int bluetoothState) {  
  3.         mEnabled = bluetoothState == BluetoothAdapter.STATE_ON  
  4.                 || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;  
  5.         mState = bluetoothState;  
  6.         mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);  
  7.     }  
Next, the fireStateChange() method is called in this handler.

  1. case MSG_STATE_CHANGED:  
  2.     fireStateChange();  
  3.     break;  

The fireStateChange() method calls back the onBluetoothStateChange() method.

  1. private void fireStateChange() {  
  2.     for (BluetoothController.Callback cb : mCallbacks) {  
  3.         fireStateChange(cb);  
  4.     }  
  5. }  
  6.   
  7. private void fireStateChange(BluetoothController.Callback cb) {  
  8.     cb.onBluetoothStateChange(mEnabled);  
  9. }  
This callback onBluetoothStateChange() method is implemented in BluetoothTile.java;

  1. private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {  
  2.         @Override  
  3.         public void onBluetoothStateChange(boolean enabled) {  
  4.             refreshState();  
  5.         }  
  6.   
  7.         @Override  
  8.         public void onBluetoothDevicesChanged() {  
  9.             mUiHandler.post(new Runnable() {  
  10.                 @Override  
  11.                 public void run() {  
  12.                     mDetailAdapter.updateItems();  
  13.                 }  
  14.             });  
  15.             refreshState();  
  16.         }  
  17.     };  
This method calls refreshState (method, which is implemented in the refreshState() method of the parent class QSTile.java; refreshState() method notifies handler by sending msg to H.REFRESH_STATE;

  1. public final void refreshState() {  
  2.         refreshState(null);  
  3.     }  
  4.   
  5.     protected final void refreshState(Object arg) {  
  6.         mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();  
  7.     }  

This handler calls the handleRefreshState() method again

  1. if (msg.what == REFRESH_STATE) {  
  2.                     name = "handleRefreshState";  
  3.                     handleRefreshState(msg.obj);  
  4.                 }   

This method then calls the handleUpdateState() method.

  1. protected void handleRefreshState(Object arg) {  
  2.         handleUpdateState(mTmpState, arg);  
  3.         final boolean changed = mTmpState.copyTo(mState);  
  4.         if (changed) {  
  5.             handleStateChanged();  
  6.         }  
  7.     }  
Because handleUpdateState() is an abstract method, the final implementation is its subclass, that is to say, it calls back to BluetoothTile.java, in which icon setting and description information content description are implemented.

  1. @Override  
  2.     protected void handleUpdateState(BooleanState state, Object arg) {  
  3.         final boolean enabled = mController.isBluetoothEnabled();  
  4.         final boolean connected = mController.isBluetoothConnected();  
  5.         final boolean connecting = mController.isBluetoothConnecting();  
  6.         state.value = enabled;  
  7.         state.autoMirrorDrawable = false;  
  8.         state.minimalContentDescription =  
  9.                 mContext.getString(R.string.accessibility_quick_settings_bluetooth);  
  10.         if (enabled) {  
  11.             state.label = null;  
  12.             if (connected) {  
  13.                 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connected);  
  14.                 state.label = mController.getLastDeviceName();  
  15.                 state.contentDescription = mContext.getString(  
  16.                         R.string.accessibility_bluetooth_name, state.label);  
  17.                 state.minimalContentDescription = state.minimalContentDescription + ","  
  18.                         + state.contentDescription;  
  19.             } else if (connecting) {  
  20.                 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_connecting);  
  21.                 state.contentDescription = mContext.getString(  
  22.                         R.string.accessibility_quick_settings_bluetooth_connecting);  
  23.                 state.label = mContext.getString(R.string.quick_settings_bluetooth_label);  
  24.                 state.minimalContentDescription = state.minimalContentDescription + ","  
  25.                         + state.contentDescription;  
  26.             } else {  
  27.                 state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);  
  28.                 state.contentDescription = mContext.getString(  
  29.                         R.string.accessibility_quick_settings_bluetooth_on) + ","  
  30.                         + mContext.getString(R.string.accessibility_not_connected);  
  31.                 state.minimalContentDescription = state.minimalContentDescription + ","  
  32.                         + mContext.getString(R.string.accessibility_not_connected);  
  33.             }  
  34.             if (TextUtils.isEmpty(state.label)) {  
  35.                 state.label = mContext.getString(R.string.quick_settings_bluetooth_label);  
  36.             }  
  37.         } else {  
  38.             state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_off);  
  39.             state.label = mContext.getString(R.string.quick_settings_bluetooth_label);  
  40.             state.contentDescription = mContext.getString(  
  41.                     R.string.accessibility_quick_settings_bluetooth_off);  
  42.         }  
  43.   
  44.         CharSequence bluetoothName = state.label;  
  45.         if (connected) {  
  46.             bluetoothName = state.dualLabelContentDescription = mContext.getString(  
  47.                     R.string.accessibility_bluetooth_name, state.label);  
  48.         }  
  49.         state.dualLabelContentDescription = bluetoothName;  
  50.         state.contentDescription = state.contentDescription + "," + mContext.getString(  
  51.                 R.string.accessibility_quick_settings_open_settings, getTileLabel());  
  52.         state.expandedAccessibilityClassName = Button.class.getName();  
  53.         state.minimalAccessibilityClassName = Switch.class.getName();  
  54.     }  
Returning to the handleUpdateState() method of QSTile.java, we continue to call the handleStateChanged() method, which is mainly used to call the callback method onStateChanged() method.

  1. private void handleStateChanged() {  
  2.        boolean delayAnnouncement = shouldAnnouncementBeDelayed();  
  3.        if (mCallbacks.size() != 0) {  
  4.            for (int i = 0; i < mCallbacks.size(); i++) {  
  5.                mCallbacks.get(i).onStateChanged(mState);  
  6.            }  
  7.            if (mAnnounceNextStateChange && !delayAnnouncement) {  
  8.                String announcement = composeChangeAnnouncement();  
  9.                if (announcement != null) {  
  10.                    mCallbacks.get(0).onAnnouncementRequested(announcement);  
  11.                }  
  12.            }  
  13.        }  
  14.        mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;  
  15.    }  
The final implementation of onStateChanged() is in the addTile() method under QSPanel.java

  1. final QSTile.Callback callback = new QSTile.Callback() {  
  2.             @Override  
  3.             public void onStateChanged(QSTile.State state) {  
  4.                 drawTile(r, state);  
  5.             }  
Call the drawTile() method downward, then call the onStateChanged() method of QSTileView.java in the drawTile() method, and send the message of msg as H.STATE_CHANGED to handler.
  1. public void onStateChanged(QSTile.State state) {  
  2.         mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget();  
  3.     }  

In this handler, the method handleStateChanged() is called again.

  1. if (msg.what == STATE_CHANGED) {  
  2.                 handleStateChanged((State) msg.obj);  
  3.             }  

Finally, the interface display is implemented in handleStateChanged(), and the whole process is completed.

  1. protected void handleStateChanged(QSTile.State state) {  
  2.         if (mIcon instanceof ImageView) {  
  3.             setIcon((ImageView) mIcon, state);  
  4.         }  
  5.         if (mDual) {  
  6.             mDualLabel.setText(state.label);  
  7.             mDualLabel.setContentDescription(state.dualLabelContentDescription);  
  8.             mTopBackgroundView.setContentDescription(state.contentDescription);  
  9.         } else {  
  10.             mLabel.setText(state.label);  
  11.             setContentDescription(state.contentDescription);  
  12.         }  
  13.     }  


Conclusion:
1. Page View starts with makeStatusBarView() of PhoneStatusBar.java.
2. The menu item will first query Settings.Secure.TILES_SETTING in the system. If it exists, it will split into tiles. Otherwise, it will read the quick_settings_tiles_default value in config.xml, and then split into tiles to generate tiles.
3. Every subclass of QSTile, such as BluetoothTile.java, will ultimately update and control the status of icon view by itself, and finally be displayed on the interface by QSTileView.java.

Posted by dan231 on Mon, 01 Apr 2019 09:54:31 -0700