Skip to content

Commit 1cf2fe6

Browse files
committed
v1.5.0
1 parent 81190e5 commit 1cf2fe6

File tree

10 files changed

+199
-38
lines changed

10 files changed

+199
-38
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Ideally, I recommend changing the package name yourself before installing. When
3838
### Installation of testOnly apps
3939
#### Easiest way (root only)
4040
1. Install NOT_RECOMMENDED app version
41-
2. Give it root rights
41+
2. Give it root rights and **don't** give it admin rights
4242
3. You will see a notification at the top of the settings screen. Click "install" button and app will update itself to the testOnly version.
4343

4444
![step3](img/Screenshot_20250604-154158_Shelter.png)

README_RU.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Android AntiForensic Tools это бесплатное приложение с
3838
### Установка testOnly версий
3939
#### Самый простой способ (только с root правами)
4040
1. Установите версию приложения с названием NOT_RECOMMENDED
41-
2. Дайте приложению рут-права
41+
2. Дайте приложению рут-права и **не давайте** ему права администратора
4242
3. Вы увидите уведомление наверху экрана настроек. Нажмите "install" и приложение само обновит себя до testOnly версии.
4343

4444
![step3](img/Screenshot_20250604-154158_Shelter.png)

app/proguard-rules.pro

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,16 @@
2020
# hide the original source file name.
2121
#-renamesourcefileattribute SourceFile
2222
-dontwarn org.joda.convert.FromString
23-
-dontwarn org.joda.convert.ToString
23+
-dontwarn org.joda.convert.ToString
24+
-keep class androidx.datastore.*.** { *; }
25+
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* {
26+
<fields>;
27+
}
28+
-dontwarn retrofit2.**
29+
-keep class retrofit2.** { *; }
30+
-keepclasseswithmembers class * {
31+
@retrofit2.http.* <methods>;
32+
}
33+
-keepattributes Signature,Exceptions,*Annotation*
34+
-dontnote retrofit2.Platform
35+
-dontwarn retrofit2.Platform$Java8

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
<string name="cancel">Cancel</string>
44
<string name="ok">OK</string>
55
<string name="logo_background">Background of logotype</string>
6+
<string name="wake_up">Caro m\'è \'l sonno, e più l\'esser di sasso, mentre che \'l danno e la vergogna dura; non veder, non sentir m\'è gran ventura; però non mi destar, deh, parla basso.</string>
67
<string name="prominent_disclosure_message">The app uses accessibility service to listen for a duress password on the lockscreen and listen for usb connections.</string>
78
</resources>

core/entities/src/main/java/com/sonozaki/entities/App.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.sonozaki.entities
22

33
import kotlinx.serialization.Serializable
44

