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

Lineageos 22.1(Android 15)应用双开

一、前言

应用双开简单说就是创建一个用户然后将已经存在的APP安装到制定的userId中,下面新建一个系统应用来实现功能。

二、系统APP

首先要调整最大用户数量这个再framework/base/core/res/res/values/config.xml下面

    <!--  Maximum number of supported users -->
    <integer name="config_multiuserMaximumUsers">1</integer>

但是LineageOS已经用overlay的方式配置过了,最大是支持4个用户,所以这里不需要调整。直接下一步制作系统应用。

清单文件,权限,以及sharedUserId配置如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:sharedUserId="android.uid.system"
    >



    <uses-permission android:name="android.permission.DELETE_PACKAGES" />


    <uses-permission android:name="android.permission.MANAGE_USERS"/>
    <uses-permission android:name="android.permission.CREATE_USERS"/>
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
    <uses-permission android:name="android.permission.INSTALL_EXISTING_PACKAGES"/>
    <uses-permission android:name="android.permission.INSTALL_PACKAGES"/>

    <uses-permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        >
        <activity
            android:name="com.example.split.MainActivity"
            android:exported="true"

            android:label="@string/app_name"
            android:theme="@style/Theme.MyApplication">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

MainActivity.kt

package com.example.split

import android.app.ActivityManager
import android.app.ActivityThread
import android.app.IActivityManager
import android.content.pm.LauncherApps
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.content.pm.PackageManager.MATCH_ANY_USER
import android.content.pm.UserInfo
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
import android.os.UserManager
import android.util.DisplayMetrics
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.internal.R.string.loading
import com.example.split.ui.theme.MyApplicationTheme
import com.example.splite.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


private const val targetPackage = "com.example.cloudclient"

private const val testUserName = "testUserName"

class MainActivity : ComponentActivity() {
    val metrics = DisplayMetrics()
    var createUserId:Int=-1

    @RequiresApi(Build.VERSION_CODES.S)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        windowManager.defaultDisplay.getMetrics(metrics)
        enableEdgeToEdge()

        val launcherApps=LauncherApps(this)
        setContent {

            val scope = rememberCoroutineScope()
            MyApplicationTheme {

                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Box(
                        Modifier
                            .fillMaxSize()
                            .padding(innerPadding)){

                        var checked = remember {
                            val userManager=UserManager.get(this@MainActivity)

                            val userInfoList: List<UserInfo> = userManager.users
                            createUserId=userInfoList.find {
                                it.name== testUserName
                            }?.id?:-1

                            val packageManager = ActivityThread.getPackageManager();
                            val slices=packageManager.getInstalledPackages(MATCH_ANY_USER.toLong(),createUserId?:-1)
                            val installed=slices.list.find {

                                if(it is PackageInfo){
                                    it.packageName==targetPackage

                                }else{
                                    false
                                }
                            }!=null

                            mutableStateOf(installed)
                        }
                        var loading= remember {
                            mutableStateOf(false)
                        }



                        val scope = rememberCoroutineScope()

                        Column {

                            Row (Modifier.fillMaxWidth().padding(20.dp), verticalAlignment = Alignment.CenterVertically){


                                Box(Modifier.weight(1f)){
                                    Image(modifier = Modifier.size(50.dp),
                                        painter = painterResource(R.mipmap.scrrr),
                                        contentDescription = "e"
                                    )
                                }


                                Switch(checked.value,{

                                    if(it){

                                        scope.launch (Dispatchers.Default){
                                            loading.value=true
                                            createUser()
                                            startBackground(createUserId)
                                            installToUserId(createUserId,checked)
                                            loading.value=false

                                        }

                                    }else{
                                        scope.launch (Dispatchers.Default){
                                            loading.value=true
                                            uninstallToUserId(createUserId,checked)
                                            loading.value=false
                                        }
                                    }

                                })


                            }


                            if(loading.value){
                                CircularProgressIndicator(modifier = Modifier.
                                align(alignment = Alignment.CenterHorizontally).
                                size(60.dp))
                            }



                        }



                    }


                }
            }
        }
    }

    @RequiresApi(Build.VERSION_CODES.S)
    private fun uninstallToUserId(userId: Int, checked: MutableState<Boolean>) {

        val packageManager = ActivityThread.getPackageManager();
        try {
            val packageInstaller =  PackageInstaller(
                packageManager.packageInstaller,packageName,null,userId

            )
            packageInstaller.uninstallExistingPackage(
                targetPackage,
                null
                );
            checked.value=false
        }catch ( e:Exception){
            e.printStackTrace();
        }
    }

    fun createUser(){

        val userManager=UserManager.get(this)
        val userInfoList: List<UserInfo> = userManager.users
        var isCreated = false
        for (userInfo in userInfoList) {
            if (testUserName == userInfo.name) {
                isCreated = true
                createUserId = userInfo.id
                break
            }
        }
        if (!isCreated) {
            val userHandle: UserHandle = userManager.createProfile(
                testUserName,
                UserManager.USER_TYPE_PROFILE_MANAGED,
                HashSet<String>()
            )!!
            createUserId = userHandle.identifier
        }

    }

    fun startBackground(userId:Int){
        //后台启动userId
        val  am: IActivityManager = ActivityManager.getService();
        try {
            am.startUserInBackground(userId);
        } catch ( e:Exception) {
            e.printStackTrace()
        }

    }
    @RequiresApi(Build.VERSION_CODES.Q)
    fun installToUserId(userId: Int, checked: MutableState<Boolean>){
        
        val packageManager = ActivityThread.getPackageManager();
        try {
            val packageInstaller =  PackageInstaller(
                packageManager.packageInstaller,packageName,null,userId

            )
            packageInstaller.installExistingPackage(
                targetPackage,
                PackageManager.INSTALL_REASON_USER,null);
            checked.value=true
        }catch ( e:Exception){
            e.printStackTrace();
        }

    }
}



