package com.bintianqi.owndroid import android.accounts.Account import android.annotation.SuppressLint import android.app.ActivityOptions import android.app.Application import android.app.PendingIntent import android.app.admin.DeviceAdminInfo import android.app.admin.DeviceAdminReceiver import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback import android.app.admin.FactoryResetProtectionPolicy import android.app.admin.IDevicePolicyManager import android.app.admin.PackagePolicy import android.app.admin.PreferentialNetworkServiceConfig import android.app.admin.SystemUpdateInfo import android.app.admin.SystemUpdatePolicy import android.app.admin.WifiSsidPolicy import android.app.usage.NetworkStats import android.app.usage.NetworkStatsManager import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.ApplicationInfo import android.content.pm.PackageInstaller import android.content.pm.PackageManager import android.graphics.Bitmap import android.net.IpConfiguration import android.net.LinkAddress import android.net.ProxyInfo import android.net.StaticIpConfiguration import android.net.Uri import android.net.wifi.WifiConfiguration import android.net.wifi.WifiManager import android.net.wifi.WifiSsid import android.os.Binder import android.os.Build.VERSION import android.os.HardwarePropertiesManager import android.os.UserHandle import android.os.UserManager import android.telephony.data.ApnSetting import androidx.annotation.RequiresApi import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.application import androidx.lifecycle.viewModelScope import com.bintianqi.owndroid.Privilege.DAR import com.bintianqi.owndroid.Privilege.DPM import com.bintianqi.owndroid.dpm.ACTIVATE_DEVICE_OWNER_COMMAND import com.bintianqi.owndroid.dpm.ApnAuthType import com.bintianqi.owndroid.dpm.ApnConfig import com.bintianqi.owndroid.dpm.ApnMvnoType import com.bintianqi.owndroid.dpm.ApnProtocol import com.bintianqi.owndroid.dpm.AppStatus import com.bintianqi.owndroid.dpm.CaCertInfo import com.bintianqi.owndroid.dpm.CreateUserResult import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions import com.bintianqi.owndroid.dpm.DelegatedAdmin import com.bintianqi.owndroid.dpm.DeviceAdmin import com.bintianqi.owndroid.dpm.FrpPolicyInfo import com.bintianqi.owndroid.dpm.HardwareProperties import com.bintianqi.owndroid.dpm.IntentFilterDirection import com.bintianqi.owndroid.dpm.IntentFilterOptions import com.bintianqi.owndroid.dpm.IpMode import com.bintianqi.owndroid.dpm.NetworkStatsData import com.bintianqi.owndroid.dpm.NetworkStatsTarget import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceInfo import com.bintianqi.owndroid.dpm.PrivateDnsConfiguration import com.bintianqi.owndroid.dpm.ProxyMode import com.bintianqi.owndroid.dpm.ProxyType import com.bintianqi.owndroid.dpm.QueryNetworkStatsParams import com.bintianqi.owndroid.dpm.RecommendedProxyConf import com.bintianqi.owndroid.dpm.SsidPolicy import com.bintianqi.owndroid.dpm.SsidPolicyType import com.bintianqi.owndroid.dpm.SystemOptionsStatus import com.bintianqi.owndroid.dpm.SystemUpdatePolicyInfo import com.bintianqi.owndroid.dpm.UserInformation import com.bintianqi.owndroid.dpm.WifiInfo import com.bintianqi.owndroid.dpm.WifiSecurity import com.bintianqi.owndroid.dpm.WifiStatus import com.bintianqi.owndroid.dpm.activateOrgProfileCommand import com.bintianqi.owndroid.dpm.delegatedScopesList import com.bintianqi.owndroid.dpm.getPackageInstaller import com.bintianqi.owndroid.dpm.handlePrivilegeChange import com.bintianqi.owndroid.dpm.isValidPackageName import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage import com.bintianqi.owndroid.dpm.runtimePermissions import com.bintianqi.owndroid.dpm.temperatureTypes import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.DhizukuRequestPermissionListener import com.topjohnwu.superuser.Shell import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.net.InetAddress import java.security.MessageDigest import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.time.ZoneId import java.time.ZonedDateTime import java.util.concurrent.Executors import kotlin.reflect.jvm.jvmErasure import kotlin.system.measureTimeMillis class MyViewModel(application: Application): AndroidViewModel(application) { val myRepo = getApplication().myRepo val PM = application.packageManager val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)) fun changeTheme(newTheme: ThemeSettings) { theme.value = newTheme SP.materialYou = newTheme.materialYou SP.darkTheme = newTheme.darkTheme SP.blackTheme = newTheme.blackTheme } val chosenPackage = Channel(1, BufferOverflow.DROP_LATEST) val installedPackages = MutableStateFlow(emptyList()) val refreshPackagesProgress = MutableStateFlow(0F) fun refreshPackageList() { viewModelScope.launch(Dispatchers.IO) { installedPackages.value = emptyList() val apps = PM.getInstalledApplications(getInstalledAppsFlags) apps.forEachIndexed { index, info -> installedPackages.update { it + getAppInfo(info) } refreshPackagesProgress.value = (index + 1).toFloat() / apps.size } } } fun getAppInfo(info: ApplicationInfo) = AppInfo(info.packageName, info.loadLabel(PM).toString(), info.loadIcon(PM), info.flags) fun getAppInfo(name: String): AppInfo { return try { getAppInfo(PM.getApplicationInfo(name, getInstalledAppsFlags)) } catch (_: PackageManager.NameNotFoundException) { AppInfo(name, "???", Color.Transparent.toArgb().toDrawable(), 0) } } val suspendedPackages = MutableStateFlow(emptyList()) @RequiresApi(24) fun getSuspendedPackaged() { val packages = PM.getInstalledApplications(getInstalledAppsFlags).filter { DPM.isPackageSuspended(DAR, it.packageName) } suspendedPackages.value = packages.map { getAppInfo(it) } } @RequiresApi(24) fun setPackageSuspended(name: String, status: Boolean): Boolean { val result = DPM.setPackagesSuspended(DAR, arrayOf(name), status) getSuspendedPackaged() return result.isEmpty() } val hiddenPackages = MutableStateFlow(emptyList()) fun getHiddenPackages() { hiddenPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter { DPM.isApplicationHidden(DAR, it.packageName) }.map { getAppInfo(it) } } fun setPackageHidden(name: String, status: Boolean): Boolean { val result = DPM.setApplicationHidden(DAR, name, status) getHiddenPackages() return result } // Uninstall blocked packages val ubPackages = MutableStateFlow(emptyList()) fun getUbPackages() { ubPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter { DPM.isUninstallBlocked(DAR, it.packageName) }.map { getAppInfo(it) } } fun setPackageUb(name: String, status: Boolean) { DPM.setUninstallBlocked(DAR, name, status) getUbPackages() } // User control disabled packages val ucdPackages = MutableStateFlow(emptyList()) @RequiresApi(30) fun getUcdPackages() { ucdPackages.value = DPM.getUserControlDisabledPackages(DAR).map { getAppInfo(it) } } @RequiresApi(30) fun setPackageUcd(name: String, status: Boolean) { DPM.setUserControlDisabledPackages( DAR, ucdPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) } ) getUcdPackages() } val packagePermissions = MutableStateFlow(emptyMap()) @RequiresApi(23) fun getPackagePermissions(name: String) { if (name.isValidPackageName) { packagePermissions.value = runtimePermissions.associate { it.permission to DPM.getPermissionGrantState(DAR, name, it.permission) } } else { packagePermissions.value = emptyMap() } } @RequiresApi(23) fun setPackagePermission(name: String, permission: String, status: Int): Boolean { val result = DPM.setPermissionGrantState(DAR, name, permission, status) getPackagePermissions(name) return result } // Metered data disabled packages val mddPackages = MutableStateFlow(emptyList()) @RequiresApi(28) fun getMddPackages() { mddPackages.value = DPM.getMeteredDataDisabledPackages(DAR).map { getAppInfo(it) } } @RequiresApi(28) fun setPackageMdd(name: String, status: Boolean): Boolean { val result = DPM.setMeteredDataDisabledPackages( DAR, mddPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) } ) getMddPackages() return result.isEmpty() } // Keep uninstalled packages val kuPackages = MutableStateFlow(emptyList()) @RequiresApi(28) fun getKuPackages() { kuPackages.value = DPM.getKeepUninstalledPackages(DAR)?.map { getAppInfo(it) } ?: emptyList() } @RequiresApi(28) fun setPackageKu(name: String, status: Boolean) { DPM.setKeepUninstalledPackages( DAR, kuPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) } ) getKuPackages() } // Cross profile packages val cpPackages = MutableStateFlow(emptyList()) @RequiresApi(30) fun getCpPackages() { cpPackages.value = DPM.getCrossProfilePackages(DAR).map { getAppInfo(it) } } @RequiresApi(30) fun setPackageCp(name: String, status: Boolean) { DPM.setCrossProfilePackages( DAR, cpPackages.value.map { it.name }.toSet().run { if (status) plus(name) else minus(name) } ) getCpPackages() } // Cross-profile widget providers val cpwProviders = MutableStateFlow(emptyList()) fun getCpwProviders() { cpwProviders.value = DPM.getCrossProfileWidgetProviders(DAR).map { getAppInfo(it) } } fun setCpwProvider(name: String, status: Boolean): Boolean { val result = if (status) { DPM.addCrossProfileWidgetProvider(DAR, name) } else { DPM.removeCrossProfileWidgetProvider(DAR, name) } getCpwProviders() return result } @RequiresApi(28) fun clearAppData(name: String, callback: (Boolean) -> Unit) { DPM.clearApplicationUserData(DAR, name, Executors.newSingleThreadExecutor()) { _, result -> callback(result) } } fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) { val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999) if(statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) { @SuppressWarnings("UnsafeIntentLaunch") context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?) } else { context.unregisterReceiver(this) if(statusExtra == PackageInstaller.STATUS_SUCCESS) { onComplete(null) } else { onComplete(parsePackageInstallerMessage(context, intent)) } } } } ContextCompat.registerReceiver( application, receiver, IntentFilter(AppInstallerViewModel.ACTION), null, null, ContextCompat.RECEIVER_EXPORTED ) val pi = if(VERSION.SDK_INT >= 34) { PendingIntent.getBroadcast( application, 0, Intent(AppInstallerViewModel.ACTION), PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE ).intentSender } else { PendingIntent.getBroadcast(application, 0, Intent(AppInstallerViewModel.ACTION), PendingIntent.FLAG_MUTABLE).intentSender } application.getPackageInstaller().uninstall(packageName, pi) } @RequiresApi(28) fun installExistingApp(name: String): Boolean { return DPM.installExistingPackage(DAR, name) } // Credential manager policy val cmPackages = MutableStateFlow(emptyList()) @RequiresApi(34) fun getCmPolicy(): Int { return DPM.credentialManagerPolicy?.let { policy -> cmPackages.value = policy.packageNames.map { getAppInfo(it) } policy.policyType } ?: -1 } fun setCmPackage(name: String, status: Boolean) { cmPackages.update { list -> if (status) list + getAppInfo(name) else list.filter { it.name != name } } } @RequiresApi(34) fun setCmPolicy(type: Int) { DPM.credentialManagerPolicy = if (type != -1 && cmPackages.value.isNotEmpty()) { PackagePolicy(type, cmPackages.value.map { it.name }.toSet()) } else null getCmPolicy() } // Permitted input method val pimPackages = MutableStateFlow(emptyList()) fun getPimPackages(): Boolean { return DPM.getPermittedInputMethods(DAR).let { packages -> pimPackages.value = packages?.map { getAppInfo(it) } ?: emptyList() packages == null } } fun setPimPackage(name: String, status: Boolean) { pimPackages.update { packages -> if (status) packages + getAppInfo(name) else packages.filter { it.name != name } } } fun setPimPolicy(allowAll: Boolean): Boolean { val result = DPM.setPermittedInputMethods( DAR, if (allowAll) null else pimPackages.value.map { it.name }) getPimPackages() return result } // Permitted accessibility services val pasPackages = MutableStateFlow(emptyList()) fun getPasPackages(): Boolean { return DPM.getPermittedAccessibilityServices(DAR).let { packages -> pasPackages.value = packages?.map { getAppInfo(it) } ?: emptyList() packages == null } } fun setPasPackage(name: String, status: Boolean) { pasPackages.update { packages -> if (status) packages + getAppInfo(name) else packages.filter { it.name != name } } } fun setPasPolicy(allowAll: Boolean): Boolean { val result = DPM.setPermittedAccessibilityServices( DAR, if (allowAll) null else pasPackages.value.map { it.name }) getPasPackages() return result } fun enableSystemApp(name: String) { DPM.enableSystemApp(DAR, name) } val appStatus = MutableStateFlow(AppStatus(false, false, false, false, false, false)) fun getAppStatus(name: String) { appStatus.value = AppStatus( if (VERSION.SDK_INT >= 24) DPM.isPackageSuspended(DAR, name) else false, DPM.isApplicationHidden(DAR, name), DPM.isUninstallBlocked(DAR, name), if (VERSION.SDK_INT >= 30) name in DPM.getUserControlDisabledPackages(DAR) else false, if (VERSION.SDK_INT >= 28) name in DPM.getMeteredDataDisabledPackages(DAR) else false, if (VERSION.SDK_INT >= 28) DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true else false ) } // Application details @RequiresApi(24) fun adSetPackageSuspended(name: String, status: Boolean) { DPM.setPackagesSuspended(DAR, arrayOf(name), status) appStatus.update { it.copy(suspend = DPM.isPackageSuspended(DAR, name)) } } fun adSetPackageHidden(name: String, status: Boolean) { DPM.setApplicationHidden(DAR, name, status) appStatus.update { it.copy(hide = DPM.isApplicationHidden(DAR, name)) } } fun adSetPackageUb(name: String, status: Boolean) { DPM.setUninstallBlocked(DAR, name, status) appStatus.update { it.copy(uninstallBlocked = DPM.isUninstallBlocked(DAR, name)) } } @RequiresApi(30) fun adSetPackageUcd(name: String, status: Boolean) { DPM.setUserControlDisabledPackages(DAR, DPM.getUserControlDisabledPackages(DAR).run { if (status) plus(name) else minus(name) }) appStatus.update { it.copy(userControlDisabled = name in DPM.getUserControlDisabledPackages(DAR)) } } @RequiresApi(28) fun adSetPackageMdd(name: String, status: Boolean) { DPM.setMeteredDataDisabledPackages(DAR, DPM.getMeteredDataDisabledPackages(DAR).run { if (status) plus(name) else minus(name) }) appStatus.update { it.copy(meteredDataDisabled = name in DPM.getMeteredDataDisabledPackages(DAR)) } } @RequiresApi(28) fun adSetPackageKu(name: String, status: Boolean) { DPM.setKeepUninstalledPackages(DAR, DPM.getKeepUninstalledPackages(DAR)?.run { if (status) plus(name) else minus(name) } ?: emptyList()) appStatus.update { it.copy(keepUninstalled = DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true ) } } @RequiresApi(34) fun setDefaultDialer(name: String): Boolean { return try { DPM.setDefaultDialerApplication(name) true } catch (e: IllegalArgumentException) { e.printStackTrace() false } } @RequiresApi(24) fun reboot() { DPM.reboot(DAR) } @RequiresApi(24) fun requestBugReport(): Boolean { return try { DPM.requestBugreport(DAR) } catch (e: Exception) { e.printStackTrace() false } } @RequiresApi(24) fun getOrgName(): String { return try { DPM.getOrganizationName(DAR)?.toString() ?: "" } catch (_: Exception) { "" } } @RequiresApi(24) fun setOrgName(name: String) { DPM.setOrganizationName(DAR, name) } @RequiresApi(31) fun setOrgId(id: String): Boolean { return try { DPM.setOrganizationId(id) true } catch (_: IllegalStateException) { false } } @RequiresApi(31) fun getEnrollmentSpecificId(): String { return DPM.enrollmentSpecificId } val systemOptionsStatus = MutableStateFlow(SystemOptionsStatus()) fun getSystemOptionsStatus() { val privilege = Privilege.status.value systemOptionsStatus.value = SystemOptionsStatus( cameraDisabled = DPM.getCameraDisabled(null), screenCaptureDisabled = DPM.getScreenCaptureDisabled(null), statusBarDisabled = if (VERSION.SDK_INT >= 34 && privilege.run { device || (profile && affiliated) }) DPM.isStatusBarDisabled else false, autoTimeEnabled = if (VERSION.SDK_INT >= 30 && privilege.run { device || org }) DPM.getAutoTimeEnabled(DAR) else false, autoTimeZoneEnabled = if (VERSION.SDK_INT >= 30 && privilege.run { device || org }) DPM.getAutoTimeZoneEnabled(DAR) else false, autoTimeRequired = if (VERSION.SDK_INT < 30) DPM.autoTimeRequired else false, masterVolumeMuted = DPM.isMasterVolumeMuted(DAR), backupServiceEnabled = if (VERSION.SDK_INT >= 26) DPM.isBackupServiceEnabled(DAR) else false, btContactSharingDisabled = if (VERSION.SDK_INT >= 23 && privilege.work) DPM.getBluetoothContactSharingDisabled(DAR) else false, commonCriteriaMode = if (VERSION.SDK_INT >= 30) DPM.isCommonCriteriaModeEnabled(DAR) else false, usbSignalEnabled = if (VERSION.SDK_INT >= 31) DPM.isUsbDataSignalingEnabled else false, canDisableUsbSignal = if (VERSION.SDK_INT >= 31) DPM.canUsbDataSignalingBeDisabled() else false ) } fun setCameraDisabled(disabled: Boolean) { DPM.setCameraDisabled(DAR, disabled) ShortcutUtils.setShortcut(application, MyShortcut.DisableCamera, !disabled) systemOptionsStatus.update { it.copy(cameraDisabled = DPM.getCameraDisabled(null)) } } fun setScreenCaptureDisabled(disabled: Boolean) { DPM.setScreenCaptureDisabled(DAR, disabled) systemOptionsStatus.update { it.copy(screenCaptureDisabled = DPM.getScreenCaptureDisabled(null)) } } @RequiresApi(23) fun setStatusBarDisabled(disabled: Boolean) { val result = DPM.setStatusBarDisabled(DAR, disabled) if (result) systemOptionsStatus.update { it.copy(statusBarDisabled = disabled) } } @RequiresApi(30) fun setAutoTimeEnabled(enabled: Boolean) { DPM.setAutoTimeEnabled(DAR, enabled) systemOptionsStatus.update { it.copy(autoTimeEnabled = DPM.getAutoTimeEnabled(DAR)) } } @RequiresApi(30) fun setAutoTimeZoneEnabled(enabled: Boolean) { DPM.setAutoTimeZoneEnabled(DAR, enabled) systemOptionsStatus.update { it.copy(autoTimeZoneEnabled = DPM.getAutoTimeZoneEnabled(DAR)) } } @Suppress("DEPRECATION") fun setAutoTimeRequired(required: Boolean) { DPM.setAutoTimeRequired(DAR, required) systemOptionsStatus.update { it.copy(autoTimeRequired = DPM.autoTimeRequired) } } fun setMasterVolumeMuted(muted: Boolean) { DPM.setMasterVolumeMuted(DAR, muted) ShortcutUtils.setShortcut(application, MyShortcut.Mute, !muted) systemOptionsStatus.update { it.copy(masterVolumeMuted = DPM.isMasterVolumeMuted(DAR)) } } @RequiresApi(26) fun setBackupServiceEnabled(enabled: Boolean) { DPM.setBackupServiceEnabled(DAR, enabled) systemOptionsStatus.update { it.copy(backupServiceEnabled = DPM.isBackupServiceEnabled(DAR)) } } @RequiresApi(23) fun setBtContactSharingDisabled(disabled: Boolean) { DPM.setBluetoothContactSharingDisabled(DAR, disabled) systemOptionsStatus.update { it.copy(btContactSharingDisabled = DPM.getBluetoothContactSharingDisabled(DAR)) } } @RequiresApi(30) fun setCommonCriteriaModeEnabled(enabled: Boolean) { DPM.setCommonCriteriaModeEnabled(DAR, enabled) systemOptionsStatus.update { it.copy(commonCriteriaMode = DPM.isCommonCriteriaModeEnabled(DAR)) } } @RequiresApi(31) fun setUsbSignalEnabled(enabled: Boolean) { DPM.isUsbDataSignalingEnabled = enabled systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) } } @RequiresApi(23) fun setKeyguardDisabled(disabled: Boolean): Boolean { return DPM.setKeyguardDisabled(DAR, disabled) } fun lockScreen(evictKey: Boolean) { if (VERSION.SDK_INT >= 26 && Privilege.status.value.work) { DPM.lockNow(if (evictKey) DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY else 0) } else { DPM.lockNow() } } val hardwareProperties = MutableStateFlow(HardwareProperties()) var hpRefreshInterval = 1000L fun setHpRefreshInterval(interval: Float) { hpRefreshInterval = (interval * 1000).toLong() } @RequiresApi(24) suspend fun getHardwareProperties() { val hpm = application.getSystemService(HardwarePropertiesManager::class.java) while (true) { val properties = HardwareProperties( temperatureTypes.map { (type, _) -> type to hpm.getDeviceTemperatures(type, HardwarePropertiesManager.TEMPERATURE_CURRENT).toList() }.toMap(), hpm.cpuUsages.map { it.active to it.total }, hpm.fanSpeeds.toList() ) if (properties.cpuUsages.isEmpty() && properties.fanSpeeds.isEmpty() && properties.temperatures.isEmpty()) { break } hardwareProperties.value = properties delay(hpRefreshInterval) } } @RequiresApi(28) fun setTime(time: Long, useCurrentTz: Boolean): Boolean { val offset = if (useCurrentTz) { ZonedDateTime.now(ZoneId.systemDefault()).offset.totalSeconds * 1000L } else 0L return DPM.setTime(DAR, time - offset) } @RequiresApi(28) fun setTimeZone(tz: String): Boolean { return DPM.setTimeZone(DAR, tz) } @RequiresApi(36) fun getAutoTimePolicy(): Int { return DPM.autoTimePolicy } @RequiresApi(36) fun setAutoTimePolicy(policy: Int) { DPM.autoTimePolicy = policy } @RequiresApi(36) fun getAutoTimeZonePolicy(): Int { return DPM.autoTimeZonePolicy } @RequiresApi(36) fun setAutoTimeZonePolicy(policy: Int) { DPM.autoTimeZonePolicy = policy } @RequiresApi(35) fun getContentProtectionPolicy(): Int { return DPM.getContentProtectionPolicy(DAR) } @RequiresApi(35) fun setContentProtectionPolicy(policy: Int) { DPM.setContentProtectionPolicy(DAR, policy) } @RequiresApi(23) fun getPermissionPolicy(): Int { return DPM.getPermissionPolicy(DAR) } @RequiresApi(23) fun setPermissionPolicy(policy: Int) { DPM.setPermissionPolicy(DAR, policy) } @RequiresApi(34) fun getMtePolicy(): Int { return DPM.mtePolicy } @RequiresApi(34) fun setMtePolicy(policy: Int): Boolean { return try { DPM.mtePolicy = policy true } catch (_: UnsupportedOperationException) { false } } @RequiresApi(31) fun getNsAppPolicy(): Int { return DPM.nearbyAppStreamingPolicy } @RequiresApi(31) fun setNsAppPolicy(policy: Int) { DPM.nearbyAppStreamingPolicy = policy } @RequiresApi(31) fun getNsNotificationPolicy(): Int { return DPM.nearbyNotificationStreamingPolicy } @RequiresApi(31) fun setNsNotificationPolicy(policy: Int) { DPM.nearbyNotificationStreamingPolicy = policy } val lockTaskPackages = MutableStateFlow(emptyList()) @RequiresApi(26) fun getLockTaskPackages() { lockTaskPackages.value = DPM.getLockTaskPackages(DAR).map { getAppInfo(it) } } @RequiresApi(26) fun setLockTaskPackage(name: String, status: Boolean) { DPM.setLockTaskPackages(DAR, lockTaskPackages.value.map { it.name } .run { if (status) plus(name) else minus(name) } .toTypedArray() ) getLockTaskPackages() } @RequiresApi(28) fun startLockTaskMode(packageName: String, activity: String): Int { if (!NotificationUtils.checkPermission(application)) return 0 if (!DPM.isLockTaskPermitted(packageName)) return 1 val options = ActivityOptions.makeBasic().setLockTaskEnabled(true) val intent = if(activity.isNotEmpty()) { Intent().setComponent(ComponentName(packageName, activity)) } else PM.getLaunchIntentForPackage(packageName) if (intent != null) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) application.startActivity(intent, options.toBundle()) return 0 } else { return 2 } } @RequiresApi(28) fun getLockTaskFeatures(): Int { return DPM.getLockTaskFeatures(DAR) } @RequiresApi(28) fun setLockTaskFeatures(flags: Int): String? { try { DPM.setLockTaskFeatures(DAR, flags) return null } catch (e: IllegalArgumentException) { return e.message } } val installedCaCerts = MutableStateFlow(emptyList()) fun getCaCerts() { installedCaCerts.value = DPM.getInstalledCaCerts(DAR).mapNotNull { parseCaCert(it) } } fun parseCaCert(uri: Uri): CaCertInfo? { return try { application.contentResolver.openInputStream(uri)?.use { parseCaCert(it.readBytes()) } } catch(e: Exception) { e.printStackTrace() null } } fun parseCaCert(bytes: ByteArray): CaCertInfo? { val hash = MessageDigest.getInstance("SHA-256").digest(bytes).toHexString() return try { val factory = CertificateFactory.getInstance("X.509") val cert = factory.generateCertificate(bytes.inputStream()) as X509Certificate CaCertInfo( hash, cert.serialNumber.toString(16), cert.issuerX500Principal.name, cert.subjectX500Principal.name, cert.notBefore.time, cert.notAfter.time, bytes ) } catch (e: CertificateException) { e.printStackTrace() null } } fun installCaCert(cert: CaCertInfo): Boolean { val result = DPM.installCaCert(DAR, cert.bytes) if (result) getCaCerts() return result } fun uninstallCaCert(cert: CaCertInfo) { DPM.uninstallCaCert(DAR, cert.bytes) getCaCerts() } fun uninstallAllCaCerts() { DPM.uninstallAllUserCaCerts(DAR) getCaCerts() } fun exportCaCert(uri: Uri, cert: CaCertInfo) { application.contentResolver.openOutputStream(uri)?.use { it.write(cert.bytes) } } val mdAccountTypes = MutableStateFlow(emptyList()) fun getMdAccountTypes() { mdAccountTypes.value = DPM.accountTypesWithManagementDisabled?.toList() ?: emptyList() } fun setMdAccountType(type: String, disabled: Boolean) { DPM.setAccountManagementDisabled(DAR, type, disabled) getMdAccountTypes() } @RequiresApi(30) fun getFrpPolicy(): FrpPolicyInfo { return try { val policy = DPM.getFactoryResetProtectionPolicy(DAR) FrpPolicyInfo( true, policy != null, policy?.isFactoryResetProtectionEnabled ?: false, policy?.factoryResetProtectionAccounts ?: emptyList() ) } catch (_: UnsupportedOperationException) { FrpPolicyInfo(false, false, false, emptyList()) } } @RequiresApi(30) fun setFrpPolicy(info: FrpPolicyInfo) { val policy = if (info.usePolicy) { FactoryResetProtectionPolicy.Builder() .setFactoryResetProtectionEnabled(info.enabled) .setFactoryResetProtectionAccounts(info.accounts) .build() } else null DPM.setFactoryResetProtectionPolicy(DAR, policy) } fun wipeData(wipeDevice: Boolean, flags: Int, reason: String) { if (wipeDevice && VERSION.SDK_INT >= 34) { DPM.wipeDevice(flags) } else { if(VERSION.SDK_INT >= 28 && reason.isNotEmpty()) { DPM.wipeData(flags, reason) } else { DPM.wipeData(flags) } } } @RequiresApi(23) fun getSystemUpdatePolicy(): SystemUpdatePolicyInfo { val policy = DPM.systemUpdatePolicy return SystemUpdatePolicyInfo( policy?.policyType ?: -1, policy?.installWindowStart ?: 0, policy?.installWindowEnd ?: 0 ) } @RequiresApi(23) fun setSystemUpdatePolicy(info: SystemUpdatePolicyInfo) { val policy = when (info.type) { SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC -> SystemUpdatePolicy.createAutomaticInstallPolicy() SystemUpdatePolicy.TYPE_INSTALL_WINDOWED -> SystemUpdatePolicy.createWindowedInstallPolicy(info.start, info.end) SystemUpdatePolicy.TYPE_POSTPONE -> SystemUpdatePolicy.createPostponeInstallPolicy() else -> null } DPM.setSystemUpdatePolicy(DAR, policy) } @RequiresApi(26) fun getPendingSystemUpdate(): PendingSystemUpdateInfo { val update = DPM.getPendingSystemUpdate(DAR) return PendingSystemUpdateInfo(update != null, update?.receivedTime ?: 0, update?.securityPatchState == SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE) } @RequiresApi(29) fun installSystemUpdate(uri: Uri, callback: (String) -> Unit) { val callback = object: InstallSystemUpdateCallback() { override fun onInstallUpdateError(errorCode: Int, errorMessage: String) { super.onInstallUpdateError(errorCode, errorMessage) val errDetail = when(errorCode) { UPDATE_ERROR_BATTERY_LOW -> R.string.battery_low UPDATE_ERROR_UPDATE_FILE_INVALID -> R.string.update_file_invalid UPDATE_ERROR_INCORRECT_OS_VERSION -> R.string.incorrect_os_ver UPDATE_ERROR_FILE_NOT_FOUND -> R.string.file_not_exist else -> R.string.unknown_error } callback(application.getString(errDetail) + "\n$errorMessage") } } DPM.installSystemUpdate(DAR, uri, application.mainExecutor, callback) } @RequiresApi(24) fun isCreatingWorkProfileAllowed(): Boolean { return DPM.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) } fun activateDoByShizuku(callback: (Boolean, String?) -> Unit) { viewModelScope.launch(Dispatchers.IO) { useShizuku(application) { service -> try { val result = IUserService.Stub.asInterface(service) .execute(ACTIVATE_DEVICE_OWNER_COMMAND) if (result == null || result.getInt("code", -1) != 0) { callback(false, null) } else { Privilege.updateStatus() handlePrivilegeChange(application) callback( true, result.getString("output") + "\n" + result.getString("error") ) } } catch (e: Exception) { e.printStackTrace() callback(false, null) } } } } fun activateDoByRoot(callback: (Boolean, String?) -> Unit) { Shell.getShell { shell -> if(shell.isRoot) { val result = Shell.cmd(ACTIVATE_DEVICE_OWNER_COMMAND).exec() val output = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n") if (result.isSuccess) { Privilege.updateStatus() handlePrivilegeChange(application) } callback(result.isSuccess, output) } else { callback(false, application.getString(R.string.permission_denied)) } } } @RequiresApi(28) fun activateDoByDhizuku(callback: (Boolean, String?) -> Unit) { DPM.transferOwnership(DAR, MyAdminComponent, null) SP.dhizuku = false Privilege.initialize(application) handlePrivilegeChange(application) callback(true, null) } fun activateDhizukuMode(callback: (Boolean, String?) -> Unit) { fun onSucceed() { SP.dhizuku = true Privilege.initialize(application) handlePrivilegeChange(application) callback(true, null) } if (Dhizuku.init(application)) { if (Dhizuku.isPermissionGranted()) { onSucceed() } else { Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() { override fun onRequestPermission(grantResult: Int) { if(grantResult == PackageManager.PERMISSION_GRANTED) onSucceed() } }) } } else { callback(false, application.getString(R.string.failed_to_init_dhizuku)) } } fun clearDeviceOwner() { DPM.clearDeviceOwnerApp(application.packageName) } @RequiresApi(24) fun clearProfileOwner() { DPM.clearProfileOwner(MyAdminComponent) } fun deactivateDhizukuMode() { SP.dhizuku = false Privilege.initialize(application) } val dhizukuClients = MutableStateFlow(emptyList>()) fun getDhizukuClients() { viewModelScope.launch(Dispatchers.IO) { dhizukuClients.value = myRepo.getDhizukuClients().mapNotNull { val packageName = PM.getNameForUid(it.uid) if (packageName == null) { myRepo.deleteDhizukuClient(it) null } else { it to getAppInfo(packageName) } } } } fun getDhizukuServerEnabled(): Boolean { return SP.dhizukuServer } fun setDhizukuServerEnabled(status: Boolean) { SP.dhizukuServer = status } fun updateDhizukuClient(info: DhizukuClientInfo) { myRepo.setDhizukuClient(info) dhizukuClients.update { list -> val ml = list.toMutableList() val index = ml.indexOfFirst { it.first.uid == info.uid } ml[index] = info to ml[index].second ml } } @RequiresApi(24) fun getLockScreenInfo(): String { return DPM.deviceOwnerLockScreenInfo?.toString() ?: "" } @RequiresApi(24) fun setLockScreenInfo(text: String) { DPM.setDeviceOwnerLockScreenInfo(DAR, text) } val delegatedAdmins = MutableStateFlow(emptyList()) @RequiresApi(26) fun getDelegatedAdmins() { val list = mutableListOf() delegatedScopesList.forEach { scope -> DPM.getDelegatePackages(DAR, scope.id)?.forEach { pkg -> val index = list.indexOfFirst { it.app.name == pkg } if (index == -1) { list += DelegatedAdmin(getAppInfo(pkg), listOf(scope.id)) } else { list[index] = DelegatedAdmin(list[index].app, list[index].scopes + scope.id) } } } delegatedAdmins.value = list } @RequiresApi(26) fun setDelegatedAdmin(name: String, scopes: List) { DPM.setDelegatedScopes(DAR, name, scopes) getDelegatedAdmins() } @RequiresApi(34) fun getDeviceFinanced(): Boolean { return DPM.isDeviceFinanced } @RequiresApi(33) fun getDpmRh(): String? { return DPM.devicePolicyManagementRoleHolderPackage } fun getStorageEncryptionStatus(): Int { return DPM.storageEncryptionStatus } @RequiresApi(28) fun getDeviceIdAttestationSupported(): Boolean { return DPM.isDeviceIdAttestationSupported } @RequiresApi(30) fun getUniqueDeviceAttestationSupported(): Boolean { return DPM.isUniqueDeviceAttestationSupported } fun getActiveAdmins(): String { return DPM.activeAdmins?.joinToString("\n") { it.flattenToShortString() } ?: application.getString(R.string.none) } @RequiresApi(24) fun getShortSupportMessage(): String { return DPM.getShortSupportMessage(DAR)?.toString() ?: "" } @RequiresApi(24) fun getLongSupportMessage(): String { return DPM.getLongSupportMessage(DAR)?.toString() ?: "" } @RequiresApi(24) fun setShortSupportMessage(text: String?) { DPM.setShortSupportMessage(DAR, text) } @RequiresApi(24) fun setLongSupportMessage(text: String?) { DPM.setLongSupportMessage(DAR, text) } val deviceAdminReceivers = MutableStateFlow(emptyList()) fun getDeviceAdminReceivers() { viewModelScope.launch(Dispatchers.IO) { deviceAdminReceivers.value = PM.queryBroadcastReceivers( Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED), PackageManager.GET_META_DATA ).mapNotNull { try { DeviceAdminInfo(application, it) } catch(_: Exception) { null } }.filter { it.isVisible && it.packageName != "com.bintianqi.owndroid" && it.activityInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 0 }.map { DeviceAdmin(getAppInfo(it.packageName), it.component) } } } @RequiresApi(28) fun transferOwnership(component: ComponentName) { DPM.transferOwnership(DAR, component, null) Privilege.updateStatus() } val userRestrictions = MutableStateFlow(emptyMap()) @RequiresApi(24) fun getUserRestrictions() { val bundle = DPM.getUserRestrictions(DAR) userRestrictions.value = bundle.keySet().associateWith { bundle.getBoolean(it) } } fun setUserRestriction(name: String, state: Boolean): Boolean { return try { if (state) { DPM.addUserRestriction(DAR, name) } else { DPM.clearUserRestriction(DAR, name) } userRestrictions.update { it.plus(name to state) } true } catch (_: SecurityException) { false } } fun createWorkProfile(options: CreateWorkProfileOptions): Intent { val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) if (VERSION.SDK_INT >= 23) { intent.putExtra( DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, MyAdminComponent ) } else { intent.putExtra( DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, application.packageName ) } if (options.migrateAccount && VERSION.SDK_INT >= 22) { intent.putExtra( DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, Account(options.accountName, options.accountType) ) if (VERSION.SDK_INT >= 26) { intent.putExtra( DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION, options.keepAccount ) } } if (VERSION.SDK_INT >= 24) { intent.putExtra( DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, options.skipEncrypt ) } if (VERSION.SDK_INT >= 33) { intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_ALLOW_OFFLINE, options.offline) } return intent } fun activateOrgProfileByShizuku(callback: (Boolean) -> Unit) { viewModelScope.launch(Dispatchers.IO) { var succeed = false useShizuku(application) { service -> val result = IUserService.Stub.asInterface(service).execute(activateOrgProfileCommand) succeed = result?.getInt("code", -1) == 0 callback(succeed) } if (succeed) Privilege.updateStatus() } } @RequiresApi(30) fun getPersonalAppsSuspendedReason(): Int { return DPM.getPersonalAppsSuspendedReasons(DAR) } @RequiresApi(30) fun setPersonalAppsSuspended(suspended: Boolean) { DPM.setPersonalAppsSuspended(DAR, suspended) } @RequiresApi(30) fun getProfileMaxTimeOff(): Long { return DPM.getManagedProfileMaximumTimeOff(DAR) } @RequiresApi(30) fun setProfileMaxTimeOff(time: Long) { DPM.setManagedProfileMaximumTimeOff(DAR, time) } fun addCrossProfileIntentFilter(options: IntentFilterOptions) { val filter = IntentFilter(options.action) if (options.category.isNotEmpty()) filter.addCategory(options.category) if (options.mimeType.isNotEmpty()) filter.addDataType(options.mimeType) val flags = when(options.direction) { IntentFilterDirection.ToManaged -> DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED IntentFilterDirection.ToParent -> DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT IntentFilterDirection.Both -> DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED or DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT } DPM.addCrossProfileIntentFilter(DAR, filter, flags) } val UM = application.getSystemService(Context.USER_SERVICE) as UserManager @RequiresApi(28) fun getLogoutEnabled(): Boolean { return DPM.isLogoutEnabled } @RequiresApi(28) fun setLogoutEnabled(enabled: Boolean) { DPM.setLogoutEnabled(DAR, enabled) } fun getUserInformation(): UserInformation { val uh = Binder.getCallingUserHandle() return UserInformation( if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false, if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false, if (VERSION.SDK_INT >= 23) UM.isSystemUser else false, if (VERSION.SDK_INT >= 34) UM.isAdminUser else false, if (VERSION.SDK_INT >= 25) UM.isDemoUser else false, if (VERSION.SDK_INT >= 23) UM.getUserCreationTime(uh) else 0, if (VERSION.SDK_INT >= 28) DPM.isLogoutEnabled else false, if (VERSION.SDK_INT >= 28) DPM.isEphemeralUser(DAR) else false, if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false, UM.getSerialNumberForUser(uh) ) } @RequiresApi(28) fun startUser(id: Int, isUserId: Boolean): Int { val uh = getUserHandle(id, isUserId) if (uh == null) return R.string.user_not_exist return getUserOperationResultText(DPM.startUserInBackground(DAR, uh)) } fun switchUser(id: Int, isUserId: Boolean): Boolean { val uh = getUserHandle(id, isUserId) if (uh == null) return false DPM.switchUser(DAR, uh) return true } @RequiresApi(28) fun stopUser(id: Int, isUserId: Boolean): Int { val uh = getUserHandle(id, isUserId) if (uh == null) return R.string.user_not_exist return getUserOperationResultText(DPM.stopUser(DAR, uh)) } fun deleteUser(id: Int, isUserId: Boolean): Boolean { val uh = getUserHandle(id, isUserId) if (uh == null) return false return DPM.removeUser(DAR, uh) } fun getUserHandle(id: Int, isUserId: Boolean): UserHandle? { return if (isUserId && VERSION.SDK_INT >= 24) { UserHandle.getUserHandleForUid(id * 100000) } else { UM.getUserForSerialNumber(id.toLong()) } } fun getUserOperationResultText(code: Int): Int { return when (code) { UserManager.USER_OPERATION_SUCCESS -> R.string.success UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> R.string.fail_managed_profile UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS -> R.string.limit_reached UserManager.USER_OPERATION_ERROR_CURRENT_USER -> R.string.fail_current_user else -> R.string.unknown } } @RequiresApi(24) fun createUser(name: String, flags: Int, callback: (CreateUserResult) -> Unit) { viewModelScope.launch(Dispatchers.IO) { try { val uh = DPM.createAndManageUser(DAR, name, DAR, null, flags) if (uh == null) { callback(CreateUserResult(R.string.failed)) } else { callback(CreateUserResult(R.string.succeeded, UM.getSerialNumberForUser(uh))) } } catch (e: Exception) { e.printStackTrace() if (VERSION.SDK_INT >= 28 && e is UserManager.UserOperationException) { callback(CreateUserResult(getUserOperationResultText(e.userOperationResult))) } else { callback(CreateUserResult(R.string.error)) } } } } val affiliationIds = MutableStateFlow(emptyList()) @RequiresApi(26) fun getAffiliationIds() { affiliationIds.value = DPM.getAffiliationIds(DAR).toList() } @RequiresApi(26) fun setAffiliationId(id: String, state: Boolean) { val newList = affiliationIds.value.run { if (state) plus(id) else minus(id) } DPM.setAffiliationIds(DAR, newList.toSet()) affiliationIds.value = newList } fun setProfileName(name: String) { DPM.setProfileName(DAR, name) } @RequiresApi(23) fun setUserIcon(bitmap: Bitmap) { DPM.setUserIcon(DAR, bitmap) } @RequiresApi(28) fun getSecondaryUsers(): List { return DPM.getSecondaryUsers(DAR).map { UM.getSerialNumberForUser(it) } } @RequiresApi(28) fun getUserSessionMessages(): Pair { return (DPM.getStartUserSessionMessage(DAR)?.toString() ?: "") to (DPM.getEndUserSessionMessage(DAR)?.toString() ?: "") } @RequiresApi(28) fun setStartUserSessionMessage(message: String?) { DPM.setStartUserSessionMessage(DAR, message) } @RequiresApi(28) fun setEndUserSessionMessage(message: String?) { DPM.setEndUserSessionMessage(DAR, message) } @RequiresApi(28) fun logoutUser(): Int { return getUserOperationResultText(DPM.logoutUser(DAR)) } val WM = application.getSystemService(Context.WIFI_SERVICE) as WifiManager // Lockdown admin configured networks @RequiresApi(30) fun getLanEnabled(): Boolean { return DPM.hasLockdownAdminConfiguredNetworks(DAR) } @RequiresApi(30) fun setLanEnabled(state: Boolean) { DPM.setConfiguredNetworksLockdownState(DAR, state) } fun setWifiEnabled(enabled: Boolean): Boolean { return WM.setWifiEnabled(enabled) } fun disconnectWifi(): Boolean { return WM.disconnect() } fun reconnectWifi(): Boolean { return WM.reconnect() } @RequiresApi(24) fun getWifiMac(): String? { return DPM.getWifiMacAddress(DAR) } val configuredNetworks = MutableStateFlow(emptyList()) fun getConfiguredNetworks() { configuredNetworks.value = WM.configuredNetworks.distinctBy { it.networkId }.map { conf -> WifiInfo( conf.networkId, conf.SSID.removeSurrounding("\""), null, conf.BSSID ?: "", null, WifiStatus.entries.find { it.id == conf.status }!!, null, "", null, null, null, null ) } } fun enableNetwork(id: Int): Boolean { return WM.enableNetwork(id, false) } fun disableNetwork(id: Int): Boolean { return WM.disableNetwork(id) } fun removeNetwork(id: Int): Boolean{ return WM.removeNetwork(id) } fun setWifi(info: WifiInfo): Boolean { val conf = WifiConfiguration() conf.SSID = "\"" + info.ssid + "\"" info.hiddenSsid?.let { conf.hiddenSSID = it } if (VERSION.SDK_INT >= 30) info.security?.let { conf.setSecurityParams(it.id) } if (info.security == WifiSecurity.Psk) conf.preSharedKey = info.password if (VERSION.SDK_INT >= 33) info.macRandomization?.let { conf.macRandomizationSetting = it.id } if (VERSION.SDK_INT >= 33 && info.ipMode != null) { val ipConf = if (info.ipMode == IpMode.Static && info.ipConf != null) { val constructor = LinkAddress::class.constructors.find { it.parameters.size == 1 && it.parameters[0].type.jvmErasure == String::class } val address = constructor!!.call(info.ipConf.address) val staticIpConf = StaticIpConfiguration.Builder() .setIpAddress(address) .setGateway(InetAddress.getByName(info.ipConf.gateway)) .setDnsServers(info.ipConf.dns.map { InetAddress.getByName(it) }) .build() IpConfiguration.Builder().setStaticIpConfiguration(staticIpConf).build() } else null conf.setIpConfiguration(ipConf) } if (VERSION.SDK_INT >= 26 && info.proxyMode != null) { val proxy = if (info.proxyMode == ProxyMode.Http) { info.proxyConf?.let { ProxyInfo.buildDirectProxy(it.host, it.port, it.exclude) } } else null conf.httpProxy = proxy } val result = if (info.id != -1) { conf.networkId = info.id WM.updateNetwork(conf) } else { WM.addNetwork(conf) } if (result != -1) { when (info.status) { WifiStatus.Current -> WM.enableNetwork(result, true) WifiStatus.Enabled -> WM.enableNetwork(result, false) WifiStatus.Disabled -> WM.disableNetwork(result) } } return result != -1 } @RequiresApi(33) fun getMinimumWifiSecurityLevel(): Int { return DPM.minimumRequiredWifiSecurityLevel } @RequiresApi(33) fun setMinimumWifiSecurityLevel(level: Int) { DPM.minimumRequiredWifiSecurityLevel = level } @RequiresApi(33) fun getSsidPolicy(): SsidPolicy { val policy = DPM.wifiSsidPolicy return SsidPolicy( SsidPolicyType.entries.find { it.id == policy?.policyType } ?: SsidPolicyType.None, policy?.ssids?.map { it.bytes.decodeToString() } ?: emptyList() ) } @RequiresApi(33) fun setSsidPolicy(policy: SsidPolicy) { val newPolicy = if (policy.type != SsidPolicyType.None) { WifiSsidPolicy( policy.type.id, policy.list.map { WifiSsid.fromBytes(it.encodeToByteArray()) }.toSet() ) } else null DPM.wifiSsidPolicy = newPolicy } @RequiresApi(24) fun getPackageUid(name: String): Int { return PM.getPackageUid(name, 0) } var networkStatsData = emptyList() @RequiresApi(23) fun readNetworkStats(stats: NetworkStats): List { val list = mutableListOf() while (stats.hasNextBucket()) { val bucket = NetworkStats.Bucket() stats.getNextBucket(bucket) list += readNetworkStatsBucket(bucket) } stats.close() return list } @RequiresApi(23) fun readNetworkStatsBucket(bucket: NetworkStats.Bucket): NetworkStatsData { return NetworkStatsData( bucket.rxBytes, bucket.rxPackets, bucket.txBytes, bucket.txPackets, bucket.uid, bucket.state, bucket.startTimeStamp, bucket.endTimeStamp, if (VERSION.SDK_INT >= 24) bucket.tag else null, if (VERSION.SDK_INT >= 24) bucket.roaming else null, if (VERSION.SDK_INT >= 26) bucket.metered else null ) } @Suppress("NewApi") fun queryNetworkStats(params: QueryNetworkStatsParams, callback: (String?) -> Unit) { viewModelScope.launch(Dispatchers.IO) { val nsm = application.getSystemService(NetworkStatsManager::class.java) try { val data = when (params.target) { NetworkStatsTarget.Device -> listOf(readNetworkStatsBucket( nsm.querySummaryForDevice( params.networkType.type, null, params.startTime, params.endTime ) )) NetworkStatsTarget.User -> listOf(readNetworkStatsBucket( nsm.querySummaryForUser( params.networkType.type, null, params.startTime, params.endTime ) )) NetworkStatsTarget.Uid -> readNetworkStats(nsm.queryDetailsForUid( params.networkType.type, null, params.startTime, params.endTime, params.uid )) NetworkStatsTarget.UidTag -> readNetworkStats(nsm.queryDetailsForUidTag( params.networkType.type, null, params.startTime, params.endTime, params.uid, params.tag )) NetworkStatsTarget.UidTagState -> readNetworkStats( nsm.queryDetailsForUidTagState( params.networkType.type, null, params.startTime, params.endTime, params.uid, params.tag, params.state.id ) ) } networkStatsData = data withContext(Dispatchers.Main) { if (data.isEmpty()) { callback(application.getString(R.string.no_data)) } else { callback(null) } } } catch(e: Exception) { e.printStackTrace() withContext(Dispatchers.Main) { callback(e.message ?: "") } } } } fun clearNetworkStats() { networkStatsData = emptyList() } @RequiresApi(29) fun getPrivateDns(): PrivateDnsConfiguration { return PrivateDnsConfiguration( DPM.getGlobalPrivateDnsMode(DAR), DPM.getGlobalPrivateDnsHost(DAR) ?: "" ) } @Suppress("PrivateApi") @RequiresApi(29) fun setPrivateDns(conf: PrivateDnsConfiguration): Boolean { return try { val field = DevicePolicyManager::class.java.getDeclaredField("mService") field.isAccessible = true val dpm = field.get(DPM) as IDevicePolicyManager val result = dpm.setGlobalPrivateDns(DAR, conf.mode, conf.host) result == DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR } catch (e: Exception) { e.printStackTrace() false } } @RequiresApi(24) fun getAlwaysOnVpnPackage(): String { return DPM.getAlwaysOnVpnPackage(DAR) ?: "" } @RequiresApi(29) fun getAlwaysOnVpnLockdown(): Boolean { return DPM.isAlwaysOnVpnLockdownEnabled(DAR) } @RequiresApi(24) fun setAlwaysOnVpn(name: String?, lockdown: Boolean): Int { return try { DPM.setAlwaysOnVpnPackage(DAR, name, lockdown) R.string.succeeded } catch (_: UnsupportedOperationException) { R.string.unsupported } catch (_: PackageManager.NameNotFoundException) { R.string.not_installed } } fun setRecommendedGlobalProxy(conf: RecommendedProxyConf) { val info = when (conf.type) { ProxyType.Off -> null ProxyType.Pac -> { if (VERSION.SDK_INT >= 30 && conf.specifyPort) { ProxyInfo.buildPacProxy(conf.url.toUri(), conf.port) } else { ProxyInfo.buildPacProxy(conf.url.toUri()) } } ProxyType.Direct -> { ProxyInfo.buildDirectProxy(conf.host, conf.port, conf.exclude) } } DPM.setRecommendedGlobalProxy(DAR, info) } // PNS: preferential network service @RequiresApi(31) fun getPnsEnabled(): Boolean { return DPM.isPreferentialNetworkServiceEnabled } @RequiresApi(31) fun setPnsEnabled(enabled: Boolean) { DPM.isPreferentialNetworkServiceEnabled = enabled } val pnsConfigs = MutableStateFlow(emptyList()) @RequiresApi(33) fun getPnsConfigs() { pnsConfigs.value = DPM.preferentialNetworkServiceConfigs.map { PreferentialNetworkServiceInfo( it.isEnabled, it.networkId, it.isFallbackToDefaultConnectionAllowed, if (VERSION.SDK_INT >= 34) it.shouldBlockNonMatchingNetworks() else false, it.excludedUids.toList(), it.includedUids.toList() ) } } @RequiresApi(33) fun buildPnsConfig( info: PreferentialNetworkServiceInfo ): PreferentialNetworkServiceConfig { return PreferentialNetworkServiceConfig.Builder().apply { setEnabled(info.enabled) @Suppress("WrongConstant") setNetworkId(info.id) setFallbackToDefaultConnectionAllowed(info.allowFallback) if (VERSION.SDK_INT >= 34) setShouldBlockNonMatchingNetworks(info.blockNonMatching) setIncludedUids(info.includedUids.toIntArray()) setExcludedUids(info.excludedUids.toIntArray()) }.build() } @RequiresApi(33) fun setPnsConfig(info: PreferentialNetworkServiceInfo, state: Boolean) { val configs = pnsConfigs.value.run { if (state) plus(info) else minus(info) }.map { buildPnsConfig(it) } DPM.preferentialNetworkServiceConfigs = configs } val apnConfigs = MutableStateFlow(listOf()) @RequiresApi(28) fun getApnEnabled(): Boolean { return DPM.isOverrideApnEnabled(DAR) } @RequiresApi(28) fun setApnEnabled(enabled: Boolean) { DPM.setOverrideApnsEnabled(DAR, enabled) } @RequiresApi(28) fun getApnConfigs() { apnConfigs.value = DPM.getOverrideApns(DAR).map { val proxy = if (VERSION.SDK_INT >= 29) it.proxyAddressAsString else it.proxyAddress.hostName val mmsProxy = if (VERSION.SDK_INT >= 29) it.mmsProxyAddressAsString else it.mmsProxyAddress.hostName ApnConfig( it.isEnabled, it.entryName, it.apnName, proxy, it.proxyPort, it.user, it.password, it.apnTypeBitmask, it.mmsc.toString(), mmsProxy, it.mmsProxyPort, ApnAuthType.entries.find { type -> type.id == it.authType }!!, ApnProtocol.entries.find { protocol -> protocol.id == it.protocol }!!, ApnProtocol.entries.find { protocol -> protocol.id == it.roamingProtocol }!!, it.networkTypeBitmask, if (VERSION.SDK_INT >= 33) it.profileId else 0, if (VERSION.SDK_INT >= 29) it.carrierId else 0, if (VERSION.SDK_INT >= 33) it.mtuV4 else 0, if (VERSION.SDK_INT >= 33) it.mtuV6 else 0, ApnMvnoType.entries.find { type -> type.id == it.mvnoType }!!, it.operatorNumeric, if (VERSION.SDK_INT >= 33) it.isPersistent else true, if (VERSION.SDK_INT >= 35) it.isAlwaysOn else true, it.id ) } } @RequiresApi(28) fun buildApnSetting(config: ApnConfig): ApnSetting? { val builder = ApnSetting.Builder() builder.setCarrierEnabled(config.enabled) builder.setEntryName(config.name) builder.setApnName(config.apn) if (VERSION.SDK_INT >= 29) builder.setProxyAddress(config.proxy) else builder.setProxyAddress(InetAddress.getByName(config.proxy)) config.port?.let { builder.setProxyPort(it) } builder.setUser(config.username) builder.setPassword(config.password) builder.setApnTypeBitmask(config.apnType) builder.setMmsc(config.mmsc.toUri()) if (VERSION.SDK_INT >= 29) builder.setMmsProxyAddress(config.mmsProxy) else builder.setMmsProxyAddress(InetAddress.getByName(config.mmsProxy)) builder.setAuthType(config.authType.id) builder.setProtocol(config.protocol.id) builder.setRoamingProtocol(config.roamingProtocol.id) builder.setNetworkTypeBitmask(config.networkType) if (VERSION.SDK_INT >= 33) config.profileId?.let { builder.setProfileId(it) } if (VERSION.SDK_INT >= 29) config.carrierId?.let { builder.setCarrierId(it) } if (VERSION.SDK_INT >= 33) { config.mtuV4?.let { builder.setMtuV4(it) } config.mtuV6?.let { builder.setMtuV6(it) } } builder.setMvnoType(config.mvno.id) builder.setOperatorNumeric(config.operatorNumeric) if (VERSION.SDK_INT >= 33) builder.setPersistent(config.persistent) if (VERSION.SDK_INT >= 35) builder.setAlwaysOn(config.alwaysOn) return builder.build() } @RequiresApi(28) fun setApnConfig(config: ApnConfig): Boolean { val settings = buildApnSetting(config) if (settings == null) return false return if (config.id == -1) { DPM.addOverrideApn(DAR, settings) != -1 } else { DPM.updateOverrideApn(DAR, config.id, settings) } } @RequiresApi(28) fun removeApnConfig(id: Int): Boolean { return DPM.removeOverrideApn(DAR, id) } } data class ThemeSettings( val materialYou: Boolean = false, val darkTheme: Int = -1, val blackTheme: Boolean = false )