5+
56
@Serializable
67
data class App(
78
val packageName: String,

features/settings/src/main/java/com/sonozaki/settings/presentation/fragments/SettingsFragment.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@ import android.view.MenuItem
88
import android.view.View
99
import android.view.ViewGroup
1010
import androidx.core.view.MenuProvider
11-
import androidx.fragment.app.Fragment
11+
import androidx.core.view.isVisible
12+
import androidx.fragment.app.viewModels
1213
import androidx.lifecycle.Lifecycle
1314
import androidx.navigation.fragment.findNavController
1415
import com.sonozaki.activitystate.ActivityState
1516
import com.sonozaki.activitystate.ActivityStateHolder
1617
import com.sonozaki.settings.R
1718
import com.sonozaki.settings.databinding.SettingsFragmentBinding
19+
import com.sonozaki.settings.presentation.viewmodel.SettingsVM
20+
import com.sonozaki.utils.TopLevelFunctions.launchLifecycleAwareCoroutine
1821
import dagger.hilt.android.AndroidEntryPoint
1922

2023
@AndroidEntryPoint
21-
class SettingsFragment: Fragment() {
24+
class SettingsFragment: AbstractSettingsFragment() {
2225
private var _binding: SettingsFragmentBinding? = null
2326
private val binding
2427
get() = _binding ?: throw RuntimeException("SettingsFragmentBinding == null")
28+
override val viewModel by viewModels<SettingsVM>()
2529
private val navController by lazy { findNavController() }
2630

2731
override fun onCreateView(
@@ -38,9 +42,20 @@ class SettingsFragment: Fragment() {
3842
super.onViewCreated(view, savedInstanceState)
3943
setupActivity()
4044
setupMenu()
45+
listenPopup()
4146
listenClickable()
4247
}
4348

49+
private fun listenPopup() {
50+
viewLifecycleOwner.launchLifecycleAwareCoroutine {
51+
viewModel.updatePopupStatusFlow.collect {
52+
with(binding) {
53+
testOnlyUpdateAlert.isVisible = it
54+
}
55+
}
56+
}
57+
}
58+
4459
/**
4560
* Setting up faq icon in action bar
4661
*/
@@ -79,6 +94,12 @@ class SettingsFragment: Fragment() {
7994
uiSettings.setOnClickListener {
8095
navController.navigate(R.id.action_settingsFragment_to_UISettingsFragment)
8196
}
97+
installTestonlyUpdate.setOnClickListener {
98+
viewModel.updateApp()
99+
}
100+
ignoreTestonlyUpdate.setOnClickListener {
101+
viewModel.disableUpdatePopup()
102+
}
82103
}
83104
}
84105

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.sonozaki.settings.presentation.viewmodel
2+
3+
import androidx.lifecycle.viewModelScope
4+
import com.bakasoft.network.NetworkError
5+
import com.bakasoft.network.RequestResult
6+
import com.sonozaki.dialogs.DialogActions
7+
import com.sonozaki.entities.Permissions
8+
import com.sonozaki.settings.R
9+
import com.sonozaki.settings.domain.usecases.appUpdate.DisableUpdatePopupUseCase
10+
import com.sonozaki.settings.domain.usecases.appUpdate.DownloadUpdateUseCase
11+
import com.sonozaki.settings.domain.usecases.appUpdate.GetUpdatePopupStatusUseCase
12+
import com.sonozaki.settings.domain.usecases.permissions.GetPermissionsUseCase
13+
import com.sonozaki.settings.domain.usecases.settings.GetSettingsUseCase
14+
import com.sonozaki.superuser.superuser.SuperUserException
15+
import com.sonozaki.superuser.superuser.SuperUserManager
16+
import com.sonozaki.utils.UIText
17+
import dagger.hilt.android.lifecycle.HiltViewModel
18+
import kotlinx.coroutines.channels.Channel
19+
import kotlinx.coroutines.flow.SharingStarted
20+
import kotlinx.coroutines.flow.combine
21+
import kotlinx.coroutines.flow.stateIn
22+
import kotlinx.coroutines.launch
23+
import okhttp3.ResponseBody
24+
import javax.inject.Inject
25+
26+
@HiltViewModel
27+
class SettingsVM @Inject constructor(
28+
getUpdatePopupStatusUseCase: GetUpdatePopupStatusUseCase,
29+
getPermissionsUseCase: GetPermissionsUseCase,
30+
getSettingsUseCase: GetSettingsUseCase,
31+
settingsActionChannel: Channel<DialogActions>,
32+
private val superUserManager: SuperUserManager,
33+
private val downloadUpdateUseCase: DownloadUpdateUseCase,
34+
private val disableUpdatePopupStatusUseCase: DisableUpdatePopupUseCase
35+
36+
): AbstractSettingsVM(settingsActionChannel, getSettingsUseCase, getPermissionsUseCase) {
37+
38+
private fun networkErrorToText(error: NetworkError): UIText.StringResource {
39+
return when(error) {
40+
is NetworkError.EmptyResponse -> UIText.StringResource(R.string.empty_response)
41+
is NetworkError.ConnectionError -> UIText.StringResource(R.string.connection_error)
42+
is NetworkError.ServerError -> UIText.StringResource(R.string.server_error, error.code, error.description)
43+
is NetworkError.UnknownError -> UIText.StringResource(R.string.unknown_error, error.error)
44+
}
45+
}
46+
47+
private suspend fun installTestOnlyUpdate(result: RequestResult.Data<ResponseBody>) {
48+
try {
49+
superUserManager.getSuperUser().installTestOnlyApp(result.data.contentLength(), result.data.source())
50+
} catch (e: SuperUserException) {
51+
showInfoDialogSuspend(
52+
UIText.StringResource(R.string.superuser_exception),
53+
message = e.messageForLogs
54+
)
55+
} catch (e: Exception) {
56+
showInfoDialogSuspend(
57+
UIText.StringResource(R.string.unknown_exception),
58+
UIText.StringResource(R.string.unknown_error, e.message ?:"")
59+
)
60+
}
61+
}
62+
63+
fun updateApp() {
64+
viewModelScope.launch {
65+
val result = downloadUpdateUseCase()
66+
when(result) {
67+
is RequestResult.Data<ResponseBody> -> {
68+
installTestOnlyUpdate(result)
69+
}
70+
is RequestResult.Error -> {
71+
showInfoDialogSuspend(
72+
UIText.StringResource(R.string.network_error),
73+
networkErrorToText(result.error)
74+
)
75+
}
76+
}
77+
}
78+
}
79+
80+
fun disableUpdatePopup() {
81+
viewModelScope.launch {
82+
disableUpdatePopupStatusUseCase()
83+
}
84+
}
85+
86+
val updatePopupStatusFlow = getUpdatePopupStatusUseCase().combine(getPermissionsUseCase()) { updatePopup: Boolean, permissions: Permissions ->
87+
updatePopup && permissions.isRoot
88+
}.stateIn(
89+
viewModelScope,
90+
SharingStarted.WhileSubscribed(0, 0),
91+
false
92+
)
93+
}

features/settings/src/main/res/layout/settings_fragment.xml

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,61 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2+
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
34
android:layout_width="match_parent"
45
android:layout_height="match_parent"
5-
xmlns:app="http://schemas.android.com/apk/res-auto"
6-
android:orientation="vertical">
6+
xmlns:tools="http://schemas.android.com/tools">
7+
8+
<LinearLayout
9+
android:layout_width="match_parent"
10+
android:layout_height="wrap_content"
11+
android:orientation="vertical">
12+
<com.google.android.material.card.MaterialCardView
13+
android:id="@+id/test_only_update_alert"
14+
android:layout_width="match_parent"
15+
android:layout_height="wrap_content"
16+
android:layout_marginHorizontal="@dimen/margin_normal"
17+
android:layout_marginVertical="@dimen/margin_normal"
18+
app:cardBackgroundColor="?attr/colorErrorContainer"
19+
android:visibility="gone"
20+
tools:visibility="visible">
21+
<LinearLayout
22+
android:paddingStart="@dimen/margin_normal"
23+
android:paddingTop="@dimen/margin_normal"
24+
android:paddingEnd="@dimen/margin_normal"
25+
android:layout_width="match_parent"
26+
android:layout_height="wrap_content"
27+
android:orientation="vertical">
28+
<com.google.android.material.textview.MaterialTextView
29+
android:layout_width="wrap_content"
30+
android:layout_height="wrap_content"
31+
style="?attr/textAppearanceTitleLarge"
32+
android:text="@string/please_install_testonly_version"/>
33+
<com.google.android.material.textview.MaterialTextView
34+
android:layout_width="wrap_content"
35+
android:layout_height="wrap_content"
36+
style="?attr/textAppearanceBodyLarge"
37+
android:text="@string/install_testonly_version_long"/>
38+
<LinearLayout
39+
android:layout_width="match_parent"
40+
android:layout_height="wrap_content"
41+
android:orientation="horizontal">
42+
<com.google.android.material.button.MaterialButton
43+
android:id="@+id/install_testonly_update"
44+
style="@style/Widget.Material3.Button.TextButton"
45+
android:layout_width="wrap_content"
46+
android:layout_height="wrap_content"
47+
android:textColor="?attr/colorOnErrorContainer"
48+
android:text="@string/install"/>
49+
<com.google.android.material.button.MaterialButton
50+
style="@style/Widget.Material3.Button.TextButton"
51+
android:id="@+id/ignore_testonly_update"
52+
android:layout_width="wrap_content"
53+
android:layout_height="wrap_content"
54+
android:textColor="?attr/colorOnErrorContainer"
55+
android:text="@string/ignore_update"/>
56+
</LinearLayout>
57+
</LinearLayout>
58+
</com.google.android.material.card.MaterialCardView>
759
<com.sonozaki.settings.presentation.views.SettingsItemView
860
android:id="@+id/permissions_settings"
961
android:layout_width="match_parent"
@@ -40,4 +92,5 @@
4092
android:layout_height="wrap_content"
4193
app:itemText="@string/ui_settings">
4294
</com.sonozaki.settings.presentation.views.SettingsItemView>
43-
</LinearLayout>
95+
</LinearLayout>
96+
</ScrollView>

features/triggerReceivers/src/main/java/com/sonozaki/triggerreceivers/services/domain/usecases/ButtonClickUseCase.kt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.sonozaki.triggerreceivers.services.domain.usecases
22

3-
import android.util.Log
43
import com.sonozaki.entities.ButtonClicked
54
import com.sonozaki.entities.ButtonSelected
65
import com.sonozaki.entities.ButtonSettings
@@ -46,29 +45,24 @@ class ButtonClickUseCase @Inject constructor(private val buttonSettingsRepositor
4645
val timestamp = System.currentTimeMillis()
4746
with(buttonSettingsRepository) {
4847
val buttonClicksData = getButtonClicksData(buttonSelected)
49-
Log.w("buttonClicks",buttonClicksData.toString())
5048
//if no clicks were performed previously, start counting clicks and return
5149
if (buttonClicksData.clicksInRow == 0) {
5250
setClicksInRow(1, buttonSelected)
53-
Log.w("buttonClicks2",buttonClicksData.toString())
5451
setLastTimestamp(timestamp, buttonSelected)
5552
return false
5653
}
5754
val latency = getLatency(buttonSettings, buttonSelected)
5855
if (timestamp - buttonClicksData.lastTimestamp <= latency) {
5956
setClicksInRow(buttonClicksData.clicksInRow + 1, buttonSelected)
60-
Log.w("buttonClicks3",buttonClicksData.toString())
6157
setLastTimestamp(timestamp, buttonSelected)
6258
} else { //if delay between last clicks and this click is smaller than latency, update number of clicks and return
6359
setClicksInRow(1, buttonSelected)
64-
Log.w("buttonClicks4",buttonClicksData.toString())
6560
setLastTimestamp(timestamp, buttonSelected)
6661
return false
6762
} //else allow for deletion and start counting clicks again
6863
val allowedClicks = getAllowedClicks(buttonSettings, buttonSelected)
6964
if (buttonClicksData.clicksInRow + 1 == allowedClicks) {
7065
setClicksInRow(0, buttonSelected)
71-
Log.w("buttonClicks5",buttonClicksData.toString())
7266
return true
7367
}
7468
return false
@@ -103,12 +97,9 @@ class ButtonClickUseCase @Inject constructor(private val buttonSettingsRepositor
10397
}
10498

10599
suspend operator fun invoke(buttonClicked: ButtonClicked): Boolean {
106-
Log.w("buttonClicks",buttonClicked.toString())
107100
mutex.withLock {
108101
val buttonSettings = buttonSettingsRepository.getButtonSettings()
109-
Log.w("buttonClicks",buttonSettings.toString())
110102
val buttonSelected = getButtonSelected(buttonSettings, buttonClicked)
111-
Log.w("buttonClicks", buttonSelected.toString())
112103
//if no correct button was clicked return
113104
if (buttonSelected == null) {
114105
return false

superuser/src/main/java/com/sonozaki/superuser/owner/Owner.kt

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package com.sonozaki.superuser.owner
22

33
import android.annotation.SuppressLint
4-
import android.app.PendingIntent
54
import android.app.admin.DevicePolicyManager
5+
import android.app.admin.IDevicePolicyManager
66
import android.content.ComponentName
77
import android.content.Context
8-
import android.content.Intent
98
import android.content.IntentSender
109
import android.content.pm.IPackageInstaller
1110
import android.content.pm.PackageInstaller
@@ -73,14 +72,14 @@ class Owner @Inject constructor(
7372
DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL
7473
val manager =
7574
dhizukuContext.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
76-
// val field = manager.javaClass.getDeclaredField("mService")
77-
// field.isAccessible = true
78-
// val oldInterface = field[manager] as IDevicePolicyManager
79-
// if (oldInterface is DhizukuBinderWrapper) return manager
80-
// val oldBinder = oldInterface.asBinder()
81-
// val newBinder = binderWrapper(oldBinder)
82-
// val newInterface = IDevicePolicyManager.Stub.asInterface(newBinder)
83-
// field[manager] = newInterface
75+
val field = manager.javaClass.getDeclaredField("mService")
76+
field.isAccessible = true
77+
val oldInterface = field[manager] as IDevicePolicyManager
78+
if (oldInterface is DhizukuBinderWrapper) return manager
79+
val oldBinder = oldInterface.asBinder()
80+
val newBinder = binderWrapper(oldBinder)
81+
val newInterface = IDevicePolicyManager.Stub.asInterface(newBinder)
82+
field[manager] = newInterface
8483
return manager
8584
}
8685

@@ -358,16 +357,6 @@ class Owner @Inject constructor(
358357
)
359358
}
360359

361-
private fun createIntentSender(context: Context?, sessionId: Int, piFlags: Int): IntentSender {
362-
val pendingIntent = PendingIntent.getBroadcast(
363-
getDhizukuContext(),
364-
sessionId,
365-
Intent(INSTALL_COMPLETE),
366-
piFlags
367-
)
368-
return pendingIntent.intentSender
369-
}
370-
371360
override suspend fun runTrim() {
372361
throw SuperUserException(
373362
NO_ROOT_RIGHTS,

0 commit comments

Comments
 (0)