@@ -4,6 +4,7 @@ import android.app.Activity
4
4
import android.app.ActivityOptions
5
5
import android.content.Context
6
6
import android.content.Intent
7
+ import android.content.pm.PackageManager
7
8
import android.os.Build
8
9
import android.widget.Toast
9
10
import com.topjohnwu.magisk.StubApk
@@ -25,7 +26,6 @@ import kotlinx.coroutines.Dispatchers
25
26
import kotlinx.coroutines.withContext
26
27
import timber.log.Timber
27
28
import java.io.File
28
- import java.io.FileOutputStream
29
29
import java.io.IOException
30
30
import java.io.OutputStream
31
31
import java.security.SecureRandom
@@ -36,6 +36,7 @@ object AppMigration {
36
36
private const val ALPHA = " abcdefghijklmnopqrstuvwxyz"
37
37
private const val ALPHADOTS = " $ALPHA ....."
38
38
private const val ANDROID_MANIFEST = " AndroidManifest.xml"
39
+ private const val TEST_PKG_NAME = " $APP_PACKAGE_NAME .test"
39
40
40
41
// Some arbitrary limit
41
42
const val MAX_LABEL_LENGTH = 32
@@ -131,21 +132,15 @@ object AppMigration {
131
132
val je = jar.getJarEntry(ANDROID_MANIFEST )
132
133
val xml = AXML (jar.getRawData(je))
133
134
val generator = classNameGenerator()
134
-
135
- if (! xml.patchStrings {
136
- for (i in it.indices) {
137
- val s = it[i]
138
- if (s.contains(APP_PACKAGE_NAME )) {
139
- it[i] = s.replace(APP_PACKAGE_NAME , pkg)
140
- } else if (s.contains(PLACEHOLDER )) {
141
- it[i] = generator.next()
142
- } else if (s == origLabel) {
143
- it[i] = label.toString()
144
- }
135
+ val p = xml.patchStrings {
136
+ when {
137
+ it.contains(APP_PACKAGE_NAME ) -> it.replace(APP_PACKAGE_NAME , pkg)
138
+ it.contains(PLACEHOLDER ) -> generator.next()
139
+ it == origLabel -> label.toString()
140
+ else -> it
145
141
}
146
- }) {
147
- return false
148
142
}
143
+ if (! p) return false
149
144
150
145
// Write apk changes
151
146
jar.getOutputStream(je).use { it.write(xml.bytes) }
@@ -159,40 +154,83 @@ object AppMigration {
159
154
}
160
155
}
161
156
162
- private fun launchApp (activity : Activity , pkg : String ) {
163
- val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ? : return
157
+ private fun patchTest (apk : File , out : File , pkg : String ): Boolean {
158
+ try {
159
+ JarMap .open(apk, true ).use { jar ->
160
+ val je = jar.getJarEntry(ANDROID_MANIFEST )
161
+ val xml = AXML (jar.getRawData(je))
162
+ val p = xml.patchStrings {
163
+ when (it) {
164
+ APP_PACKAGE_NAME -> pkg
165
+ TEST_PKG_NAME -> " $pkg .test"
166
+ else -> it
167
+ }
168
+ }
169
+ if (! p) return false
170
+
171
+ // Write apk changes
172
+ jar.getOutputStream(je).use { it.write(xml.bytes) }
173
+ val keys = Keygen ()
174
+ out .outputStream().use { SignApk .sign(keys.cert, keys.key, jar, it) }
175
+ return true
176
+ }
177
+ } catch (e: Exception ) {
178
+ Timber .e(e)
179
+ return false
180
+ }
181
+ }
182
+
183
+ private fun launchApp (context : Context , pkg : String ) {
184
+ val intent = context.packageManager.getLaunchIntentForPackage(pkg) ? : return
164
185
intent.putExtra(Const .Key .PREV_CONFIG , Config .toBundle())
165
186
val options = ActivityOptions .makeBasic()
166
187
if (Build .VERSION .SDK_INT >= 34 ) {
167
188
options.setShareIdentityEnabled(true )
168
189
}
169
- activity.startActivity(intent, options.toBundle())
170
- activity.finish()
190
+ context.startActivity(intent, options.toBundle())
191
+ if (context is Activity ) {
192
+ context.finish()
193
+ }
171
194
}
172
195
173
- private suspend fun patchAndHide (activity : Activity , label : String ): Boolean {
174
- val stub = File (activity .cacheDir, " stub.apk" )
196
+ suspend fun patchAndHide (context : Context , label : String , pkg : String? = null ): Boolean {
197
+ val stub = File (context .cacheDir, " stub.apk" )
175
198
try {
176
- activity .assets.open(" stub.apk" ).writeTo(stub)
199
+ context .assets.open(" stub.apk" ).writeTo(stub)
177
200
} catch (e: IOException ) {
178
201
Timber .e(e)
179
202
return false
180
203
}
181
204
182
- // Generate a new random package name and signature
183
- val repack = File (activity.cacheDir, " patched.apk" )
184
- val pkg = genPackageName()
205
+ // Generate a new random signature and package name if needed
206
+ val pkg = pkg ? : genPackageName()
185
207
Config .keyStoreRaw = " "
186
208
187
- if (! patch(activity, stub, FileOutputStream (repack), pkg, label))
188
- return false
209
+ // Check and patch the test APK
210
+ try {
211
+ val info = context.packageManager.getApplicationInfo(TEST_PKG_NAME , 0 )
212
+ val testApk = File (info.sourceDir)
213
+ val testRepack = File (context.cacheDir, " test.apk" )
214
+ if (! patchTest(testApk, testRepack, pkg))
215
+ return false
216
+ val cmd = " adb_pm_install $testRepack $pkg .test"
217
+ if (! Shell .cmd(cmd).exec().isSuccess)
218
+ return false
219
+ } catch (e: PackageManager .NameNotFoundException ) {
220
+ }
221
+
222
+ val repack = File (context.cacheDir, " patched.apk" )
223
+ repack.outputStream().use {
224
+ if (! patch(context, stub, it, pkg, label))
225
+ return false
226
+ }
189
227
190
228
// Install and auto launch app
191
229
val cmd = " adb_pm_install $repack $pkg "
192
230
if (Shell .cmd(cmd).exec().isSuccess) {
193
231
Config .suManager = pkg
194
232
Shell .cmd(" touch $AppApkPath " ).exec()
195
- launchApp(activity , pkg)
233
+ launchApp(context , pkg)
196
234
return true
197
235
} else {
198
236
return false
@@ -216,6 +254,18 @@ object AppMigration {
216
254
}
217
255
}
218
256
257
+ suspend fun restoreApp (context : Context ): Boolean {
258
+ val apk = StubApk .current(context)
259
+ val cmd = " adb_pm_install $apk $APP_PACKAGE_NAME "
260
+ if (Shell .cmd(cmd).await().isSuccess) {
261
+ Config .suManager = " "
262
+ Shell .cmd(" touch $AppApkPath " ).exec()
263
+ launchApp(context, APP_PACKAGE_NAME )
264
+ return true
265
+ }
266
+ return false
267
+ }
268
+
219
269
@Suppress(" DEPRECATION" )
220
270
suspend fun restore (activity : Activity ) {
221
271
val dialog = android.app.ProgressDialog (activity).apply {
@@ -224,13 +274,7 @@ object AppMigration {
224
274
setCancelable(false )
225
275
show()
226
276
}
227
- val apk = StubApk .current(activity)
228
- val cmd = " adb_pm_install $apk $APP_PACKAGE_NAME "
229
- if (Shell .cmd(cmd).await().isSuccess) {
230
- Config .suManager = " "
231
- Shell .cmd(" touch $AppApkPath " ).exec()
232
- launchApp(activity, APP_PACKAGE_NAME )
233
- } else {
277
+ if (! restoreApp(activity)) {
234
278
activity.toast(R .string.failure, Toast .LENGTH_LONG )
235
279
}
236
280
dialog.dismiss()
0 commit comments