MTK - Android12 默认唯一Launcher作为HOME程序-手势导航左右滑动无效问题

提示: 需求很简单就是 在多Launcher-HOME程序下,要默认一个主HOME程序

文章目录


前言-需求

1、实际场景

  • 平台-版本:MTK平台,Android12
  • 需求:产品有多个Launcher/HOME 程序,又不能去掉他们的HOME属性,保证默认Home 程序
  • 开机默认的是手势导航,而不是底部虚拟按键

2、遇到问题-需要实现的技术点

  • 1、默认Home程序,在多HOME程序下如何开机默认一个HOME程序,又不能改HOME属性的优先级
  • 2、系统默认的是手势导航,如何解决手势导航无效问题

一、实际遇到问题

这里列举遇到的实际问题,然后后面逐一给出解决方案。

多HOME程序如何开机默认一个主屏程序

这里直接提供截图说明,多个HOME程序,是需要默认一个的,不然会存在选择HOEM程序一说,比如是需要进入系统设置去设置主屏幕才不会有开机HOME程序选择一说。

手势导航不可用-设置了默认Launcher后手势不可用,必须上划一次才能够用手势导航

已经开机实现了设置默认的 HOME 主屏程序,但是手势导航左右滑动无用,必须上划一次才能够正常使用。

涉及到的知识点广而深

在解决实际问题时候发现涉及到蛮多问题的,小结如下:

  • Launcher,手势滑动相关,在Android12 及以后版本,手势相关业务逻辑都移动到Launcher3 里面去了
  • SystemUI,手势分发、开机就在SystemUIService 里面设置默认主屏应用
  • Role ,角色设置问题,比如默认HOME,系统是有角色一说的。
  • Permission,权限一说,Android体系作为Linux 子体系一种,各个方面都涉及到权限一说的。系统里面有一个app 就是处理权限的 PermissionController

所以 ,当我们一个一个去解决实际问题时候,会不断暴漏新的问题,如果需要解决实际问题,仅仅AI 编程是无法帮你解决的,还是需要理解并掌握各个模块基本内容,这里面知识点广且深,需要慢慢理解并掌握。

二、参考资料

备注-提醒:

  • 平台差异性: 针对具体功能,很多时候跟平台相关,比如 我这里是MTK平台解决HOME 默认主屏幕,上划、开机启动等跳转到指定HOME程序界面时候,尽量以具体平台资料为准,其它仅为参考可自行验证,切不可混为一谈。比如 大量RK 平台相关资料,其实实际并不适用MTK平台;
  • 版本差异性: 针对不同的Android版本,可能有不同的API、方法、业务流程、模块可能都不一样,尽量多思索,多看源码,整理思路, 然后具体实现。

这里仅仅提供以前关联的相关文章,仅供参考,补充技能,知识点理解:

Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用)
MTK平台-内置应用作为系统Launcher
Android11-Android15-Launcher3去抽屉功能-定制应用-另辟蹊径方案
MTK平台-去除第一次开机-默认权限提示框

三、修改-相关文件

如下文件修改后即可完成项目需求:这些文件都是和需求相关的 修改或者新增

java 复制代码
/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/SystemUIService.java [修改]
vendor/mediatek/proprietary/packages/apps/SystemUI/AndroidManifest.xml  [修改]
/vendor/mediatek/proprietary/packages/apps/PermissionController/AndroidManifest.xml  [修改]
/vendor/mediatek/proprietary/packages/apps/PermissionController/src/com/android/permissioncontroller/SetDefaultHomeBroadcastReceiver.java   [新增]
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/AndroidManifest-launcher.xml   [修改-也可不改]
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java  [修改]

四、实现方案

A、默认主屏幕

项目中产品本身有多个Home 程序,客户各种原因多个Home程序的App 不去更改,但是Home 程序有优先级,

为什么要默认主屏幕

  • 客户本身有多个Home 应用,用优先级区分:android:priority="1"
  • 客户又是可以进入系统设置的,导致用户可以进行主屏幕切换导致bug
  • 客户有应用市场,可以下载各种软件,导致存在多个Home 程序,会造成Launcher 选择
  • 多个Home 程序下,机器本身是没有设置主屏,存在一定的稳定性风险

为什么没有参考RK平台默认主屏幕方案

首先个人并没有验证成功, RK平台以工控为主 平板产品 ;我用的MTK平台 手机方案为主,所以在修改部分业务逻辑时候存在各种问题。

比如: 网上很多参考资料,默认主屏,大家也可以参考,基本思路:

  • 主屏选择界面直接跳转到自己的HOME 程序
  • 在AMS、PMS 里面设置 进入HOME程序时候、查询HOME程序时候直接返回到自己的 HOME主程序

我就在想,那何必不直接设置主屏程序不就可以了嘛。

