首页 > 其他分享 >实现多个 Launcher 并存切换

实现多个 Launcher 并存切换

时间:2023-12-07 14:12:02浏览次数:27  
标签:Log Launcher TAG intent 切换 并存 new Intent

实现这样一个功能,系统自带Launcher保留,用户可选择其他应用作为开机桌面(替代系统Launcher),并可以切回原来的桌面

一、建立一个作为桌面的app

首先看下Android 自带Launcner 的xml配置(Android13):
/packages/apps/Launcher3/AndroidManifest.xml

<!--
43         Main launcher activity. When extending only change the name, and keep all the
44         attributes and intent filters the same
45         -->
46         <activity
47             android:name="com.android.launcher3.Launcher"
48             android:launchMode="singleTask"
49             android:clearTaskOnLaunch="true"
50             android:stateNotNeeded="true"
51             android:windowSoftInputMode="adjustPan"
52             android:screenOrientation="unspecified"
53             android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
54             android:resizeableActivity="true"
55             android:resumeWhilePausing="true"
56             android:taskAffinity=""
57             android:exported="true"
58             android:enabled="true">
59             <intent-filter>
60                 <action android:name="android.intent.action.MAIN" />
61                 <action android:name="android.intent.action.SHOW_WORK_APPS" />
62                 <category android:name="android.intent.category.HOME" />
63                 <category android:name="android.intent.category.DEFAULT" />
64                 <category android:name="android.intent.category.MONKEY"/>
65                 <category android:name="android.intent.category.LAUNCHER_APP" />
66             </intent-filter>
67            ···
70         </activity>

可以看到其中设置了几个与常规activity不常见的category,各自对应的的Intent中的几个属性,定义如下:

	// 作为开机启动界面
	public static final String CATEGORY_HOME = "android.intent.category.HOME";
	// monkey或其他测试工具 可执行
	public static final String CATEGORY_MONKEY = "android.intent.category.MONKEY";
	// 标记Home app (非必须)
	public static final String CATEGORY_LAUNCHER_APP = "android.intent.category.LAUNCHER_APP";

创建一个应用,一般作为桌面会用到很多系统权限,配置为系统应用
image

android:sharedUserId="android.uid.system"

然后给启动activity 做属性配置,直接使用Launcher的配置即可,也可做部分删减,打包安装到设备上,返回主页的时候,就会发现有提示 要选择哪一个作为桌面,添加一个应用作为桌面的功能到此完成。

二、跳过系统选择提示框,直接进入配置的桌面

大概的实现思路有几下几种:
1、全局拦截,类似与一般app中使用hook跳转登录界面的做法,当判断跳转系统桌面时,就进入自定义的界面
2、直接替换系统预装Launcher.apk,一般的目录system/priv-app/Launcher/Launcher.apk,这种方式需要获取设备权限,且替换之后需要重启等操作才能生效,可以作为备选
3、走系统自带流程,做默认选择应用动作 直接跳过弹框选择操作

这里采用的时第三种方案实现的

1、抓取 弹框相关信息

在弹出出现时,抓取当前窗口信息和activity信息,发现此时时处在 ResolverActivity中,对应抓取命令可使用

dumpsys window | grep mCurrentFocus  //抓取前台activity
dumpsys window windows  // 查看当前的窗口信息

找到对应的代码,做进一步分析
frameworks\base\core\java\com\android\internal\app\ResolverActivity.java

2、界面逻辑简单梳理

获取所有配置android.intent.category.HOME 的界面,根据各种判断决定时候弹出弹框给用户选择

3、处理方式

3.1 实现思路

在进入当前界面时,直接判断是否配置了需要的 launcher,然后做对应跳转,直接finish ResolverActivity

3.2 具体逻辑

找到ResolverActivityonCreate()方法如下

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Use a specialized prompt when we're handling the 'Home' app startActivity()
        Log.d(TAG, "onCreate: 11111111111111111111111111111");
        final Intent intent = makeMyIntent();
        final Set<String> categories = intent.getCategories();
        if (Intent.ACTION_MAIN.equals(intent.getAction())
                && categories != null
                && categories.size() == 1
                && categories.contains(Intent.CATEGORY_HOME)) {
            // Note: this field is not set to true in the compatibility version.
            mResolvingHome = true;
        }

        setSafeForwardingMode(true);

        onCreate(savedInstanceState, intent, null, 0, null, null, true);
    }

   /**
     * Compatibility version for other bundled services that use this overload without
     * a default title resource
     */
    @UnsupportedAppUsage
    protected void onCreate(Bundle savedInstanceState, Intent intent,
                            CharSequence title, Intent[] initialIntents,
                            List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
        Log.d(TAG, "onCreate: 2222222222222222222222222222");
        onCreate(savedInstanceState, intent, title, 0, initialIntents, rList,
                supportsAlwaysUseOption);
    }

    protected void onCreate(Bundle savedInstanceState, Intent intent,
                            CharSequence title, int defaultTitleRes, Intent[] initialIntents,
                            List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
        setTheme(appliedThemeResId());
        super.onCreate(savedInstanceState);
        Log.w(TAG, "--------------------  onCreate: 333333333333333333333333");
//-------- add code --------
        if (mResolvingHome) {
            if (setDefaultLauncher()) {
                finish();
                return;
            }
        }
//-------- add code ---------
        // Determine whether we should show that intent is forwarded
        // from managed profile to owner or other way around.
        setProfileSwitchMessage(intent.getContentUserHint());

        mLaunchedFromUid = getLaunchedFromUid();
        ···
    }

