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