默认主屏幕实现方案

思路

参考资料:Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用) 之前自己写过相关的DEMO和相关源码分析,可参考,直接用。 之前分析过业务流程和源码,可实战参考:

具体实现方案

我选择在SystemUIService 里面去实现设置主屏幕操作,或者在其它系统服务中的合适位置即可,核心代码如下:

涉及到源码修改:

java 复制代码
/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/SystemUIService.java [修改]
vendor/mediatek/proprietary/packages/apps/SystemUI/AndroidManifest.xml  [修改]

在onCreate 方法中用Handler 延时处理

java 复制代码
	// modify by fangchen start
 
 		
		 Log.d(TAG,"=======SystemUIService  zhunbei setDefaultHome ======");
		// setDefaultHome(getApplicationContext());
		 homeHandler.removeCallbacksAndMessages(null);
         homeHandler.postDelayed(setHomeRunable,10000);
		// modify by fangchen end 	 

开启线程,处理设置 Home 程序逻辑

java 复制代码
	 // modify by fangchen start
     Runnable setHomeRunable=new Runnable() {
        @Override
        public void run() {
		 String isFristStart=  SystemProperties.get("persist.sys.firststart","");
		 Log.d(TAG,"======setHomeRunable=======isFristStart:"+isFristStart);
		 if(!"1".equals(isFristStart)){
			Log.d(TAG," diyici kaiji setDefaultHome ");
			setDefaultHome(getApplicationContext());
		 }else{
			 Log.d(TAG,"  has set  setDefaultHome ");
		 }
		}
		
	};
	// modify by fangchen end

最后,设置HOME 程序,业务逻辑

java 复制代码
		// modify by fangchen start 
	private void setDefaultHome(Context mContext) {
    
            Log.d(TAG,"=============setDefaultHome====");	
	     
        
	
 
    Log.d(TAG,"=============setDefaultHome====");	

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        return;
    }

    RoleManager roleManager = mContext.getSystemService(RoleManager.class);
    Executor executor = mContext.getMainExecutor();
    UserHandle userHandle = UserHandle.of(0); 

    Consumer<Boolean> callback = successful -> {
        if (successful) {
            Log.i(TAG, "set home success ");
            SystemProperties.set("persist.sys.firststart","1");
			
		 /*try {
        
        Intent intent = new Intent("com.android.systemui.action.RESTART");
        intent.setPackage("com.android.systemui");
        mContext.sendBroadcast(intent);
        Log.i(TAG, "==========com.android.systemui.action.RESTART");
         } catch (Exception e) {
        Log.e(TAG, "  SystemUI restart error ..." );
          }*/
			
			
			
			Log.d(TAG," ========================to startActivity  homeIntent ");
			Intent homeIntent = new Intent(Intent.ACTION_MAIN);
            homeIntent.addCategory(Intent.CATEGORY_HOME);
            homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mContext.startActivity(homeIntent);
			
			
			
		  new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
			 

			 /*try {
				
                 Log.i(TAG, "Restart TouchInteractionService  start ==============");						 
               Intent touchIntent = new Intent("android.intent.action.QUICKSTEP_SERVICE");
               touchIntent.setPackage("com.komect.ajlauncher");  
               mContext.stopService(touchIntent);
               mContext.startService(touchIntent);

 
               Intent restartIntent = new Intent("com.android.systemui.action.RESTART");
               restartIntent.setPackage("com.android.systemui");
               mContext.sendBroadcast(restartIntent);                  
                Log.i(TAG, "Restart TouchInteractionService ok");
                } catch (Exception e) {
					e.printStackTrace();
                    Log.e(TAG, "TouchInteractionService restart error ");
                }*/                      
                Log.d(TAG,"========send BroadCast to PermissionController================"); 
                try {
                  Intent intent = new Intent("com.android.action.SET_DEFAULT_HOME");
                  intent.setPackage("com.android.permissioncontroller");
                  intent.putExtra("home_package", "com.komect.ajlauncher");
                  mContext.sendBroadcast(intent);
                    } catch (Exception e) {
                     e.printStackTrace();
                }	
            }, 5000); 
            // =================================================		
        } else {
            Log.e(TAG, "set home failed ");
        }
	
		
    };

    try {
        
        Method addRoleHolderAsUser = roleManager.getClass().getMethod(
                "addRoleHolderAsUser",
                String.class,
                String.class,
                int.class,        // flags = 0 / 1
                UserHandle.class,
                Executor.class,
                Consumer.class
        );
        addRoleHolderAsUser.setAccessible(true);

      
        addRoleHolderAsUser.invoke(
                roleManager,
                RoleManager.ROLE_HOME,  
                "com.komect.ajlauncher", 
                0,                    
                userHandle,
                executor,
                callback
        );
        // ================ to sync role ================
       try {
            Method sync = roleManager.getClass().getMethod("syncRolesAsync");
            sync.setAccessible(true);
            sync.invoke(roleManager);
        } catch (Exception ignored) {}
        // ==============================================

       } catch (Exception e) {
           e.printStackTrace();
           Log.e(TAG, "setHomeError:" + e.getMessage());
       }
  }
 	
	
	
	// modify by fangchen end 

