Android定制实战:从 SettingsIntelligence 源码定位应用搜索结果过滤点

发布时间:2026/11/3 2:30:03
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());}

几个值得注意的细节:

  1. 数据源是PackageManager.getInstalledApplications()直查,不经过ApplicationsState,因此 SettingsLib 里的过滤器对它无效;
  2. 匹配的是应用 label(显示名),不是包名——用户输入中文应用名同样能命中;
  3. 结果的 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。两类目标的过滤点不同,这是本篇最值得记住的经验。

五、验证与编译

  1. 编译(注意目标不是 Settings):
sourcebuild/envsetup.sh lunch<product>-userdebugmakeSettingsIntelligence-j16
  1. 推送/刷入 SettingsIntelligence APK 后验证:
    • 设置 → 顶部搜索框 → 输入该应用的中文名/包名关键字:无结果;
    • 输入普通应用名(如 “Bluetooth”):其它应用照常出现,确认过滤器没有误伤;
    • make Settings SettingsLib的列表过滤结果同时生效(两个 APK 都要更新)。

六、总结:定制“隐藏”需求的链路检查清单

结合上篇(列表过滤),这类需求完整落地要过三关:

入口数据链路过滤点编译目标
设置 → 所有应用(含“显示系统应用”)ManageApplications→ApplicationsStateFILTER_NOT_HIDE.filterApp()SettingsLib/Settings
设置顶部搜索 → 应用名SettingsIntelligenceInstalledAppResultTaskquery()遍历前置排除SettingsIntelligence
桌面图标(如有)Launcher3不在本文范围,按需处理Launcher3

三条链路互相独立、源码分属不同进程甚至不同 APK。定制前先画清楚“用户能从几个地方看到这个应用”,再逐条找到各链路的统一收口点,才不会上线后被一条漏网路径打回。另外,本方案仅影响显示,com.example.dmapp的运行、被系统其它模块拉起的能力完全不受影响。