android 11 Setting panel的一次源码分析

2023-05-16

Android 11 Setting panel的一次源码分析

Android 11关于Wi-Fi的开关等设置,不允许用户直接调用,需要从panel设置。
启动代码如下:

val panelIntent = Intent(Settings.Panel.ACTION_WIFI)
startActivityForResult(panelIntent, 10)

其中的action包括:

ACTION_WIFI 单Wi-Fi,包括Wi-Fi开关和连接
ACTION_NFC NFC
ACTION_INTERNET_CONNECTIVITY Wi-Fi ,流量和飞行模式
ACTION_VOLUME 音量

进入设置

action进入一个叫panel.SettingsPanelActivity 的dialog activity。
主要进入的是PanelFragment,由PanelFeatureProviderImpl解析。
解析数据:

public class PanelFeatureProviderImpl implements PanelFeatureProvider {

    @Override
    public PanelContent getPanel(Context context, Bundle bundle) {
        if (context == null) {
            return null;
        }

        final String panelType =
                bundle.getString(SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT);
        final String mediaPackageName =
                bundle.getString(SettingsPanelActivity.KEY_MEDIA_PACKAGE_NAME);

        switch (panelType) {
            case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
                return InternetConnectivityPanel.create(context);
            case ACTION_MEDIA_OUTPUT:
                return MediaOutputPanel.create(context, mediaPackageName);
            case Settings.Panel.ACTION_NFC:
                return NfcPanel.create(context);
            case Settings.Panel.ACTION_WIFI:
                return WifiPanel.create(context);
            case Settings.Panel.ACTION_VOLUME:
                return VolumePanel.create(context);
            case ACTION_MEDIA_OUTPUT_GROUP:
                return MediaOutputGroupPanel.create(context, mediaPackageName);
        }

        throw new IllegalStateException("No matching panel for: "  + panelType);
    }
}

上面可以看出,由传入的action来选择,其中ACTION_MEDIA_OUTPUT_GROUP这个没有试过,后续有空,再继续跟进。

ACTION_INTERNET_CONNECTIVITY

以ACTION_INTERNET_CONNECTIVITY为例,来看页面的填充。

InternetConnectivityPanel 这个是网络相关的部分,提供的能力包含在
getSlices方法里面:

 public List<Uri> getSlices() {
        final List<Uri> uris = new ArrayList<>();
        uris.add(CustomSliceRegistry.WIFI_SLICE_URI);
        uris.add(CustomSliceRegistry.MOBILE_DATA_SLICE_URI);
        uris.add(AirplaneModePreferenceController.SLICE_URI);
        return uris;
    }

这个是slice的逻辑,从uri解析出具体的数据。
看下创建逻辑,回到PanelFragment

