├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── LICENSE ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── top │ │ └── zsh2401 │ │ └── imagehelper │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── top │ │ │ └── zsh2401 │ │ │ └── imagehelper │ │ │ ├── App.kt │ │ │ ├── core │ │ │ ├── RebootHelper.kt │ │ │ ├── image │ │ │ │ ├── CommandHelper.kt │ │ │ │ ├── FileHelper.kt │ │ │ │ ├── ImageExtractor.kt │ │ │ │ ├── ImageFinder.kt │ │ │ │ ├── ImageFlasher.kt │ │ │ │ ├── ImageNotFoundException.kt │ │ │ │ └── Images.kt │ │ │ └── su │ │ │ │ ├── ExecResult.kt │ │ │ │ ├── NoSuException.kt │ │ │ │ ├── Su.kt │ │ │ │ └── SuManager.kt │ │ │ └── ux │ │ │ ├── DonateHelper.kt │ │ │ ├── FileUtil.java │ │ │ ├── Flow.kt │ │ │ ├── LanguageHelper.kt │ │ │ ├── MainActivity.kt │ │ │ └── PageAdapater.kt │ └── res │ │ ├── drawable │ │ └── btm_nav_color.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── item_view_page_extract.xml │ │ ├── item_view_page_flash.xml │ │ └── nav_header.xml │ │ ├── menu │ │ ├── bottom_menu.xml │ │ └── nav_menu.xml │ │ ├── mipmap-mdpi │ │ ├── ic_about.png │ │ ├── ic_boot.png │ │ ├── ic_donate.png │ │ ├── ic_extract.png │ │ ├── ic_flash.png │ │ ├── ic_flash_main.png │ │ ├── ic_github.png │ │ ├── ic_language.png │ │ └── ic_rec.png │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── top │ └── zsh2401 │ └── imagehelper │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 26 9 | defaultConfig { 10 | applicationId "top.zsh2401.imagehelper" 11 | minSdkVersion 19 12 | targetSdkVersion 26 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 26 | implementation 'com.android.support:appcompat-v7:26.1.0' 27 | implementation 'com.android.support:design:26.1.0' 28 | implementation 'com.android.support:percent:26.1.0' 29 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 33 | } 34 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/top/zsh2401/imagehelper/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("top.zsh2401.imagehelper", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/App.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.content.res.Configuration 6 | import android.net.Uri 7 | import android.os.Environment 8 | import android.util.Log 9 | import top.zsh2401.imagehelper.core.image.ImageExtractor 10 | import top.zsh2401.imagehelper.core.image.ImageFinder 11 | import top.zsh2401.imagehelper.core.image.Images 12 | import top.zsh2401.imagehelper.ux.FileUtil 13 | import android.provider.MediaStore 14 | 15 | 16 | 17 | /** 18 | * Created by zsh24 on 02/01/2018. 19 | */ 20 | class App : Application() { 21 | companion object { 22 | val TAG = "App" 23 | 24 | val current:App get() = _current 25 | private lateinit var _current:App 26 | 27 | lateinit var gConfig:Configuration 28 | val context: Context 29 | get() =_context 30 | private lateinit var _context:Context 31 | } 32 | override fun onCreate() { 33 | super.onCreate() 34 | var storage = Environment.getExternalStorageDirectory().absolutePath 35 | // getRealPathFromURI("content://") 36 | Log.d(TAG,storage) 37 | val config = resources.configuration 38 | _current = this 39 | gConfig = config 40 | _context = this.applicationContext 41 | } 42 | 43 | fun getRealPathFromURI(contentUri: Uri): String? { 44 | var res: String? = null 45 | val proj = arrayOf(MediaStore.Images.Media.DATA) 46 | val cursor = contentResolver.query(contentUri, proj, null, null, null) 47 | if (cursor!!.moveToFirst()) { 48 | val column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) 49 | res = cursor.getString(column_index) 50 | } 51 | cursor.close() 52 | return res 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/RebootHelper.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core 2 | 3 | import top.zsh2401.imagehelper.core.su.SuManager 4 | 5 | /** 6 | * Created by zsh24 on 02/02/2018. 7 | */ 8 | fun rebootToRecovery(){ 9 | SuManager.getSu().setCommandAndExecute("reboot recovery") 10 | } 11 | fun reboot(){ 12 | SuManager.getSu().setCommandAndExecute("reboot") 13 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/image/CommandHelper.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.image 2 | 3 | /** 4 | * Created by zsh24 on 02/01/2018. 5 | */ 6 | object CommandHelper { 7 | fun haveFindCommand():Boolean{ 8 | try{ 9 | Runtime.getRuntime().exec("find").destroy() 10 | return true 11 | }catch (ex:Exception){ 12 | ex.printStackTrace() 13 | return false 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/image/FileHelper.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.image 2 | 3 | import android.net.Uri 4 | import top.zsh2401.imagehelper.App 5 | import java.io.File 6 | import java.io.FileOutputStream 7 | 8 | /** 9 | * Created by zsh24 on 02/02/2018. 10 | */ 11 | fun getTempFile(uri: Uri):String{ 12 | val tempPath = App.current.externalCacheDir.absolutePath + "/temp_file" 13 | val reader = App.current.contentResolver.openInputStream(uri) 14 | val writer = FileOutputStream(tempPath) 15 | val buffer = ByteArray(1024) 16 | var readSize = 0 17 | while(true){ 18 | readSize = reader.read(buffer) 19 | if(readSize==-1){ 20 | break 21 | } 22 | writer.write(buffer,0,readSize) 23 | } 24 | writer.flush() 25 | writer.close() 26 | reader.close() 27 | return tempPath 28 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/image/ImageExtractor.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.image 2 | 3 | import top.zsh2401.imagehelper.core.su.Su 4 | import top.zsh2401.imagehelper.core.su.SuManager 5 | 6 | /** 7 | * Created by zsh24 on 01/31/2018. 8 | */ 9 | class ImageExtractor(private val img:Images) :Runnable{ 10 | private lateinit var su: Su 11 | override fun run(){ 12 | var path = ImageFinder.pathOf(img) 13 | su = SuManager.getSu() 14 | su.setCommandAndExecute("cp $path /sdcard/${img.toString().toLowerCase()}.img") 15 | waitFor() 16 | } 17 | fun runAsync(callback:((Boolean)->Unit)? = null){ 18 | Thread({ 19 | try{ 20 | run() 21 | callback?.invoke(su.process.exitValue() == 0) 22 | }catch (ex:Exception){ 23 | callback?.invoke(false) 24 | } 25 | }).start() 26 | } 27 | fun cancel(){ 28 | su.process.destroy() 29 | } 30 | fun waitFor():Int{ 31 | return su.waitFor() 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/image/ImageFinder.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.image 2 | 3 | import android.util.Log 4 | import top.zsh2401.imagehelper.core.su.SuManager 5 | 6 | /** 7 | * Created by zsh24 on 02/01/2018. 8 | */ 9 | 10 | object ImageFinder { 11 | val TAG = "ImageFinder" 12 | @Throws(ImageNotFoundException::class) 13 | fun pathOf(img: Images):String{ 14 | return method1(img) ?: 15 | method2(img) ?: 16 | throw ImageNotFoundException() 17 | } 18 | private fun method1(img: Images):String?{ 19 | if(!CommandHelper.haveFindCommand())return null 20 | var findResult = 21 | SuManager.execSuCommand( 22 | "find /dev/ -name ${img.toString().toLowerCase()}") 23 | Log.d(TAG,"output: "+ findResult.output) 24 | if(findResult.exitValue != 0)return null 25 | var lines = findResult.output.split("\n") 26 | Log.d(TAG,"splited...") 27 | var line:String? = null 28 | for(i in lines.indices){ 29 | Log.d(TAG,"finding...crt: " + lines[i]) 30 | if(pathIsRight(lines[i])){ 31 | line = lines[i] 32 | break 33 | } 34 | } 35 | Log.d(TAG,"method1 result: " + (line?:"null")) 36 | return line 37 | } 38 | private fun method2(img: Images):String?{ 39 | var maybePath1 = 40 | "/dev/block/platform/*/by-name/${img.toString().toLowerCase()}" 41 | var maybePath2 = 42 | "/dev/block/platform/soc*/*/by-name/${img.toString().toLowerCase()}" 43 | var result:String?= null 44 | if(pathIsRight(maybePath1)){ 45 | result = maybePath1 46 | }else if(pathIsRight(maybePath2)){ 47 | result = maybePath2 48 | } 49 | Log.d(TAG,"method2 result: " + (result?:"null")) 50 | return result 51 | } 52 | private fun pathIsRight(path:String):Boolean{ 53 | if(path.isNullOrEmpty())return false 54 | return SuManager.execSuCommand("ls -l " + path).exitValue == 0 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/image/ImageFlasher.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.image 2 | 3 | import android.media.Image 4 | import android.net.Uri 5 | import android.util.Log 6 | import top.zsh2401.imagehelper.App 7 | import top.zsh2401.imagehelper.core.su.Su 8 | import top.zsh2401.imagehelper.core.su.SuManager 9 | import top.zsh2401.imagehelper.ux.FileUtil 10 | import top.zsh2401.imagehelper.ux.Flow 11 | import java.io.File 12 | import java.net.URI 13 | 14 | /** 15 | * Created by zsh24 on 02/01/2018. 16 | */ 17 | class ImageFlasher(private val img:Images,private val fileUri:Uri):Runnable { 18 | private val TAG = "Image Flasher" 19 | lateinit var su: Su 20 | override fun run() { 21 | Log.d(TAG,"the uri: " + fileUri) 22 | var tempPath =getTempFile(fileUri) 23 | var targetPath = ImageFinder.pathOf(img) 24 | su = SuManager.getSu() 25 | su.setCommandAndExecute("mv $tempPath $targetPath") 26 | su.waitFor() 27 | } 28 | fun runAsync(callback:((Boolean)->Unit)?=null){ 29 | Thread({ 30 | try{ 31 | run() 32 | callback?.invoke(su.process.exitValue() == 0) 33 | }catch (ex:Exception){ 34 | ex.printStackTrace() 35 | callback?.invoke(false) 36 | } 37 | }).start() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/image/ImageNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.image 2 | 3 | /** 4 | * Created by zsh24 on 02/01/2018. 5 | */ 6 | class ImageNotFoundException:Exception() -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/image/Images.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.image 2 | 3 | /** 4 | * Created by zsh24 on 01/31/2018. 5 | */ 6 | enum class Images { 7 | Boot, 8 | Recovery 9 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/su/ExecResult.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.su 2 | 3 | /** 4 | * Created by zsh24 on 02/01/2018. 5 | */ 6 | data class ExecResult(val output:String,val exitValue:Int){ 7 | override fun toString(): String { 8 | return output 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/su/NoSuException.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.su 2 | 3 | /** 4 | * Created by zsh24 on 02/01/2018. 5 | */ 6 | class NoSuException:Exception() -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/su/Su.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.su 2 | 3 | import java.io.BufferedInputStream 4 | import java.io.BufferedReader 5 | import java.io.DataOutputStream 6 | import java.io.InputStreamReader 7 | 8 | /** 9 | * Created by zsh24 on 02/01/2018. 10 | */ 11 | class Su(val process:Process) { 12 | var writer:DataOutputStream = 13 | DataOutputStream(process.outputStream) 14 | var reader:BufferedReader = 15 | BufferedReader(InputStreamReader(process.inputStream)) 16 | 17 | fun setCommandAndExecute(vararg commands:String){ 18 | for(cmd in commands){ 19 | writer.writeBytes(cmd + "\n") 20 | } 21 | writer.writeBytes("exit\n") 22 | writer.flush() 23 | } 24 | fun getOutput():String{ 25 | var sb = StringBuilder() 26 | var line:String? = null 27 | while(true){ 28 | line = reader.readLine() 29 | if(line.isNullOrEmpty())break 30 | sb.append(line + "\n") 31 | } 32 | return sb.toString() 33 | } 34 | fun waitFor():Int{ 35 | return process.waitFor() 36 | } 37 | fun stop(){ 38 | process.destroy() 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/core/su/SuManager.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.core.su 2 | 3 | import java.io.* 4 | 5 | 6 | 7 | /** 8 | * Created by zsh24 on 02/01/2018. 9 | */ 10 | object SuManager { 11 | val TAG = "SuManager" 12 | fun deviceIsRoot():Boolean{ 13 | try{ 14 | return File("/system/bin/su").exists() || 15 | File("/system/xbin/su").exists() 16 | }catch (e:Exception){ 17 | e.printStackTrace() 18 | } 19 | return false 20 | } 21 | fun haveRootPermission():Boolean{ 22 | try{ 23 | // if(!deviceIsRoot())return false 24 | Runtime.getRuntime().exec("su").destroy() 25 | return true 26 | }catch (ex:Exception){ 27 | ex.printStackTrace() 28 | return false 29 | } 30 | } 31 | @Throws(NoSuException::class) 32 | fun getSu():Su{ 33 | suCheck() 34 | return Su(Runtime.getRuntime().exec("su")) 35 | } 36 | @Throws(NoSuException::class) 37 | fun execSuCommand(cmd:String): ExecResult { 38 | suCheck() 39 | val su = Runtime.getRuntime().exec("su") 40 | val writer = DataOutputStream(su.outputStream) 41 | val reader = BufferedReader(InputStreamReader(su.inputStream)) 42 | //exe command and wait to exit... 43 | writer.writeBytes(cmd + "\n") 44 | writer.writeBytes("exit\n") 45 | writer.flush() 46 | su.waitFor() 47 | // reader.readText() 48 | return ExecResult(reader.readText(), su.exitValue()) 49 | } 50 | fun suCheck(){ 51 | if(!haveRootPermission()){ 52 | throw NoSuException() 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/ux/DonateHelper.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.ux 2 | 3 | import android.content.ClipboardManager 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import top.zsh2401.imagehelper.App 8 | 9 | /** 10 | * Created by zsh24 on 02/01/2018. 11 | */ 12 | private val alipayClientPkgName = "com.eg.android.AlipayGphone" 13 | private val wechatAccount = "Ryme2401" 14 | private val alipayAccount = "zsh2401@163.com" 15 | private val alipayRedpacketCode = "" 16 | private val URL_FORMAT= "intent://platformapi/startapp?saId=10000007&" + 17 | "clientVersion=3.7.0.0718&qrcode=https%3A%2F%2Fqr.alipay.com%2FFKX06104YFIEAHNXXKP7E2%3F_s" + 18 | "%3Dweb-other&_t=1472443966571#Intent;" + 19 | "scheme=alipayqr;package=com.eg.android.AlipayGphone;end" 20 | private val clipboardManager:ClipboardManager = 21 | App.current.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 22 | 23 | fun gotoAlipay():Boolean{ 24 | if(isInstallAlipayClient()){ 25 | App.context.startActivity(Intent.parseUri(URL_FORMAT,Intent.URI_INTENT_SCHEME)) 26 | return true 27 | } 28 | return false 29 | } 30 | fun copyWechatAccount(){ 31 | clipboardManager.text = wechatAccount 32 | } 33 | fun copyAlipayAccount(){ 34 | clipboardManager.text = alipayAccount 35 | } 36 | fun copyAlipayRedpacketCode(){ 37 | clipboardManager.text = alipayRedpacketCode 38 | } 39 | 40 | private fun isInstallAlipayClient() :Boolean{ 41 | try{ 42 | return App.context.packageManager.getPackageInfo(alipayClientPkgName,0) != null 43 | }catch (ex: PackageManager.NameNotFoundException){ 44 | return false 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/ux/FileUtil.java: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.ux; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.os.Environment; 9 | import android.provider.DocumentsContract; 10 | import android.provider.MediaStore; 11 | import android.util.Log; 12 | 13 | import java.io.File; 14 | import java.io.FileInputStream; 15 | import java.io.FileOutputStream; 16 | import java.io.InputStream; 17 | import java.net.URISyntaxException; 18 | 19 | /** 20 | * Created by zsh24 on 02/02/2018. 21 | */ 22 | 23 | public class FileUtil{ 24 | 25 | 26 | ///////////////////////复制文件////////////////////////////// 27 | /** 28 | * 复制单个文件 29 | * @param oldPath String 原文件路径 如:c:/fqf.txt 30 | * @param newPath String 复制后路径 如:f:/fqf.txt 31 | * @return boolean 32 | */ 33 | public static boolean copyFile(String oldPath, String newPath) { 34 | boolean isok = true; 35 | try { 36 | int bytesum = 0; 37 | int byteread = 0; 38 | File oldfile = new File(oldPath); 39 | if (oldfile.exists()) { //文件存在时 40 | InputStream inStream = new FileInputStream(oldPath); //读入原文件 41 | FileOutputStream fs = new FileOutputStream(newPath); 42 | byte[] buffer = new byte[1024]; 43 | int length; 44 | while ( (byteread = inStream.read(buffer)) != -1) { 45 | bytesum += byteread; //字节数 文件大小 46 | //System.out.println(bytesum); 47 | fs.write(buffer, 0, byteread); 48 | } 49 | fs.flush(); 50 | fs.close(); 51 | inStream.close(); 52 | } 53 | else 54 | { 55 | Log.d("FileUtil","file not exist"); 56 | isok = false; 57 | } 58 | } 59 | catch (Exception e) { 60 | Log.d("FileUtil","Copy failed.."); 61 | e.printStackTrace(); 62 | isok = false; 63 | } 64 | return isok; 65 | } 66 | public static String getRealFilePath( final Context context, final Uri uri ) { 67 | if ( null == uri ) return null; 68 | final String scheme = uri.getScheme(); 69 | String data = null; 70 | if ( scheme == null ) 71 | data = uri.getPath(); 72 | else if ( ContentResolver.SCHEME_FILE.equals( scheme ) ) { 73 | data = uri.getPath(); 74 | } else if ( ContentResolver.SCHEME_CONTENT.equals( scheme ) ) { 75 | Cursor cursor = context.getContentResolver().query( uri, new String[] { MediaStore.Images.ImageColumns.DATA }, null, null, null ); 76 | if ( null != cursor ) { 77 | if ( cursor.moveToFirst() ) { 78 | int index = cursor.getColumnIndex( MediaStore.Images.ImageColumns.DATA ); 79 | if ( index > -1 ) { 80 | data = cursor.getString( index ); 81 | } 82 | } 83 | cursor.close(); 84 | } 85 | } 86 | return data; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/ux/Flow.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.ux 2 | 3 | import android.app.Activity 4 | import android.app.AlertDialog 5 | import android.app.ProgressDialog 6 | import android.content.Intent 7 | import android.net.Uri 8 | import android.support.design.widget.Snackbar 9 | import android.util.Log 10 | import android.view.View 11 | import top.zsh2401.imagehelper.App 12 | import top.zsh2401.imagehelper.R 13 | import top.zsh2401.imagehelper.core.image.ImageExtractor 14 | import top.zsh2401.imagehelper.core.image.ImageFlasher 15 | import top.zsh2401.imagehelper.core.image.Images 16 | import top.zsh2401.imagehelper.core.rebootToRecovery 17 | import top.zsh2401.imagehelper.core.su.SuManager 18 | import java.io.File 19 | import java.net.URI 20 | 21 | /** 22 | * Created by zsh24 on 02/01/2018. 23 | */ 24 | object Flow { 25 | val FILE_SELECT_REQUEST_CODE = 2401 26 | val TAG = "Flow" 27 | lateinit var view:View 28 | lateinit var mainActivity: MainActivity 29 | fun extractRecovery(){ 30 | extract(Images.Recovery) 31 | } 32 | fun extractBoot(){ 33 | extract(Images.Boot) 34 | } 35 | private fun extract(img:Images){ 36 | if(!rootCheck())return 37 | var bar = ProgressDialog(mainActivity) 38 | bar.setMessage(App.current.getString(R.string.msg_extracting)) 39 | var extractor = ImageExtractor(img) 40 | bar.setOnDismissListener({ 41 | extractor.cancel() 42 | }) 43 | bar.show() 44 | extractor.runAsync({isSuccessful-> 45 | mainActivity.runOnUiThread({ 46 | if(bar.isShowing) 47 | bar.dismiss() 48 | var builder = AlertDialog.Builder(mainActivity) 49 | builder.setTitle( 50 | if(isSuccessful)R.string.title_successed 51 | else R.string.title_failed) 52 | builder.setMessage( 53 | if(isSuccessful)R.string.msg_extract_ok 54 | else R.string.msg_extract_failed) 55 | builder.setNegativeButton("ok",null) 56 | builder.show() 57 | }) 58 | }) 59 | } 60 | fun flashRecovery(){ 61 | flash(Images.Recovery) 62 | } 63 | fun flashBoot(){ 64 | flash(Images.Boot) 65 | } 66 | private fun flash(img:Images){ 67 | if(!rootCheck())return 68 | selectAFile() 69 | mainActivity.onFileSelectedCallback = {path-> 70 | flash(path,img) 71 | } 72 | } 73 | private fun flash(pathUri: Uri, img: Images){ 74 | var bar = ProgressDialog(mainActivity) 75 | bar.setMessage(App.current.getString(R.string.msg_extracting)) 76 | var flasher = ImageFlasher(img,pathUri) 77 | bar.setOnDismissListener({ 78 | flasher.su.process.destroy() 79 | }) 80 | flasher.runAsync({isSuccessful-> 81 | var dialog = AlertDialog.Builder(mainActivity) 82 | dialog.setTitle( 83 | if(isSuccessful) R.string.title_successed 84 | else R.string.title_failed) 85 | dialog.setMessage( 86 | if(isSuccessful)R.string.msg_flash_ok 87 | else R.string.msg_flash_failed) 88 | if(isSuccessful && img == Images.Recovery){ 89 | dialog.setPositiveButton(R.string.reboot_to_recovery,{_,_-> 90 | rebootToRecovery() 91 | }) 92 | dialog.setNegativeButton(R.string.donot_reboot,null) 93 | }else{ 94 | dialog.setPositiveButton("ok",null) 95 | } 96 | 97 | mainActivity.runOnUiThread({ 98 | bar.dismiss() 99 | dialog.show() 100 | }) 101 | }) 102 | bar.show() 103 | } 104 | private fun selectAFile(){ 105 | var intent = Intent(Intent.ACTION_GET_CONTENT) 106 | intent.setType("*/*") 107 | intent.addCategory(Intent.CATEGORY_OPENABLE) 108 | mainActivity.startActivityForResult(Intent.createChooser(intent, "请选择一个要上传的文件"), 109 | FILE_SELECT_REQUEST_CODE) 110 | } 111 | private fun rootCheck():Boolean{ 112 | if(SuManager.haveRootPermission()){ 113 | return true 114 | }else{ 115 | Snackbar.make(view,R.string.warning_no_root,Snackbar.LENGTH_LONG) 116 | .setAction("ok",null) 117 | .show() 118 | return false 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/ux/LanguageHelper.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.ux 2 | 3 | import top.zsh2401.imagehelper.App 4 | import top.zsh2401.imagehelper.R 5 | import java.util.* 6 | 7 | /** 8 | * Created by zsh24 on 02/01/2018. 9 | */ 10 | object LanguageHelper{ 11 | val languages:Array = arrayOf( 12 | App.context.getString(R.string.l_auto), 13 | App.context.getString(R.string.l_zh_cn), 14 | App.context.getString(R.string.l_en_us)) 15 | 16 | fun setLanguageBySerial(languageCode:Int){ 17 | when(languageCode){ 18 | 1->{ 19 | App.gConfig.locale = Locale.CHINESE} 20 | 2->{ 21 | App.gConfig.locale = Locale.ENGLISH 22 | } 23 | else->{ 24 | App.gConfig.locale = Locale.getDefault()} 25 | } 26 | 27 | } 28 | fun setAuto(){ 29 | setLanguageBySerial(0) 30 | } 31 | 32 | fun getCurrentLanguageSerial():Int{ 33 | if(isSetAutoLanguage())return 0 34 | var crtLaguageName = App.context.getString(R.string.language_name) 35 | return languages.indexOf(crtLaguageName) 36 | } 37 | fun isSetAutoLanguage():Boolean{ 38 | return true 39 | } 40 | private fun getSavedChoice():Int{ 41 | return 1 42 | } 43 | private fun saveChoice(choice:Int){ 44 | 45 | } 46 | fun initLanguage(){ 47 | setLanguageBySerial(getSavedChoice()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/top/zsh2401/imagehelper/ux/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package top.zsh2401.imagehelper.ux 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.content.ContentUris 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.PackageManager 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.os.Bundle 12 | import android.os.Environment 13 | import android.provider.DocumentsContract 14 | import android.provider.MediaStore 15 | import android.support.design.widget.BottomNavigationView 16 | import android.support.design.widget.NavigationView 17 | import android.support.design.widget.Snackbar 18 | import android.support.v4.app.ActivityCompat 19 | import android.support.v4.content.ContextCompat 20 | import android.support.v4.view.ViewPager 21 | import android.support.v4.widget.DrawerLayout 22 | import android.support.v7.app.ActionBarDrawerToggle 23 | import android.support.v7.app.AlertDialog 24 | import android.support.v7.app.AppCompatActivity 25 | import android.support.v7.widget.Toolbar 26 | import android.util.Log 27 | import android.view.MenuItem 28 | import android.view.View 29 | import android.view.Window 30 | import android.view.WindowManager 31 | import android.widget.Button 32 | import top.zsh2401.imagehelper.App 33 | import top.zsh2401.imagehelper.R 34 | import java.net.URI 35 | 36 | class MainActivity : AppCompatActivity(), 37 | View.OnClickListener, 38 | BottomNavigationView.OnNavigationItemSelectedListener, 39 | ViewPager.OnPageChangeListener 40 | { 41 | 42 | private val TAG = "MainActivity" 43 | private lateinit var viewList:ArrayList 44 | private lateinit var mNavView:NavigationView 45 | private lateinit var mToolbar:Toolbar 46 | private lateinit var mDrawer:DrawerLayout 47 | private lateinit var mViewPager: ViewPager 48 | private lateinit var mBottomNav:BottomNavigationView 49 | override fun onCreate(savedInstanceState: Bundle?) { 50 | super.onCreate(savedInstanceState) 51 | setContentView(R.layout.activity_main) 52 | initViewObj() 53 | initViewPager() 54 | initDrawer() 55 | Flow.mainActivity = this 56 | Flow.view = mNavView 57 | initEvent() 58 | checkAndRequestPermission() 59 | } 60 | private val myPermissionRequestCode = 2405 61 | private fun checkAndRequestPermission(){ 62 | if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M)return 63 | var permissions = arrayOf( 64 | "android.permission.READ_EXTERNAL_STORAGE", 65 | "android.permission.WRITE_EXTERNAL_STORAGE") 66 | var allGrated =checkPermissionAllGranted(permissions) 67 | if(allGrated)return 68 | ActivityCompat.requestPermissions(this, permissions,myPermissionRequestCode) 69 | } 70 | 71 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 72 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 73 | if(requestCode==myPermissionRequestCode){ 74 | var isAllGranted = true 75 | for(grant in grantResults){ 76 | if(grant != PackageManager.PERMISSION_GRANTED){ 77 | isAllGranted = false 78 | break 79 | } 80 | } 81 | 82 | if(!isAllGranted){ 83 | var builder = AlertDialog.Builder(this) 84 | builder.setTitle(R.string.title_sorry) 85 | builder.setMessage(R.string.msg_permission_deined) 86 | builder.setOnDismissListener({ 87 | finish() 88 | }) 89 | builder.setNegativeButton("ok",null) 90 | // builder.setNeutralButton("ok",null) 91 | builder.show() 92 | } 93 | } 94 | } 95 | 96 | private fun checkPermissionAllGranted(permissions: Array): Boolean { 97 | for (permission in permissions) { 98 | if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { 99 | // 只要有一个权限没有被授予, 则直接返回 false 100 | return false 101 | } 102 | } 103 | return true 104 | } 105 | 106 | private fun initEvent(){ 107 | mBottomNav.setOnNavigationItemSelectedListener(this) 108 | mViewPager.addOnPageChangeListener(this) 109 | initNavEvent() 110 | viewList[0].findViewById