mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-10-31 10:40:52 +01:00 
			
		
		
		
	Compare commits
	
		
			123 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f16e93c7db | ||
|   | 1b0ddec66e | ||
|   | cd8820f563 | ||
|   | b70192ca3e | ||
|   | d42ec5da9a | ||
|   | 742913ebcb | ||
|   | ed206c6480 | ||
|   | f9a8052583 | ||
|   | f4fdd516f9 | ||
|   | 5925a71f94 | ||
|   | 3cda9beb93 | ||
|   | 8b7d1ffcdd | ||
|   | 8d02d0632e | ||
|   | dd743f6f7e | ||
|   | cf483ad4d2 | ||
|   | 4aed644e08 | ||
|   | 0acc39cec0 | ||
|   | 8b3a44344f | ||
|   | 8b49eda85a | ||
|   | 7057d4c7f1 | ||
|   | aab8344058 | ||
|   | 7cccf83b37 | ||
|   | f10ad93c4e | ||
|   | f143b5df15 | ||
|   | 71213cc6f4 | ||
|   | e2a1774e5b | ||
|   | 0222527a1e | ||
|   | 312bfe1bab | ||
|   | 48c62a1dae | ||
|   | cfc2bcb665 | ||
|   | 94b1ff674f | ||
|   | 111136733a | ||
|   | c8caaa98f5 | ||
|   | 8d28f10a3f | ||
|   | 177a456d8b | ||
|   | ef4e230258 | ||
|   | 17082af438 | ||
|   | 1df5b34175 | ||
|   | ea5fe7525d | ||
|   | a75c335261 | ||
|   | 3903f42cf6 | ||
|   | fb0c4ea838 | ||
|   | bc89c60977 | ||
|   | bd657c354c | ||
|   | 675b5f9565 | ||
|   | 1b2c43268e | ||
|   | 653730d75e | ||
|   | d472e9c36e | ||
|   | 484d53ef7e | ||
|   | c4e2985677 | ||
|   | 42d9f87bc9 | ||
|   | 2e4fa6864c | ||
|   | e2abb648ac | ||
|   | 3599dcedfb | ||
|   | ea72666df8 | ||
|   | bd2a47ba18 | ||
|   | b861671391 | ||
|   | e91fc75d86 | ||
|   | 78f5cd55c7 | ||
|   | 9787a69528 | ||
|   | 87b8fe374d | ||
|   | 7b706bb0cb | ||
|   | c1491b8d2b | ||
|   | 5cbaf2ae11 | ||
|   | 8ebc6207b4 | ||
|   | 7848ee616b | ||
|   | fd193c3cae | ||
|   | 36d33c7a85 | ||
|   | 5caf28d27c | ||
|   | 2c39d0234d | ||
|   | c313812129 | ||
|   | af51880a81 | ||
|   | db8d832707 | ||
|   | 8dc23d0ead | ||
|   | b4287700d5 | ||
|   | 8d10ab89f2 | ||
|   | 49fdc1addb | ||
|   | 1333d3b986 | ||
|   | 335146a6a2 | ||
|   | eaf9527971 | ||
|   | da937a88c8 | ||
|   | 9476e7282d | ||
|   | 251c3c3e0e | ||
|   | cd0eca20b0 | ||
|   | 6839cb9ab2 | ||
|   | d11a3397d8 | ||
|   | 975120d6a6 | ||
|   | e489b3b6dd | ||
|   | 589a270b8d | ||
|   | 7961be5cfa | ||
|   | 959430e030 | ||
|   | 2923c8ccd1 | ||
|   | 7df4a9d74f | ||
|   | bf4ed295da | ||
|   | a5fca960dc | ||
|   | f99912b9db | ||
|   | a54bdb54e4 | ||
|   | cd9851a1fe | ||
|   | 9ca469898c | ||
|   | 0665549473 | ||
|   | 9d7a14b335 | ||
|   | 62e29fee74 | ||
|   | e472db552b | ||
|   | 466e4bd4e1 | ||
|   | 4cf525c588 | ||
|   | c8aec2510d | ||
|   | ccbfe0e66e | ||
|   | 23ea28de6f | ||
|   | 55c3ee3a6f | ||
|   | 2a42ca2b8f | ||
|   | a897e82fa4 | ||
|   | ffa15831d3 | ||
|   | a344ebf28c | ||
|   | 78f7fa348e | ||
|   | d8c448b99d | ||
|   | d4b83b6a44 | ||
|   | e5d36d1d24 | ||
|   | ff18cb8e70 | ||
|   | 37a9724a54 | ||
|   | d660401063 | ||
|   | 88541d6f49 | ||
|   | ecd6129fe5 | ||
|   | 6dfe9df9e2 | 
							
								
								
									
										2
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							| @@ -45,7 +45,7 @@ runs: | ||||
|       env: | ||||
|         SCCACHE_DIRECT: false | ||||
|         SCCACHE_DIR: ${{ github.workspace }}/.sccache | ||||
|         SCCACHE_CACHE_SIZE: 2G | ||||
|         SCCACHE_CACHE_SIZE: ${{ inputs.is-asset-build == 'true' && '2G' || '300M'  }} | ||||
|         SCCACHE_IDLE_TIMEOUT: 0 | ||||
|       run: | | ||||
|         bash $GITHUB_ACTION_PATH/sccache.sh | ||||
|   | ||||
							
								
								
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -82,12 +82,10 @@ jobs: | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35] | ||||
|         version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, "CANARY"] | ||||
|         type: [""] | ||||
|         include: | ||||
|           - version: 36 | ||||
|             type: "google_apis" | ||||
|           - version: 36 | ||||
|           - version: "CANARY" | ||||
|             type: "google_apis_ps16k" | ||||
|  | ||||
|     steps: | ||||
| @@ -107,7 +105,7 @@ jobs: | ||||
|           sudo udevadm trigger --name-match=kvm | ||||
|  | ||||
|       - name: Run AVD test | ||||
|         timeout-minutes: 10 | ||||
|         timeout-minutes: 15 | ||||
|         env: | ||||
|           AVD_TEST_LOG: 1 | ||||
|         run: scripts/avd.sh test ${{ matrix.version }} ${{ matrix.type }} | ||||
| @@ -148,7 +146,7 @@ jobs: | ||||
|           sudo udevadm trigger --name-match=kvm | ||||
|  | ||||
|       - name: Run AVD test | ||||
|         timeout-minutes: 10 | ||||
|         timeout-minutes: 15 | ||||
|         env: | ||||
|           FORCE_32_BIT: 1 | ||||
|           AVD_TEST_LOG: 1 | ||||
| @@ -193,7 +191,7 @@ jobs: | ||||
|           scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }} | ||||
|  | ||||
|       - name: Run Cuttlefish test | ||||
|         timeout-minutes: 10 | ||||
|         timeout-minutes: 15 | ||||
|         run: sudo -E -u $USER scripts/cuttlefish.sh test | ||||
|  | ||||
|       - name: Upload logs on error | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -4,9 +4,6 @@ | ||||
| [submodule "lz4"] | ||||
| 	path = native/src/external/lz4 | ||||
| 	url = https://github.com/lz4/lz4.git | ||||
| [submodule "xz"] | ||||
| 	path = native/src/external/xz | ||||
| 	url = https://github.com/xz-mirror/xz.git | ||||
| [submodule "libcxx"] | ||||
| 	path = native/src/external/libcxx | ||||
| 	url = https://github.com/topjohnwu/libcxx.git | ||||
|   | ||||
| @@ -24,6 +24,7 @@ import androidx.core.widget.ImageViewCompat | ||||
| import androidx.databinding.BindingAdapter | ||||
| import androidx.databinding.InverseBindingAdapter | ||||
| import androidx.databinding.InverseBindingListener | ||||
| import androidx.databinding.InverseMethod | ||||
| import androidx.interpolator.view.animation.FastOutSlowInInterpolator | ||||
| import androidx.recyclerview.widget.DividerItemDecoration | ||||
| import androidx.recyclerview.widget.GridLayoutManager | ||||
| @@ -33,9 +34,11 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager | ||||
| import com.google.android.material.button.MaterialButton | ||||
| import com.google.android.material.card.MaterialCardView | ||||
| import com.google.android.material.chip.Chip | ||||
| import com.google.android.material.slider.Slider | ||||
| import com.google.android.material.textfield.TextInputLayout | ||||
| import com.topjohnwu.magisk.R | ||||
| import com.topjohnwu.magisk.core.di.ServiceLocator | ||||
| import com.topjohnwu.magisk.core.model.su.SuPolicy | ||||
| import com.topjohnwu.magisk.utils.TextHolder | ||||
| import com.topjohnwu.superuser.internal.UiThreadHandler | ||||
| import com.topjohnwu.widget.IndeterminateCheckBox | ||||
| @@ -306,3 +309,38 @@ fun TextView.setText(text: TextHolder) { | ||||
| fun Spinner.setAdapter(items: Array<Any>, layoutRes: Int) { | ||||
|     adapter = ArrayAdapter(context, layoutRes, items) | ||||
| } | ||||
|  | ||||
| @BindingAdapter("labelFormatter") | ||||
| fun Slider.setLabelFormatter(formatter: (Float) -> Int) { | ||||
|     setLabelFormatter { value -> resources.getString(formatter(value)) } | ||||
| } | ||||
|  | ||||
| @InverseBindingAdapter(attribute = "android:value") | ||||
| fun Slider.getValueBinding() = value | ||||
|  | ||||
| @BindingAdapter("android:valueAttrChanged") | ||||
| fun Slider.setListener(attrChange: InverseBindingListener) { | ||||
|     addOnSliderTouchListener(object : Slider.OnSliderTouchListener { | ||||
|         override fun onStartTrackingTouch(slider: Slider) = Unit | ||||
|         override fun onStopTrackingTouch(slider: Slider) = attrChange.onChange() | ||||
|     }) | ||||
| } | ||||
|  | ||||
| @InverseMethod("sliderValueToPolicy") | ||||
| fun policyToSliderValue(policy: Int): Float { | ||||
|     return when (policy) { | ||||
|         SuPolicy.DENY -> 1f | ||||
|         SuPolicy.RESTRICT -> 2f | ||||
|         SuPolicy.ALLOW -> 3f | ||||
|         else -> 1f | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun sliderValueToPolicy(value: Float): Int { | ||||
|     return when (value) { | ||||
|         1f -> SuPolicy.DENY | ||||
|         2f -> SuPolicy.RESTRICT | ||||
|         3f -> SuPolicy.ALLOW | ||||
|         else -> SuPolicy.DENY | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -322,6 +322,12 @@ object Reauthenticate : BaseSettingsItem.Toggle() { | ||||
|     override var value by Config::suReAuth | ||||
|  | ||||
|     override fun refresh() { | ||||
|         isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Info.showSuperUser | ||||
|         isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O | ||||
|     } | ||||
| } | ||||
|  | ||||
| object Restrict : BaseSettingsItem.Toggle() { | ||||
|     override val title = CoreR.string.settings_su_restrict_title.asText() | ||||
|     override val description = CoreR.string.settings_su_restrict_summary.asText() | ||||
|     override var value by Config::suRestrict | ||||
| } | ||||
|   | ||||
| @@ -22,11 +22,11 @@ import com.topjohnwu.magisk.core.ktx.activity | ||||
| import com.topjohnwu.magisk.core.ktx.toast | ||||
| import com.topjohnwu.magisk.core.tasks.AppMigration | ||||
| import com.topjohnwu.magisk.core.utils.LocaleSetting | ||||
| import com.topjohnwu.magisk.core.utils.RootUtils | ||||
| import com.topjohnwu.magisk.databinding.bindExtra | ||||
| import com.topjohnwu.magisk.events.AddHomeIconEvent | ||||
| import com.topjohnwu.magisk.events.AuthEvent | ||||
| import com.topjohnwu.magisk.events.SnackbarEvent | ||||
| import com.topjohnwu.superuser.Shell | ||||
| import kotlinx.coroutines.launch | ||||
|  | ||||
| class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { | ||||
| @@ -83,6 +83,9 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { | ||||
|                 // Can hide overlay windows on 12.0+ | ||||
|                 list.remove(Tapjack) | ||||
|             } | ||||
|             if (Const.Version.atLeast_30_1()) { | ||||
|                 list.add(Restrict) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return list | ||||
| @@ -127,7 +130,8 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler { | ||||
|     } | ||||
|  | ||||
|     private fun createHosts() { | ||||
|         Shell.cmd("add_hosts_module").submit { | ||||
|         viewModelScope.launch { | ||||
|             RootUtils.addSystemlessHosts() | ||||
|             AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -4,11 +4,13 @@ import android.graphics.drawable.Drawable | ||||
| import androidx.databinding.Bindable | ||||
| import com.topjohnwu.magisk.BR | ||||
| import com.topjohnwu.magisk.R | ||||
| import com.topjohnwu.magisk.core.Config | ||||
| import com.topjohnwu.magisk.core.model.su.SuPolicy | ||||
| import com.topjohnwu.magisk.databinding.DiffItem | ||||
| import com.topjohnwu.magisk.databinding.ItemWrapper | ||||
| import com.topjohnwu.magisk.databinding.ObservableRvItem | ||||
| import com.topjohnwu.magisk.databinding.set | ||||
| import com.topjohnwu.magisk.core.R as CoreR | ||||
|  | ||||
| class PolicyRvItem( | ||||
|     private val viewModel: SuperuserViewModel, | ||||
| @@ -33,14 +35,34 @@ class PolicyRvItem( | ||||
|     var isExpanded = false | ||||
|         set(value) = set(value, field, { field = it }, BR.expanded) | ||||
|  | ||||
|     val showSlider = Config.suRestrict || item.policy == SuPolicy.RESTRICT | ||||
|  | ||||
|     @get:Bindable | ||||
|     var isEnabled | ||||
|         get() = item.policy == SuPolicy.ALLOW | ||||
|         get() = item.policy >= SuPolicy.ALLOW | ||||
|         set(value) = setImpl(value, isEnabled) { | ||||
|             notifyPropertyChanged(BR.enabled) | ||||
|             viewModel.togglePolicy(this, value) | ||||
|             viewModel.updatePolicy(this, if (it) SuPolicy.ALLOW else SuPolicy.DENY) | ||||
|         } | ||||
|  | ||||
|     @get:Bindable | ||||
|     var sliderValue | ||||
|         get() = item.policy | ||||
|         set(value) = setImpl(value, sliderValue) { | ||||
|             notifyPropertyChanged(BR.sliderValue) | ||||
|             notifyPropertyChanged(BR.enabled) | ||||
|             viewModel.updatePolicy(this, it) | ||||
|         } | ||||
|  | ||||
|     val sliderValueToPolicyString: (Float) -> Int = { value -> | ||||
|         when (value.toInt()) { | ||||
|             1 -> CoreR.string.deny | ||||
|             2 -> CoreR.string.restrict | ||||
|             3 -> CoreR.string.grant | ||||
|             else -> CoreR.string.deny | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @get:Bindable | ||||
|     var shouldNotify | ||||
|         get() = item.notification | ||||
|   | ||||
| @@ -156,15 +156,16 @@ class SuperuserViewModel( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun togglePolicy(item: PolicyRvItem, enable: Boolean) { | ||||
|     fun updatePolicy(item: PolicyRvItem, policy: Int) { | ||||
|         val items = itemsPolicies.filter { it.item.uid == item.item.uid } | ||||
|         fun updateState() { | ||||
|             viewModelScope.launch { | ||||
|                 val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny | ||||
|                 item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY | ||||
|                 val res = if (policy >= SuPolicy.ALLOW) R.string.su_snack_grant else R.string.su_snack_deny | ||||
|                 item.item.policy = policy | ||||
|                 db.update(item.item) | ||||
|                 items.forEach { | ||||
|                     it.notifyPropertyChanged(BR.enabled) | ||||
|                     it.notifyPropertyChanged(BR.sliderValue) | ||||
|                 } | ||||
|                 SnackbarEvent(res.asText(item.appName)).publish() | ||||
|             } | ||||
|   | ||||
| @@ -43,10 +43,6 @@ enum class Theme( | ||||
|  | ||||
|     val isSelected get() = Config.themeOrdinal == ordinal | ||||
|  | ||||
|     fun select() { | ||||
|         Config.themeOrdinal = ordinal | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         val selected get() = values().getOrNull(Config.themeOrdinal) ?: Piplup | ||||
|     } | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
|  | ||||
|         <include | ||||
|             android:id="@+id/log_track_container" | ||||
|             bullet="@{item.log.action == 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}" | ||||
|             bullet="@{item.log.action >= 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}" | ||||
|             isBottom="@{item.isBottom}" | ||||
|             isSelected="@{item.log.action != 2}" | ||||
|             isTop="@{item.isTop}" | ||||
|   | ||||
| @@ -5,6 +5,8 @@ | ||||
|  | ||||
|     <data> | ||||
|  | ||||
|         <import type="com.topjohnwu.magisk.databinding.DataBindingAdaptersKt" /> | ||||
|  | ||||
|         <variable | ||||
|             name="item" | ||||
|             type="com.topjohnwu.magisk.ui.superuser.PolicyRvItem" /> | ||||
| @@ -85,16 +87,32 @@ | ||||
|                         app:layout_constraintVertical_bias="0" | ||||
|                         tools:text="com.topjohnwu.magisk" /> | ||||
|  | ||||
|                     <com.google.android.material.switchmaterial.SwitchMaterial | ||||
|                     <FrameLayout | ||||
|                         android:id="@+id/policy_indicator" | ||||
|                         android:layout_width="wrap_content" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:layout_marginEnd="@dimen/l1" | ||||
|                         android:checked="@={item.enabled}" | ||||
|                         android:nextFocusLeft="@id/policy" | ||||
|                         app:layout_constraintBottom_toBottomOf="parent" | ||||
|                         app:layout_constraintEnd_toEndOf="parent" | ||||
|                         app:layout_constraintTop_toTopOf="parent" /> | ||||
|                         app:layout_constraintTop_toTopOf="parent"> | ||||
|  | ||||
|                         <com.google.android.material.switchmaterial.SwitchMaterial | ||||
|                             gone="@{item.showSlider}" | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:checked="@={item.enabled}" /> | ||||
|  | ||||
|                         <com.google.android.material.slider.Slider | ||||
|                             goneUnless="@{item.showSlider}" | ||||
|                             labelFormatter="@{item.sliderValueToPolicyString}" | ||||
|                             android:layout_width="96dp" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:stepSize="1" | ||||
|                             android:value="@={DataBindingAdaptersKt.policyToSliderValue(item.sliderValue)}" | ||||
|                             android:valueFrom="1" | ||||
|                             android:valueTo="3" /> | ||||
|                     </FrameLayout> | ||||
|  | ||||
|                 </androidx.constraintlayout.widget.ConstraintLayout> | ||||
|  | ||||
|   | ||||
| @@ -18,12 +18,6 @@ gradlePlugin { | ||||
|     } | ||||
| } | ||||
|  | ||||
| kotlin { | ||||
|     compilerOptions { | ||||
|         languageVersion = KotlinVersion.KOTLIN_2_0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation(kotlin("gradle-plugin", libs.versions.kotlin.get())) | ||||
|     implementation(libs.android.gradle.plugin) | ||||
|   | ||||
| @@ -55,7 +55,7 @@ fun Project.setupCommon() { | ||||
|         compileSdkVersion(36) | ||||
|         buildToolsVersion = "36.0.0" | ||||
|         ndkPath = "$sdkDirectory/ndk/magisk" | ||||
|         ndkVersion = "28.1.13356709" | ||||
|         ndkVersion = "29.0.13846066" | ||||
|  | ||||
|         defaultConfig { | ||||
|             minSdk = 23 | ||||
|   | ||||
| @@ -309,9 +309,9 @@ fun Project.setupStubApk() { | ||||
|             outputs.dir(outResDir) | ||||
|             doLast { | ||||
|                 val apkTmp = File("${apk}.tmp") | ||||
|                 exec { | ||||
|                 providers.exec { | ||||
|                     commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk) | ||||
|                 } | ||||
|                 }.result.get() | ||||
|  | ||||
|                 val bos = ByteArrayOutputStream() | ||||
|                 ZipFile(apkTmp).use { src -> | ||||
|   | ||||
| @@ -27,10 +27,15 @@ android { | ||||
|         aidl = true | ||||
|         buildConfig = true | ||||
|     } | ||||
|  | ||||
|     compileOptions { | ||||
|         isCoreLibraryDesugaringEnabled = true | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     api(project(":shared")) | ||||
|     coreLibraryDesugaring(libs.jdk.libs) | ||||
|  | ||||
|     api(libs.timber) | ||||
|     api(libs.markwon.core) | ||||
|   | ||||
							
								
								
									
										1
									
								
								app/core/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								app/core/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							| @@ -38,3 +38,4 @@ | ||||
| -allowaccessmodification | ||||
|  | ||||
| -dontwarn org.junit.** | ||||
| -dontwarn org.apache.** | ||||
|   | ||||
| @@ -6,4 +6,5 @@ package com.topjohnwu.magisk.core.utils; | ||||
| interface IRootUtils { | ||||
|     android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid); | ||||
|     IBinder getFileSystem(); | ||||
|     boolean addSystemlessHosts(); | ||||
| } | ||||
|   | ||||
| @@ -32,6 +32,7 @@ object Config : PreferenceConfig, DBConfig { | ||||
|         const val SU_NOTIFICATION = "su_notification" | ||||
|         const val SU_REAUTH = "su_reauth" | ||||
|         const val SU_TAPJACK = "su_tapjack" | ||||
|         const val SU_RESTRICT = "su_restrict" | ||||
|         const val CHECK_UPDATES = "check_update" | ||||
|         const val RELEASE_CHANNEL = "release_channel" | ||||
|         const val CUSTOM_CHANNEL = "custom_channel" | ||||
| @@ -147,6 +148,7 @@ object Config : PreferenceConfig, DBConfig { | ||||
|         } | ||||
|     var suReAuth by preference(Key.SU_REAUTH, false) | ||||
|     var suTapjack by preference(Key.SU_TAPJACK, true) | ||||
|     var suRestrict by preference(Key.SU_RESTRICT, false) | ||||
|  | ||||
|     private const val SU_FINGERPRINT = "su_fingerprint" | ||||
|     private const val UPDATE_CHANNEL = "update_channel" | ||||
|   | ||||
| @@ -26,9 +26,11 @@ object Const { | ||||
|         const val MIN_VERSION = "v22.0" | ||||
|         const val MIN_VERCODE = 22000 | ||||
|  | ||||
|         fun atLeast_24_0() = Info.env.versionCode >= 24000 | ||||
|         fun atLeast_25_0() = Info.env.versionCode >= 25000 | ||||
|         fun atLeast_28_0() = Info.env.versionCode >= 28000 | ||||
|         private fun isCanary() = (Info.env.versionCode % 100) != 0 | ||||
|         fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary() | ||||
|         fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary() | ||||
|         fun atLeast_28_0() = Info.env.versionCode >= 28000 || isCanary() | ||||
|         fun atLeast_30_1() = Info.env.versionCode >= 30100 || isCanary() | ||||
|     } | ||||
|  | ||||
|     object ID { | ||||
|   | ||||
| @@ -47,6 +47,8 @@ object Info { | ||||
|         private set | ||||
|     var slot = "" | ||||
|         private set | ||||
|     var isVendorBoot = false | ||||
|         private set | ||||
|     @JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1" | ||||
|     @JvmStatic val isFDE get() = crypto == "block" | ||||
|     @JvmStatic var ramdisk = false | ||||
| @@ -113,6 +115,7 @@ object Info { | ||||
|         crypto = getVar("CRYPTOTYPE") | ||||
|         slot = getVar("SLOT") | ||||
|         legacySAR = getBool("LEGACYSAR") | ||||
|         isVendorBoot = getBool("VENDORBOOT") | ||||
|  | ||||
|         // Default presets | ||||
|         Config.recovery = getBool("RECOVERYMODE") | ||||
|   | ||||
| @@ -109,7 +109,7 @@ fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? { | ||||
|             return null | ||||
|         } | ||||
|         // Try to find package name from PID | ||||
|         val proc = RootUtils.obj?.getAppProcess(pid) | ||||
|         val proc = RootUtils.getAppProcess(pid) | ||||
|         if (proc == null) { | ||||
|             if (uid == Process.SHELL_UID) { | ||||
|                 // It is possible that some apps installed are sharing UID with shell. | ||||
|   | ||||
| @@ -14,10 +14,11 @@ import java.io.IOException | ||||
| import java.io.InputStream | ||||
| import java.io.OutputStream | ||||
| import java.lang.reflect.Field | ||||
| import java.text.DateFormat | ||||
| import java.text.SimpleDateFormat | ||||
| import java.time.Instant | ||||
| import java.time.ZoneId | ||||
| import java.time.format.DateTimeFormatter | ||||
| import java.time.format.FormatStyle | ||||
| import java.util.Collections | ||||
| import java.util.Locale | ||||
|  | ||||
| inline fun <In : Closeable, Out : Closeable> withInOut( | ||||
|     input: In, | ||||
| @@ -83,19 +84,15 @@ inline fun <T, R> Flow<T>.concurrentMap(crossinline transform: suspend (T) -> R) | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun Long.toTime(format: DateFormat) = format.format(this).orEmpty() | ||||
| fun Long.toTime(format: DateTimeFormatter): String = format.format(Instant.ofEpochMilli(this)) | ||||
|  | ||||
| // Some devices don't allow filenames containing ":" | ||||
| val timeFormatStandard by lazy { | ||||
|     SimpleDateFormat( | ||||
|         "yyyy-MM-dd'T'HH.mm.ss", | ||||
|         Locale.ROOT | ||||
|     ) | ||||
| val timeFormatStandard: DateTimeFormatter by lazy { | ||||
|     DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH.mm.ss").withZone(ZoneId.systemDefault()) | ||||
| } | ||||
| val timeDateFormat: DateFormat by lazy { | ||||
|     DateFormat.getDateTimeInstance( | ||||
|         DateFormat.DEFAULT, | ||||
|         DateFormat.DEFAULT, | ||||
|         Locale.ROOT | ||||
|     ) | ||||
| val timeDateFormat: DateTimeFormatter by lazy { | ||||
|     DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withZone(ZoneId.systemDefault()) | ||||
| } | ||||
| val dateFormat: DateTimeFormatter by lazy { | ||||
|     DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault()) | ||||
| } | ||||
|   | ||||
| @@ -7,8 +7,7 @@ import com.squareup.moshi.JsonClass | ||||
| import com.squareup.moshi.JsonQualifier | ||||
| import com.squareup.moshi.ToJson | ||||
| import kotlinx.parcelize.Parcelize | ||||
| import java.time.LocalDateTime | ||||
| import java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME | ||||
| import java.time.Instant | ||||
|  | ||||
| @JsonClass(generateAdapter = true) | ||||
| class UpdateJson( | ||||
| @@ -40,13 +39,13 @@ data class ReleaseAssets( | ||||
|  | ||||
| class DateTimeAdapter { | ||||
|     @ToJson | ||||
|     fun toJson(date: LocalDateTime): String { | ||||
|     fun toJson(date: Instant): String { | ||||
|         return date.toString() | ||||
|     } | ||||
|  | ||||
|     @FromJson | ||||
|     fun fromJson(date: String): LocalDateTime { | ||||
|         return LocalDateTime.parse(date, ISO_OFFSET_DATE_TIME) | ||||
|     fun fromJson(date: String): Instant { | ||||
|         return Instant.parse(date) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -57,7 +56,7 @@ data class Release( | ||||
|     val prerelease: Boolean, | ||||
|     val assets: List<ReleaseAssets>, | ||||
|     val body: String, | ||||
|     @Json(name = "created_at") val createdTime: LocalDateTime, | ||||
|     @Json(name = "created_at") val createdTime: Instant, | ||||
| ) { | ||||
|     val versionCode: Int get() { | ||||
|         return if (tag[0] == 'v') { | ||||
|   | ||||
| @@ -13,7 +13,7 @@ import java.io.IOException | ||||
| import java.util.Locale | ||||
|  | ||||
| data class LocalModule( | ||||
|     private val base: ExtendedFile, | ||||
|     val base: ExtendedFile, | ||||
| ) : Module() { | ||||
|     private val svc get() = ServiceLocator.networkService | ||||
|  | ||||
|   | ||||
| @@ -4,15 +4,16 @@ import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB | ||||
|  | ||||
| class SuPolicy( | ||||
|     val uid: Int, | ||||
|     var policy: Int = INTERACTIVE, | ||||
|     var policy: Int = QUERY, | ||||
|     var remain: Long = -1L, | ||||
|     var logging: Boolean = true, | ||||
|     var notification: Boolean = true, | ||||
| ) { | ||||
|     companion object { | ||||
|         const val INTERACTIVE = 0 | ||||
|         const val QUERY = 0 | ||||
|         const val DENY = 1 | ||||
|         const val ALLOW = 2 | ||||
|         const val RESTRICT = 3 | ||||
|     } | ||||
|  | ||||
|     fun toMap(): MutableMap<String, Any> { | ||||
|   | ||||
| @@ -10,13 +10,13 @@ import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL | ||||
| import com.topjohnwu.magisk.core.Info | ||||
| import com.topjohnwu.magisk.core.data.GithubApiServices | ||||
| import com.topjohnwu.magisk.core.data.RawUrl | ||||
| import com.topjohnwu.magisk.core.ktx.dateFormat | ||||
| import com.topjohnwu.magisk.core.model.Release | ||||
| import com.topjohnwu.magisk.core.model.ReleaseAssets | ||||
| import com.topjohnwu.magisk.core.model.UpdateInfo | ||||
| import retrofit2.HttpException | ||||
| import timber.log.Timber | ||||
| import java.io.IOException | ||||
| import java.time.format.DateTimeFormatter | ||||
|  | ||||
| class NetworkService( | ||||
|     private val raw: RawUrl, | ||||
| @@ -74,7 +74,7 @@ class NetworkService( | ||||
|  | ||||
|     private inline fun Release.asPublicInfo(selector: (ReleaseAssets) -> Boolean): UpdateInfo { | ||||
|         val version = tag.drop(1) | ||||
|         val date = createdTime.format(DateTimeFormatter.ofPattern("yyyy.M.d")) | ||||
|         val date = dateFormat.format(createdTime) | ||||
|         return UpdateInfo( | ||||
|             version = version, | ||||
|             versionCode = versionCode, | ||||
|   | ||||
| @@ -70,7 +70,7 @@ object SuCallbackHandler { | ||||
|         }.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids) | ||||
|  | ||||
|         if (notify) | ||||
|             notify(context, log.action == SuPolicy.ALLOW, log.appName) | ||||
|             notify(context, log.action >= SuPolicy.ALLOW, log.appName) | ||||
|  | ||||
|         runBlocking { ServiceLocator.logRepo.insert(log) } | ||||
|     } | ||||
| @@ -86,7 +86,7 @@ object SuCallbackHandler { | ||||
|             pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm) | ||||
|         }.getOrNull() ?: "[UID] $uid" | ||||
|  | ||||
|         notify(context, policy == SuPolicy.ALLOW, appName) | ||||
|         notify(context, policy >= SuPolicy.ALLOW, appName) | ||||
|     } | ||||
|  | ||||
|     private fun notify(context: Context, granted: Boolean, appName: String) { | ||||
|   | ||||
| @@ -82,7 +82,11 @@ class SuRequestHandler( | ||||
|     } | ||||
|  | ||||
|     suspend fun respond(action: Int, time: Long) { | ||||
|         policy.policy = action | ||||
|         if (action == SuPolicy.ALLOW && Config.suRestrict) { | ||||
|             policy.policy = SuPolicy.RESTRICT | ||||
|         } else { | ||||
|             policy.policy = action | ||||
|         } | ||||
|         if (time >= 0) { | ||||
|             policy.remain = TimeUnit.MINUTES.toSeconds(time) | ||||
|         } else { | ||||
|   | ||||
| @@ -81,10 +81,12 @@ abstract class MagiskInstallImpl protected constructor( | ||||
|     } | ||||
|  | ||||
|     private fun findImage(slot: String): Boolean { | ||||
|         val bootPath = ( | ||||
|             "(RECOVERYMODE=${Config.recovery} " + | ||||
|             "SLOT=$slot find_boot_image; " + | ||||
|             "echo \$BOOTIMAGE)").fsh() | ||||
|         val cmd = | ||||
|             "RECOVERYMODE=${Config.recovery} " + | ||||
|             "VENDORBOOT=${Info.isVendorBoot} " + | ||||
|             "SLOT=$slot " + | ||||
|             "find_boot_image; echo \$BOOTIMAGE" | ||||
|         val bootPath = ("($cmd)").fsh() | ||||
|         if (bootPath.isEmpty()) { | ||||
|             console.add("! Unable to detect target image") | ||||
|             return false | ||||
|   | ||||
| @@ -7,11 +7,14 @@ import android.content.ServiceConnection | ||||
| import android.os.IBinder | ||||
| import android.system.Os | ||||
| import androidx.core.content.getSystemService | ||||
| import com.topjohnwu.magisk.core.Const | ||||
| import com.topjohnwu.magisk.core.Info | ||||
| import com.topjohnwu.superuser.Shell | ||||
| import com.topjohnwu.superuser.ShellUtils | ||||
| import com.topjohnwu.superuser.ipc.RootService | ||||
| import com.topjohnwu.superuser.nio.FileSystemManager | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.withContext | ||||
| import timber.log.Timber | ||||
| import java.io.File | ||||
| import java.util.concurrent.locks.AbstractQueuedSynchronizer | ||||
| @@ -43,16 +46,7 @@ class RootUtils(stub: Any?) : RootService() { | ||||
|         return object : IRootUtils.Stub() { | ||||
|             override fun getAppProcess(pid: Int) = safe(null) { getAppProcessImpl(pid) } | ||||
|             override fun getFileSystem(): IBinder = FileSystemManager.getService() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private inline fun <T> safe(default: T, block: () -> T): T { | ||||
|         return try { | ||||
|             block() | ||||
|         } catch (e: Throwable) { | ||||
|             // The process died unexpectedly | ||||
|             Timber.e(e) | ||||
|             default | ||||
|             override fun addSystemlessHosts() = safe(false) { addSystemlessHostsImpl() } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -78,6 +72,26 @@ class RootUtils(stub: Any?) : RootService() { | ||||
|         return null | ||||
|     } | ||||
|  | ||||
|     private fun addSystemlessHostsImpl(): Boolean { | ||||
|         val module = File(Const.MODULE_PATH, "hosts") | ||||
|         if (module.exists()) return true | ||||
|         val hosts = File(module, "system/etc/hosts") | ||||
|         if (!hosts.parentFile.mkdirs()) return false | ||||
|         File(module, "module.prop").outputStream().writer().use { | ||||
|             it.write(""" | ||||
|                 id=hosts | ||||
|                 name=Systemless Hosts | ||||
|                 version=1.0 | ||||
|                 versionCode=1 | ||||
|                 author=Magisk | ||||
|                 description=Magisk app built-in systemless hosts module | ||||
|             """.trimIndent()) | ||||
|         } | ||||
|         File("/system/etc/hosts").copyTo(hosts) | ||||
|         File(module, "update").createNewFile() | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     object Connection : AbstractQueuedSynchronizer(), ServiceConnection { | ||||
|         init { | ||||
|             state = 1 | ||||
| @@ -131,11 +145,25 @@ class RootUtils(stub: Any?) : RootService() { | ||||
|                 return field | ||||
|             } | ||||
|             private set | ||||
|         var obj: IRootUtils? = null | ||||
|         private var obj: IRootUtils? = null | ||||
|             get() { | ||||
|                 Connection.await() | ||||
|                 return field | ||||
|             } | ||||
|             private set | ||||
|  | ||||
|         fun getAppProcess(pid: Int) = safe(null) { obj?.getAppProcess(pid) } | ||||
|  | ||||
|         suspend fun addSystemlessHosts() = | ||||
|             withContext(Dispatchers.IO) { safe(false) { obj?.addSystemlessHosts() ?: false } } | ||||
|  | ||||
|         private inline fun <T> safe(default: T, block: () -> T): T { | ||||
|             return try { | ||||
|                 block() | ||||
|             } catch (e: Throwable) { | ||||
|                 // The process died unexpectedly | ||||
|                 Timber.e(e) | ||||
|                 default | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,9 +12,11 @@ import com.topjohnwu.magisk.test.Environment.Companion.INVALID_ZYGISK | ||||
| import com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST | ||||
| import com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST | ||||
| import com.topjohnwu.magisk.test.Environment.Companion.SEPOLICY_RULE | ||||
| import com.topjohnwu.magisk.test.Environment.Companion.UPGRADE_TEST | ||||
| import com.topjohnwu.superuser.Shell | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import org.junit.After | ||||
| import org.junit.Assert.assertArrayEquals | ||||
| import org.junit.Assert.assertEquals | ||||
| import org.junit.Assert.assertFalse | ||||
| import org.junit.Assert.assertNotNull | ||||
| @@ -55,7 +57,7 @@ class AdditionalTest : BaseTest { | ||||
|  | ||||
|     @Test | ||||
|     fun testModuleCount() { | ||||
|         var expected = 2 | ||||
|         var expected = 4 | ||||
|         if (Environment.mount()) expected++ | ||||
|         if (Environment.preinit()) expected++ | ||||
|         if (Environment.lsposed()) expected++ | ||||
| @@ -90,17 +92,18 @@ class AdditionalTest : BaseTest { | ||||
|  | ||||
|         assertNotNull("$MOUNT_TEST is not installed", modules.find { it.id == MOUNT_TEST }) | ||||
|         assertTrue( | ||||
|             "/system/etc/newfile should exist", | ||||
|             RootUtils.fs.getFile("/system/etc/newfile").exists() | ||||
|             "/system/fonts/newfile should exist", | ||||
|             RootUtils.fs.getFile("/system/fonts/newfile").exists() | ||||
|         ) | ||||
|         assertFalse( | ||||
|             "/system/bin/screenrecord should not exist", | ||||
|             RootUtils.fs.getFile("/system/bin/screenrecord").exists() | ||||
|         ) | ||||
|         val egg = RootUtils.fs.getFile("/system/app/EasterEgg").list() ?: arrayOf() | ||||
|         assertTrue( | ||||
|             "/system/app/EasterEgg should be empty", | ||||
|             egg.isEmpty() | ||||
|         assertArrayEquals( | ||||
|             "/system/app/EasterEgg should be replaced", | ||||
|             egg, | ||||
|             arrayOf("newfile") | ||||
|         ) | ||||
|     } | ||||
|  | ||||
| @@ -134,5 +137,25 @@ class AdditionalTest : BaseTest { | ||||
|     @Test | ||||
|     fun testRemoveModule() { | ||||
|         assertNull("$REMOVE_TEST should be removed", modules.find { it.id == REMOVE_TEST }) | ||||
|         assertTrue( | ||||
|             "Uninstaller of $REMOVE_TEST should be run", | ||||
|             RootUtils.fs.getFile(Environment.REMOVE_TEST_MARKER).exists() | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     fun testModuleUpgrade() { | ||||
|         val module = modules.find { it.id == UPGRADE_TEST } | ||||
|         assertNotNull("$UPGRADE_TEST is not installed", module) | ||||
|         module!! | ||||
|         assertFalse("$UPGRADE_TEST should be disabled", module.enable) | ||||
|         assertTrue( | ||||
|             "$UPGRADE_TEST should be updated", | ||||
|             module.base.getChildFile("post-fs-data.sh").exists() | ||||
|         ) | ||||
|         assertFalse( | ||||
|             "$UPGRADE_TEST should be updated", | ||||
|             module.base.getChildFile("service.sh").exists() | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -58,12 +58,15 @@ class Environment : BaseTest { | ||||
|             return Build.VERSION.SDK_INT >= 27 | ||||
|         } | ||||
|  | ||||
|         private const val MODULE_UPDATE_PATH  = "/data/adb/modules_update" | ||||
|         private const val MODULE_ERROR = "Module zip processing incorrect" | ||||
|         const val MOUNT_TEST = "mount_test" | ||||
|         const val SEPOLICY_RULE = "sepolicy_rule" | ||||
|         const val INVALID_ZYGISK = "invalid_zygisk" | ||||
|         const val REMOVE_TEST = "remove_test" | ||||
|         const val REMOVE_TEST_MARKER = "/dev/.remove_test_removed" | ||||
|         const val EMPTY_ZYGISK = "empty_zygisk" | ||||
|         const val UPGRADE_TEST = "upgrade_test" | ||||
|     } | ||||
|  | ||||
|     object TimberLog : CallbackList<String>(Runnable::run) { | ||||
| @@ -98,8 +101,8 @@ class Environment : BaseTest { | ||||
|         val error = "$MOUNT_TEST setup failed" | ||||
|         val path = root.getChildFile(MOUNT_TEST) | ||||
|  | ||||
|         // Create /system/etc/newfile | ||||
|         val etc = path.getChildFile("system").getChildFile("etc") | ||||
|         // Create /system/fonts/newfile | ||||
|         val etc = path.getChildFile("system").getChildFile("fonts") | ||||
|         assertTrue(error, etc.mkdirs()) | ||||
|         assertTrue(error, etc.getChildFile("newfile").createNewFile()) | ||||
|  | ||||
| @@ -108,6 +111,9 @@ class Environment : BaseTest { | ||||
|         assertTrue(error, egg.mkdirs()) | ||||
|         assertTrue(error, egg.getChildFile(".replace").createNewFile()) | ||||
|  | ||||
|         // Create /system/app/EasterEgg/newfile | ||||
|         assertTrue(error, egg.getChildFile("newfile").createNewFile()) | ||||
|  | ||||
|         // Delete /system/bin/screenrecord | ||||
|         val bin = path.getChildFile("system").getChildFile("bin") | ||||
|         assertTrue(error, bin.mkdirs()) | ||||
| @@ -116,6 +122,12 @@ class Environment : BaseTest { | ||||
|         assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess) | ||||
|     } | ||||
|  | ||||
|     private fun setupSystemlessHost() { | ||||
|         val error = "hosts setup failed" | ||||
|         assertTrue(error, runBlocking { RootUtils.addSystemlessHosts() }) | ||||
|         assertTrue(error, RootUtils.fs.getFile(Const.MODULE_PATH).getChildFile("hosts").exists()) | ||||
|     } | ||||
|  | ||||
|     private fun setupSepolicyRuleModule(root: ExtendedFile) { | ||||
|         val error = "$SEPOLICY_RULE setup failed" | ||||
|         val path = root.getChildFile(SEPOLICY_RULE) | ||||
| @@ -163,12 +175,39 @@ class Environment : BaseTest { | ||||
|         // Create a new module but mark is as "remove" | ||||
|         val module = LocalModule(path) | ||||
|         assertTrue(error, path.mkdirs()) | ||||
|         // Create uninstaller script | ||||
|         path.getChildFile("uninstall.sh").newOutputStream().writer().use { | ||||
|             it.write("touch $REMOVE_TEST_MARKER") | ||||
|         } | ||||
|         assertTrue(error, path.getChildFile("service.sh").createNewFile()) | ||||
|         module.remove = true | ||||
|  | ||||
|         assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess) | ||||
|     } | ||||
|  | ||||
|     private fun setupUpgradeModule(root: ExtendedFile, update: ExtendedFile) { | ||||
|         val error = "$UPGRADE_TEST setup failed" | ||||
|         val oldPath = root.getChildFile(UPGRADE_TEST) | ||||
|         val newPath = update.getChildFile(UPGRADE_TEST) | ||||
|  | ||||
|         // Create an existing module but mark as "disable | ||||
|         val module = LocalModule(oldPath) | ||||
|         assertTrue(error, oldPath.mkdirs()) | ||||
|         module.enable = false | ||||
|         // Install service.sh into the old module | ||||
|         assertTrue(error, oldPath.getChildFile("service.sh").createNewFile()) | ||||
|  | ||||
|         // Create an upgrade module | ||||
|         assertTrue(error, newPath.mkdirs()) | ||||
|         // Install post-fs-data.sh into the new module | ||||
|         assertTrue(error, newPath.getChildFile("post-fs-data.sh").createNewFile()) | ||||
|  | ||||
|         assertTrue(error, Shell.cmd( | ||||
|             "set_default_perm $oldPath", | ||||
|             "set_default_perm $newPath", | ||||
|         ).exec().isSuccess) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     fun setupEnvironment() { | ||||
|         runBlocking { | ||||
| @@ -213,11 +252,14 @@ class Environment : BaseTest { | ||||
|         } | ||||
|  | ||||
|         val root = RootUtils.fs.getFile(Const.MODULE_PATH) | ||||
|         if (mount()) { setupMountTest(root) } | ||||
|         if (preinit()) { setupSepolicyRuleModule(root) } | ||||
|         setupEmptyZygiskModule(root) | ||||
|         setupInvalidZygiskModule(root) | ||||
|         val update = RootUtils.fs.getFile(MODULE_UPDATE_PATH) | ||||
|         if (mount()) { setupMountTest(update) } | ||||
|         if (preinit()) { setupSepolicyRuleModule(update) } | ||||
|         setupSystemlessHost() | ||||
|         setupEmptyZygiskModule(update) | ||||
|         setupInvalidZygiskModule(update) | ||||
|         setupRemoveModule(root) | ||||
|         setupUpgradeModule(root, update) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user