最后,提供SystemUI App 修改Role ,即设置HOME程序的权限

java 复制代码
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

B、解决手势导航失效问题

实际实现项目需求,发现:

  • 开机主屏幕设置成功了的,但是手势导航失效。
  • 一定要去系统设置,在默认主屏幕中去点击一次后,手势导航才有效果;或者上划滑动一次,进入历史任务后手势导航左右滑动手势就有效果的。

经过反复验证和实践发现,就是手势导航没有关联到HOME程序,手势失效了,哪怕重启机器也没有用的。

思路-踩坑

这里有两个思路:

  • 直接在SystemUIService 里面去绑定不就可以了嘛
  • 直接模拟在 默认主屏幕应用的App 中,去模拟一个后台的服务或者广播,去设置主屏幕、去绑定手势导航不就可以了嘛。

踩坑:

  • 网上资料少,AI解决方案太多,都是无效的,比如如下:重启 SystemUI 服务、绑定主屏幕应用、反射调用AMS、PMS、ROLE 方法,都是无效的,比如如下: 具体原因:刷新的核心原理根部不是网上说的这些,必须了解机制才行,不然都是无用功。
java 复制代码
	 /*try {
				
                 Log.i(TAG, "Restart TouchInteractionService  start ==============");						 
               Intent touchIntent = new Intent("android.intent.action.QUICKSTEP_SERVICE");
               touchIntent.setPackage("com.komect.ajlauncher");  
               mContext.stopService(touchIntent);
               mContext.startService(touchIntent);

 
               Intent restartIntent = new Intent("com.android.systemui.action.RESTART");
               restartIntent.setPackage("com.android.systemui");
               mContext.sendBroadcast(restartIntent);                  
                Log.i(TAG, "Restart TouchInteractionService ok");
                } catch (Exception e) {
					e.printStackTrace();
                    Log.e(TAG, "TouchInteractionService restart error ");
                }*/         

解决方案

涉及到修改文件:

java 复制代码
/vendor/mediatek/proprietary/packages/apps/PermissionController/AndroidManifest.xml  [修改]
/vendor/mediatek/proprietary/packages/apps/PermissionController/src/com/android/permissioncontroller/SetDefaultHomeBroadcastReceiver.java   [新增]

参考资料:Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用) 之前自己写过相关的DEMO和相关源码分析,可参考,直接用。 之前分析过业务流程和源码,可实战参考:

还是去搞清楚 流程、系统是如何实现的,扣源码,找到具体方法:

1、新增广播-模拟设置主屏幕应用
java 复制代码
package com.android.permissioncontroller;

import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

import com.android.permissioncontroller.role.model.Role;
import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.ui.DefaultAppViewModel;

public class SetDefaultHomeBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "SetDefaultHome";

    public static final String ACTION_SET_DEFAULT_HOME = "com.android.action.SET_DEFAULT_HOME";
    public static final String EXTRA_HOME_PACKAGE = "home_package";

    @Override
    public void onReceive(Context context, Intent intent) {
		
		 Log.i(TAG, "===========set  HOME  start  =onReceive==");
        if (!ACTION_SET_DEFAULT_HOME.equals(intent.getAction())) {
            return;
        }

        String homePackage = intent.getStringExtra(EXTRA_HOME_PACKAGE);
        if (homePackage == null || homePackage.isEmpty()) {
            return;
        }

		Log.i(TAG, "===========set  HOME  start  ===" + homePackage);

        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
        UserHandle user = UserHandle.of(userManager.getUserHandle());

         Role role = Roles.get(context).get(android.app.role.RoleManager.ROLE_HOME);

         DefaultAppViewModel.Factory factory = new DefaultAppViewModel.Factory(
                role,
                user,
                (Application) context.getApplicationContext()
        );
        DefaultAppViewModel viewModel = factory.create(DefaultAppViewModel.class);

         viewModel.setDefaultApp(homePackage);

        Log.i(TAG, "===========set  HOME  end ===" + homePackage);
		
	    	 Intent homeIntent = new Intent(Intent.ACTION_MAIN);
        homeIntent.addCategory(Intent.CATEGORY_HOME);
        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(homeIntent);
		
		
    }
}

最核心的模拟的就是调用了这行代码:

java 复制代码
 viewModel.setDefaultApp(homePackage);
