当前位置: 首页 > news >正文

Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用))

Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用))

文章目录

  • 场景需求
  • 参考资料
  • 思路
    • 期待效果
  • 实现方案
  • 源码流程分析和思路实现
    • DefaultAppActivity
    • HandheldDefaultAppFragment
    • HandheldDefaultAppPreferenceFragment
    • DefaultAppChildFragment
    • DefaultAppViewModel
    • ManageRoleHolderStateLiveData
    • 小结
    • RoleManager
      • addRoleHolderAsUser
    • IRoleManager aidl 接口
    • RoleService addRoleHolderAsUser
    • RoleControllerManager
    • RoleControllerService
    • IRoleController
    • RoleControllerServiceImpl
      • addRoleHolderInternal
      • RoleManager addRoleHolderFromController
    • RoleService
      • addRoleHolderFromController
      • getOrCreateUserState
    • RoleUserState ->addRoleHolder
      • scheduleWriteFileLocked
      • writeFile
    • RolesPersistenceImpl ->writeForUser
      • getFile 方法
      • roles.xml
      • 小结
      • Launcher 指定默认流程总结
      • 指定其它默认App 方案
  • 总结
    • 扩展知识
    • adb 命令来动态设置默认的Launcher
    • adb 命令实现思考


场景需求

  • 客需项目中,客户需要有自己Launcher 形态出现
    Android系统本身有自己的Launcher,目前看到的基本上都是Launcher3,几年前还看到Launcher2. 目前接触到的原生Launcher基本都是Launcher3,客户需要有自己Launcher 形态出现

  • 客户在定制自己产品时候有自己默认Launcher,或者整个产品形态要么只有自己一个Launcher 要么客需Launcher和系统Launcher3,但是Launcher3 从不显示

  • 在已经成型的产品中,可能多个Launcher存在,客户需要在已有产品的基础上,动态切换自己默认的Launcher。 很多白牌产品,借用已有的产品形态,更换Launcher 就是一个新的产品,最重要的是实际商业中不需要备货一说

备注: 默认电话、短信、浏览器和主屏 一样的思路,代码都完全一样,参数不一样而已。

参考资料

Setting 里面切换不同Launcher

高通Android 8.1/9/10/11/12/13 通过包名设置默认launcher
android 10 设置app为默认浏览器
Android11 设置第三方Launcher并默认 与 如何预置apk

本来是分析 默认Launcher 的,实际实验发现 默认电话、短信、桌面、浏览器 是一样的思路,索性放在一起,以默认Launcher为例进行总结说明。

思路

从设置默认主屏幕地方入手,设置->Launcher应用->主屏幕应用选择-> 桌面应用切换
然后进行源码分析,实现的具体方案是怎样的。

如下,实际设置中切换的入口
在这里插入图片描述

期待效果

能在应用层实现这个效果,一个界面动态切换不同的Launcher
可以adb 命令实现这个功能
在效果一的基础上,可以考虑集成到framework层,对外提供接口,应用调用framework层接口也可以。

如下测试图,验证过,三个按钮实现切换。

在这里插入图片描述

实现方案

对 RoleManager 类的 addRoleHolderAsUser 方法,进行反射实现。

路径:/packages/modules/Permission/framework-s/java/android/app/role/RoleManager.java

实际控制方法 如下反射实现:

 val roleManager: RoleManager =ContextProvider.get().context.getSystemService<RoleManager>(RoleManager::class.java)val executor: Executor = ContextProvider.get().context.getMainExecutor()val callback =Consumer<Boolean> { successful: Boolean ->if (successful) {Log.i(TAG, " 成功了==> Package " + "added" + " as role holder, role: " + "测试Home 桌面" + ", package: " + "com.nd.android.pandahome2")} else {Log.i(TAG, " 失败了==>  Package " + "added" + " as role holder, role: " + "测试Home 桌面" + ", package: " + "com.nd.android.pandahome2")}}val addRoleHolderAsUser = roleManager.javaClass.getMethod("addRoleHolderAsUser",String::class.java, String::class.java, Int::class.java,UserHandle::class.java,Executor::class.java,Consumer::class.java)addRoleHolderAsUser.isAccessible = trueval userHandle = NavigationHelper.newInstance(UserHandle::class.java, arrayOf<Class<*>?>(Int::class.javaPrimitiveType), 0) as UserHandleaddRoleHolderAsUser.invoke(roleManager, "android.app.role.HOME", "com.nd.android.pandahome2",1,userHandle,executor,callback)}

源码流程分析和思路实现

如上 思路 说明部分,以设置进入Launcher 选择为切入点,进行分析,下面主要涉及到的内和方法。

相关调用
DefaultAppActivityfragment = AutoDefaultAppFragment.newInstance(roleName, user);
HandheldDefaultAppFragmentHandheldDefaultAppPreferenceFragment.newInstance(mRoleName, mUser);
HandheldDefaultAppPreferenceFragmentDefaultAppChildFragment.newInstance(mRoleName,mUser);
DefaultAppChildFragmentonPreferenceClick ->setDefaultApp(packageName); -> mViewModel.setDefaultApp
DefaultAppViewModelsetDefaultApp
ManageRoleHolderStateLiveDatasetRoleHolderAsUser
RoleManageraddRoleHolderAsUser
IRoleManageraidl 接口功能定义 addRoleHolderAsUser
RoleServiceaddRoleHolderAsUser
RoleControllerManageronAddRoleHolder
RoleControllerServiceonAddRoleHolder
IRoleControllerIRoleController.aidl 文件
RoleControllerServiceImplonAddRoleHolder ->addRoleHolderInternal
RoleManageraddRoleHolderFromController
RoleServiceaddRoleHolderFromController
RoleUserStateaddRoleHolder->scheduleWriteFileLocked->writeFile
RolesPersistencewriteForUser
RolesPersistenceImplwriteForUser

