美文网首页Android进阶之路
Android 10.0 StatusBar—下拉菜单快捷方式

Android 10.0 StatusBar—下拉菜单快捷方式

作者: 孤街酒客0911 | 来源:发表于2022-07-09 14:13 被阅读0次

    学习笔记:


    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:

    相关文章

      网友评论

        本文标题:Android 10.0 StatusBar—下拉菜单快捷方式

        本文链接:https://www.haomeiwen.com/subject/jzombrtx.html