Android定制实战:从 SettingsIntelligence 源码定位应用搜索结果过滤点
场景承接上篇《让指定系统应用在“所有应用”列表中隐身》:应用列表已经通过
ApplicationsState.FILTER_NOT_HIDE过滤掉了,但用户在设置顶部搜索框里输入应用名,依然能搜到它。本文记录这条搜索链路的完整定位过程与修改方案。
一、问题:为什么列表过滤了,搜索还能搜到?
上篇方案改的是packages/apps/Settings(借 SettingsLib 的ApplicationsState)。但设置首页/各页面顶部的搜索框点击后,实际跳转到的是另一个独立 APK。
从SearchMenuController可以确认这一点:
// packages/apps/Settings/src/com/android/settings/search/actionbar/SearchMenuController.javafinalStringSettingsIntelligencePkgName=activity.getString(R.string.config_settingsintelligence_package_name);if(!Utils.isPackageEnabled(activity,SettingsIntelligencePkgName)){return;// 搜索菜单依赖这个包,没有它连搜索图标都不显示}...searchItem.setOnMenuItemClickListener(target->{finalIntentintent=FeatureFactory.getFactory(activity).getSearchFeatureProvider().buildSearchIntent(activity,mPageId);// 跳出到独立应用mHost.startActivityForResult(intent,SearchFeatureProvider.REQUEST_CODE);...});包名在Settings/res/values/config.xml里:
<stringname="config_settingsintelligence_package_name"translatable="false">com.android.settings.intelligence</string>即 AOSP 的SettingsIntelligence(packages/apps/SettingsIntelligence)。它是一个搜索聚合应用,负责界面、索引和查询编排;而 Settings 只是通过SearchIndexablesProvider(ContentProvider)把自己的可搜索条目暴露给它。
所以结论很明确:"所有应用列表"和"搜索"是两条完全独立的数据链路,只改 SettingsLib 后搜索结果里仍然会出现目标应用——这是一个非常典型的"改了一处以为全覆盖,漏了另一条链路"的定制坑。
二、搜索结果的数据来源
SettingsIntelligence 的搜索结果由多个SearchQueryTask.QueryWorker并行产出(设置项索引、应用列表、开关建议等),最终合并排序展示。应用类的结果来自:
packages/apps/SettingsIntelligence/src/com/android/settings/intelligence/search/query/InstalledAppResultTask.java核心query()逻辑:
List<ApplicationInfo>appsInfo=mPackageManager.getInstalledApplications(PackageManager.MATCH_DISABLED_COMPONENTS|PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS|PackageManager.MATCH_INSTANT);for(ApplicationInfoinfo:appsInfo){if(!info.enabled&&mPackageManager.getApplicationEnabledSetting(info.packageName)!=PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER){// Disabled by something other than user, skip.continue;}finalCharSequencelabel=info.loadLabel(mPackageManager);finalintwordDiff=SearchQueryUtils.getWordDifference(label.toString(),mQuery);if(wordDiff==SearchQueryUtils.NAME_NO_MATCH){continue;// 应用名与搜索词不匹配,跳过}finalIntentintent=newIntent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(Uri.fromParts(INTENT_SCHEME,info.packageName,null/* fragment */))...;// 组装 AppSearchResult,点击后跳到该应用的"应用详情"页results.add(builder.build());}几个值得注意的细节:
- 数据源是
PackageManager.getInstalledApplications()直查,不经过ApplicationsState,因此 SettingsLib 里的过滤器对它无效; - 匹配的是应用 label(显示名),不是包名——用户输入中文应用名同样能命中;
- 结果的 payload 是
ACTION_APPLICATION_DETAILS_SETTINGSintent,也就是说搜索结果里点一下就能直达该应用的详情页——如果详情页本身也被隐藏需求覆盖,这条路径必须一并堵住。
三、修改方案
与列表过滤同样的思路:白名单数组 + 遍历入口处前置排除。
publicclassInstalledAppResultTaskextendsSearchQueryTask.QueryWorker{/** Custom: packages always hidden from Settings search results. */privatestaticfinalString[]HIDE_ALWAYS_PACKAGES={"com.example.dmapp",};@OverrideprotectedList<?extendsSearchResult>query(){...for(ApplicationInfoinfo:appsInfo){// Custom: always hide these packages from Settings search results.if(Arrays.asList(HIDE_ALWAYS_PACKAGES).contains(info.packageName)){continue;}if(!info.enabled&&...){continue;}...}}}别忘了import java.util.Arrays;。
完整 diff
packages/apps/SettingsIntelligence/src/com/android/settings/intelligence/search/query/InstalledAppResultTask.java +import java.util.Arrays; + public class InstalledAppResultTask extends SearchQueryTask.QueryWorker { ... + /** Custom: packages always hidden from Settings search results. */ + private static final String[] HIDE_ALWAYS_PACKAGES = { + "com.example.dmapp", + }; + @Override protected List<? extends SearchResult> query() { ... for (ApplicationInfo info : appsInfo) { + // Custom: always hide these packages from Settings search results. + if (Arrays.asList(HIDE_ALWAYS_PACKAGES).contains(info.packageName)) { + continue; + } if (!info.enabled四、为什么不改搜索的"索引"而改"查询"
SettingsIntelligence 有两阶段:
- 索引阶段(Indexing):启动/更新时通过各应用的
SearchIndexablesProvider抓取可搜索条目建库; - 查询阶段(Query):用户输入关键词时实时跑各
QueryWorker。
应用类结果不走索引库——它是每次搜索时实时枚举已安装应用的,所以只能(也只需)在查询侧InstalledAppResultTask过滤。而设置项类结果走索引库,如果以后要隐藏某个设置项,思路则是在对应SearchIndexableRaw/BaseSearchIndexProvider的getNonIndexableKeys里返回该 key。两类目标的过滤点不同,这是本篇最值得记住的经验。
五、验证与编译
- 编译(注意目标不是 Settings):
sourcebuild/envsetup.sh lunch<product>-userdebugmakeSettingsIntelligence-j16- 推送/刷入 SettingsIntelligence APK 后验证:
- 设置 → 顶部搜索框 → 输入该应用的中文名/包名关键字:无结果;
- 输入普通应用名(如 “Bluetooth”):其它应用照常出现,确认过滤器没有误伤;
make Settings SettingsLib的列表过滤结果同时生效(两个 APK 都要更新)。
六、总结:定制“隐藏”需求的链路检查清单
结合上篇(列表过滤),这类需求完整落地要过三关:
| 入口 | 数据链路 | 过滤点 | 编译目标 |
|---|---|---|---|
| 设置 → 所有应用(含“显示系统应用”) | ManageApplications→ApplicationsState | FILTER_NOT_HIDE.filterApp() | SettingsLib/Settings |
| 设置顶部搜索 → 应用名 | SettingsIntelligenceInstalledAppResultTask | query()遍历前置排除 | SettingsIntelligence |
| 桌面图标(如有) | Launcher3 | 不在本文范围,按需处理 | Launcher3 |
三条链路互相独立、源码分属不同进程甚至不同 APK。定制前先画清楚“用户能从几个地方看到这个应用”,再逐条找到各链路的统一收口点,才不会上线后被一条漏网路径打回。另外,本方案仅影响显示,com.example.dmapp的运行、被系统其它模块拉起的能力完全不受影响。