//PanelFragment.java
  private void loadAllSlices() {
        mSliceLiveData.clear();
        ....
        for (Uri uri : sliceUris) {
            final LiveData<Slice> sliceLiveData = SliceLiveData.fromUri(getActivity(), uri,
                    (int type, Throwable source)-> {
                            removeSliceLiveData(uri);
                            mPanelSlicesLoaderCountdownLatch.markSliceLoaded(uri);
                    });
                     mSliceLiveData.put(uri, sliceLiveData);

            sliceLiveData.observe(getViewLifecycleOwner(), slice -> {
                // If the Slice has already loaded, do nothing.
                if (mPanelSlicesLoaderCountdownLatch.isSliceLoaded(uri)) {
                    return;
                }
          ........      
          }
          .....
         loadPanelWhenReady();           
                    

先看SliceLiveData创建slice,SliceLiveData是在androidx.slice:slice-view,引用的时候注意。

SliceLiveData.java
public static @NonNull LiveData<Slice> fromUri(@NonNull Context context, @NonNull Uri uri) {
        return new SliceLiveDataImpl(context.getApplicationContext(), uri);
    }

源码里面是三个参数,我这里没有看到,猜测那个回调,就是数据生成了以后回调一下,然后记录下需要的数据是否都生成了,生存了就可以加载了。
上面真正的数据是:SliceLiveDataImpl 是一个LiveData,他是SliceLiveData的内部类。

private static class SliceLiveDataImpl extends LiveData<Slice> {
        final Intent mIntent;
        final SliceViewManager mSliceViewManager;
        Uri mUri;

        SliceLiveDataImpl(Context context, Uri uri) {
            super();
            mSliceViewManager = SliceViewManager.getInstance(context);
            mUri = uri;
            mIntent = null;
            // TODO: Check if uri points at a Slice?
        }

        SliceLiveDataImpl(Context context, Intent intent) {
            super();
            mSliceViewManager = SliceViewManager.getInstance(context);
            mUri = null;
            mIntent = intent;
        }

        @Override
        protected void onActive() {
            AsyncTask.execute(mUpdateSlice);
            if (mUri != null) {
                mSliceViewManager.registerSliceCallback(mUri, mSliceCallback);
            }
        }

        @Override
        protected void onInactive() {
            if (mUri != null) {
                mSliceViewManager.unregisterSliceCallback(mUri, mSliceCallback);
            }
        }

        private final Runnable mUpdateSlice = new Runnable() {
            @Override
            public void run() {
                try {
                    Slice s = mUri != null ? mSliceViewManager.bindSlice(mUri)
                            : mSliceViewManager.bindSlice(mIntent);
                    if (mUri == null && s != null) {
                        mUri = s.getUri();
                        mSliceViewManager.registerSliceCallback(mUri, mSliceCallback);
                    }
                    postValue(s);
                } catch (Exception e) {
                    Log.e(TAG, "Error binding slice", e);
                    postValue(null);
                }
            }
        };

        final SliceViewManager.SliceCallback mSliceCallback =
                new SliceViewManager.SliceCallback() {
            @Override
            public void onSliceUpdated(@NonNull Slice s) {
                postValue(s);
            }
        };
    }

我们知道LiveData被监听的时候,lifecycle会调用,最后会走到LiveData的onActive,这里就是产生真正的数据了,我们自己继承的时候可以,LiveData源码里面关于这个是没有做的,我们自己可以从这个方法生成数据然后post出去给观察者。
所以Slice真正的产生数据都是在上面的mUpdateSlice。

  /**
         * @hide
         */
        @RestrictTo(LIBRARY)
        protected void updateSlice() {
            try {
                Slice s = mSliceViewManager.bindSlice(mUri);
                mSliceCallback.onSliceUpdated(s);
            } catch (Exception e) {
                mListener.onSliceError(OnErrorListener.ERROR_UNKNOWN, e);
            }
        }

其中mSliceViewManager:

 public static @NonNull SliceViewManager getInstance(@NonNull Context context) {
        if (Build.VERSION.SDK_INT >= 28) {
            return new SliceViewManagerWrapper(context);
        } else {
            return new SliceViewManagerCompat(context);
        }
    }

我们是Android 11,api30,所以是SliceViewManagerWrapper#bindSlice(URI)

 public androidx.slice.Slice bindSlice(@NonNull Uri uri) {
        if (isAuthoritySuspended(uri.getAuthority())) {
            return null;
        }
        return SliceConvert.wrap(mManager.bindSlice(uri, mSpecs), mContext);
    }

这里主要是检查URi,uri都是 contentProvider提供的,先找下能不能找到provider,而且provider是不是合法。
继续就是mManager.bindSlice(uri, mSpecs) 。这个manager就是

ontext.getSystemService(android.app.slice.SliceManager.class)

这个获取就比较高级了。。。。跟平时我们看到的不一样啊。不管这个,这个就直接当成SliceManager就完了。进去看代码.

生成Slice

前面的生成方法是:bindSlice(uri, mSpecs)。

 public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> supportedSpecs) {
        Objects.requireNonNull(uri, "uri");
        ContentResolver resolver = mContext.getContentResolver();
        try (ContentProviderClient provider = resolver.acquireUnstableContentProviderClient(uri)) {
            if (provider == null) {
                Log.w(TAG, String.format("Unknown URI: %s", uri));
                return null;
            }
            Bundle extras = new Bundle();
            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
                    new ArrayList<>(supportedSpecs));
            final Bundle res = provider.call(SliceProvider.METHOD_SLICE, null, extras);
            Bundle.setDefusable(res, true);
            if (res == null) {
                return null;
            }
            return res.getParcelable(SliceProvider.EXTRA_SLICE);
        } catch (RemoteException e) {
            // Arbitrary and not worth documenting, as Activity
            // Manager will kill this process shortly anyway.
            return null;
        }
    }

这里就是IContentProvider 调用SliceProvider.METHOD_SLICE。注意后面有参数的。IContentProvider是与Uri的Authority相关的.其实就是获取这个Authority的contentProvider,这里是一个SliceProvider的子类,调用的方法是:public Bundle call(String method, String arg, Bundle extras)。这个设计碉堡了,完全不用知道具体方法是什么,类似与一个标识符,可以无限扩展。
在manifest找到对应的provider:

 <provider android:name=".slices.SettingsSliceProvider"
                  android:authorities="com.android.settings.slices;android.settings.slices"
                  android:exported="true"
                  android:grantUriPermissions="true" />

对应上面的com.android.settings.slices,android.settings.slices这两个,上面InternetConnectivityPanel的三个uri,有两个Authority,对应的都是这个SettingsSliceProvider。
我只找 public Slice onBindSlice(Uri sliceUri) 可能与我下载的slice的扩展版本不对。

public Slice onBindSlice(Uri sliceUri) {
.....
              if (CustomSliceRegistry.isValidUri(sliceUri)) {
                final Context context = getContext();
                return FeatureFactory.getFactory(context)
                        .getSlicesFeatureProvider().getSliceableFromUri(context, sliceUri)
                        .getSlice();
            }

            if (CustomSliceRegistry.WIFI_CALLING_URI.equals(sliceUri)) {
                return FeatureFactory.getFactory(getContext())
                        .getSlicesFeatureProvider()
                        .getNewWifiCallingSliceHelper(getContext())
                        .createWifiCallingSlice(sliceUri);
            } else if (CustomSliceRegistry.ZEN_MODE_SLICE_URI.equals(sliceUri)) {
                return ZenModeSliceBuilder.getSlice(getContext());
            } else if (CustomSliceRegistry.BLUETOOTH_URI.equals(sliceUri)) {
                return BluetoothSliceBuilder.getSlice(getContext());
            } else if (CustomSliceRegistry.ENHANCED_4G_SLICE_URI.equals(sliceUri)) {
                return FeatureFactory.getFactory(getContext())
                        .getSlicesFeatureProvider()
                        .getNewEnhanced4gLteSliceHelper(getContext())
                        .createEnhanced4gLteSlice(sliceUri);
            } else if (CustomSliceRegistry.WIFI_CALLING_PREFERENCE_URI.equals(sliceUri)) {
                return FeatureFactory.getFactory(getContext())
                        .getSlicesFeatureProvider()
                        .getNewWifiCallingSliceHelper(getContext())
                        .createWifiCallingPreferenceSlice(sliceUri);
            }

            final SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
            if (cachedSliceData == null) {
                loadSliceInBackground(sliceUri);
                return getSliceStub(sliceUri);
            }

            // Remove the SliceData from the cache after it has been used to prevent a memory-leak.
            if (!getPinnedSlices().contains(sliceUri)) {
                mSliceWeakDataCache.remove(sliceUri);
            }
            return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData);

.....

这里生成,开始有区别了:
CustomSliceRegistry.WIFI_SLICE_URI和CustomSliceRegistry.MOBILE_DATA_SLICE_URI是可以从CustomSliceRegistry直接找到类的。

 sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
 sUriToSlice.put(MOBILE_DATA_SLICE_URI, MobileDataSlice.class);

AirplaneModePreferenceController.SLICE_URI在这里找不到的。

WIFI_SLICE_URI

能找到的我们取Wi-Fi分析。
WifiSlice.class
public Slice getSlice(){ //生成正式的slice
。。。。。
ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* accessPoint /);//首先创建Wi-Fi开关部分
。。。。
if (isFirstApActive) {
// refresh header subtext
listBuilder = getListBuilder(true /
isWifiEnabled */, apList.get(0));
}
//这里主要是如果是有Wi-Fi连接的话刷新下。就是一个开关和当前链接的ap的信息
。。。。。。下面是列出当前所有的ap,里面还有添加了每一个ap的点击操作,

}
开关部分的action:

