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的监听来完成的?