1、QS创建
QSPanel 创建是从 CentralSurfacesImpl#makeStatusBarView 开始的,Qs面板创建这块,与之前版本对比,没啥变化。
com.android.systemui.statusbar.phone.CentralSurfacesImpl.java
protected void makeStatusBarView() {
......
// 设置快速设置面板
// R.id.qs_frame 是一个 FrameLayout 布局,将 QSFragment 布局添加到其中。所以 R.id.qs_frame 最终显示的是 QSFragment 。
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
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(),
mBrightnessSliderFactory,
(visible) -> {
mBrightnessMirrorVisible = visible;
updateScrimController();
});
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
mQSPanelController = ((QSFragment) qs).getQSPanelController();
((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
}
});
}
......
}
接下来就先看看 QSFragment 的 onCreateView() 方法:
com.android.systemui.qs.QSFragment.java
......
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
try {
Trace.beginSection("QSFragment#onCreateView");
inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
R.style.Theme_SystemUI_QuickSettings));
// 在这里返回了布局,R.layout.qs_panel
return inflater.inflate(R.layout.qs_panel, container, false);
} finally {
Trace.endSection();
}
}
......
再看 QSFragment 的构造函数:
com.android.systemui.qs.QSFragment.java
......
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
QSTileHost qsTileHost,
StatusBarStateController statusBarStateController, CommandQueue commandQueue,
@Named(QS_PANEL) MediaHost qsMediaHost,
@Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
KeyguardBypassController keyguardBypassController,
QSFragmentComponent.Factory qsComponentFactory,
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
FalsingManager falsingManager, DumpManager dumpManager) {
......
mHost = qsTileHost;
......
}
......
这里注意 @Inject 注解,这个是 Android dagger里的一种注解。
在这里,与Android 9.0及其以下版本实例化 QSTileHost类的方式不一样,这里是通dagger来实例化的。所以这里实例化了QSTileHost 。
下面我们就进入到 QSTileHost 的构造方法:
com.android.systemui.qs.QSTileHost.java
......
@Inject
public QSTileHost(Context context,
StatusBarIconController iconController,
QSFactory defaultFactory,
@Main Handler mainHandler,
@Background Looper bgLooper,
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
Optional<CentralSurfaces> centralSurfacesOptional,
QSLogger qsLogger,
UiEventLogger uiEventLogger,
UserTracker userTracker,
SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory
) {
......
mainHandler.post(() -> {
// This is technically a hack to avoid circular dependency of
// QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
// 在创建任何图块之前完成。
tunerService.addTunable(this, TILES_SETTING);
// AutoTileManager 可以修改 mTiles,因此请确保 mTiles 已经初始化。
mAutoTiles = autoTiles.get();
mTileServiceRequestController.init();
});
}
......
在 QSTileHost 的构造函数里,我们主要看 tunerService.addTunable(this, TILES_SETTING); 很明显,调用 tunerService 里的 addTunabe() 方法,跟进去会发现,最终的是调用的 TunerServiceImpl 里面的 addTunabe() 方法。
com.android.systemui.tuner.TunerServiceImpl.java
......
public void addTunable(Tunable tunable, String... keys) {
for (String key : keys) {
addTunable(tunable, key);
}
}
private void addTunable(Tunable tunable, String key) {
......
// 从数据库读取数据;刷机第一次数据库为空,这里也会空,后面程序会从配置文件读取;
String value = DejankUtils.whitelistIpcs(() -> Settings.Secure
.getStringForUser(mContentResolver, key, mCurrentUser));
tunable.onTuningChanged(key, value);
}
......
tunable.onTuningChanged() 回调 QSTileHost#onTuningChanged():
com.android.systemui.qs.QSTileHost.java
@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);
// put 到 Map 中
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();
// put 到 Map 中
mTiles.putAll(newTiles);
for (int i = 0; i < mCallbacks.size(); i++) {
//注册,当开发状态改变时回调
mCallbacks.get(i).onTilesChanged();
}
}
这里有两个重要的方法:一个是获取 config 里字符串信息 loadTileSpecs(mContext, newValue);一个实例化 Tile 的 createTile(tileSpec)。
先看第一个 QSTileHost#loadTileSpecs() 这里和Android 10 有点出入。
com.android.systemui.qs.QSTileHost.java
protected static List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources();
// tileList 为空,则获取一个 “default” 字符串
if (TextUtils.isEmpty(tileList)) {
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;
Set<String> addedSpecs = new ArraySet<>();
for (String tile : tileList.split(",")) {
tile = tile.trim();
if (tile.isEmpty()) continue;
// 第一次 tileList 为空,取了默认值,
if (tile.equals("default")) {
if (!addedDefault) {
// 从 config 文件获取
List<String> defaultSpecs = getDefaultSpecs(context);
for (String spec : defaultSpecs) {
if (!addedSpecs.contains(spec)) {
tiles.add(spec);
addedSpecs.add(spec);
}
}
addedDefault = true;
}
} else {
if (!addedSpecs.contains(tile)) {
tiles.add(tile);
addedSpecs.add(tile);
}
}
}
// 省略其他代码......
return tiles;
}
上述代码中第一次 tileList 为空,调用了 getDefaultSpecs(context) 获取字符串,该方法比较简单,这里就不做分析了。
接着看第二个 QSTileHost#createTile(tileSpec) 方法:
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;
}
}
// M: @ {
if (mQuickSettingsExt != null && mQuickSettingsExt.doOperatorSupportTile(tileSpec)) {
// WifiCalling
return (QSTile) mQuickSettingsExt.createTile(this, tileSpec);
}
// @ }
return null;
}
这里调用 QSFactory#createTile(),而 QSFactory 接口又由 QSFactoryImpl 实现。所以这里直接看 QSFactoryImpl #createTile():
com.android.systemui.qs.tileimpl.QSFactoryImpl.java
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 mWifiTileProvider.get();
case "internet":
return mInternetTileProvider.get();
case "bt":
return mBluetoothTileProvider.get();
case "cell":
return mCellularTileProvider.get();
case "dnd":
return mDndTileProvider.get();
case "inversion":
return mColorInversionTileProvider.get();
case "airplane":
return mAirplaneModeTileProvider.get();
case "work":
return mWorkModeTileProvider.get();
case "rotation":
return mRotationLockTileProvider.get();
case "flashlight":
return mFlashlightTileProvider.get();
case "location":
return mLocationTileProvider.get();
case "cast":
return mCastTileProvider.get();
case "hotspot":
return mHotspotTileProvider.get();
case "battery":
return mBatterySaverTileProvider.get();
case "saver":
return mDataSaverTileProvider.get();
case "night":
return mNightDisplayTileProvider.get();
case "nfc":
return mNfcTileProvider.get();
case "dark":
return mUiModeNightTileProvider.get();
case "screenrecord":
return mScreenRecordTileProvider.get();
// 省略其他代码......
}
// 省略其他代码......
return null;
}
看到这里通过对应的字符串分别实例化了对应的 Tile。
2、QS显示
以上涉及资源文件加载及对应实例化,接下来看看如何显示出来的。和Android 11 对比出入有点大,加了一个控制器。
这里要回到 QSFragment#onViewCreated() 方法:
com.android.systemui.qs.QSFragment.java
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
mQSPanelController = qsFragmentComponent.getQSPanelController();
mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
// 一些初始化,init() 是 抽象类 ViewController 的 public 方法。
mQSPanelController.init();
mQuickQSPanelController.init();
mQSFooterActionController.init();
// 扩展的 qs 滚动视图
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
// 省略其他代码......
}
经过上述分析,我们来看看 ViewController#init():
public void init() {
if (mInited) {
return;
}
onInit(); // 要在 onViewAttached() 方法之前运行,
mInited = true;
if (isAttachedToWindow()) {
// 调用内部 onViewAttachedToWindow() 方法,去添加视图。
mOnAttachStateListener.onViewAttachedToWindow(mView);
}
addOnAttachStateChangeListener(mOnAttachStateListener);
}
private OnAttachStateChangeListener mOnAttachStateListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
// 调用自己的抽象方法 onViewAttached() ,在子类具体实现,添加 Tiles.
ViewController.this.onViewAttached();
}
@Override
public void onViewDetachedFromWindow(View v) {
ViewController.this.onViewDetached();
}
};
这个添加在 QSPanelController 的父类 QSPanelControllerBase 中的 onViewAttached() 方法中。
QSPanelControllerBase#onViewAttached()
com.android.systemui.qs.QSPanelControllerBase.java
@Override
protected void onViewAttached() {
mQsTileRevealController = createTileRevealController();
if (mQsTileRevealController != null) {
mQsTileRevealController.setExpansion(mRevealExpansion);
}
mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener);
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.addCallback(mQSHostCallback);
// 这里设置 Tiles
setTiles();
mLastOrientation = getResources().getConfiguration().orientation;
switchTileLayout(true);
mDumpManager.registerDumpable(mView.getDumpableTag(), this);
}
/** */
public void setTiles() {
// 这里 getTiles() 就是获取,前面我们说的 Tiles 实例,在 QSTileHost 中。
setTiles(mHost.getTiles(), false);
}
/** */
public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
// TODO(b/168904199): move this logic into QSPanelController.
if (!collapsedView && mQsTileRevealController != null) {
mQsTileRevealController.updateRevealedTiles(tiles);
}
for (QSPanelControllerBase.TileRecord record : mRecords) {
mView.removeTile(record);
record.tile.removeCallback(record.callback);
}
mRecords.clear();
mCachedSpecs = "";
for (QSTile tile : tiles) {
addTile(tile, collapsedView);
}
}
private void addTile(final QSTile tile, boolean collapsedView) {
// 这里会创建对应的视图。
final TileRecord r =
new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView));
// 注意:这个 mView 是 QSPanel,在 QSPanelController 的构造方法通过super传到 QSPanelControllerBase 的。这里也是视图的添加。
mView.addTile(r);
mRecords.add(r);
mCachedSpecs = getTilesSpecs();
}
这里只需关注QSPanel#addTile():
com.android.systemui.qs.QSPanel.java
void addTile(QSPanelControllerBase.TileRecord tileRecord) {
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
drawTile(tileRecord, state);
}
};
tileRecord.tile.addCallback(callback);
tileRecord.callback = callback;
tileRecord.tileView.init(tileRecord.tile);
tileRecord.tile.refreshState();
if (mTileLayout != null) {
mTileLayout.addTile(tileRecord);
}
}
由 TileLayout#addTile() 实现:
com.android.systemui.qs.TileLayout.java
public void addTile(TileRecord tile) {
mRecords.add(tile);
tile.tile.setListening(this, mListening);
addTileView(tile);
}
protected void addTileView(TileRecord tile) {
// 注:TileLayout 继承的是 ViewGroup。
addView(tile.tileView);
}
至此,SystemUI 下拉状态栏快捷开关模块代码流程分析完毕。
QS一个有3种呈现方式,如图:
我这分析的是第 2 种。其他的展示方法也类似。
res/layout/qs_panel.xml
<com.android.systemui.qs.QSContainerImpl xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:clipChildren="false">
<!-- 第二种布局 -->
<com.android.systemui.qs.NonInterceptingScrollView
android:id="@+id/expanded_qs_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/qs_panel_elevation"
android:importantForAccessibility="no"
android:scrollbars="none"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_weight="1">
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:focusable="true"
android:accessibilityTraversalBefore="@android:id/edit"
android:clipToPadding="false"
android:clipChildren="false">
<include layout="@layout/qs_footer_impl" />
</com.android.systemui.qs.QSPanel>
</com.android.systemui.qs.NonInterceptingScrollView>
<!-- 第一种布局 -->
<include layout="@layout/quick_status_bar_expanded_header" />
<include
layout="@layout/footer_actions"
android:id="@+id/qs_footer_actions"
android:layout_height="@dimen/footer_actions_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
/>
<!-- 第三种布局 -->
<include
android:id="@+id/qs_customize"
layout="@layout/qs_customize_panel"
android:visibility="gone" />
</com.android.systemui.qs.QSContainerImpl>
标签:case,QS,return,状态栏,get,Android13,......,qs,tile
From: https://blog.csdn.net/u010345983/article/details/130883228