private ListBuilder getListBuilder(boolean isWifiEnabled, AccessPoint accessPoint) {
        final PendingIntent toggleAction = getBroadcastIntent(mContext);
        final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
                null /* actionTitle */, isWifiEnabled);
        final ListBuilder builder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
                .setAccentColor(COLOR_NOT_TINTED)
                .setKeywords(getKeywords())
                .addRow(getHeaderRow(isWifiEnabled, accessPoint))
                .addAction(toggleSliceAction);
        return builder;
    }

ap添加部分的action

 private ListBuilder.RowBuilder getAccessPointRow(AccessPoint accessPoint) {
        final boolean isCaptivePortal = accessPoint.isActive() && isCaptivePortal();
        final CharSequence title = accessPoint.getTitle();
        final CharSequence summary = getAccessPointSummary(accessPoint, isCaptivePortal);
        final IconCompat levelIcon = getAccessPointLevelIcon(accessPoint);
        final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
                .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
                .setTitle(title)
                .setSubtitle(summary)
                .setPrimaryAction(getAccessPointAction(accessPoint, isCaptivePortal, levelIcon,
                        title));

        if (isCaptivePortal) {
            rowBuilder.addEndItem(getCaptivePortalEndAction(accessPoint, title));
        } else {
            final IconCompat endIcon = getEndIcon(accessPoint);
            if (endIcon != null) {
                rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
            }
        }
        return rowBuilder;
    }

