diff --git a/app/build.gradle b/app/build.gradle index 760dc59..25d6bab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,7 +14,7 @@ android { minSdkVersion 24 targetSdkVersion 30 versionCode 5 - versionName "1.1.1" + versionName "1.1.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -63,4 +63,7 @@ dependencies { implementation "androidx.room:room-ktx:2.2.5" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6" + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.google.code.gson:gson:2.8.6' } \ No newline at end of file diff --git a/app/src/main/java/com/cczhr/otglocation/BaseActivity.kt b/app/src/main/java/com/cczhr/otglocation/BaseActivity.kt index 3772828..a846797 100644 --- a/app/src/main/java/com/cczhr/otglocation/BaseActivity.kt +++ b/app/src/main/java/com/cczhr/otglocation/BaseActivity.kt @@ -17,9 +17,8 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import com.google.android.material.floatingactionbutton.FloatingActionButton -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.cancel +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext /** @@ -32,6 +31,9 @@ abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() private lateinit var permissionsResult: (Boolean) -> Unit protected abstract val layoutId: Int protected abstract fun init() + override val coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Main + + CoroutineExceptionHandler { _, exception -> handleException(exception) } + protected open fun handleException(t: Throwable) = t.printStackTrace() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(layoutId) diff --git a/app/src/main/java/com/cczhr/otglocation/MainActivity.kt b/app/src/main/java/com/cczhr/otglocation/MainActivity.kt index 7ef608d..02b2e44 100644 --- a/app/src/main/java/com/cczhr/otglocation/MainActivity.kt +++ b/app/src/main/java/com/cczhr/otglocation/MainActivity.kt @@ -1,13 +1,25 @@ package com.cczhr.otglocation import android.annotation.SuppressLint +import android.app.Dialog +import android.app.ProgressDialog import android.content.Intent +import android.text.TextUtils +import android.util.Log import android.view.View import androidx.lifecycle.Observer import com.amap.api.mapcore.util.it +import com.cczhr.otglocation.net.RetrofitManager import com.cczhr.otglocation.utils.* import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.lang.Exception class MainActivity : BaseActivity() { @@ -19,9 +31,20 @@ class MainActivity : BaseActivity() { var isConnected = false var hasDeveloperImg = false + var progressDialog: ProgressDialog? = null @SuppressLint("SetTextI18n") + + + override fun handleException(t: Throwable) { + super.handleException(t) + CommonUtil.showToast(Application.context, "下载失败") + progressDialog?.dismiss() + } + override fun init() { + + version?.text = "V ${Application.getVersion()}" latitude.setText(Application.getLat()) longitude.setText(Application.getLon()) @@ -48,7 +71,7 @@ class MainActivity : BaseActivity() { }) hotPlugTools.register(this, { deviceNode -> - libTools.startUsbmuxd(deviceNode,{ + libTools.startUsbmuxd(deviceNode, { isConnected = true connect_status.setText(R.string.connected) }, { @@ -78,8 +101,10 @@ class MainActivity : BaseActivity() { log.setText("") } + } + override fun onDestroy() { super.onDestroy() libTools.release() @@ -88,21 +113,23 @@ class MainActivity : BaseActivity() { fun logAdd(str: String) { + log.append(str + "\n") log.setSelection(log.text.toString().length) + } fun selectLocation(view: View) { val lat = latitude.text.toString().toDoubleOrNull() val lon = longitude.text.toString().toDoubleOrNull() - val intent=Intent(this, MapActivity::class.java) + val intent = Intent(this, MapActivity::class.java) if (lat != null && lon != null) { - intent.putExtra("lat",lat) - intent.putExtra("lon",lon) + intent.putExtra("lat", lat) + intent.putExtra("lon", lon) } - startActivityForResult(intent , 102) + startActivityForResult(intent, 102) } @@ -141,7 +168,7 @@ class MainActivity : BaseActivity() { } fun modifyLocation(view: View) { - if(!checkStatus()) + if (!checkStatus()) return var lat = latitude.text.toString().toDoubleOrNull() var lon = longitude.text.toString().toDoubleOrNull() @@ -165,7 +192,7 @@ class MainActivity : BaseActivity() { } fun restoreLocation(view: View) { - if(!checkStatus()) + if (!checkStatus()) return libTools.resetLocation { @@ -186,11 +213,89 @@ class MainActivity : BaseActivity() { CommonUtil.showToast(Application.context, "请安装组件!") else if (!isConnected) CommonUtil.showToast(Application.context, "请连接设备!") - else if(!hasDeveloperImg) + else if (!hasDeveloperImg) CommonUtil.showToast(Application.context, "开发者驱动未挂载!") - return hasLib&&isConnected&&hasDeveloperImg + return hasLib && isConnected && hasDeveloperImg } + fun downloadDriver(view: View) { + val version = product_version.text.toString() + if (version.isEmpty()) { + CommonUtil.showToast(Application.context, "请连接设备后再点击下载!") + return + } + progressDialog = CommonUtil.getProgressDialog(this, R.string.please_wait) + launch(Dispatchers.Main) { + var downloadUrl = "" + val deviceSupport = RetrofitManager.getInstance().getBaseApi().getDeviceSupport() + if (deviceSupport == null) { + return@launch + } else { + for (item in deviceSupport) { + if (item.name.contains(version)) { + downloadUrl = item.download_url + break + } + + } + } + if (downloadUrl.isEmpty()) { + progressDialog?.dismiss() + CommonUtil.showToast(Application.context, "没有找到对应的开发者驱动") + logAdd("没有找到对应的开发者驱动!") + return@launch + } + + downloadUrl = downloadUrl.replace( + "https://raw.githubusercontent.com/", + "https://raw.fastgit.org/" + ) + logAdd("正在下载") + + RetrofitManager.getInstance().getBaseApi().get(downloadUrl) + .downloadFile("ios.zip", IMobileDeviceTools.DEVICE_PATH).collect { + when (it) { + -1 -> { + progressDialog?.dismiss() + logAdd("下载失败") + } + 100 -> { + progressDialog?.dismiss() + logAdd("下载完成") + } + else -> { + progressDialog?.progress = it + } + } + } + logAdd("正在解压") + withContext(Dispatchers.IO) { + ZipUtils.unzipFile( + IMobileDeviceTools.DEVICE_PATH + File.separator + "ios.zip", + IMobileDeviceTools.DEVICE_PATH + ) + } + val path1 = + FileUtils.findFile(File(IMobileDeviceTools.DEVICE_PATH), "DeveloperDiskImage.dmg") + val path2 = FileUtils.findFile( + File(IMobileDeviceTools.DEVICE_PATH), + "DeveloperDiskImage.dmg.signature" + ) + if (path1.isNotEmpty() && path2.isNotEmpty()) { + libTools.mountImage(version, path1, path2) { + hasDeveloperImg = it + developer_img.text = it.toString() + if (it) + logAdd("重要的事情说两遍:写入成功!现在你可以修改定位了!\n") + else + logAdd("开发者镜像写入失败!\n") + } + } else { + logAdd("下载文件不完整 路径1:$path1 路径2:$path2") + } + + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/cczhr/otglocation/net/Api.kt b/app/src/main/java/com/cczhr/otglocation/net/Api.kt new file mode 100644 index 0000000..54c9fba --- /dev/null +++ b/app/src/main/java/com/cczhr/otglocation/net/Api.kt @@ -0,0 +1,23 @@ +package com.cczhr.otglocation.net + +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Streaming +import retrofit2.http.Url + + +/** + * @author cczhr + * @description + * @since 2021/6/10 10:06 + */ +interface Api { + @GET("repos/iGhibli/iOS-DeviceSupport/contents/DeviceSupport") + suspend fun getDeviceSupport(): DeviceSupportBean? + + @Streaming + @GET + fun get(@Url fileUrl: String): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/cczhr/otglocation/net/DeviceSupportBean.kt b/app/src/main/java/com/cczhr/otglocation/net/DeviceSupportBean.kt new file mode 100644 index 0000000..4640a3f --- /dev/null +++ b/app/src/main/java/com/cczhr/otglocation/net/DeviceSupportBean.kt @@ -0,0 +1,25 @@ +package com.cczhr.otglocation.net + +/** + * @author cczhr + * @description + * @since 2021/6/10 09:33 + */ +class DeviceSupportBean : ArrayList() + +data class DeviceSupportBeanItem( + val _links: Links, + val download_url: String, + val html_url: String, + val name: String, + val path: String, + val sha: String, + val size: Any, + val type: String, + val url: String +) + +data class Links( + val html: String, + val self: String +) \ No newline at end of file diff --git a/app/src/main/java/com/cczhr/otglocation/net/LoggingInterceptor.kt b/app/src/main/java/com/cczhr/otglocation/net/LoggingInterceptor.kt new file mode 100644 index 0000000..3d2e89c --- /dev/null +++ b/app/src/main/java/com/cczhr/otglocation/net/LoggingInterceptor.kt @@ -0,0 +1,84 @@ +package com.cczhr.otglocation.net + +import android.util.Log +import okhttp3.* +import okio.Buffer +import org.json.JSONException +import org.json.JSONObject + + +/** + * @author cczhr + * @description + * @since 2021/6/10 10:17 + */ +class LoggingInterceptor : Interceptor { + val TAG = "LoggingInterceptor" + override fun intercept(chain: Interceptor.Chain): Response { + val request: Request = chain.request() + val t1 = System.nanoTime() //请求发起的时间 + + val method: String = request.method() + val jsonObject = JSONObject() + if ("POST" == method || "PUT" == method) { + if (request.body() is FormBody) { + val body = request.body() as? FormBody + if (body != null) { + for (i in 0 until body.size()) { + try { + jsonObject.put(body.name(i), body.encodedValue(i)) + } catch (e: JSONException) { + e.printStackTrace() + } + } + } + Log.e( + TAG, + "request" + java.lang.String.format( + "发送请求 %s on %s %nRequestParams:%s%nMethod:%s", + request.url(), chain.connection(), jsonObject.toString(), request.method() + ) + ) + } else { + val buffer = Buffer() + val requestBody: RequestBody? = request.body() + if (requestBody != null) { + request.body()!!.writeTo(buffer) + val body: String = buffer.readUtf8() + Log.e( + TAG, + "request" + java.lang.String.format( + "发送请求 %s on %s %nRequestParams:%s%nMethod:%s", + request.url(), chain.connection(), body, request.method() + ) + ) + } + } + } else { + Log.e( + TAG, + "request" + java.lang.String.format( + "发送请求 %s on %s%nMethod:%s", + request.url(), chain.connection(), request.method() + ) + ) + } + val response = chain.proceed(request) + return try { + val t2 = System.nanoTime() //收到响应的时间 + val responseBody = response.peekBody((1024 * 1024).toLong()) + Log.e( + TAG, + "request" + String.format( + "接收响应: %s %n返回json:【%s】 %n耗时:%.1fms", + response.request().url(), + responseBody.string(), + (t2 - t1) / 1e6 + ) + ) + response + } catch (e: Exception) { + response + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cczhr/otglocation/net/RetrofitManager.kt b/app/src/main/java/com/cczhr/otglocation/net/RetrofitManager.kt new file mode 100644 index 0000000..23fd1a4 --- /dev/null +++ b/app/src/main/java/com/cczhr/otglocation/net/RetrofitManager.kt @@ -0,0 +1,66 @@ +package com.cczhr.otglocation.net + +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.io.File +import java.io.FileOutputStream + + +/** + * @author cczhr + * @description + * @since 2021/6/10 09:51 + */ +class RetrofitManager { + @Volatile + private var apiSingleton: Api? = null + private var BASEURL = "https://api.github.com/" + + companion object { + @Volatile + private var INSTANCE: RetrofitManager? = null + fun getInstance(): RetrofitManager { + val tempInstance = INSTANCE + if (tempInstance != null) { + return tempInstance + } + synchronized(this) { + val instance = RetrofitManager() + INSTANCE = instance + return instance + } + } + } + + + fun getBaseApi(): Api { + synchronized(this) { + if (apiSingleton == null) { + val retrofit = Retrofit.Builder() + .baseUrl(BASEURL) + .addConverterFactory(GsonConverterFactory.create()) + .client(OkHttpClient.Builder() .build())// + .build() + + apiSingleton = retrofit.create(Api::class.java) + } + } + return apiSingleton!! + } + + + /*.apply { + // addInterceptor(LoggingInterceptor()) + + + }*/ + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/cczhr/otglocation/utils/CommonUtil.kt b/app/src/main/java/com/cczhr/otglocation/utils/CommonUtil.kt index ced94ca..2186997 100644 --- a/app/src/main/java/com/cczhr/otglocation/utils/CommonUtil.kt +++ b/app/src/main/java/com/cczhr/otglocation/utils/CommonUtil.kt @@ -72,6 +72,17 @@ open class CommonUtil { false } } + fun getProgressDialog(@NonNull context: Context, @StringRes messageId: Int ): ProgressDialog { + val progressDialog = ProgressDialog(context) + progressDialog.setCancelable(false) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setMessage(context.getString(messageId)) + progressDialog.max=100 + progressDialog.setProgress(0); + progressDialog.show() + return progressDialog + } + } diff --git a/app/src/main/java/com/cczhr/otglocation/utils/FileUtils.kt b/app/src/main/java/com/cczhr/otglocation/utils/FileUtils.kt new file mode 100644 index 0000000..e29c25f --- /dev/null +++ b/app/src/main/java/com/cczhr/otglocation/utils/FileUtils.kt @@ -0,0 +1,92 @@ +package com.cczhr.otglocation.utils + +import java.io.File +import java.io.FileFilter + +/** + * @author cczhr + * @description + * @since 2021/6/11 16:31 + */ +class FileUtils { + companion object{ + fun findFile(dir: File,fileName:String):String{ + if (dir.isDirectory) { + val files = dir.listFiles() + for (file in files) { + if(file.isDirectory) + return findFile(file,fileName) + else if(file.isFile&&file.name==fileName){ + return file.absolutePath + } + } + } + return "" + } + + /** + * Delete the all in directory. + * + * @param dir The directory. + * @return `true`: success

`false`: fail + */ + fun deleteAllInDir(dir: File?): Boolean { + return deleteFilesInDirWithFilter(dir, FileFilter { true }) + } + + /** + * Delete all files that satisfy the filter in directory. + * + * @param dir The directory. + * @param filter The filter. + * @return `true`: success

`false`: fail + */ + fun deleteFilesInDirWithFilter(dir: File?, filter: FileFilter?): Boolean { + if (dir == null || filter == null) return false + // dir doesn't exist then return true + if (!dir.exists()) return true + // dir isn't a directory then return false + if (!dir.isDirectory) return false + val files = dir.listFiles() + if (files != null && files.size != 0) { + for (file in files) { + if (filter.accept(file)) { + if (file.isFile) { + if (!file.delete()) return false + } else if (file.isDirectory) { + if (!deleteDir(file)) return false + } + } + } + } + return true + } + + /** + * Delete the directory. + * + * @param dir The directory. + * @return `true`: success

`false`: fail + */ + private fun deleteDir(dir: File?): Boolean { + if (dir == null) return false + // dir doesn't exist then return true + if (!dir.exists()) return true + // dir isn't a directory then return false + if (!dir.isDirectory) return false + val files = dir.listFiles() + if (files != null && files.isNotEmpty()) { + for (file in files) { + if (file.isFile) { + if (!file.delete()) return false + } else if (file.isDirectory) { + if (!deleteDir(file)) return false + } + } + } + return dir.delete() + } + + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cczhr/otglocation/utils/Helper.kt b/app/src/main/java/com/cczhr/otglocation/utils/Helper.kt index 58de46b..9349423 100644 --- a/app/src/main/java/com/cczhr/otglocation/utils/Helper.kt +++ b/app/src/main/java/com/cczhr/otglocation/utils/Helper.kt @@ -1,47 +1,110 @@ package com.cczhr.otglocation.utils import android.content.Context +import android.os.Environment +import android.util.Log import androidx.core.content.ContextCompat +import com.amap.api.mapcore.util.it +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response import java.io.* - fun InputStream.readString(): String { val baos = ByteArrayOutputStream() this.copyTo(baos) return baos.toString() } -fun Context.runMainThread(run:()->Unit){ +fun Context.runMainThread(run: () -> Unit) { ContextCompat.getMainExecutor(this).execute { run() } } -fun InputStream.saveFilesDir(filePath:String, fileName:String):String { + + +fun Call.downloadFile( + filename: String, + downloadPath: String + +) = flow { + val response = this@downloadFile.execute() + if (response.isSuccessful) { + File(downloadPath).apply { + if (!exists()) { + mkdirs() + } else { + //删除已存在的文件 + this.listFiles()?.forEach { + it.delete() + } + } + } + var fos: FileOutputStream? = null + val buffer = ByteArray(4096) + var len = 0 + var sum: Long = 0 + val off = 0 + try { + val filepath = downloadPath + File.separator + filename + val total = response.body()!!.contentLength() + fos = FileOutputStream(File(filepath)) + val inputStream = response.body()!!.byteStream() + while (inputStream.read(buffer).apply { len = this } > 0) { + fos.write(buffer, off, len) + sum += len.toLong() + val progress = (sum * 1.0f / total * 100).toInt() + emit(progress) + } + if (sum == 0L) + emit(-1) + + } catch (e: Exception) { + e.printStackTrace() + emit(-1) + + } finally { + fos?.flush() + fos?.close() + } + } else { + emit(-1) + } + + +}.flowOn(Dispatchers.IO) + + +fun InputStream.saveFilesDir(filePath: String, fileName: String): String { File(filePath).let { - if(!it.exists()) + if (!it.exists()) it.mkdirs() } - val savePath="$filePath/$fileName" + val savePath = "$filePath/$fileName" val file = File(filePath, fileName) val os: FileOutputStream? - var bis:BufferedInputStream?=null - var bos:BufferedOutputStream?=null - try { - os = FileOutputStream( file ) + var bis: BufferedInputStream? = null + var bos: BufferedOutputStream? = null + try { + os = FileOutputStream(file) bis = BufferedInputStream(this) bos = BufferedOutputStream(os) var length: Int while (-1 != bis.read().also { length = it }) { bos.write(length) } - }catch (e:Exception){ + } catch (e: Exception) { e.printStackTrace() - }finally { + } finally { try { bos!!.close() bis!!.close() - }catch (e:Exception){ + } catch (e: Exception) { e.printStackTrace() } } diff --git a/app/src/main/java/com/cczhr/otglocation/utils/IMobileDeviceTools.kt b/app/src/main/java/com/cczhr/otglocation/utils/IMobileDeviceTools.kt index 5609e26..e9140c9 100644 --- a/app/src/main/java/com/cczhr/otglocation/utils/IMobileDeviceTools.kt +++ b/app/src/main/java/com/cczhr/otglocation/utils/IMobileDeviceTools.kt @@ -3,6 +3,7 @@ package com.cczhr.otglocation.utils import android.annotation.SuppressLint import android.content.Context import android.content.res.AssetManager +import android.os.Environment import android.os.SystemClock import android.util.Log import androidx.lifecycle.MutableLiveData @@ -30,18 +31,27 @@ class IMobileDeviceTools { var successResult: BufferedReader? = null var errorResult: BufferedReader? = null var os: DataOutputStream? = null + @Volatile - var isKilling=false + var isKilling = false + + companion object { + var DEVICE_PATH: String = + Application.context.getExternalFilesDir(null)!!.absolutePath + File.separator + "drivers" + + } fun killUsbmuxd(deviceNode: String = "") { - if(!isKilling){ - isKilling=true + if (!isKilling) { + isKilling = true SystemClock.sleep(1500) - val killSystemMtp = if (deviceNode.isNotEmpty()) "kill `lsof -t $deviceNode`\n" else deviceNode - val killPort="kill `netstat -tunlp | grep 27015|awk '{print $7} '|awk -F '/' '{print $1}'`" + val killSystemMtp = + if (deviceNode.isNotEmpty()) "kill `lsof -t $deviceNode`\n" else deviceNode + val killPort = + "kill `netstat -tunlp | grep 27015|awk '{print $7} '|awk -F '/' '{print $1}'`" runCommand("$killSystemMtp.$saveFilePath/usbmuxd -X -v -f\n$killPort") SystemClock.sleep(2000)//保证进程杀死 休眠一下 - isKilling=false + isKilling = false } } @@ -83,17 +93,48 @@ class IMobileDeviceTools { } + + fun mountImage( + version: String, + driver1Path: String, + driver2Path: String, + developerImg: (status: Boolean) -> Unit + ) { + runCommand( + "mkdir -p /sdcard/lockdown/drivers/$version\n" + + "mv -f $driver1Path /sdcard/lockdown/drivers/$version\n" + + "mv -f $driver2Path /sdcard/lockdown/drivers/$version\n" + + ".${saveFilePath}/ideviceimagemounter /sdcard/lockdown/drivers/$version/DeveloperDiskImage.dmg /sdcard/lockdown/drivers/$version/DeveloperDiskImage.dmg.signature", + isFinish = { + runCommand(".${saveFilePath}/ideviceimagemounter -l", { + if (!it.contains("Status", true)) { + developerImg.invoke( + !it.contains( + "ERROR", + true + ) && !it.contains( + "ImageSignature[0]", true + ) && !it.contains("No device found", true) + ) + } + } + ) + } + ) + + } + fun startUsbmuxd( - deviceNode: String, - connect: () -> Unit, - mag: (msg: String) -> Unit, - version: (msg: String) -> Unit, - deviceName: (msg: String) -> Unit, - developerImg: (status: Boolean) -> Unit + deviceNode: String, + connect: () -> Unit, + mag: (msg: String) -> Unit, + version: (msg: String) -> Unit, + deviceName: (msg: String) -> Unit, + developerImg: (status: Boolean) -> Unit ) { fixedThreadPool.execute { try { - if(isKilling) + if (isKilling) return@execute killUsbmuxd(deviceNode) process = Runtime.getRuntime().exec("su", arrayOf("LD_LIBRARY_PATH=$saveFilePath")) @@ -111,24 +152,53 @@ class IMobileDeviceTools { line?.let { Application.context.runMainThread { mag(it) - if (it.contains("Finished preflight on device", true) || it.contains("is_device_connected", true)){ + if (it.contains( + "Finished preflight on device", + true + ) || it.contains("is_device_connected", true) + ) { connect.invoke() - runCommand(".${saveFilePath}/ideviceinfo -k DeviceName", { dName -> - deviceName.invoke(dName) - - runCommand(".${saveFilePath}/ideviceinfo -k ProductVersion", { pVersion -> version.invoke(pVersion) - - runCommand(".${saveFilePath}/ideviceimagemounter /sdcard/lockdown/drivers/$pVersion/DeveloperDiskImage.dmg /sdcard/lockdown/drivers/$pVersion/DeveloperDiskImage.dmg.signature", isFinish = { - - runCommand(".${saveFilePath}/ideviceimagemounter -l", { - if (!it.contains("Status", true)) { - developerImg.invoke(!it.contains("ERROR", true) && !it.contains("ImageSignature[0]", true)) - } - } - )} - ) + runCommand( + ".${saveFilePath}/ideviceinfo -k DeviceName", + { dName -> + deviceName.invoke(dName) + + runCommand( + ".${saveFilePath}/ideviceinfo -k ProductVersion", + { pVersion -> + version.invoke(pVersion) + + runCommand( + ".${saveFilePath}/ideviceimagemounter /sdcard/lockdown/drivers/$pVersion/DeveloperDiskImage.dmg /sdcard/lockdown/drivers/$pVersion/DeveloperDiskImage.dmg.signature", + isFinish = { + + runCommand( + ".${saveFilePath}/ideviceimagemounter -l", + { + if (!it.contains( + "Status", + true + ) + ) { + developerImg.invoke( + !it.contains( + "ERROR", + true + ) && !it.contains( + "ImageSignature[0]", + true + ) && !it.contains( + "No device found", + true + ) + ) + } + } + ) + } + ) + }) }) - }) } } } @@ -189,15 +259,14 @@ class IMobileDeviceTools { assetManager.open(fileName).saveFilesDir(binSavePath, it) } runCommand( - "mkdir /sdcard/lockdown\n" + - "mkdir /sdcard/lockdown/drivers\n" + - "cp -rf $libSavePath/* $saveFilePath\n" + - "cp -rf $binSavePath/* $saveFilePath\n" + - "$libPermission" + - "chmod 777 -R $saveFilePath" - , isFinish = { - isSuccess.invoke(checkInstallLib(context)) - }) + "mkdir -p /sdcard/lockdown\n" + + "mkdir -p /sdcard/lockdown/drivers\n" + + "cp -rf $libSavePath/* $saveFilePath\n" + + "cp -rf $binSavePath/* $saveFilePath\n" + + "$libPermission" + + "chmod 777 -R $saveFilePath", isFinish = { + isSuccess.invoke(checkInstallLib(context)) + }) } @@ -213,10 +282,8 @@ class IMobileDeviceTools { deleteCommand.append("rm -f $saveFilePath/$it\n") } runCommand( - "${deleteCommand}rm -f $saveFilePath/usbmuxd.pid\n" + - "rm -rf /sdcard/lockdown" - - , isFinish = isFinish + "${deleteCommand}rm -f $saveFilePath/usbmuxd.pid\n" + + "rm -rf /sdcard/lockdown", isFinish = isFinish ) } @@ -251,17 +318,18 @@ class IMobileDeviceTools { private fun runCommand( - cmd: String, - input: ((str: String) -> Unit)? = null, - error: ((str: String) -> Unit)? = null, - isFinish: (() -> Unit)? = null + cmd: String, + input: ((str: String) -> Unit)? = null, + error: ((str: String) -> Unit)? = null, + isFinish: (() -> Unit)? = null ) { fixedThreadPool.execute { try { val successResult: BufferedReader val errorResult: BufferedReader val os: DataOutputStream - val process: Process = Runtime.getRuntime().exec("su", arrayOf("LD_LIBRARY_PATH=$saveFilePath")) + val process: Process = + Runtime.getRuntime().exec("su", arrayOf("LD_LIBRARY_PATH=$saveFilePath")) successResult = BufferedReader(InputStreamReader(process.inputStream)) errorResult = BufferedReader(InputStreamReader(process.errorStream)) os = DataOutputStream(process.outputStream) diff --git a/app/src/main/java/com/cczhr/otglocation/utils/ZipUtils.kt b/app/src/main/java/com/cczhr/otglocation/utils/ZipUtils.kt new file mode 100644 index 0000000..1a14d3d --- /dev/null +++ b/app/src/main/java/com/cczhr/otglocation/utils/ZipUtils.kt @@ -0,0 +1,508 @@ +package com.cczhr.otglocation.utils + +import android.util.Log +import java.io.* +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +/** + * @author cczhr + * @description + * @since 2021/6/11 14:45 + */ +class ZipUtils { + companion object { + private const val BUFFER_LEN = 8192 + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFilePath The path of ZIP file. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFiles( + srcFiles: Collection, + zipFilePath: String? + ): Boolean { + return zipFiles(srcFiles, zipFilePath, null) + } + + /** + * Zip the files. + * + * @param srcFilePaths The paths of source files. + * @param zipFilePath The path of ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFiles( + srcFilePaths: Collection?, + zipFilePath: String?, + comment: String? + ): Boolean { + if (srcFilePaths == null || zipFilePath == null) return false + var zos: ZipOutputStream? = null + return try { + zos = ZipOutputStream(FileOutputStream(zipFilePath)) + for (srcFile in srcFilePaths) { + if (!getFileByPath(srcFile)?.let { zipFile(it, "", zos, comment) }!!) return false + } + true + } finally { + if (zos != null) { + zos.finish() + zos.close() + } + } + } + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFile The ZIP file. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFiles(srcFiles: Collection, zipFile: File): Boolean { + return zipFiles(srcFiles, zipFile, null) + } + + /** + * Zip the files. + * + * @param srcFiles The source of files. + * @param zipFile The ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFiles( + srcFiles: Collection?, + zipFile: File?, + comment: String? + ): Boolean { + if (srcFiles == null || zipFile == null) return false + var zos: ZipOutputStream? = null + return try { + zos = ZipOutputStream(FileOutputStream(zipFile)) + for (srcFile in srcFiles) { + if (!zipFile(srcFile, "", zos, comment)) return false + } + true + } finally { + if (zos != null) { + zos.finish() + zos.close() + } + } + } + + /** + * Zip the file. + * + * @param srcFilePath The path of source file. + * @param zipFilePath The path of ZIP file. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFile( + srcFilePath: String, + zipFilePath: String + ): Boolean { + return zipFile( + getFileByPath(srcFilePath), + getFileByPath(zipFilePath), + null + ) + } + + /** + * Zip the file. + * + * @param srcFilePath The path of source file. + * @param zipFilePath The path of ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFile( + srcFilePath: String, + zipFilePath: String, + comment: String? + ): Boolean { + return zipFile( + getFileByPath(srcFilePath), + getFileByPath(zipFilePath), + comment + ) + } + + /** + * Zip the file. + * + * @param srcFile The source of file. + * @param zipFile The ZIP file. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFile( + srcFile: File?, + zipFile: File? + ): Boolean { + return zipFile(srcFile, zipFile, null) + } + + /** + * Zip the file. + * + * @param srcFile The source of file. + * @param zipFile The ZIP file. + * @param comment The comment. + * @return `true`: success

`false`: fail + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun zipFile( + srcFile: File?, + zipFile: File?, + comment: String? + ): Boolean { + if (srcFile == null || zipFile == null) return false + var zos: ZipOutputStream? = null + return try { + zos = ZipOutputStream(FileOutputStream(zipFile)) + zipFile(srcFile, "", zos, comment) + } finally { + if (zos != null) { + zos.close() + } + } + } + + @Throws(IOException::class) + private fun zipFile( + srcFile: File, + rootPath: String, + zos: ZipOutputStream, + comment: String? + ): Boolean { + var rootPath = rootPath + rootPath = + rootPath + (if (isSpace(rootPath)) "" else File.separator) + srcFile.name + if (srcFile.isDirectory) { + val fileList = srcFile.listFiles() + if (fileList == null || fileList.size <= 0) { + val entry = ZipEntry("$rootPath/") + entry.comment = comment + zos.putNextEntry(entry) + zos.closeEntry() + } else { + for (file in fileList) { + if (!zipFile(file, rootPath, zos, comment)) return false + } + } + } else { + var `is`: InputStream? = null + try { + `is` = BufferedInputStream(FileInputStream(srcFile)) + val entry = ZipEntry(rootPath) + entry.comment = comment + zos.putNextEntry(entry) + val buffer = ByteArray(BUFFER_LEN) + var len: Int + while (`is`.read(buffer, 0, BUFFER_LEN).also { len = it } != -1) { + zos.write(buffer, 0, len) + } + zos.closeEntry() + } finally { + `is`?.close() + } + } + return true + } + + /** + * Unzip the file. + * + * @param zipFilePath The path of ZIP file. + * @param destDirPath The path of destination directory. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFile( + zipFilePath: String, + destDirPath: String + ): List? { + return unzipFileByKeyword(zipFilePath, destDirPath, null) + } + + /** + * Unzip the file. + * + * @param zipFile The ZIP file. + * @param destDir The destination directory. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFile( + zipFile: File?, + destDir: File? + ): List? { + return unzipFileByKeyword(zipFile, destDir, null) + } + + /** + * Unzip the file by keyword. + * + * @param zipFilePath The path of ZIP file. + * @param destDirPath The path of destination directory. + * @param keyword The keyboard. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFileByKeyword( + zipFilePath: String, + destDirPath: String, + keyword: String? + ): List? { + return unzipFileByKeyword( + getFileByPath(zipFilePath), + getFileByPath(destDirPath), + keyword + ) + } + + /** + * Unzip the file by keyword. + * + * @param zipFile The ZIP file. + * @param destDir The destination directory. + * @param keyword The keyboard. + * @return the unzipped files + * @throws IOException if unzip unsuccessfully + */ + @Throws(IOException::class) + fun unzipFileByKeyword( + zipFile: File?, + destDir: File?, + keyword: String? + ): List? { + if (zipFile == null || destDir == null) return null + val files: MutableList = ArrayList() + val zip = ZipFile(zipFile) + val entries: Enumeration<*> = zip.entries() + zip.use { zip -> + if (isSpace(keyword)) { + while (entries.hasMoreElements()) { + val entry = entries.nextElement() as ZipEntry + val entryName = entry.name.replace("\\", "/") + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: $entryName is dangerous!") + continue + } + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files + } + } else { + while (entries.hasMoreElements()) { + val entry = entries.nextElement() as ZipEntry + val entryName = entry.name.replace("\\", "/") + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: $entryName is dangerous!") + continue + } + if (entryName.contains(keyword!!)) { + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files + } + } + } + } + return files + } + + @Throws(IOException::class) + private fun unzipChildFile( + destDir: File, + files: MutableList, + zip: ZipFile, + entry: ZipEntry, + name: String + ): Boolean { + val file = File(destDir, name) + files.add(file) + if (entry.isDirectory) { + return createOrExistsDir(file) + } else { + if (!createOrExistsFile(file)) return false + var `in`: InputStream? = null + var out: OutputStream? = null + try { + `in` = BufferedInputStream(zip.getInputStream(entry)) + out = BufferedOutputStream(FileOutputStream(file)) + val buffer = ByteArray(BUFFER_LEN) + var len: Int + while (`in`.read(buffer).also { len = it } != -1) { + out.write(buffer, 0, len) + } + } finally { + `in`?.close() + out?.close() + } + } + return true + } + + /** + * Return the files' path in ZIP file. + * + * @param zipFilePath The path of ZIP file. + * @return the files' path in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getFilesPath(zipFilePath: String): List? { + return getFilesPath(getFileByPath(zipFilePath)) + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param dirPath The path of directory. + * @return `true`: exists or creates successfully

`false`: otherwise + */ + fun createOrExistsDir(dirPath: String): Boolean { + return createOrExistsDir(getFileByPath(dirPath)) + } + /** + * Return the files' path in ZIP file. + * + * @param zipFile The ZIP file. + * @return the files' path in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getFilesPath(zipFile: File?): List? { + if (zipFile == null) return null + val paths: MutableList = ArrayList() + val zip = ZipFile(zipFile) + val entries: Enumeration<*> = zip.entries() + while (entries.hasMoreElements()) { + val entryName = (entries.nextElement() as ZipEntry).name.replace("\\", "/") + if (entryName.contains("../")) { + Log.e("ZipUtils", "entryName: $entryName is dangerous!") + paths.add(entryName) + } else { + paths.add(entryName) + } + } + zip.close() + return paths + } + + /** + * Return the files' comment in ZIP file. + * + * @param zipFilePath The path of ZIP file. + * @return the files' comment in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getComments(zipFilePath: String): List? { + return getComments(getFileByPath(zipFilePath)) + } + + /** + * Return the files' comment in ZIP file. + * + * @param zipFile The ZIP file. + * @return the files' comment in ZIP file + * @throws IOException if an I/O error has occurred + */ + @Throws(IOException::class) + fun getComments(zipFile: File?): List? { + if (zipFile == null) return null + val comments: MutableList = ArrayList() + val zip = ZipFile(zipFile) + val entries: Enumeration<*> = zip.entries() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() as ZipEntry + comments.add(entry.comment) + } + zip.close() + return comments + } + private fun getFileByPath(filePath: String): File? { + return if (isSpace(filePath)) null else File(filePath) + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return `true`: exists or creates successfully

`false`: otherwise + */ + fun createOrExistsDir(file: File?): Boolean { + return file != null && if (file.exists()) file.isDirectory else file.mkdirs() + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param filePath The path of file. + * @return `true`: exists or creates successfully

`false`: otherwise + */ + fun createOrExistsFile(filePath: String?): Boolean { + return createOrExistsFile(getFileByPath(filePath!!)) + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return `true`: exists or creates successfully

`false`: otherwise + */ + fun createOrExistsFile(file: File?): Boolean { + if (file == null) return false + if (file.exists()) return file.isFile + return if (!createOrExistsDir(file.parentFile)) false else try { + file.createNewFile() + } catch (e: IOException) { + e.printStackTrace() + false + } + } + + private fun isSpace(s: String?): Boolean { + if (s == null) return true + var i = 0 + val len = s.length + while (i < len) { + if (!Character.isWhitespace(s[i])) { + return false + } + ++i + } + return true + } + + + + + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 91a9eb5..0811986 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -159,6 +159,15 @@ android:layout_height="wrap_content" android:text="@string/cancel_location_offset" /> +