2、配置文件中-配置广播
java 复制代码
       <receiver
         android:name=".SetDefaultHomeBroadcastReceiver"
         android:exported="true"
          android:enabled="true">
           <intent-filter>
             <action android:name="com.android.action.SET_DEFAULT_HOME" />
           </intent-filter>
        </receiver>
3、调用广播-实现业务逻辑

如上,在SystemUIService 中,设置了主屏幕后,做了一件事,就是 通知广播,通知的广播就是这里的广播,模拟设置主屏幕应用:

java 复制代码
  try {
                  Intent intent = new Intent("com.android.action.SET_DEFAULT_HOME");
                  intent.setPackage("com.android.permissioncontroller");
                  intent.putExtra("home_package", "com.komect.ajlauncher");
                  mContext.sendBroadcast(intent);
                    } catch (Exception e) {
                     e.printStackTrace();
                }	
4、同步手势导航-彻底解决手势无效问题

在设置完主屏幕后,最后一个步骤就是 让主屏幕应用 同步绑定手势一次即可:我们看看广播里面我们是如何绑定的,就是重新启动了一次HOME程序

java 复制代码
  	      Intent homeIntent = new Intent(Intent.ACTION_MAIN);
        homeIntent.addCategory(Intent.CATEGORY_HOME);
        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(homeIntent);
让系统重新走一遍 "桌面启动流程"
  • = 等价于你手动按了一下 HOME 键
  • = 等价于你在设置界面点击确认
系统内部会悄悄自动执行 3 个动作
  • 设置默认 HOME(你做了)
  • 自动触发:AMS 重新解析 HOME Intent
  • 自动触发:SystemUI/QuickStep 重新绑定
小结

实战项目中,主屏幕也设置了,就是手势无效;在RK平台中自己做过相关需求,不会有这样的问题。 仔细分析代码 自己在SystemUIService 和 广播里面设置了主屏应用成功了,就是手势导航无效。

自己就思考:都是设置主屏幕应用,为什么在 默认主屏幕应用界面中点击后手势导航有效,而 在服务和广播里面就是无效呢? 或者说 为什么滑动上划一次 左右手势导航就有效了呢?

最后自己找了很多资料,发现: 界面点击是「前台可见操作」,系统自动触发了 SystemUI/Launcher 绑定广播是「后台隐形操作」,系统 不会自动触发绑定!

更奇怪的是哪怕有主屏幕应用,重启机器也无效,所以特别特别奇怪。 好在 最后模拟重新进入Home 程序,不就行了嘛。

C、解决上划崩溃问题

涉及到源码:

java 复制代码
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/AndroidManifest-launcher.xml   [修改-也可不改]
/vendor/mediatek/proprietary/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java  [修改]

为什么这里要改:

  • 原生系统默认的主屏幕就一个 Launcher3,所以关联的HOME 包名类名不需要处理的。但是现在HOME 主屏幕定制了的,所以这里要改成原生Launcher3 的包名,不要变。
  • 为什么要保留: 这里跟手势挂钩 也就是说 Launcher3 跟手势挂钩的,用定制化的Home 程序包名类名后,手势导航直接失效。
java 复制代码
    public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
        mContext = context;
        mDeviceState = deviceState;
        mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
                //.addCategory(Intent.CATEGORY_HOME)//huanghb modify
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
        ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
        //huanghb modify
        //ComponentName myHomeComponent =
        //        new ComponentName(context.getPackageName(), info.activityInfo.name);
        ComponentName myHomeComponent =
                new ComponentName("com.android.launcher3", "com.android.launcher3.uioverrides.QuickstepLauncher");
        //huanghb end
        mMyHomeIntent.setComponent(myHomeComponent);
        mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);

        ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
        mFallbackIntent = new Intent(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_DEFAULT)
                .setComponent(fallbackComponent)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        try {
            ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
                    mFallbackIntent.getComponent(), 0 /* flags */);
            mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
        } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }

        mContext.registerReceiver(mUserPreferenceChangeReceiver,
                new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
        updateOverviewTargets();
    }

D、解决MTK 平台,开机总是默认打开 权限授权界面

涉及源码:vendor/mediatek/proprietary/packages/apps/PermissionController/AndroidManifest.xml

就是如下界面,之前有笔记详细分析过,这里不做重点讲解,直接用之前的方案即可: MTK平台-去除第一次开机-默认权限提示框

总结

  • 多看源码,默认主屏幕、手势导航相关和其它平台不一样,多思考、多看源码、多推敲。
  • 涉及到的手势知识点蛮多的,建议自行补充基本知识点。在遇到手势不可用的时候要有自己的思路,然后一步一步解决实际问题
  • 这里在在这里插入代码片中 默认主屏幕和在广播中模拟设置主屏幕其实是重复的,没关系这里实现功能即可。