DefaultAppActivity

路径:/packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppActivity.java 
com.google.android.permissioncontroller/com.android.permissioncontroller.role.ui.DefaultAppActivity 

从包名路径就可以看到,这是一个包,看看实际源码结构,看着是一个软件包 如下,实际发现安卓系统结构里面又没有这个包名的包。导入工程分析
结合界面,就是要分析 桌面应用选择点击动作,看看如下截图,发现它只是一个入口,啥也没有。 进入到HandheldDefaultAppFragment 看看

在这里插入图片描述

HandheldDefaultAppFragment

实际代码截图如下,也是几行代码,它也是一个入口而已
在这里插入图片描述

HandheldDefaultAppPreferenceFragment

实际代码截图如下,发现它也还是一个简单的类,入口而已

在这里插入图片描述

DefaultAppChildFragment

看代码截图,和点击事件有点像的就是 就是 onPreferenceClick 方法了。 继续分析源码

在这里插入图片描述

DefaultAppViewModel

看看类注释,就是默认APP 的功能, setDefaultApp 方法也是设置包名作为默认app 的功能,


/*** {@link ViewModel} for a default app.*/
public class DefaultAppViewModel extends AndroidViewModel {/*** Set an application as the default app.** @param packageName the package name of the application*/public void setDefaultApp(@NonNull String packageName) {if (mManageRoleHolderStateLiveData.getValue() != ManageRoleHolderStateLiveData.STATE_IDLE) {Log.i(LOG_TAG, "Trying to set default app while another request is on-going");return;}mManageRoleHolderStateLiveData.setRoleHolderAsUser(mRole.getName(), packageName, true, 0,mUser, getApplication());}

在这里插入图片描述

ManageRoleHolderStateLiveData

居然是一个 LiveData, 订阅机制。 这也是为什么 更改默认之后全局通知的原因吧,看方法参数解释也是 角色、包名、flag、user 等,说明跟角色、权限类型相关的。

/*** Set whether an application is a holder of a role, and update the state accordingly. Will* be no-op if the current state is not {@link #STATE_IDLE}.** @param roleName the name of the role* @param packageName the package name of the application* @param add whether to add or remove the application as a role holder* @param flags optional behavior flags* @param user the user to manage the role holder for* @param context the {@code Context} to retrieve system services*/public void setRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,boolean add, int flags, @NonNull UserHandle user, @NonNull Context context) {if (getValue() != STATE_IDLE) {Log.e(LOG_TAG, "Already (tried) managing role holders, requested role: " + roleName+ ", requested package: " + packageName);return;}if (DEBUG) {Log.i(LOG_TAG, (add ? "Adding" : "Removing") + " package as role holder, role: "+ roleName + ", package: " + packageName);}mLastPackageName = packageName;mLastAdd = add;mLastFlags = flags;mLastUser = user;setValue(STATE_WORKING);RoleManager roleManager = context.getSystemService(RoleManager.class);Executor executor = context.getMainExecutor();Consumer<Boolean> callback = successful -> {if (successful) {if (DEBUG) {Log.i(LOG_TAG, "Package " + (add ? "added" : "removed")+ " as role holder, role: " + roleName + ", package: " + packageName);}setValue(STATE_SUCCESS);} else {if (DEBUG) {Log.i(LOG_TAG, "Failed to " + (add ? "add" : "remove")+ " package as role holder, role: " + roleName + ", package: "+ packageName);}setValue(STATE_FAILURE);}};if (add) {roleManager.addRoleHolderAsUser(roleName, packageName, flags, user, executor, callback);} else {roleManager.removeRoleHolderAsUser(roleName, packageName, flags, user, executor,callback);}}

小结

上面通过界面到业务代码分析,发现都是设置默认App 入口,经过来4个类的入口调用 直到 ManageRoleHolderStateLiveData 类里面调用到 RoleManager 的 addRoleHolderAsUser 方法,才进入框架层面

RoleManager

路径:

/packages/modules/Permission/framework-s/java/android/app/role/RoleManager.java 

看类注释说明如下: 提供权限roles 管理的功能,对外通过getService 获取它

/*** This class provides information about and manages roles.* <p>* A role is a unique name within the system associated with certain privileges. The list of
..... */
@SystemService(Context.ROLE_SERVICE)
public final class RoleManager {
..............
}

addRoleHolderAsUser

上面关联到的方法,看看源码说明,

  • 从方法注释上看就是权限添加的功能,如果已经存在 权限,那么添加后会被替换replaced
  • 看到注释上面,这个方法是一个隐藏的方法了
/*** Add a specific application to the holders of a role. If the role is exclusive, the previous* holder will be replaced.* <p>* <strong>Note:</strong> Using this API requires holding* {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user* {@code android.permission.INTERACT_ACROSS_USERS_FULL}.** @param roleName the name of the role to add the role holder for* @param packageName the package name of the application to add to the role holders* @param flags optional behavior flags* @param user the user to add the role holder for* @param executor the {@code Executor} to run the callback on.* @param callback the callback for whether this call is successful** @see #getRoleHoldersAsUser(String, UserHandle)* @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)* @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)** @hide*/@RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)@SystemApipublic void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,@ManageHoldersFlags int flags, @NonNull UserHandle user,@CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {........try {mService.addRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),createRemoteCallback(executor, callback));} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

关注mService 是什么,看当前类定义地方,

