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

### 编译 LineageOS 22.1 for Redmi K60e 的具体步骤 编译 LineageOS 22.1 for Redmi K60e 需要遵循一系列严格的步骤,确保设备支持并能够正确运行自定义ROM。以下是详细的指南: #### 环境准备 1. **安装必要的软件和工具** - 安装 Ubuntu 或 Debian 系统作为发环境。 - 安装 Java Development Kit (JDK)Android SDK 工具[^1]。 - 确保系统已安装 `repo` 工具。可以通过以下命令安装: ```bash sudo apt update sudo apt install repo ``` 2. **配置存储空间** - 确保有足够的磁盘空间(至少 200GB),因为编译过程需要大量存储。 3. **解锁 Bootloader** - 使用官方工具解锁 Redmi K60e 的 Bootloader。参考解锁教程以完成此步骤[^2]。 #### 源码获取 1. **初始化 LineageOS 源码仓库** 创建一个目录用于存放源码,并初始化 LineageOS 仓库: ```bash mkdir lineageos-source cd lineageos-source repo init -u https://github.com/LineageOS/android.git -b lineage-22.1 repo sync --force-sync --no-tags --no-clone-bundle ``` 2. **添加设备特定的代码** 克隆 Redmi K60e 的设备树和内核源码: ```bash git clone https://gitcode.com/projectrembrandt/android_device_xiaomi_k60e -b lineage-22.1 device/xiaomi/k60e git clone https://gitcode.com/projectrembrandt/android_kernel_xiaomi_sm8450 -b lineage-22.1 kernel/xiaomi/sm8450 git clone https://gitcode.com/projectrembrandt/android_vendor_xiaomi -b lineage-22.1 vendor/xiaomi ``` #### 编译设置 1. **配置编译环境** 设置编译目标为 Redmi K60e: ```bash source build/envsetup.sh lunch lineage_k60e-userdebug ``` 2. **始编译** 使用以下命令启动编译过程: ```bash mka target-files-package otatools ``` 此命令将生成最终的 ROM 文件包。 #### 测试与刷入 1. **生成的 ROM 文件** 编译完成后,ROM 文件通常位于 `out/target/product/k60e/` 目录下。 2. **刷入 ROM** 使用 Fastboot 工具将生成的 ROM 刷入设备: ```bash fastboot flash recovery out/target/product/k60e/recovery.img fastboot boot out/target/product/k60e/boot.img ``` 3. **验证功能** 确保所有功能正常工作,包括 Wi-Fi、蓝牙、相机等。 ### 注意事项 - 解锁 Bootloader 可能会导致设备保修失效,请谨慎操作[^2]。 - 确保设备驱动程序已正确安装在电脑上[^2]。 - 如果遇到编译错误,请检查日志文件并修复相关问题后再重新尝试。 ```python # 示例 Python 脚本:检查设备连接状态 import subprocess def check_device_connection(): result = subprocess.run(['adb', 'devices'], stdout=subprocess.PIPE) devices = result.stdout.decode('utf-8').split('\n')[1:] return [device.split('\t')[0] for device in devices if device] connected_devices = check_device_connection() print("Connected Devices:", connected_devices) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值