这个部分里面是listBuilder的操作,具体的可以进去看。简单的理解是toggle触发走toggleSliceAction,单行尾部触发走getCaptivePortalEndAction。
上面的toggleSliceAction出发最后走到的是本类里面的onNotifyChange。

@Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        final String key = intent.getStringExtra(EXTRA_SLICE_KEY);

        if (CustomSliceRegistry.isValidAction(action)) {
            final CustomSliceable sliceable =
                    CustomSliceable.createInstance(context,
                            CustomSliceRegistry.getSliceClassByUri(Uri.parse(action)));
            sliceable.onNotifyChange(intent);
            return;
        }
       .....
       }

然后就是wifislice里面

 public void onNotifyChange(Intent intent) {
        final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
                mWifiManager.isWifiEnabled());
        mWifiManager.setWifiEnabled(newState);
        // Do not notifyChange on Uri. The service takes longer to update the current value than it
        // does for the Slice to check the current value again. Let {@link WifiScanWorker}
        // handle it.
    }

看到这里不知道大家发现没有,这个居然直接创建了一个新的wifislice,那么当前显示的是怎么知道的?
注意上面的注释:WifiScanWorker

不具体分析,兜兜转转最后走到的是

context.getContentResolver().notifyChange(uri, null);

以上分析是大概浏览源码,关于通知变更,请大家指点,这部分我不大确认,因为里面各种创建实例。

AirplaneModePreferenceController.SLICE_URI

这个的生成就稍微麻烦一点了。这个走的是 loadSliceInBackground(sliceUri);

......
  final SliceData sliceData;
        try {
            sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri);
        } catch (IllegalStateException e) {
            Log.d(TAG, "Could not create slicedata for uri: " + uri, e);
            return;
        }

        final BasePreferenceController controller = SliceBuilderUtils.getPreferenceController(
                getContext(), sliceData);

......