@NonNullprivate final IRoleManager mService;/*** Create a new instance of this class.** @param context the {@link Context}* @param service the {@link IRoleManager} service** @hide*/public RoleManager(@NonNull Context context, @NonNull IRoleManager service) {mContext = context;mService = service;}

在这里插入图片描述

IRoleManager aidl 接口

IRoleManager 源码路径和源码如下:它只是一个接口定义,一般情况下 这个aidl 在一个服务里面引用,服务来实现 aidl 接口,实现对应的功能。
这里通过 grep 来看看 ,找到了

packages/modules/Permission/service/java/com/android/role/RoleService.java
packages/modules/Permission/service/java/com/android/role/RoleShellCommand.java

在这里插入图片描述

这里先关注 RoleService

RoleService addRoleHolderAsUser

直接看相关代码

 private class Stub extends IRoleManager.Stub {....................@NonNull@Overridepublic List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) {enforceCrossUserPermission(userId, false, "getRoleHoldersAsUser");..........}@Overridepublic void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,@RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,@NonNull RemoteCallback callback) {..............getOrCreateController(userId).onAddRoleHolder(roleName, packageName, flags,callback);}...........}@NonNullprivate RoleControllerManager getOrCreateController(@UserIdInt int userId) {synchronized (mLock) {RoleControllerManager controller = mControllers.get(userId);if (controller == null) {Context systemContext = getContext();Context context;try {context = systemContext.createPackageContextAsUser(systemContext.getPackageName(), 0, UserHandle.of(userId));} catch (PackageManager.NameNotFoundException e) {throw new RuntimeException(e);}controller = RoleControllerManager.createWithInitializedRemoteServiceComponentName(ForegroundThread.getHandler(), context);mControllers.put(userId, controller);}return controller;}}

上面两个方法可以理解为 :
RoleService 类的方法 addRoleHolderAsUser 实际上执行的是 RoleControllerManager 的对象调用了 onAddRoleHolder 方法

RoleControllerManager

路径:/packages/modules/Permission/framework-s/java/android/app/role/RoleControllerManager.java 

直接看源码,方法注释让我们关注 RoleControllerService 的 onAddRoleHolder 方法

 /*** @see RoleControllerService#onAddRoleHolder(String, String, int)** @hide*/public void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,@RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {AndroidFuture<Bundle> future = new AndroidFuture<>();service.onAddRoleHolder(roleName, packageName, flags,new RemoteCallback(future::complete));return future;});propagateCallback(operation, "onAddRoleHolder", callback);}

RoleControllerService

承接上文,这里看 onAddRoleHolder 方法源码

先看类注释说明

@Deprecated
@SystemApi
public abstract class RoleControllerService extends Service {

再看方法说明及注释

    private void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,@RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {boolean successful = onAddRoleHolder(roleName, packageName, flags);callback.sendResult(successful ? Bundle.EMPTY : null);}/*** Add a specific application to the holders of a role. If the role is exclusive, the previous* holder will be replaced.* <p>* Implementation should enforce the role requirements and grant or revoke the relevant* privileges of roles.** @param roleName the name of the role to add the role holder for* @param packageName the package name of the application to add to the role holders* @param flags optional behavior flags** @return whether this call was successful** @see RoleManager#addRoleHolderAsUser(String, String, int, UserHandle, Executor,*      RemoteCallback)*/@WorkerThreadpublic abstract boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,@RoleManager.ManageHoldersFlags int flags);

这里发现 RoleControllerService 是个服务,但也是一个抽象的类。 最终共调用到 onAddRoleHolder,需要子类来实现的。

回归到 RoleControllerManager 源码分析,mRemoteService 调用,其实是集合 mRemoteService 里面获取的。

private final ServiceConnector<IRoleController> mRemoteService;

那也就是 要看看 IRoleController 是什么,发现图也是一个aidl 接口。

IRoleController

路径 :

packages/modules/Permission/framework-s/java/android/app/role/IRoleController.aidl

源码如下:
在这里插入图片描述
但是 搜索发现一个问题,如下截图看看:你会发现实现类其实就是上面分析的RoleControllerService , 再进一步找到实现类 RoleControllerServiceImpl,那就看实现类里面的oAddRoleHolder 方法
在这里插入图片描述

RoleControllerServiceImpl

看一下 onAddRoleHolder 方法

  @Override@WorkerThreadpublic boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,int flags) {。。。。。。。。。。。。。。。Role role = Roles.get(this).get(roleName);。。。。。。。。。。。。。。。。。。boolean dontKillApp = hasFlag(flags, RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP);added = addRoleHolderInternal(role, packageName, dontKillApp,role.shouldOverrideUserWhenGranting(), added);if (!added) {return false;}role.onHolderAddedAsUser(packageName, Process.myUserHandle(), this);role.onHolderChangedAsUser(Process.myUserHandle(), this);return true;}

关注下 addRoleHolderInternal方法

addRoleHolderInternal

代码如下 ,

 @WorkerThreadprivate boolean addRoleHolderInternal(@NonNull Role role, @NonNull String packageName,boolean overrideUserSetAndFixedPermissions) {return addRoleHolderInternal(role, packageName, false, overrideUserSetAndFixedPermissions,false);}@WorkerThreadprivate boolean addRoleHolderInternal(@NonNull Role role, @NonNull String packageName,boolean dontKillApp, boolean overrideUserSetAndFixedPermissions, boolean added) {role.grant(packageName, dontKillApp, overrideUserSetAndFixedPermissions, this);String roleName = role.getName();if (!added) {added = mRoleManager.addRoleHolderFromController(roleName, packageName);}if (!added) {Log.e(LOG_TAG, "Failed to add role holder in RoleManager, package: " + packageName+ ", role: " + roleName);}return added;}

