需求描述:客户需要在状态栏下拉列表里添加更改屏幕密度density开关按钮
Android版本:android8.1
这是一个长故事:
public class StatusBar extends SystemUI //Statusbar是继承自SystemUI的
StatusBar是个很大很大的类,里面加载了太多的对象了,在StatusBar开始的时候执行了start()方法
@Override
public void start() {
....
createAndAddWindows(); //创建并添加窗口
....
}
看看createAndAddWindows里
public void createAndAddWindows() {
addStatusBarWindow();
}
-------------------------
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputController = new RemoteInputController(mHeadsUpManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
makeStatusBarView里有上千行代码,其中有一段
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
SystemUIFactory.getInstance().createQSTileHost
SystemUIFactory去创建QuickStatubar
public QSTileHost createQSTileHost(Context context, StatusBar statusBar,
StatusBarIconController iconController) {
return new QSTileHost(context, statusBar, iconController);
}
接着就进入了QSTileHost的构造方法里
public QSTileHost(Context context, StatusBar statusBar,
StatusBarIconController iconController) {
mIconController = iconController;
mContext = context;
mStatusBar = statusBar;
mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER));
mQsFactories.add(new QSFactoryImpl(this)); //创建新的实体
Dependency.get(PluginManager.class).addPluginListener(this, QSFactory.class, true);
Dependency.get(TunerService.class).addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
mAutoTiles = new AutoTileManager(context, this);
}
创建新的QSFactoryImpl实体 new QSFactoryImpl(this)
接着在QSTileHost里的回调**onTuningChanged(String key, String newValue)**里调用了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;
}
这里就去调用了QSFactoryImpl实体的createTile
public QSTile createTile(String tileSpec) {
if (tileSpec.equals("wifi")) return new WifiTile(mHost);
else if (tileSpec.equals("bt")) return new BluetoothTile(mHost);
else if (tileSpec.equals("cell")) return new CellularTile(mHost);
else if (tileSpec.equals("dnd")) return new DndTile(mHost);
else if (tileSpec.equals("inversion")) return new ColorInversionTile(mHost);
else if (tileSpec.equals("airplane")) return new AirplaneModeTile(mHost);
else if (tileSpec.equals("work")) return new WorkModeTile(mHost);
else if (tileSpec.equals("rotation")) return new RotationLockTile(mHost);
else if (tileSpec.equals("flashlight")) return new FlashlightTile(mHost);
else if (tileSpec.equals("density")) return new DensityTile(mHost);
else if (tileSpec.equals("location")) return new LocationTile(mHost);
else if (tileSpec.equals("cast")) return new CastTile(mHost);
else if (tileSpec.equals("hotspot")) return new HotspotTile(mHost);
else if (tileSpec.equals("user")) return new UserTile(mHost);
else if (tileSpec.equals("battery")) return new BatterySaverTile(mHost);
else if (tileSpec.equals("saver")) return new DataSaverTile(mHost);
else if (tileSpec.equals("night")) return new NightDisplayTile(mHost);
else if (tileSpec.equals("nfc")) return new NfcTile(mHost);
else if (tileSpec.equals("dataconnection") && !SIMHelper.isWifiOnlyDevice())
return new MobileDataTile(mHost);
else if (tileSpec.equals("simdataconnection") && !SIMHelper.isWifiOnlyDevice() &&
quickSettingsPlugin.customizeAddQSTile(new SimDataConnectionTile(mHost)) != null) {
return (SimDataConnectionTile) quickSettingsPlugin.customizeAddQSTile(
new SimDataConnectionTile(mHost));
} else if (tileSpec.equals("dulsimsettings") && !SIMHelper.isWifiOnlyDevice() &&
quickSettingsPlugin.customizeAddQSTile(new DualSimSettingsTile(mHost)) != null) {
return (DualSimSettingsTile) quickSettingsPlugin.customizeAddQSTile(
new DualSimSettingsTile(mHost));
} else if (tileSpec.equals("apnsettings") && !SIMHelper.isWifiOnlyDevice() &&
quickSettingsPlugin.customizeAddQSTile(new ApnSettingsTile(mHost)) != null) {
return (ApnSettingsTile) quickSettingsPlugin.customizeAddQSTile(
new ApnSettingsTile(mHost));
}
}
这里都是根据tileSpec创建Tile,比如:flashlight就对应的创建 new FlashlightTile(mHost)
而这里的tileSpecs 来自于前面的回调onTuningChanged(String key, String newValue)
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
protected List<String> loadTileSpecs(Context context, String tileList) {
final Resources res = context.getResources();
String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
Log.d(TAG, "loadTileSpecs() default tile list: " + defaultTileList);
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;
}
发现 原来这个tilespace的字符来源是R.string.quick_settings_tiles_default
全局所属xml,发现是这样的
<string name="quick_settings_tiles_default" translatable="false">
wifi,bt,dnd,flashlight,density,rotation,battery,cell,airplane,cast
string>
这里每一个item对应着下拉列表的一个item项
所以,我们就可以依葫芦画瓢的,在这里添加一个。
然后再createTile里,加一行我们自己的
剩下的内容就是抄袭系统是如何添加item的,但还是得了解那个QSTile对象是什么
##下面以AirplaneModeTile为例来分析怎么添加自己的QSTile
进入AirplaneModeTile,发现
public class AirplaneModeTile extends QSTileImpl<BooleanState>
它继承了QSTileImpl 以及泛型 BooleanState
实际上,createTile里,所有的tile,都是QSTileImpl的子类,分别实现了不同的功能
在AirplaneModeTile 里,我们会发现很多的重写方法,看不到调用轨迹
第一个成员变量,
private final Icon mIcon =
ResourceIcon.get(R.drawable.ic_signal_airplane);
里面的xml是svg矢量图
预览效果
这不就是下拉列表里的飞行模式开关吗。
接着分析成员方法
重写方法
@Override
public void handleClick() {
setEnabled(!mState.value);
}
处理图标点击事件
private void setEnabled(boolean enabled) {
final ConnectivityManager mgr =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mgr.setAirplaneMode(enabled);
}
调用系统的connectivityManager去设置飞行模式开关~~
呵呵,写的这么简单
@Override
public CharSequence getTileLabel() {
return mContext.getString(R.string.airplane_mode);
}
返回tile名字,直接从string里找
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue();
final boolean airplaneMode = value != 0;
state.value = airplaneMode;
state.label = mContext.getString(R.string.airplane_mode);
state.icon = mIcon;
if (state.slash == null) {
state.slash = new SlashState();
}
state.slash.isSlashed = !airplaneMode;
state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
}
处理状态更新,state来自于booleanState 以及arg
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_AIRPLANEMODE;
}
得到getMetricsCategory, 这个是系统必须写的,至于什么作用,猜着估计是用来标识用的,在metrics_constants.proto 这个文件里有定义,我们需要给自己的tile加一个值即可。
然后就是
@Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_on);
} else {
return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_off);
}
}
很明显,就是简单的得到string
然后,, 没了,一个简单的AirplanmodeTile就写完了,抽象出来的方法只有这么几个需要重写,其他构造和处理逻辑都写在了父类里,它的父类QSTileImpl是个抽象类,不过我们不需要去重新写父类,这大概在设计者设计的时候,就已经考虑好了解耦,设计思想很明显。
然后,我们只需要重新几个自己的方法就可以了,在handleClick里就是去处理点击事件的开关,我们只需要去做自己想做的开关即可。
客户的需求是添加控制系统density的开关,
我就照着重写了个DensityTile,重写里面的方法,
在click的里面借来了开发者选项里改写系统density的代码
try {
final Resources res = mContext.getResources();
final DisplayMetrics metrics = res.getDisplayMetrics();
final int newSwDp = Math.max(enable?480:320, 320);
final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels);
final int newDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / newSwDp;
final int densityDpi = Math.max(newDensity, 120);
DisplayDensityUtils.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, densityDpi);
} catch (Exception e) {
// TODO: display a message instead of silently failing.
Slog.e(TAG, "Couldn't save density", e);
}
setforceDisplayDensity是个新收获,
public static void setForcedDisplayDensity(final int displayId, final int density) {
final int userId = UserHandle.myUserId();
AsyncTask.execute(() -> {
try {
final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
wm.setForcedDisplayDensityForUser(displayId, density, userId);
} catch (RemoteException exc) {
Log.w(LOG_TAG, "Unable to save forced display density setting");
}
});
}
可以看到,调用的是IWindowManager接口对象,而且是直接从Global获取,而得到的对象实体,实际就是
WindowManagerService ,—public class WindowManagerService extends IWindowManager.Stub
而WindowManagerService里面还有许多其他设置系统显示的方法,所以,我们可以用这种方式,很容易的从其他地方调用windowservice里的方法,不得不感叹这是系统做的好。
这个开关就是设置系统density显示分辨率的开关,打开就是7201280,关闭就是480854,任务完成。