客户定制
GMS 版本下,要求去除谷歌浏览器中分享菜单下短信备选项,最终实现效果如下
要输入网页后右上角菜单中才会显示分享功能。
解决办法
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7413,11 +7417,44 @@ public class PackageManagerService extends IPackageManager.Stub if (sortResult) { Collections.sort(result, RESOLVE_PRIORITY_SORTER); } + + android.util.Log.i("LogUtils", "step 3"); + if (result!=null) { + android.util.Log.i("LogUtils", "step 4=="+result.size());//cczheng + } + if ("android.intent.action.SEND".equals(intent.getAction())) { + blockGoogleMessagingShare(result); + } return applyPostResolutionFilter( result, instantAppPkgName, allowDynamicSplits, filterCallingUid, resolveForStart, userId, intent); } + //cczheng add + private void blockGoogleMessagingShare(List<ResolveInfo> result){ + android.util.Log.d("LogUtils", "into blockGoogleMessagingShare"); + ActivityManager am = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE); + List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1); + if (!tasks.isEmpty()) { + ComponentName topActivity = tasks.get(0).topActivity; + String currentPackageName = topActivity.getPackageName(); + if ("com.android.chrome".equals(currentPackageName)) { + for (int i = 0; i < result.size(); i++) { + ResolveInfo resolveInfo = result.get(i); + android.util.Log.i("LogUtils", "resolveInfoCount="+resolveInfo.toString()); + if ("com.google.android.apps.messaging".equals(resolveInfo.activityInfo.packageName)){ + result.remove(i); + } + } + android.util.Log.e("LogUtils", "step 4=="+result.size()); + } + } + }//end + private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent, String resolvedType, int flags, int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
frameworks/base/core/java/com/android/internal/app/ChooserActivity.java
@@ -588,6 +588,26 @@ public class ChooserActivity extends ResolverActivity implements } }; + //cchzeng add start + private String getLastPausedActivity() { + String result = ""; + try { + java.lang.Process p = java.lang.Runtime.getRuntime().exec("dumpsys activity activities grep mLastPausedActivity "); + java.io.InputStream input = p.getInputStream(); + java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(input)); + StringBuffer stringBuffer = new StringBuffer(); + String content = ""; + while ((content = in.readLine()) != null) { + stringBuffer.append(content); + } + result = stringBuffer.toString(); + int status = p.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + }//cczheng add end + @Override protected void onCreate(Bundle savedInstanceState) { final long intentReceivedTime = System.currentTimeMillis(); @@ -692,7 +712,26 @@ public class ChooserActivity extends ResolverActivity implements pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS); - + //cczheng add start + String lastPausedActivityStr = getLastPausedActivity(); + Log.d(TAG,"ResolverActivity onCreate "+lastPausedActivityStr); + try { + if (!TextUtils.isEmpty(lastPausedActivityStr)) { + lastPausedActivityStr = lastPausedActivityStr.substring(lastPausedActivityStr.indexOf("mLastPausedActivity"), + lastPausedActivityStr.indexOf("* Task")); + Log.d(TAG,"lastPausedActivityStr= "+lastPausedActivityStr); + if (lastPausedActivityStr.contains("com.android.chrome")) { + android.provider.Settings.System.putInt(getContentResolver(), "tempflag", 1); + }else{ + android.provider.Settings.System.putInt(getContentResolver(), "tempflag", 0); + } + }else{ + android.provider.Settings.System.putInt(getContentResolver(), "tempflag", 0); + } + } catch (Exception e) { + e.printStackTrace(); + } //cczheng add end + // Exclude out Nearby from main list if chip is present, to avoid duplication ComponentName nearbySharingComponent = getNearbySharingComponent(); boolean hasNearby = nearbySharingComponent != null;
frameworks/base/core/java/com/android/internal/app/ResolverActivity.java
@@ -139,7 +139,7 @@ public class ResolverActivity extends Activity implements + //cchzeng add start + private String getLastPausedActivity() { + String result = ""; + try { + java.lang.Process p = java.lang.Runtime.getRuntime().exec("dumpsys activity activities grep mLastPausedActivity "); + java.io.InputStream input = p.getInputStream(); + java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(input)); + StringBuffer stringBuffer = new StringBuffer(); + String content = ""; + while ((content = in.readLine()) != null) { + stringBuffer.append(content); + } + result = stringBuffer.toString(); + int status = p.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + }//cczheng add end + @Override protected void onCreate(Bundle savedInstanceState) { // Use a specialized prompt when we're handling the 'Home' app startActivity() @@ -321,6 +341,26 @@ public class ResolverActivity extends Activity implements mResolvingHome = true; } + //cczheng add start + String lastPausedActivityStr = getLastPausedActivity(); + Log.d(TAG,"ResolverActivity onCreate "+lastPausedActivityStr); + try { + if (!TextUtils.isEmpty(lastPausedActivityStr)) { + lastPausedActivityStr = lastPausedActivityStr.substring(lastPausedActivityStr.indexOf("mLastPausedActivity"), + lastPausedActivityStr.indexOf("* Task")); + Log.d(TAG,"lastPausedActivityStr= "+lastPausedActivityStr); + if (lastPausedActivityStr.contains("com.android.chrome")) { + android.provider.Settings.System.putInt(getContentResolver(), "tempflag", 1); + }else{ + android.provider.Settings.System.putInt(getContentResolver(), "tempflag", 0); + } + }else{ + android.provider.Settings.System.putInt(getContentResolver(), "tempflag", 0); + } + } catch (Exception e) { + e.printStackTrace(); + } //cczheng add end + setSafeForwardingMode(true); onCreate(savedInstanceState, intent, null, 0, null, null, true);
frameworks/base/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -433,6 +433,16 @@ public class ResolverListAdapter extends BaseAdapter { // Check whether {@code dri} should be added into mDisplayList. protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) { + //cczheng add start + int flag =android.provider.Settings.System.getInt(mContext.getContentResolver(), "tempflag", 0); + if (flag == 1) { + String driPackageName = dri.getResolvedComponentName().getPackageName(); + Log.d(TAG, "shouldAddResolveInfo driPackageName: " + driPackageName); + if ("com.google.android.apps.messaging".equals(driPackageName)) { + return false; + } + } + //cczheng add end // Checks if this info is already listed in display. for (DisplayResolveInfo existingInfo : mDisplayList) { if (mResolverListCommunicator
分析过程
一开始有点懵,毕竟没有 message app 的源码,一时不知道从哪里下手,要想完成这个需求,毋庸置疑要改 framework
先看了下 message app 的 AndroidManifest.xml,在其中搜到了 send 相关的字样,然后自己新建个 demo,把配置
<intent-filter > <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter>
copy 了一份安装后发现 demo 也出现在分享列表里了,这么说就是和 android.intent.action.SEND 这个东东相关了。
按照惯例看下这个分享页面到底是系统界面还是 chrome 界面,得到结论
点 share 第一次弹出界面为 chrome 界面,com.android.chrome/com.google.android.apps.chrome.Main
点 more 按钮二次显示界面为系统界面,android/com.android.internal.app.ChooserActivity
这样看来两个地方走的逻辑不太一样,我们先从简单的系统界面入手,直接去找 ChooserActivity 源码
找源码之前我们要学会分析 log, 因为 log 中经常会藏有很多有用信息,我们要过滤的 message 包名
为 com.google.android.apps.messaging,在 log 中我发现了匹配的串
I/ResolverListAdapter: Add DisplayResolveInfo component: ComponentInfo{com.google.android.apps.messaging/com.google.android.apps.messaging.ui.conversationlist.ShareIntentActivity}, intent component: ComponentInfo{com.google.android.apps.messaging/com.google.android.apps.messaging.ui.conversationlist.ShareIntentActivity} I/ResolverListAdapter: Add DisplayResolveInfo component: ComponentInfo{com.android.chrome/org.chromium.chrome.browser.printing.PrintShareActivity}, intent component: ComponentInfo{com.android.chrome/org.chromium.chrome.browser.printing.PrintShareActivity} I/ResolverListAdapter: Add DisplayResolveInfo component: ComponentInfo{com.yandex.browser/com.yandex.browser.ShareActivity}, intent component: ComponentInfo{com.yandex.browser/com.yandex.browser.ShareActivity} I/ResolverListAdapter: Add DisplayResolveInfo component: ComponentInfo{com.hj119.sygjx/com.e4a.runtime.components.impl.android.hjfzdjtb类库.CopyToClipboard}, intent component: ComponentInfo{com.hj119.sygjx/com.e4a.runtime.components.impl.android.hjfzdjtb类库.CopyToClipboard} I/ResolverListAdapter: Add DisplayResolveInfo component: ComponentInfo{com.android.bluetooth/com.android.bluetooth.opp.BluetoothOppLauncherActivity}, intent component: ComponentInfo{com.android.bluetooth/com.android.bluetooth.opp.BluetoothOppLauncherActivity} I/ResolverListAdapter: Add DisplayResolveInfo component: ComponentInfo{com.google.android.gm/com.google.android.gm.ComposeActivityGmailExternal}, intent component: ComponentInfo{com.google.android.gm/com.google.android.gm.ComposeActivityGmailExternal} I/ResolverListAdapter: Add DisplayResolveInfo component: ComponentInfo{com.google.android.apps.docs/com.google.android.apps.docs.common.shareitem.UploadMenuActivity}, intent component: ComponentInfo{com.google.android.apps.docs/com.google.android.apps.docs.common.shareitem.UploadMenuActivity}
仔细一看发现和 ChooserActivity 界面显示 icon 个数正好对上,那岂不是找对地方了。看了源码发现 ChooserActivity 继承 ResolverActivity,
通过 ResolverListAdapter 填充数据,所以我们只需要去 Add DisplayResolveInfo component 打印地方将 message 过滤即可。
但这里有个麻烦点,并不是所有应用调用 ChooserActivity 时都过滤 message ,客户只指定了 chrome,所以我们还需要知道是从哪个 app 中拉起的系统 ChooserActivity
这个一开始在找各种 api 是否能知道是谁拉起的 Activity,后来通过巧妙的方法来达到了要求,秘密就是
adb shell dumpsys activity activities | grep mLastPausedActivity 指令,查询到 ActivityRecord 中上一个 pause 的 Activity 不就是调用者。
mLastPausedActivity: ActivityRecord{ae7cc5b u0 com.android.chrome/com.google.android.apps.chrome.Main t83}
mLastPausedActivity: ActivityRecord{1fcfbc6 u0 com.android.launcher3/com.android.searchlauncher.SearchLauncher t6}
java 代码中执行 dumpsys activity activities | grep mLastPausedActivity 实际并没有过滤到和命令行中执行一样的结果,
而是返回完整串需要 subString 处理一下,通过 Settings.System.putInt 存储 mLastPausedActivity 结果临时值
在 ResolverListAdapter 中取出进行过滤。
通过以下代码可以拉起 ChooserActivity/ResolverActivity
Intent intentss = new Intent(); intentss.setAction(Intent.ACTION_SEND); intentss.putExtra(Intent.EXTRA_TEXT, "文本内容"); intentss.setType("text/plain"); //startActivity(intentss);//android/com.android.internal.app.ResolverActivity startActivity(Intent.createChooser(intentss, "share title"));//android/com.android.internal.app.ChooserActivity
简单的搞定了,接下来就是 chrome 里这个界面了,这个界面有点难顶,前后找了几天,最终发现了 ShareActionProvider 这货
使用ShareActionProvider分享数据
同样整了个简单 demo 跑起来发现和 chrome 里显示列表一毛一样,简单代码如下
res/文件夹下新建 menu 文件夹,新增 share.xml 文件
<!--androidX 版本--> <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_item_share" app:showAsAction="ifRoom" app:actionProviderClass="androidx.appcompat.widget.ShareActionProvider" android:title="Share" /> </menu> <!--android低版本--> <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_item_share" android:showAsAction="ifRoom" android:actionProviderClass="android.widget.ShareActionProvider" android:title="share"/> </menu>
注意看到一个是 androidx 一个是默认 widget 包里面的 ShareActionProvider,widget 包中的源码是存在 aosp 里的
androidX 中的都是库,很不幸 chrome 就是用的 androidX
随便新建一个 Activity 主题要求带 ActionBar,这样右上角才能添加 menu 菜单
<!--androidX 版本--> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!--android低版本--> android:theme="@android:style/Theme.Holo.Light.DarkActionBar"
在 Activity 中添加 share menu 功能
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.share, menu); MenuItem item = menu.findItem(R.id.menu_item_share); //androidX 版本 ShareActionProvider mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item); //android低版本 //ShareActionProvider mShareActionProvider = (ShareActionProvider)item.getActionProvider(); Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_TEXT,"aaaaa"); mShareActionProvider.setShareIntent(shareIntent); return true; }
这样在 demo 中点击右上角分享图标就能显示一样的分享列表数据
好了主角登场,跟进 ShareActionProvider.java 中
@Override public void onPrepareSubMenu(SubMenu subMenu) { // Clear since the order of items may change. subMenu.clear(); ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName); PackageManager packageManager = mContext.getPackageManager(); final int expandedActivityCount = dataModel.getActivityCount(); final int collapsedActivityCount = Math.min(expandedActivityCount, mMaxShownActivityCount); // Populate the sub-menu with a sub set of the activities. for (int i = 0; i < collapsedActivityCount; i++) { ResolveInfo activity = dataModel.getActivity(i); subMenu.add(0, i, i, activity.loadLabel(packageManager)) .setIcon(activity.loadIcon(packageManager)) .setOnMenuItemClickListener(mOnMenuItemClickListener); } if (collapsedActivityCount < expandedActivityCount) { // Add a sub-menu for showing all activities as a list item. SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount, collapsedActivityCount, mContext.getString(R.string.abc_activity_chooser_view_see_all)); for (int i = 0; i < expandedActivityCount; i++) { ResolveInfo activity = dataModel.getActivity(i); expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager)) .setIcon(activity.loadIcon(packageManager)) .setOnMenuItemClickListener(mOnMenuItemClickListener); } } }
dataModel.getActivityCount() 关键信息,按字面展开 Activity 个数,下面 for 循环依次添加 icon 项
具体怎么获取 count? 跟进 ActivityChooserModel 中 mActivities
/** * Loads the activities for the current intent if needed which is * if they are not already loaded for the current intent. * * @return Whether loading was performed. */ private boolean loadActivitiesIfNeeded() { if (mReloadActivities && mIntent != null) { mReloadActivities = false; mActivities.clear(); List<ResolveInfo> resolveInfos = mContext.getPackageManager() .queryIntentActivities(mIntent, 0); final int resolveInfoCount = resolveInfos.size(); for (int i = 0; i < resolveInfoCount; i++) { ResolveInfo resolveInfo = resolveInfos.get(i); mActivities.add(new ActivityResolveInfo(resolveInfo)); } return true; } return false; }
可以看到这个 mActivities.add 要是旧版本的 ShareActionProvider 我们在此处进行过滤应该就能生效
但 chrome 用的 androidX 库,只能从 queryIntentActivities() 入手,通过简单模拟 shareIntent
下面这段代码应该就是查询 share 列表代码
Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("text/plain"); List<ResolveInfo> resolveInfos = getPackageManager().queryIntentActivities(shareIntent, 0); String action = shareIntent.getAction(); final int resolveInfoCount = resolveInfos.size(); LogUtils.d("resolveInfoCount="+resolveInfoCount + " action="+action); for (int i = 0; i < resolveInfos.size(); i++) { ResolveInfo resolveInfo = resolveInfos.get(i); LogUtils.d("resolveInfoCount="+resolveInfo.toString()); LogUtils.i("resolveInfoCount=ResolveInfo{a8238ee "+resolveInfo.activityInfo.packageName+"/"+resolveInfo.activityInfo.name); }
看到 log 打印正好是分享界面显示的数据,那很明显了需要去 PackageManagerService 中做文章
同样也需要知道是谁调用了 queryIntentActivities 方法,获取当前任务栈中顶部 Activity 包名
ActivityManager.getRunningTasks(1) 如果是 chrome 则过滤 message
queryIntentActivitiesInternal() 有很多地方调用,log 会打印会多,通过多次过滤分析,点击分享瞬间
传递 action 为 android.intent.action.SEND,只有当 action 为 SEND 时才需要过滤,通过上面 demo
调用代码也能发现实际上就是 android.intent.action.SEND 决定的。
@Override public List<ResolveInfo> queryIntentActivities( Intent intent, String resolvedType, int flags, int filterCallingUid, int userId) { return PackageManagerService.this .queryIntentActivitiesInternal(intent, resolvedType, flags, 0, filterCallingUid, userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); } private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { if (!mUserManager.exists(userId)) return Collections.emptyList(); final String instantAppPkgName = getInstantAppPackageName(filterCallingUid); mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission */, false /* checkShell */, "query intent activities"); final String pkgName = intent.getPackage();//cczheng String action = intent.getAction(); android.util.Log.d("LogUtils", "instantAppPkgName="+instantAppPkgName+" resolveInfoCount="+pkgName + " action="+action); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { intent = intent.getSelector(); comp = intent.getComponent(); } } .... android.util.Log.i("LogUtils", "step 3"); if (result!=null) { android.util.Log.i("LogUtils", "step 4=="+result.size());//cczheng } if ("android.intent.action.SEND".equals(intent.getAction())) { blockGoogleMessagingShare(result); } return applyPostResolutionFilter( result, instantAppPkgName, allowDynamicSplits, filterCallingUid, resolveForStart, userId, intent); }
关于 init.rc 启动可以参考下面
Android的init过程(二):初始化语言(init.rc)解析