image

3.3 添加默认跳转逻辑

private boolean setDefaultLauncher() {
        try {
            String configLauncher = SystemProperties.get("persist.skg.home.pkg", null);
            // 未配置其他app,走默认的流程
            if (TextUtils.isEmpty(configLauncher)) {
                Log.i(TAG, "configLauncher is empty,use default");
                return false;
            }
            String[] packConfig = configLauncher.split("/");
            String defPackageName = packConfig[0];
            String defClassName = packConfig[1];

            Log.i(TAG, "Resolver configLauncher : PackageName = " + defPackageName + " ClassName = " + defClassName);

            final PackageManager pm = getPackageManager();
            try {
                PackageInfo packageInfo = pm.getPackageInfo(defPackageName, PackageManager.GET_GIDS);
                Log.i(TAG, "configLauncher: " + packageInfo);
                if (packageInfo == null) {
                    Log.e(TAG, "配置的launcher app 未安装");
                    return false;
                }
                //TODO 过滤掉三方应用也可放在此处,一般设备不允许非系统应用由此功能
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "默认Launcher应用未安装", e);
                return false;
            }

            //清除当前默认launcher
            ArrayList<IntentFilter> intentList = new ArrayList<IntentFilter>();
            ArrayList<ComponentName> cnList = new ArrayList<ComponentName>();
            pm.getPreferredActivities(intentList, cnList, null);
            IntentFilter dhIF = null;
            for (int i = 0; i < cnList.size(); i++) {
                dhIF = intentList.get(i);
                if (dhIF.hasAction(Intent.ACTION_MAIN) && dhIF.hasCategory(Intent.CATEGORY_HOME)) {
                    pm.clearPackagePreferredActivities(cnList.get(i).getPackageName());
                }
            }
            // 添加Launcher
            IntentFilter filter = new IntentFilter();
            filter.addAction("android.intent.action.MAIN");
            filter.addCategory("android.intent.category.HOME");
            filter.addCategory("android.intent.category.DEFAULT");

            Intent intent = new Intent(Intent.ACTION_MAIN);
            intent.addCategory(Intent.CATEGORY_HOME);
            List<ResolveInfo> list = new ArrayList<ResolveInfo>();
            list = pm.queryIntentActivities(intent, 0);
            final int N = list.size();
            ComponentName[] set = new ComponentName[N];
            int bestMatch = 0;
            for (int i = 0; i < N; i++) {
                ResolveInfo r = list.get(i);
                set[i] = new ComponentName(r.activityInfo.packageName, r.activityInfo.name);
                if (r.match > bestMatch) bestMatch = r.match;
            }
            ComponentName preActivity = new ComponentName(defPackageName, defClassName);
            pm.addPreferredActivity(filter, bestMatch, set, preActivity);

            return startConfigLauncher(defPackageName, defClassName);
        } catch (Exception e) {
            Log.e(TAG, "set default launchar fail", e);
            return false;
        }
    }
    private boolean startConfigLauncher(String pkg, String cls) {
        try {
            Intent intent1 = new Intent(Intent.ACTION_MAIN);
            intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent1.addCategory(Intent.CATEGORY_LAUNCHER);
            intent1.setComponent(new ComponentName(pkg, cls));
            startActivity(intent1);
            return true;
        } catch (Exception e) {
            Log.i(TAG, pkg + " start Fail");
            return false;
        }
    }

SystemProperties中的key 值可以在mk 文件中配置做预置

PRODUCT_PROPERTY_OVERRIDES += persist.skg.home.pkg=pkgName/classname

3.4 验证

1、修改对应的 SystemProperties 值后,重启直接进入配置的应用,跳过弹框询问
2、开机进入桌面,进入一个应用,此时修改SystemProperties,按Home 或者Intent跳转主页,跳转到配置的桌面应用,生效

