Android 状态栏下拉列表添加自定义item开关

需求描述:客户需要在状态栏下拉列表里添加更改屏幕密度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矢量图
预览效果
Android 状态栏下拉列表添加自定义item开关_第1张图片
这不就是下拉列表里的飞行模式开关吗。

接着分析成员方法

重写方法

    @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里的方法,不得不感叹这是系统做的好。


最后效果:
Android 状态栏下拉列表添加自定义item开关_第2张图片

这个开关就是设置系统density显示分辨率的开关,打开就是7201280,关闭就是480854,任务完成。

你可能感兴趣的:(SystemUI)