@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MyApplicationTheme {
    }
}

步骤还是比较清晰,这里我只对某个已经安装的自己的App做双开:
1.进入双开应用的时候检查是否已经安装到指定用户,如果已经安装就配置UI的switch
2.switch切换的时候安装,总共三步
2.1 创建user并获取userid
2.2 启动user
2.3 将制定包名的app安装到对应的userid
3.切换回来的时候进行卸载应用
在这里插入图片描述

Lineageos的桌面支持两

❯ adb shell ps -A | grep cloud
u13_a236 9858 24619 14622780 122908 SyS_epoll_wait 0 S com.example.cloudclient
u0_a236 10199 24619 14672596 133628 SyS_epoll_wait 0 S com.example.cloudclient

相关文章:

  • 图解AUTOSAR_CP_EEPROM_Abstraction
  • Nordic nRF528xxx Beacon功能开发学习方案总结
  • Metasploit 跳板攻击
  • doris:负载均衡
  • centos 9 编译安装 rtpengine
  • 【WebGL】texImage2D函数
  • 1.5.7 掌握Scala内建控制结构 - 变量作用域
  • 虚拟机的三种 Linux 网络配置原理图解读
  • 在Fedora-Workstation-Live-x86_64-41-1.4中使用最新版本firefox和腾讯翻译插件让英文网页显示中文翻译
  • Linux C/C++ 程序的内存泄漏定位方法
  • k8s中service概述(二)NodePort
  • 奇迹科技:蓝牙网关赋能少儿篮球教育的创新融合案例研究
  • SOFAStack-00-sofa 技术栈概览
  • Day22:二叉搜索树中第k大的节点
  • C++第三种异质集合 std::any方式实现
  • 【Mybatis-plus】在mybatis-plus中 if test标签如何判断 list不为空
  • 尝试在软考65天前开始成为软件设计师-计算机网络
  • Spring Boot 集成 Quartz 实现定时任务(Cron 表达式示例)
  • Qt窗口控件之对话框QDialog
  • 基线定位系统:长基线与超短基线的原理与应用
  • 医学统计专家童新元逝世,终年61岁
  • 69岁朱自强被查,曾任南京地铁总经理
  • 深入贯彻中央八项规定精神学习教育中央指导组派驻地方和单位名单公布
  • 外交部:美方应在平等、尊重和互惠的基础上同中方开展对话
  • 京津冀“飘絮之困”如何破解?专家坦言仍面临关键技术瓶颈
  • 北京动物园:大熊猫“萌兰”没有参加日本大阪世博会的计划