这里来了个查库。。。。。什么时候入库的我不知道,可能是我没有编译出源码,这部分太高级。。。我默认获取的controller就是AirplaneModePreferenceController,因为刚好是子类。。。最后生成

 return new SliceData.Builder()
                .setKey(key)
                .setTitle(title)
                .setSummary(summary)
                .setScreenTitle(screenTitle)
                .setKeywords(keywords)
                .setIcon(iconResource)
                .setFragmentName(fragmentClassName)
                .setPreferenceControllerClassName(controllerClassName)
                .setUri(uri)
                .setSliceType(sliceType)
                .setUnavailableSliceSubtitle(unavailableSliceSubtitle)
                .build();
 
 private static Slice buildToggleSlice(Context context, SliceData sliceData,
            BasePreferenceController controller) {
        final PendingIntent contentIntent = getContentPendingIntent(context, sliceData);
        final IconCompat icon = getSafeIcon(context, sliceData);
        final CharSequence subtitleText = getSubtitleText(context, controller, sliceData);
        @ColorInt final int color = Utils.getColorAccentDefaultColor(context);
        final TogglePreferenceController toggleController =
                (TogglePreferenceController) controller;
        final SliceAction sliceAction = getToggleAction(context, sliceData,
                toggleController.isChecked());
        final Set<String> keywords = buildSliceKeywords(sliceData);
        final RowBuilder rowBuilder = new RowBuilder()
                .setTitle(sliceData.getTitle())
                .setPrimaryAction(
                        SliceAction.createDeeplink(contentIntent, icon,
                                ListBuilder.ICON_IMAGE, sliceData.getTitle()))
                .addEndItem(sliceAction);
        if (!Utils.isSettingsIntelligence(context)) {
            rowBuilder.setSubtitle(subtitleText);
        }

        return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY)
                .setAccentColor(color)
                .addRow(rowBuilder)
                .setKeywords(keywords)
                .build();
    }

点击走的setChecked

以上,分析到此。

疑惑

点击事件触发以后,分析的逻辑是不是对的?是不是都通过 uri的监听来完成的?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android 11 Setting panel的一次源码分析 的相关文章