// 跳转代码
Intent intent = new Intent();  //创建Intent对象
intent.setAction(intent.ACTION_MAIN);  //设置action动作属性
intent.addCategory(intent.CATEGORY_HOME); //设置categoty种类显示主屏幕
startActivity(intent); //启动Activity

3.5 编译

  • 只修改了ResolverActivity,就直接重新编译 framworks.jar,再替换设备中的,替换方式可查看 这里是替换方式
  • 与之属性到mk中。重新编包刷机

3.5 待改进项

在跳转进入 ResolverActivity 之前就做默认操作,少一个跳转开启activity 的动作,或者在启动Launcher 的其他环节做处理

参考:
https://zhuanlan.zhihu.com/p/623528604

标签:Log,Launcher,TAG,intent,切换,并存,new,Intent
From: https://www.cnblogs.com/qiyuexiaxun/p/17881866.html

相关文章

  • linux - 使用sudo 和 su 切换用户身份
    1.su切换到root身份su-切换到其他普通用户su-lgogs切换到其他普通用户并执行命令su-lgogs-cpwd2.sudo切换到root身份sudo-i切换到root身份sudosu-重新设定root的密码sudopasswdroot以提升的权限执行命令sudoless/etc/sudoers以普通......
  • 配置多个版本JDK可互相切换
     同一电脑下,可安装多个版本jdk,并且互相切换使用。 JavaJDK官网:https://www.oracle.com/java/technologies/downloads/#jdk17-windows 在配置前,需要做如下配置: 1.  编辑系统变量-->变量名:CLASSPATH变量值:.;%JAVA_HOME%\lib\dt.jar;......
  • ue 切换像素流分辨率
    参考文档https://docs.unrealengine.com/4.27/zh-CN/SharingAndReleasing/PixelStreaming/PixelStreamingIntro/https://docs.unrealengine.com/5.0/zh-CN/unreal-engine-pixel-streaming-reference/准备工作信令服务和前端可以先把信令服务起起来,如至于前端的话,参考:htt......
  • 手机被植入了木马,银行APP显示切换用户
    前几天一个大哥找我咨询,说跟之前的合作伙伴发生了矛盾,后来怀疑他的手机被监控了,问了几个问题:大哥问:我的银行APP显示切换用户,对方是不是利用短信接持了我的短信,登录了我的银行app。我答:显示切换了用户是否有转账呢?如果能短信劫持到你的短信大概率你手机上的任何app都能利用劫持的短......
  • 信号自动切换的功能
    1、需求说明 需求:        很多时候会遇到矩阵的自动切换逻辑的问题,以下分享两个比较常用的宏模块说明       自动切换信号源    当检测到有1路信号输入时,两个输出为复制模式    当检测到有2路信号输入时,两个输出分别输出2路信号    当检测到......
  • 一个自动切换模块【苏】
    1、需求说明输入信号分别是1/2/3/4、输入就一个信号信号源的优先级从高到低,为1~4可以手动或自动切换两种方式代码如下#DEFAULT_VOLATILE#ENABLE_STACK_CHECKING#ENABLE_TRACEDIGITAL_INPUTEnable,_skip_,Video_Detecte[4];ANALOG_INPUTAin[2];ANALOG_OUTPUTou......
  • Qt/C++音视频开发57-切换音视频轨道/切换节目流/分别切换音频视频轨道
    一、前言对各种音视频文件格式的支持,是一个播放器的基础功能。一般的音视频文件只有1路流,比如音频文件只有1路音频流,视频文件只有1路音频1路视频流,实践过程中发现,还有一种ts格式的文件,可能有多路流,这种格式一般是将多路节目流封装到一个文件中,用户可以根据自己的需要切换不同的节......
  • 直播平台源代码,实现一个简单的带tabs选项卡切换的首页导航功能
    直播平台源代码,实现一个简单的带tabs选项卡切换的首页导航功能 package.json: { "name":"angular-router", "version":"0.0.0", "scripts":{  "ng":"ng",  "start":"ngserve",  "bui......
  • 安装多个版本的Node以及版本切换nvm
    一、安装多个版本的Node(也可以直接通过本文第二种方法下载多个版本的Node)1.下载Node.js安装包(window系统下载以msi结尾的安装包)  2.安装第一个版本的Node(此处首先安装的较低版本:12.22.12)2.1先创建Node文件夹,再创建v12.22.12文件夹(用于保存所有版本的Node)  ......
  • win11照片无法切换到下一张
    1–更改文件夹搜索选项1.只需按住键盘上的Windows+E组合键即可打开文件资源管理器。在文件资源管理器的顶部,单击三点菜单并选择选项。2.这将为您打开“文件夹选项”窗口。单击“搜索”选项卡将其选中。3.在这里,选中与在文件夹中搜索系统文件时不使用索引相关的框(搜索......