继续跟踪代码:

 mRoleManager.addRoleHolderFromController(roleName, packageName);

RoleManager addRoleHolderFromController

这里直接贴出源码

 /*** Add a specific application to the holders of a role, only modifying records inside* {@link RoleManager}. Should only be called from* {@link android.app.role.RoleControllerService}.* <p>* <strong>Note:</strong> Using this API requires holding* {@link #PERMISSION_MANAGE_ROLES_FROM_CONTROLLER}.** @param roleName the name of the role to add the role holder for* @param packageName the package name of the application to add to the role holders** @return whether the operation was successful, and will also be {@code true} if a matching*         role holder is already found.** @see #getRoleHolders(String)* @see #removeRoleHolderFromController(String, String)** @deprecated This is only usable by the role controller service, which is an internal*             implementation detail inside role.** @hide*/@Deprecated@RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)@SystemApipublic boolean addRoleHolderFromController(@NonNull String roleName,@NonNull String packageName) {Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");try {return mService.addRoleHolderFromController(roleName, packageName);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

这里又要开始追踪 mService 了 。

看构造方法:

  /*** Create a new instance of this class.** @param context the {@link Context}* @param service the {@link IRoleManager} service** @hide*/public RoleManager(@NonNull Context context, @NonNull IRoleManager service) {mContext = context;mService = service;}

追踪 mService 定义:

    @NonNullprivate final IRoleManager mService;

上面再追踪 IRoleManager 时候,已经分析了这是一个aidl 接口调用,最终实现是 RoleService。

所以上面的 mService.addRoleHolderFromController(roleName, packageName); 最总到

RoleService -> addRoleHolderFromController

RoleService

addRoleHolderFromController

源码如下:

  @Overridepublic boolean addRoleHolderFromController(@NonNull String roleName,@NonNull String packageName) {getContext().enforceCallingOrSelfPermission(RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,"addRoleHolderFromController");Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");int userId = UserHandleCompat.getUserId(Binder.getCallingUid());return getOrCreateUserState(userId).addRoleHolder(roleName, packageName);}

getOrCreateUserState

   private RoleUserState getOrCreateUserState(@UserIdInt int userId) {synchronized (mLock) {RoleUserState userState = mUserStates.get(userId);if (userState == null) {userState = new RoleUserState(userId, mPlatformHelper, this);mUserStates.put(userId, userState);}return userState;}}

这个方法返回的实例是什么,RoleUserState ,那不就是调用功能 RoleUserState 的 addRoleHolder 方法了嘛

RoleUserState ->addRoleHolder

    /*** Add a holder to a role.** @param roleName the name of the role to add the holder to* @param packageName the package name of the new holder** @return {@code false} if and only if the role is not found*/@CheckResultpublic boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {boolean changed;synchronized (mLock) {......changed = roleHolders.add(packageName);if (changed) {scheduleWriteFileLocked();}}if (changed) {mCallback.onRoleHoldersChanged(roleName, mUserId);}return true;}

scheduleWriteFileLocked

 /*** Schedule writing the state to file.*/@GuardedBy("mLock")private void scheduleWriteFileLocked() {if (mDestroyed) {return;}if (!mWriteScheduled) {mWriteHandler.postDelayed(this::writeFile, WRITE_DELAY_MILLIS);mWriteScheduled = true;}}

writeFile

 @WorkerThreadprivate void writeFile() {RolesState roles;synchronized (mLock) {if (mDestroyed) {return;}mWriteScheduled = false;roles = new RolesState(mVersion, mPackagesHash,(Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked());}mPersistence.writeForUser(roles, UserHandle.of(mUserId));}

继续跟踪 mPersistence,如下定义:

    private final RolesPersistence mPersistence = RolesPersistence.createInstance();

如下 看RolesPersistence 源码,是一个接口,那么就找实现类:RolesPersistenceImpl
在这里插入图片描述

RolesPersistenceImpl ->writeForUser

终于到头了,这里看到XML 解析并写文件
在这里插入图片描述

getFile 方法

  @NonNullprivate static File getFile(@NonNull UserHandle user) {ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME);File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user);return new File(dataDirectory, ROLES_FILE_NAME);}

看看 这个类的参数定义:

public class RolesPersistenceImpl implements RolesPersistence {private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName();private static final String APEX_MODULE_NAME = "com.android.permission";private static final String ROLES_FILE_NAME = "roles.xml";

那么我继续看看 roles.xml 是什么

roles.xml

位置

./vendor/mediatek/proprietary/packages/apps/PermissionController/res/xml/roles.xml
./vendor/mediatek/proprietary/packages/apps/PermissionController/tests/mocking/main_res/xml/roles.xml

这里举例HOME 相关的Launcher 部分,方便分析:

   <rolename="android.app.role.HOME"behavior="HomeRoleBehavior"description="@string/role_home_description"exclusive="true"label="@string/role_home_label"overrideUserWhenGranting="true"requestDescription="@string/role_home_request_description"requestTitle="@string/role_home_request_title"searchKeywords="@string/role_home_search_keywords"shortLabel="@string/role_home_short_label"><!-- Also used by HomeRoleBehavior.getFallbackHolder(). --><required-components><activity><intent-filter><action name="android.intent.action.MAIN" /><category name="android.intent.category.HOME" /></intent-filter></activity></required-components><preferred-activities><preferred-activity><activity><intent-filter><action name="android.intent.action.MAIN" /><category name="android.intent.category.HOME" /></intent-filter></activity><intent-filter><action name="android.intent.action.MAIN" /><category name="android.intent.category.HOME" /></intent-filter></preferred-activity></preferred-activities></role>

看name 属性,不就是 如上分析RoleManager 里面 如下方法的role 参数值嘛:

 roleManager.addRoleHolderAsUser(roleName, packageName, flags, user, executor, callback);

小结

Launcher 指定默认流程总结

所以,整套流程分析下来,设置默认App,这里HomeLauncher 举例,核心逻辑就是给包名一个role 权限,然后写入到文件,最后同步一次,通知系统。

指定其它默认App 方案

通过Launcher 的举例,默认其它App 作为默认的逻辑,比如默认浏览器、打电话、短信… 等,看一下 roles.xml 文件中定义的role name 是什么。 最终实现方案如上通过反射传递 packageName 和 role Name 不就可以了嘛。

总结

  • 如上分析了一整套默认Launcher 的代码逻辑业务,同理对于默认浏览器、短信、打电话等完全适用。
  • 通过反射实现 想要的功能,反射RoleManager 类,的addRoleHolderAsUser 方法比较合适,而且有回调。 整个流程里面发现其它类的调用并不合适

扩展知识

上面 动态实现了自己想要的功能,可以集成到framework层的服务里面对外提供接口来用,也可以最简单方式 让应用端直接反射实现。 那么有木有可能adb 命令来实现上面的需求呢?

adb 命令来动态设置默认的Launcher

实现方案如下:

 pm set-home-activity   com.zeasn.whale.saas.touch[需要设置Launcher的包名]

adb 命令实现思考

adb 为什么可以实现? 我们在上面分析了几个aidl 接口串来串去,其实adb 命令系统提供了支持。
如下截图 部分源码分享:
在这里插入图片描述

 private int runSetHomeActivity() {final PrintWriter pw = getOutPrintWriter();int userId = UserHandle.USER_SYSTEM;String opt;while ((opt = getNextOption()) != null) {switch (opt) {case "--user":userId = UserHandle.parseUserArg(getNextArgRequired());break;default:pw.println("Error: Unknown option: " + opt);return 1;}}String pkgName;String component = getNextArg();if (component.indexOf('/') < 0) {// No component specified, so assume it's just a package name.pkgName = component;} else {ComponentName componentName =component != null ? ComponentName.unflattenFromString(component) : null;if (componentName == null) {pw.println("Error: invalid component name");return 1;}pkgName = componentName.getPackageName();}final int translatedUserId =translateUserId(userId, UserHandle.USER_NULL, "runSetHomeActivity");final CompletableFuture<Boolean> future = new CompletableFuture<>();try {RoleManager roleManager = mContext.getSystemService(RoleManager.class);roleManager.addRoleHolderAsUser(RoleManager.ROLE_HOME, pkgName, 0,UserHandle.of(translatedUserId), FgThread.getExecutor(), future::complete);boolean success = future.get();if (success) {pw.println("Success");return 0;} else {pw.println("Error: Failed to set default home.");return 1;}} catch (Exception e) {pw.println(e.toString());return 1;}}

其实最终调用的也是 RoleManager 的 addRoleHolderAsUser 方法来实现的。

相关文章:

Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用))

Android 动态设置默认Launcher(默认应用 电话-短信-浏览器-主屏幕应用&#xff09;) 文章目录 场景需求参考资料思路期待效果 实现方案源码流程分析和思路实现DefaultAppActivityHandheldDefaultAppFragmentHandheldDefaultAppPreferenceFragmentDefaultAppChildFragmentDefaul…...

23种设计模式-策略(Strategy)设计模式

策略设计模式 &#x1f6a9;什么是策略设计模式&#xff1f;&#x1f6a9;策略设计模式的特点&#x1f6a9;策略设计模式的结构&#x1f6a9;策略设计模式的优缺点&#x1f6a9;策略设计模式的Java实现&#x1f6a9;代码总结&#x1f6a9;总结 &#x1f6a9;什么是策略设计模式…...

游戏引擎学习第188天

回顾并计划今天的内容 原本这周的目标是进行可视化操作的尝试&#xff0c;但每一天都被一些棘手的bug和问题所阻碍&#xff0c;导致我们一直没能实现这个目标。直到今天&#xff0c;星期四&#xff0c;我们终于解决了这些问题&#xff0c;所有功能都能正常运行了&#xff0c;所…...

TF-IDF——自然语言处理——红楼梦案例

目录 一、红楼梦数据分析 &#xff08;1&#xff09;红楼梦源文件 &#xff08;2&#xff09;数据预处理——分卷实现思路 &#xff08;3&#xff09;分卷代码 二、分卷处理&#xff0c;删除停用词&#xff0c;将文章转换为标准格式 1.实现的思路及细节 2.代码实现&#…...

Oracle数据库数据编程SQL<2.3 DML增、删、改及merge into>

目录 一、DML数据操纵语言&#xff08;Aate Manipulation Language) 二、【insert】插入数据 1、单行插入 2、批量插入 3、将数据同时插入到多张表insert all/insert first 三、【update】 更新数据 1、语法 2、举例 3、update使用注意事项&#xff1a; 四、【delete…...

面试计算机操作系统解析(一中)

判断 1. 一般来说&#xff0c;先进先出页面置换算法比最近最少使用页面置换算法有较少的缺页率。&#xff08;✘&#xff09; 正确答案&#xff1a;错误解释&#xff1a;FIFO&#xff08;先进先出&#xff09;页面置换算法可能导致“Belady异常”&#xff0c;即页面数增加反而…...

启山智软实现b2c单商户商城对比传统单商户的优势在哪里?

启山智软实现 B2C 单商户商城具有以下对比优势&#xff1a; 技术架构方面 先进的框架选型&#xff1a;基于 SpringCloud 等主流框架开发&#xff0c;是百万真实用户沉淀并检验的商城系统&#xff0c;技术成熟稳定&#xff0c;能应对高并发场景&#xff0c;保证系统在大流量访…...

蓝桥杯备考:贪心问题之均分纸牌

咱的贪心策略就是每次分好一个堆儿&#xff0c;如果某个堆已经是满足题意了&#xff0c;就不用管这个堆了&#xff0c;否则要向下一个堆借几个元素 #include <iostream> using namespace std; const int N 110; typedef long long ll; int a[N]; int n; ll x; int cnt…...

免去繁琐的手动埋点,Gin 框架可观测性最佳实践

作者&#xff1a;牧思 背景 在云原生时代的今天&#xff0c;Golang 编程语言越来越成为开发者们的首选&#xff0c;而对于 Golang 开发者来说&#xff0c;最著名的 Golang Web 框架莫过于 Gin [ 1] 框架了&#xff0c;Gin 框架作为 Golang 编程语言官方的推荐框架 [ 2] &…...

centos7 linux VMware虚拟机新添加的网卡,能看到网卡名称,但是看不到网卡的配置文件

问题现象&#xff1a;VMware虚拟机新添加的网卡&#xff0c;能看到网卡&#xff0c;但是看不到网卡的配置文件 解决方案&#xff1a; nmcli connection show nmcli connection add con-name ens36 ifname ens36 type ethernet #创建一个网卡连接配置文件&#xff0c;这里con…...

python的内置方法getitem和len

完整小测试&#xff1a; #python的内置函数&#xff0c;getitemclass Animal():def __init__(self,name):self.name namedef __str__(self):return f"This is {self.name}"class Zoo():def __init__(self,animal_list):self.animal_list animal_listdef __getite…...

深入理解 Git Stash:功能、用法与实战示例

文章目录 深入理解 Git Stash&#xff1a;功能、用法与实战示例一、Git Stash 的核心概念二、Git Stash 的基本用法1. 存储当前修改2. 查看 Stash 列表3. 恢复 Stash4. 恢复并删除 Stash5. 删除 Stash&#xff08;1&#xff09;删除指定 Stash&#xff08;2&#xff09;清空所有…...

SQL 复杂查询和性能优化

一、掌握复杂查询的核心技能 1. ​理解 SQL 执行顺序 SQL 语句的逻辑执行顺序&#xff08;非书写顺序&#xff09;&#xff1a; FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT ​关键点&#xff1a;每一步的结果会传递给下一步&a…...

【web应用安全】关于web应用安全的几个主要问题的思考

文章目录 防重放攻击1. **Token机制&#xff08;一次性令牌&#xff09;**2. **时间戳 超时验证**3. **Nonce&#xff08;一次性随机数&#xff09;**4. **请求签名&#xff08;如HMAC&#xff09;**5. **HTTPS 安全Cookie**6. **幂等性设计****综合防御策略建议****注意事项…...

看懂roslunch输出

自编了一个demo 第一步&#xff1a;创建功能包 cd ~/catkin_ws/src catkin_create_pkg param_demo roscpp第二步&#xff1a;写 main.cpp 创建文件&#xff1a;param_demo/src/param_node.cpp #include <ros/ros.h> #include <string>int main(int argc, char*…...

如何查看Unity打包生成的ab文件

文章目录 前言AssetStudioab文件介绍1. 动态加载资源2. 资源分离与模块化3. 平台兼容性4. 资源压缩与加密5. 资源管理与更新6. 减少安装包大小7. 资源加载灵活性8. 资源打包与分发9. 实际应用场景10. 注意事项 总结 前言 问题来源于工作又回归到工作&#xff0c;当发现发布包里…...

从泛读到精读:合合信息文档解析如何让大模型更懂复杂文档

从泛读到精读&#xff1a;合合信息文档解析如何让大模型更懂复杂文档 一、引言&#xff1a;破解文档“理解力”瓶颈二、核心功能&#xff1a;合合信息的“破局”亮点功能亮点1&#xff1a;复杂图表的高精度解析图表解析&#xff1a;为大模型装上精准“标尺”表格数据精准还原 功…...

Python SciPy面试题及参考答案

目录 什么是 SciPy?它与 NumPy 有什么区别? 如何在 Python 中安装 SciPy? 如何导入 SciPy 库? SciPy 中有哪些子模块?简要介绍它们的功能。 如何使用 SciPy 进行数值积分?请举例说明。 SciPy 中提供了哪些求解微分方程的函数? 什么是插值?SciPy 中如何进行插值?…...

21.Excel自动化:如何使用 xlwings 进行编程

一 将Excel用作数据查看器 使用 xlwings 中的 view 函数。 1.导包 import datetime as dt import xlwings as xw import pandas as pd import numpy as np 2.view 函数 创建一个基于伪随机数的DataFrame&#xff0c;它有足够多的行&#xff0c;使得只有首尾几行会被显示。 df …...

【redis】数据类型之Stream

Redis Stream是Redis 5.0版本引入的一种新的数据类型&#xff0c;它提供了一种持久化的、可查询的、可扩展的消息队列服务。 它结合了Redis高性能的特性与持久化能力&#xff0c;支持&#xff1a; 多消费者组模式&#xff08;Consumer Groups&#xff09;消息回溯&#xff08…...

day17 周末两天偷懒没更新,今天炼丹加学习,完结STL常用容器部分

还剩下两个常用容器&#xff0c;一个是set(集合容器) &#xff0c; 一个是map容器 set/multiset 容器 set容器是关联式容器&#xff0c;该容器的特点是&#xff1a;所有元素都会在插入时被自动排序 set/multiset 都是关联式容器 &#xff0c;其底层结构是使用二叉树实现的。…...

嵌入式开发场景中Shell脚本执行方式的对比

‌Shell脚本执行方式对比表‌ ‌执行方式‌‌命令示例‌‌是否需要执行权限‌‌是否启动子Shell‌‌环境变量影响范围‌‌适用场景‌‌嵌入式开发中的典型应用‌‌直接执行脚本‌./script.sh是是子Shell内有效独立运行的脚本&#xff0c;需固定环境自动化构建脚本&#xff08;…...

数据结构之多项式相加的链表实现

在计算机科学中&#xff0c;多项式的表示和运算经常会用到。使用链表来表示多项式是一种常见且有效的方法&#xff0c;它可以方便地处理多项式的各项&#xff0c;并且在进行多项式相加等运算时具有较好的灵活性。 多项式通常由一系列的项组成&#xff0c;每一项包含一个系数和…...

Java 实现将Word 转换成markdown

日常的开发中&#xff0c;需要将word 等各类文章信息转换成格式化语言&#xff0c;因此需要使用各类语言将word 转换成Markdown 1、引入 jar包 <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version&g…...

IEEE PDF Xpress校验出现 :字体无法嵌入问题以及pdf版本问题

文章目录 问题描述一、字体嵌入问题首先查看一下&#xff0c;哪些字体没有被嵌入查看window的font文件夹里的字体下载字体的网站修复字体嵌入问题 二、pdf版本不对 问题描述 在处理IEEE的camera ready的时候&#xff0c;提交到IEEE express的文件没有办法通过validate&#xf…...

Sa-Token

简介 Sa-Token 是一个轻量级 Java 权限认证框架&#xff0c;主要解决&#xff1a;登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。 官方文档 常见功能 登录认证 本框架 用户提交 name password 参数&#xff0c;调用登…...

StarRocks 中 CURRENT_TIMESTAMP 和 CURRENT_TIME 分区过滤问题

背景 本文基于Starrocks 3.3.5 最近在进行Starrocks 跑数据的时候&#xff0c;发现了一个SQL 扫描了所有分区的数据&#xff0c;简化后的SQL如下&#xff1a; select date_created from tableA where date_createddate_format(current_time(), %Y-%m-%d %H:%i:%S) limit 20其…...

GithubPages+自定义域名+Cloudfare加速+浏览器收录(2025最新排坑)

前言 最近刷到一个小视频&#xff0c;讲述了选择域名选择的三宗罪&#xff0c;分别是 不要使用 .net&#xff0c;因为它价格贵&#xff0c;但是在顶级域名中的 SEO 效果却不是很好&#xff0c;也就是性价比很低不要使用 .cn&#xff0c;因为国外访问该网站可能会很慢&#xf…...

Canvas粒子系统终极指南:从基础运动到复杂交互的全流程实现

文章目录 一、粒子系统基础架构1.1 粒子数据结构设计1.2 粒子系统管理器 二、基础粒子效果实现2.1 重力场模拟2.2 弹性碰撞效果 三、高级交互实现3.1 鼠标吸引效果3.2 颜色渐变粒子 四、性能优化策略4.1 粒子池复用4.2 分层渲染 五、复杂效果实现5.1 烟花爆炸效果5.2 流体模拟 …...

【QT】新建QT工程(详细步骤)

新建QT工程 1.方法(1)点击new project按钮&#xff0c;弹出对话框&#xff0c;新建即可&#xff0c;步骤如下&#xff1a;(2) 点击文件菜单&#xff0c;选择新建文件或者工程&#xff0c;后续步骤如上 2.QT工程文件介绍(1).pro文件 --》QT工程配置文件(2)main.cpp --》QT工程主…...

详解Http:在QT中使用Http协议

目录 一、HTTP 概述 1、主要特点 2、HTTP 方法 3、HTTP 状态码 4、HTTP 头部 5、HTTP的工作原理 二、在Qt中使用HTTP 1、发送简单的HTTP请求 2、发送POST请求 3、处理异步请求 4、使用QSslConfiguration进行HTTPS 5、 处理JSON响应 6、处理错误 三、总结 一、HTTP…...

Next.js 中间件鉴权绕过漏洞 (CVE-2025-29927) 复现利用与原理分析

免责声明 本文所述漏洞复现方法仅供安全研究及授权测试使用&#xff1b; 任何个人/组织须在合法合规前提下实施&#xff0c;严禁用于非法目的&#xff1b; 作者不对任何滥用行为及后果负责&#xff0c;如发现新漏洞请及时联系厂商并遵循漏洞披露规则。 漏洞原理 Next.js 是一个…...

AI时代的数据底座:火山引擎多模态数据湖的设计与实践

资料来源&#xff1a;火山引擎-开发者社区 随着大模型的发展和应用&#xff0c;文本的边界被拓宽&#xff0c;图像、视频、语音各种模态涌现&#xff0c;并给数据管理、检索、计算带来巨大挑战。 火山引擎多模态数据湖 解决方案则可实现海量结构化、半结构化及非结构化数据的统…...

Numpy用法(二)

一.数组变维 1.1 reshape reshape() 可以改变数组维度&#xff0c;但是返回的是一个新的数组&#xff0c;原数组的形状不会被修改.reshape后产生的新数组是原数组的一个视图&#xff0c;即它与原数组共享相同的数据&#xff0c;但可以有不同的形状或维度&#xff0c;且对视图…...

STM32 IIC通信

目录 IIC简介硬件电路连接I2C时序基本单元IIC完整数据帧MPU6050封装硬件IIC内部电路 IIC简介 IIC&#xff08;Inter-Integrated Circuit&#xff09;是 IIC Bus 简称&#xff0c;中文叫集成电路总线。它是一种串行通信总线&#xff0c;使用多主从架构&#xff0c;由飞利浦公司…...

快速入门 JSON 数据格式

引言 JSON&#xff0c;全称 JavaScript Object Notion&#xff0c;类似于XML&#xff0c;YAML&#xff0c;Properties等&#xff0c;是一种数据交换格式&#xff0c;相比于XML&#xff0c;更简单&#xff0c;更轻量&#xff0c;更容易理解。 JSON vs XML 使用 JSON 目前被广…...

FFmpeg —— 中标麒麟系统下使用FFmpeg内核+Qt界面,制作完整功能音视频播放器(附:源码)

🔔 FFmpeg 相关音视频技术、疑难杂症文章合集(掌握后可自封大侠 ⓿_⓿)(记得收藏,持续更新中…) 程序运行效果...

硬件测试工装设计不合理的补救措施

硬件测试工装设计不合理的补救措施主要包括重新评估设计需求、优化工装结构、强化工装校准与验证。其中&#xff0c;优化工装结构尤其重要&#xff0c;通过结构优化能够有效解决因设计不合理导致的测试准确性下降和可靠性不足的问题。根据工程实践数据&#xff0c;经过优化结构…...

任意文件读取漏洞

fofa语句&#xff1a;body"/vite/client" /fs/etc/passwd?import&raw?? https://35.175.173.157/fs/etc/passwd?import&raw?? http://geometer.dev.mvergely.com/fs/etc/passwd?import&raw??...

如何使用RK平台的spi驱动 spidev

RK平台spidev驱动读取RC522版本号示例 1. 硬件与驱动确认 确认SPI接口连接&#xff1a;RC522的SPI引脚与RK开发板的对应SPI控制器正确连接&#xff08;CS、CLK、MOSI、MISO&#xff09;检查内核配置&#xff1a; Bash # 内核需启用以下配置 CONFIG_SPIy CONFIG_SPI_MASTERy…...

网路传输层UDP/TCP

一、端口号 1.端口号 1.1 五元组 端口号(port)标识了一个主机上进行通信的不同的应用程序. 如图所示, 在一个机器上运行着许多进程, 每个进程使用的应用层协议都不一样, 比如FTP, SSH, SMTP, HTTP等. 当主机接收到一个报文中, 网络层一定封装了一个目的ip标识我这台主机, …...

1.2-WAF\CDN\OSS\反向代理\负载均衡

WAF&#xff1a;就是网站应用防火墙&#xff0c;有硬件类、软件类、云WAF&#xff1b; 还有网站内置的WAF&#xff0c;内置的WAF就是直接嵌在代码中的安全防护代码 硬件类&#xff1a;Imperva、天清WAG 软件&#xff1a;安全狗、D盾、云锁 云&#xff1a;阿里云盾、腾讯云WA…...

Dify 服务器部署指南

1. 系统要求 在开始部署之前&#xff0c;请确保你的服务器满足以下要求&#xff1a; 操作系统&#xff1a;Linux&#xff08;推荐使用 Ubuntu 20.04 或更高版本&#xff09;内存&#xff1a;至少 4GB RAM存储&#xff1a;至少 20GB 可用空间网络&#xff1a;稳定的互联网连接…...

从车间到数字生态:MES如何引领制造业智能化革命‌

在全球制造业加速迈向工业4.0的浪潮中&#xff0c;传统生产模式正经历颠覆性变革。制造执行系统&#xff08;MES&#xff09;作为连接物理车间与数字世界的核心纽带&#xff0c;正从“生产辅助工具”升级为“智能决策大脑”&#xff0c;推动制造业向数据驱动、柔性化与可持续化…...

Error:Flash Download failed

出现这个就是编译器要换...

Spring容器生命周期详解

Spring容器生命周期详解 Spring容器的生命周期从启动到关闭分为多个阶段&#xff0c;包括Bean的加载、实例化、初始化、使用和销毁。以下是详细流程和关键点&#xff1a; 1. 容器启动阶段 1.1 容器实例化 核心接口&#xff1a;BeanFactory&#xff08;基础容器&#xff09;或…...

革新测试管理 2.0丨Storm UTP统一测试管理平台智能化升级与全流程优化

承接上篇&#xff1a;从基础架构到深度协同 在首篇文章《革新测试管理 | 统一测试管理平台如何实现远程、协同、自动化&#xff1f;》中&#xff0c;我们探讨了Storm UTP如何通过云端协作、自动化测试框架和分布式执行能力打破传统测试壁垒。经过一年多的客户实践与技术迭代&a…...

将 char [] str = “hello,you,world” 改为 “world,you,hello“,要求空间复杂度为1

题目&#xff1a; 将 char [] str “hello,you,world” 改为 "world,you,hello",要求空间复杂度为1 &#xff08;也就是使用的变量只能是单个字符或者常数&#xff0c;不能使用数组&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 解…...

运维规则之总结(Summary of Operation and Maintenance Rules)

运维规则之总结 在运维领域&#xff0c;经验和流程往往决定了系统的稳定性与可靠性。一个运维人&#xff0c;总结出了以下10条运维规则&#xff0c;涵盖了从基础管理到高级策略的全面内容&#xff0c;旨在帮助运维人员更好地应对各种挑战&#xff0c;确保系统的平稳运行。 1.…...

MongoDB 创建数据库

MongoDB 创建数据库 引言 MongoDB 是一款高性能、可扩展的 NoSQL 数据库&#xff0c;广泛应用于大数据领域。在 MongoDB 中&#xff0c;创建数据库是进行数据存储的第一步。本文将详细介绍 MongoDB 数据库的创建方法&#xff0c;包括手动创建和自动创建两种方式。 MongoDB 数…...