随机推荐

  • 1 操作系统第一章 操作系统概念、功能、四大特征、操作系统发展与分类

    文章目录 1 1 操作系统概念1 2 操作系统功能1 3 操作系统四大特征1 3 1 并发1 3 2 共享1 3 3 并发性和共享区别及对应关系 xff1a 1 3 4 虚拟1 3 5 异步 1 4 操作系统的发展与分类1 4 1 手工操作
  • 删除数组中指定的数字

    删除数组中指定的数字 题目 xff1a 有一个整数序列 xff08 可能有重复的整数 xff09 xff0c 现删除指定的某一个整数 xff0c 输出删除指定数字之后的序列 xff0c 序列中未被删除数字的前后位置没有发生改变 代码实现如下
  • session与Cookie

    目录 session与Cookie 1 session的一些方法与概述 2 Cookie的一些方法与概述 Cookie保存的类容如含特殊符号 需要转16径直文件 session与Cookie 1 session的一些方法与概述 sessio
  • 【Linux信号量】

    Linux信号量 POSIX信号量信号量的原理信号量的概念信号量函数 二元信号量模拟实现互斥功能基于环形队列的生产消费模型空间资源和数据资源生产者和消费者申请和释放资源必须遵守的两个规则信号量保护环形队列的原理 POSIX信号量 信号量的原
  • win11系统任务栏居中以及透明美化

    win11系统任务栏居中以及透明美化 文章目录 win11系统任务栏居中以及透明美化前言一 win11系统任务栏居中二 使用步骤1 如何找的安装的startallback2 startallback的功能 总结 前言 拥有一个漂亮的桌面会使
  • 求conio头文件

    conio头文件哪里下载呀
  • C语言程序设计:删除字符串中的数字

    题目内容 xff1a 编程 xff1a 从键盘上输入一个含有数字的字符串 xff0c 然后在 原字符串 中删除其中的数字 如输入的字符串为 ab12cd345e xff0c 则处理后的字符串为 abcde 程序中只能定义一个数组 提示 xf
  • 在Windows Server 2016 中共享及权限的操作

    如图 技术部 张工 蔡工 研发部 黄工 xff08 头儿 xff09 软件部 李工 周工 首先 xff0c 创建组和用户 xff0c 在服务管理器 工具 计算机管理 用户和组
  • 基于ssm的个人健康信息管理系统

    结合当今大数据技术研究的潮流 综合ssm框架 jsp技术 MySQL数据库等关键技术 构建了一个基于ssm的个人健康信息管理系统 首先 本文对个人健康信息管理系统涉及到的理论与相关技术进行了全面的分析 随后 通过对个人健康信息定义的理解和对
  • linux创建sftp

    SFTP简介 百度百科 xff1a sftp是Secure File Transfer Protocol的缩写 xff0c 安全文件传送协议 可以为传输文件提供一种安全的网络的加密方法 SFTP 为 SSH的其中一部分 xff0c 是一种传
  • Altium Designer 16.1中绘制蛇形走线的方法

    这是在线已经连接好的情况下进行的蛇形走线 1 首先英文输入法的情况下 xff0c 按下快捷键T 43 R xff0c 鼠标变成十字形 xff0c 点击需要进行蛇形走线的线段 2 然后按下Tab键 xff0c 设置蛇形走线参数 3 我一般选择
  • 5 计算机组成原理第四章 指令系统

    文章目录 1 指令格式1 1 指令定义1 2 指令格式1 3 指令格式 地址码1 4 指令格式 操作码1 5 操作码分类1 6 操作类型 2 数据存放指令寻址2 1 数据存放方式2 2 指令寻址2 3 操作数类型2 4 数据寻址2 4 1立
  • html ----checkbox使用

    1 html checkbox使用 lt html gt lt head gt lt title gt 选择 lt title gt lt head gt lt body gt 请选择你喜欢的水果 xff1a lt br gt lt for
  • Qt:QJsonArray

    说明 QJsonArray中存储了一系列的QJsonValue 可以向其中插入 删除QJsonValue 一个QJsonArray可以与QVariantList互相转换 可以通过size 访问其中的元素数 xff0c insert remo
  • Qt——日志输出

    普通的打印输出 用 QtCreator 开发 Qt 程序时 xff0c 经常需要向控制台打印一些参数 有时候是查看对象的属性是否被正确设置 xff0c 有时候是查看程序是否执行了某一段代码 xff0c 或者执行了多少次这一段代码 尽管使用调
  • 消息队列的重要技术讲解

    一 什么是消息队列 xff1f 消息队列不知道大家看到这个词的时候 xff0c 会不会觉得它是一个比较高端的技术 xff0c 反正我是觉得它好像是挺牛逼的 消息队列 xff0c 一般我们会简称它为MQ Message Queue xff0c
  • [android nfc] onNewIntent 中 无法获取 tag, 或者 intent.extras 为空, 或者 intent.action 为空

    在做android nfc 场景的时候 大方向上有2种 foreground 调度系统enableReaderMode api foreground 调度系统 发现android12下无法获取tag信息 经过排查 原因如下 在 androi
  • linux系统常用命令列举

    Linux系统入门 Linux特点 开源多用户 xff1a 系统可以在保证各个用户之间的安全 xff0c 隐私多任务 xff1a 良好的界面 xff1a Linux同时支持两种环境 xff0c 字符界面 图形化界面支持多种平台 xff1a
  • linux 常用命令

    查看所有分区 du sh window 想查看进程的端口号有这样几个方法 1 使用netstat ano查看所有连接和监听端口 xff0c 以及每个连接相关的进程ID 2 使用进程管理器查看进程PID 默认情况下可能不显示PID xff0c
  • android 11 Setting panel的一次源码分析

    Android 11 Setting panel的一次源码分析 Android 11关于Wi Fi的开关等设置 xff0c 不允许用户直接调用 xff0c 需要从panel设置 启动代码如下 xff1a val panelIntent 61