SystemUI 下拉状态栏快捷开关是 QSPanel,qs_panel.xml,@+id/quick_settings_panel,本篇文章就来看看这些快捷开关是如何呈现的以及如何新增一个快捷开关?基于 AOSP 9.0 分析。
SystemUI 下拉状态栏快捷开关
QSPanel 创建是从 StatusBarmakeStatusBarView 开始的。
StatusBarmakeStatusBarView
protected void makeStatusBarView() {
//省略其他代码
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
(visible) -> {
mBrightnessMirrorVisible = visible;
updateScrimController();
});
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
((QSFragment) qs).setHost(qsh);
mQSPanel = ((QSFragment) qs).getQsPanel();
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mKeyguardStatusBar.setQSPanel(mQSPanel);
}
});
//省略其他代码
}
先看 SystemUIFactorycreateQSTileHost。
SystemUIFactorycreateQSTileHost
public QSTileHost createQSTileHost(Context context, StatusBar statusBar,
StatusBarIconController iconController) {
return new QSTileHost(context, statusBar, iconController);
}
这里进行 QSTileHost 初始化。
QSTileHost构造函数
public QSTileHost(Context context, StatusBar statusBar,
StatusBarIconController iconController) {
//省略其他代码
Dependency.get(TunerService.class).addTunable(this, TILES_SETTING);
//省略其他代码
}
这里进行了 TunerService 注册,在 TunerServiceImpladdTunable 重写。
TunerServiceImpladdTunable
@Override
public void addTunable(Tunable tunable, String... keys) {
for (String key : keys) {
addTunable(tunable, key);
}
}
private void addTunable(Tunable tunable, String key) {
if (!mTunableLookup.containsKey(key)) {
mTunableLookup.put(key, new ArraySet());
}
mTunableLookup.get(key).add(tunable);
if (LeakDetector.ENABLED) {
mTunables.add(tunable);
Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables");
}
Uri uri = Settings.Secure.getUriFor(key);
if (!mListeningUris.containsKey(uri)) {
mListeningUris.put(uri, key);
mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
}
// Send the first state.
String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
tunable.onTuningChanged(key, value);
}
tunable.onTuningChanged 回调 QSTileHost#onTuningChanged。
QSTileHostonTuningChanged
@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);
}
//调用 QSTileHostloadTileSpecs,获得 config 里字符串信息
final List 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 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();
}
}
看下 QSTileHostloadTileSpecs,是获得 config 里字符串信息。
QSTileHostloadTileSpecs
protected List 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 tiles = new ArrayList();
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 里:
wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
这里就是我们所看到的快捷开关的文本描述。
再看 QSTileHostonTuningChanged 中的调用 QSTileHostcreateTile 方法。
QSTileHostcreateTile
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;
}
调用 QSFactorycreateTile,由 QSFactoryImplcreateTile 实现了。
QSFactoryImplcreateTile
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。
以上涉及资源文件加载及对应实例化,接下来看看代码如何加载的,看 QSPanelonAttachedToWindow 方法。
QSPanelonAttachedToWindow
@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 tiles) {
setTiles(tiles, false);
}
public void setTiles(Collection 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);由 PagedTileLayoutaddTile 实现。
PagedTileLayoutaddTile
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 下拉状态栏快捷开关模块代码流程分析完毕。
新增一个快捷开关
0、国际惯例,先上效果图,新增一个Camera,随便用了蓝牙的图标:
1、首先在 AOSP/frameworks/base/packages/SystemUI/res/values/config.xml 里面添加截屏 Camera 的选项
wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,camera
2、在 AOSP/frameworks/base/packages/SystemUI/res/values/strings.xml 里面还要加一个字符串
Camera
3、在 AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/ 目录下创建 CameraTile.java,实现 QSTileImpl:
package com.android.systemui.qs.tiles;
import android.content.Intent;
import android.provider.MediaStore;
import android.widget.Toast;
//手动添加
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
//手动添加
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
public class CameraTile extends QSTileImpl {
public CameraTile(QSHost host) {
super(host);
}
@Override
public BooleanState newTileState() {
return new BooleanState();
}
@Override
protected void handleClick() {
Toast.makeText(mContext,"Camera Click",Toast.LENGTH_LONG).show();
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.label = mContext.getString(R.string.quick_settings_camera_label);
//定义图标,随便用了蓝牙的图标
state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
}
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_CAMERA;
}
@Override
public Intent getLongClickIntent() {
return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
}
@Override
protected void handleSetListening(boolean listening) {
}
@Override
public CharSequence getTileLabel() {
return mContext.getString(R.string.quick_settings_camera_label);
}
}
4、在 AOSP/frameworks/base/proto/src/metrics_constants.proto,增加常量:
QS_CAMERA = 1568;
5、在AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java,增加:
private QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
switch (tileSpec) {
case "wifi":
return new WifiTile(mHost);
// 省略部分代码
case "nfc":
return new NfcTile(mHost);
case "camera":
return new CameraTile(mHost);
}
// 省略部分代码
}
6、整编代码,运行模拟器,有效果,棒棒哒。