学习笔记:
QSPanel 创建是从 StatusBar#makeStatusBarView 开始的。
protected void makeStatusBarView() { //省略其他代码 FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, mExtensionController .newExtension(QS.class) .withPlugin(QS.class) .withDefault(this::createDefaultQSFragment) .build()); mBrightnessMirrorController = new BrightnessMirrorController( mNotificationShadeWindowView, mNotificationPanelViewController, mNotificationShadeDepthControllerLazy.get(), (visible) -> { mBrightnessMirrorVisible = visible; updateScrimController(); }); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; if (qs instanceof QSFragment) { mQSPanel = ((QSFragment) qs).getQsPanel(); mQSPanel.setBrightnessMirror(mBrightnessMirrorController); mFooter = ((QSFragment) qs).getFooter(); } }); //省略其他代码 }
先看 QSFragment 的构造函数:
@Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, InjectionInflationController injectionInflater, QSTileHost qsTileHost, StatusBarStateController statusBarStateController, CommandQueue commandQueue, QSContainerImplController.Builder qsContainerImplControllerBuilder) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mInjectionInflater = injectionInflater; mQSContainerImplControllerBuilder = qsContainerImplControllerBuilder; commandQueue.observe(getLifecycle(), this); mHost = qsTileHost; mStatusBarStateController = statusBarStateController; }
这里注意 @Inject 注解,这个是 Android dagger里的一种解决。
在这里,与Android 9.0及其以下版本实例化 QSTileHost类的方式不一样,这里是通dagger来实例化的。
QSTileHost的构造函数
mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation // finishes before creating any tiles. tunerService.addTunable(this, TILES_SETTING); // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. mAutoTiles = autoTiles.get(); });
在QSTileHost的构造函数里,我们主要看tunerService.addTunable(this, TILES_SETTING);很明显,调用tunerService里的 addTunabe() 方法,跟进去会发现,最终的是调用的 TunerServiceImpl 里面的addTunabe()
TunerServiceImpl #addTunable
我们只关注下面两句话:
// 读取config.xml里的字符串(例如:nfc,wifi) String value = DejankUtils.whitelistIpcs(() -> Settings.Secure .getStringForUser(mContentResolver, key, mCurrentUser)); tunable.onTuningChanged(key, value);
tunable.onTuningChanged 回调 QSTileHost#onTuningChanged。
QSTileHost#onTuningChanged
@Override public void onTuningChanged(String key, String newValue) { if (!TILES_SETTING.equals(key)) { return; } if (DEBUG) Log.d(TAG, "Recreating tiles"); if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } //调用 QSTileHost#loadTileSpecs,获得 config 里字符串信息 final List<String> tileSpecs = loadTileSpecs(mContext, newValue); int currentUser = ActivityManager.getCurrentUser(); if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; //进行了过滤 mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( tile -> { if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey()); tile.getValue().destroy(); }); final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>(); for (String tileSpec : tileSpecs) { QSTile tile = mTiles.get(tileSpec); if (tile != null && (!(tile instanceof CustomTile) || ((CustomTile) tile).getUser() == currentUser)) { if (tile.isAvailable()) { if (DEBUG) Log.d(TAG, "Adding " + tile); tile.removeCallbacks(); if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) { tile.userSwitch(currentUser); } newTiles.put(tileSpec, tile); } else { tile.destroy(); } } else { if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec); try { //这里通过 字符串 一个个实例化 Tile tile = createTile(tileSpec); if (tile != null) { if (tile.isAvailable()) { tile.setTileSpec(tileSpec); newTiles.put(tileSpec, tile); } else { tile.destroy(); } } } catch (Throwable t) { Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); } } } mCurrentUser = currentUser; mTileSpecs.clear(); mTileSpecs.addAll(tileSpecs); mTiles.clear(); mTiles.putAll(newTiles); for (int i = 0; i < mCallbacks.size(); i++) { //注册,当开发状态改变时回调 mCallbacks.get(i).onTilesChanged(); } }
看下 QSTileHost#loadTileSpecs,是获得 config 里字符串信息。
QSTileHost#loadTileSpecs
protected List<String> loadTileSpecs(Context context, String tileList) { final Resources res = context.getResources(); final String defaultTileList = res.getString(R.string.quick_settings_tiles_default); if (tileList == null) { tileList = res.getString(R.string.quick_settings_tiles); if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList); } else { if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList); } final ArrayList<String> tiles = new ArrayList<String>(); boolean addedDefault = false; for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; if (tile.equals("default")) { if (!addedDefault) { tiles.addAll(Arrays.asList(defaultTileList.split(","))); addedDefault = true; } } else { tiles.add(tile); } } return tiles; }
其中 quick_settings_tiles_default 值在 AOSP/frameworks/base/packages/SystemUI/res/values/config.xml 里:
<string name="quick_settings_tiles_default" translatable="false"> wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast </string>
这里就是我们所看到的快捷开关的文本描述。
再看 QSTileHost#onTuningChanged 中的调用 QSTileHost#createTile 方法。
QSTileHost#createTile
public QSTile createTile(String tileSpec) { for (int i = 0; i < mQsFactories.size(); i++) { QSTile t = mQsFactories.get(i).createTile(tileSpec); if (t != null) { return t; } } return null; }
调用 QSFactory#createTile,由 QSFactoryImpl#createTile 实现了。
QSFactoryImpl#createTile
public QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { tile.handleStale(); // Tile was just created, must be stale. } return tile; } private QSTileImpl createTileInternal(String tileSpec) { // Stock tiles. switch (tileSpec) { case "wifi": return new WifiTile(mHost); case "bt": return new BluetoothTile(mHost); case "cell": return new CellularTile(mHost); case "dnd": return new DndTile(mHost); case "inversion": return new ColorInversionTile(mHost); case "airplane": return new AirplaneModeTile(mHost); case "work": return new WorkModeTile(mHost); case "rotation": return new RotationLockTile(mHost); case "flashlight": return new FlashlightTile(mHost); case "location": return new LocationTile(mHost); case "cast": return new CastTile(mHost); case "hotspot": return new HotspotTile(mHost); case "user": return new UserTile(mHost); case "battery": return new BatterySaverTile(mHost); case "saver": return new DataSaverTile(mHost); case "night": return new NightDisplayTile(mHost); case "nfc": return new NfcTile(mHost); } // Intent tiles. if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec); if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec); // Debug tiles. if (Build.IS_DEBUGGABLE) { if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) { return new GarbageMonitor.MemoryTile(mHost); } } // Broken tiles. Log.w(TAG, "Bad tile spec: " + tileSpec); return null; }
看到这里通过对应的字符串分别实例化 Tile。
以上涉及资源文件加载及对应实例化,接下来看看代码如何加载的,看 QSPanel#onAttachedToWindow 方法。
QSPanel#onAttachedToWindow
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); final TunerService tunerService = Dependency.get(TunerService.class); tunerService.addTunable(this, QS_SHOW_BRIGHTNESS); if (mHost != null) { setTiles(mHost.getTiles()); } if (mBrightnessMirrorController != null) { mBrightnessMirrorController.addCallback(this); } } public void setTiles(Collection<QSTile> tiles) { setTiles(tiles, false); } public void setTiles(Collection<QSTile> tiles, boolean collapsedView) { if (!collapsedView) { mQsTileRevealController.updateRevealedTiles(tiles); } for (TileRecord record : mRecords) { mTileLayout.removeTile(record); record.tile.removeCallback(record.callback); } mRecords.clear(); for (QSTile tile : tiles) { addTile(tile, collapsedView); } } protected TileRecord addTile(final QSTile tile, boolean collapsedView) { final TileRecord r = new TileRecord(); r.tile = tile; r.tileView = createTileView(tile, collapsedView); //省略其他代码 r.tileView.init(r.tile); r.tile.refreshState(); mRecords.add(r); if (mTileLayout != null) { mTileLayout.addTile(r); } return r; }
mTileLayout.addTile(r);由 PagedTileLayout#addTile 实现。
PagedTileLayout#addTile
PagedTileLayout 是 ViewPager,重点看 setAdapter,看数据源如何 add 的。
@Override public void addTile(TileRecord tile) { mTiles.add(tile); postDistributeTiles(); } private void postDistributeTiles() { removeCallbacks(mDistribute); post(mDistribute); } private final Runnable mDistribute = new Runnable() { @Override public void run() { distributeTiles(); } }; private void distributeTiles() { if (DEBUG) Log.d(TAG, "Distributing tiles"); final int NP = mPages.size(); for (int i = 0; i < NP; i++) { mPages.get(i).removeAllViews(); } int index = 0; final int NT = mTiles.size(); for (int i = 0; i < NT; i++) { TileRecord tile = mTiles.get(i); if (mPages.get(index).isFull()) { if (++index == mPages.size()) { if (DEBUG) Log.d(TAG, "Adding page for " + tile.tile.getClass().getSimpleName()); mPages.add((TilePage) LayoutInflater.from(getContext()) .inflate(R.layout.qs_paged_page, this, false)); } } if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to " + index); mPages.get(index).addTile(tile); } if (mNumPages != index + 1) { mNumPages = index + 1; while (mPages.size() > mNumPages) { mPages.remove(mPages.size() - 1); } if (DEBUG) Log.d(TAG, "Size: " + mNumPages); mPageIndicator.setNumPages(mNumPages); setAdapter(mAdapter); mAdapter.notifyDataSetChanged(); setCurrentItem(0, false); } }
至此,SystemUI 下拉状态栏快捷开关模块代码流程分析完毕。
新增一个快捷开关
例如:wifi
1、找到对应的config.xml,添加对应的tile。
2、在 AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/ 目录下创建 WifiTile.java,实现 QSTileImpl。
在快捷设置添加新项时,需要重写getMetricsCategory方法。
@Override public int getMetricsCategory() { return MetricsEvent.QS_WIFI; }
这个LED_BRIGHTNESS_LEVEL的定义在frameworks\base\proto\src\metrics_constants.proto ,需要往后翻到预留的位置添加新的ID QS_WIFI= 126;这个ID需要避免重复。
其他与这个MetricsEvent相关的文件在:
frameworks\base\core\java\com\android\internal\logging\MetricsLogger.java
frameworks\base\packages\SettingsLib\src\com\android\settingslib\drawer\Tile.java
frameworks\base\packages\SettingsLib\src\com\android\settingslib\drawer\TileUtils.java
3、在 QSFactoryImpl.java 中的 createTileInternal() 方法中,增加对应的case: