├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── licenses │ │ ├── afilechooser.txt │ │ ├── kotlin.txt │ │ ├── libsuperuser.txt │ │ └── usbmountr.txt │ ├── java │ └── streetwalrus │ │ └── usbmountr │ │ ├── ActivityResultDispatcher.kt │ │ ├── FilePickerPreference.kt │ │ ├── HostPreferenceFragment.kt │ │ ├── ImageChooserActivity.kt │ │ ├── ImageFilesAdapter.kt │ │ ├── LicenseActivity.kt │ │ ├── MainActivity.kt │ │ ├── RequestCodes.kt │ │ └── UsbMountrApplication.kt │ └── res │ ├── layout │ ├── activity_image_chooser.xml │ ├── activity_licenses.xml │ ├── activity_main.xml │ ├── dialog_license.xml │ └── image_chooser_row.xml │ ├── menu │ ├── menu_image_chooser.xml │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-de │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-lt │ └── strings.xml │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── host_preferences.xml │ └── licenses.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.apk 2 | *.iml 3 | .gradle 4 | /local.properties 5 | /.idea/ 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Streetwalrus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | This is a fork of streetwalrus' USB Mountr application. 3 | I am planning to call it PhoneStick, but this project is still in its infancy. 4 | Below is the original README of USB Mountr. 5 | 6 | # USB Mountr 7 | A helper application to set the Mass Storage Device gadget up in Android kernels 8 | [Get it on F-Droid](https://f-droid.org/app/streetwalrus.usbmountr) 11 | ![Screenshot](/screenshot.png?raw=true) 12 | 13 | ## How it works 14 | Android kernels still include a USB MSD component in their device gadget nowadays, though it is mostly unused since 15 | Android started using MTP. Some OEM ROMs still use it to provide a drivers installation "disc", but it is otherwise 16 | useless. 17 | This application leverages the module in order to let you use your device as a standard USB thumbdrive for the purpose 18 | of, e.g., booting a distro ISO. 19 | 20 | ## Building 21 | Standard gradle build process. 22 | 23 | ## Contributions... 24 | ...are welcome, I'm looking for a better icon, and if you feel like implementing it before I do, a menu to create blank 25 | images. Feel free to translate the application to your own language as well. 26 | 27 | ## See also 28 | - @morfikov has written up [a tutorial](https://gist.github.com/morfikov/0bd574817143d0239c5a0e1259613b7d) on setting up 29 | your phone as a boot device for a LUKS setup 30 | 31 | ## Donations 32 | I've been asked for donation info, so here it is. 33 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SHUNWU2HDU7EY) 34 | BTC: 199wYd9jB9yfBdXsfCXXESjzDHLHoKpBdd 35 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | def getVersionName = { -> 6 | def stdout = new ByteArrayOutputStream() 7 | exec { 8 | commandLine 'git', 'describe', '--tags' 9 | standardOutput = stdout 10 | } 11 | return stdout.toString().trim() 12 | } 13 | 14 | android { 15 | compileSdkVersion 28 16 | buildToolsVersion "28.0.3" 17 | defaultConfig { 18 | applicationId "streetwalrus.usbmountr" 19 | minSdkVersion 19 20 | targetSdkVersion 28 21 | versionCode 8 22 | versionName getVersionName() 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | lintOptions { 34 | checkReleaseBuilds false 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation fileTree(include: ['*.jar'], dir: 'libs') 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 41 | implementation 'eu.chainfire:libsuperuser:1.0.0.+' 42 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 43 | } 44 | repositories { 45 | mavenCentral() 46 | } 47 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/streetwalrus/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/assets/licenses/afilechooser.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2007-2008 OpenIntents.org 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /app/src/main/assets/licenses/kotlin.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010-2016 JetBrains s.r.o. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /app/src/main/assets/licenses/libsuperuser.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012-2015 Jorrit "Chainfire" Jongma 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /app/src/main/assets/licenses/usbmountr.txt: -------------------------------------------------------------------------------- 1 | ../../../../../LICENSE -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/ActivityResultDispatcher.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.content.Intent 4 | import android.util.Log 5 | 6 | class ActivityResultDispatcher { 7 | private val TAG = "ActivityResDispatcher" 8 | 9 | private val mHandlers: MutableMap = mutableMapOf() 10 | private var mCurId = 0 11 | 12 | interface ActivityResultHandler { 13 | fun onActivityResult(resultCode: Int, resultData: Intent?) 14 | } 15 | 16 | fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { 17 | if (mHandlers.containsKey(requestCode)) { 18 | mHandlers[requestCode]?.onActivityResult(resultCode, resultData) 19 | } else { 20 | Log.w(TAG, "No handler for request ID $requestCode!") 21 | } 22 | } 23 | 24 | fun registerHandler(handler: ActivityResultHandler): Int { 25 | mHandlers[mCurId] = handler 26 | return mCurId++ 27 | } 28 | 29 | fun removeHandler(id: Int) { 30 | mHandlers.remove(id) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/FilePickerPreference.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.preference.Preference 7 | import android.util.AttributeSet 8 | import android.util.Log 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import android.widget.Toast 12 | import java.io.File 13 | 14 | class FilePickerPreference : Preference, ActivityResultDispatcher.ActivityResultHandler { 15 | val TAG = "FilePickerPreference" 16 | 17 | constructor(context: Context) : super(context) 18 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) 19 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) 20 | : super(context, attrs, defStyleAttr) 21 | 22 | val appContext = context.applicationContext as UsbMountrApplication 23 | private val mActivityResultId = appContext.mActivityResultDispatcher.registerHandler(this) 24 | 25 | override fun onCreateView(parent: ViewGroup?): View { 26 | updateSummary() 27 | return super.onCreateView(parent) 28 | } 29 | override fun onPrepareForRemoval() { 30 | super.onPrepareForRemoval() 31 | 32 | val appContext = context.applicationContext as UsbMountrApplication 33 | appContext.mActivityResultDispatcher.removeHandler(mActivityResultId) 34 | } 35 | 36 | override fun onClick() { 37 | val intent = Intent(context.applicationContext, ImageChooserActivity::class.java) 38 | 39 | val activity = context as Activity 40 | activity.startActivityForResult(intent, mActivityResultId) 41 | } 42 | 43 | override fun onActivityResult(resultCode: Int, resultData: Intent?) { 44 | if (resultCode == Activity.RESULT_OK && resultData != null) { 45 | val path = resultData.getStringExtra("path")!! 46 | Log.d(TAG, "Picked file $path") 47 | persistString(path) 48 | updateSummary() 49 | } 50 | } 51 | 52 | private fun updateSummary() { 53 | val value = getPersistedString("") 54 | if (value.equals("")) { 55 | summary = context.getString(R.string.file_picker_nofile) 56 | } else { 57 | summary = File(value).name 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/HostPreferenceFragment.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.os.Bundle 4 | import android.preference.PreferenceFragment 5 | 6 | class HostPreferenceFragment : PreferenceFragment() { 7 | private val TAG = "HostPreferenceFragment" 8 | 9 | val SOURCE_KEY = "host_source_file" 10 | val RO_KEY = "host_ro" 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | addPreferencesFromResource(R.xml.host_preferences) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/ImageChooserActivity.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.os.Bundle 4 | import android.app.Activity 5 | import android.app.ProgressDialog 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.net.Uri 9 | import android.os.AsyncTask 10 | import android.provider.OpenableColumns 11 | import android.util.Log 12 | import android.view.Menu 13 | import android.view.MenuItem 14 | import androidx.recyclerview.widget.DefaultItemAnimator 15 | import androidx.recyclerview.widget.DividerItemDecoration 16 | import androidx.recyclerview.widget.LinearLayoutManager 17 | import androidx.recyclerview.widget.RecyclerView 18 | import java.io.File 19 | import java.io.FileInputStream 20 | 21 | class ImageChooserActivity : Activity() { 22 | @Suppress("unused") 23 | private val TAG = "ImageChooserActivity" 24 | 25 | private var directory = File("/") 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | directory = filesDir 29 | setContentView(R.layout.activity_image_chooser) 30 | actionBar?.setDisplayHomeAsUpEnabled(true) 31 | 32 | val recyclerView = findViewById(R.id.recycler_view) 33 | recyclerView.layoutManager = LinearLayoutManager(applicationContext) 34 | recyclerView.itemAnimator = DefaultItemAnimator() 35 | recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL)) 36 | 37 | val adapter = ImageFilesAdapter(directory, this) 38 | recyclerView.adapter = adapter 39 | } 40 | 41 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 42 | menuInflater.inflate(R.menu.menu_image_chooser, menu) 43 | return super.onCreateOptionsMenu(menu) 44 | } 45 | 46 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 47 | when(item.itemId) { 48 | R.id.image_chooser_add -> { 49 | val intent = Intent(Intent.ACTION_GET_CONTENT) 50 | intent.addCategory(Intent.CATEGORY_OPENABLE) 51 | intent.type = "*/*" 52 | intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true) 53 | 54 | startActivityForResult(intent, -1) 55 | 56 | return true 57 | } 58 | else -> { 59 | return super.onOptionsItemSelected(item) 60 | } 61 | } 62 | } 63 | 64 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { 65 | if(resultCode == RESULT_CANCELED) return 66 | if(data.data == null) return 67 | CopyInTask(this, directory).execute(data.data) 68 | Log.d(TAG, "onActivityResult") 69 | } 70 | 71 | private class CopyInTask(context: Context, private val directory: File): AsyncTask, Unit>() { 72 | var totalSize = 0L 73 | private val progressDialog = ProgressDialog(context) 74 | private val contentResolver = context.contentResolver 75 | val TAG = "CopyInTask" 76 | override fun doInBackground(vararg params: Uri) { 77 | val fds = params.map { Pair(it, contentResolver.openFileDescriptor(it, "r")!!) } 78 | fds.last {(_, fileDescriptor) -> 79 | val statSize = fileDescriptor.statSize 80 | if(statSize == -1L) { 81 | totalSize = -1L 82 | false 83 | } else { 84 | totalSize += statSize 85 | true 86 | } 87 | } 88 | var transfered = 0L 89 | var counter = 0 90 | fds.forEach {(uri, fileDescriptor) -> 91 | var filename: String = 92 | contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)!!.use { 93 | it.moveToFirst() 94 | it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) 95 | } 96 | filename = filename.replace('/', '_') 97 | if(File(directory, filename).exists()) { 98 | var i = 1 99 | val index = filename.findLastAnyOf(arrayListOf("."))?.first ?: filename.length 100 | val basename = filename.substring(0 until index) 101 | val extension = filename.substring(index until filename.length) 102 | do { 103 | filename = "%s.%d%s".format(basename, i, extension) 104 | i++ 105 | } while (File(directory, filename).exists()) 106 | } 107 | 108 | FileInputStream(fileDescriptor.fileDescriptor).use { fileInputStream -> 109 | File(directory, filename).outputStream().use { fileOutputStream -> 110 | val buffer = ByteArray(1 shl 14) 111 | while (true){ 112 | if(isCancelled) return@forEach 113 | val readBytes = fileInputStream.read(buffer) 114 | if(readBytes <= 0) break 115 | fileOutputStream.write(buffer, 0, readBytes) 116 | transfered += readBytes 117 | counter++ 118 | if (counter % 10 == 0) publishProgress(Pair((transfered shr 10).toInt(), (totalSize shr 10).toInt())) 119 | } 120 | } 121 | } 122 | 123 | } 124 | } 125 | 126 | override fun onPreExecute() { 127 | //progressDialog.isIndeterminate = false 128 | progressDialog.setTitle(R.string.image_chooser_copying_dialog) 129 | progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) 130 | progressDialog.show() 131 | } 132 | 133 | override fun onProgressUpdate(vararg values: Pair) { 134 | val (progress, max) = values[0] 135 | progressDialog.progress = progress 136 | progressDialog.max = max 137 | progressDialog.isIndeterminate = max == -1 138 | } 139 | 140 | override fun onPostExecute(result: Unit?) { 141 | progressDialog.dismiss() 142 | } 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/ImageFilesAdapter.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.view.LayoutInflater 7 | import androidx.recyclerview.widget.RecyclerView 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.TextView 11 | import android.widget.Toast 12 | import java.io.File 13 | 14 | class ImageFilesAdapter(directory: File, val activity: Activity) : RecyclerView.Adapter() { 15 | private var fileList = directory.listFiles()!! 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageFilesViewHolder { 18 | val view = LayoutInflater.from(parent.context).inflate(R.layout.image_chooser_row, parent, false) 19 | return ImageFilesViewHolder(view) 20 | } 21 | 22 | override fun getItemCount(): Int { 23 | return fileList.size 24 | } 25 | 26 | override fun onBindViewHolder(holder: ImageFilesViewHolder, position: Int) { 27 | val file = fileList[position] 28 | val size = if(file.isDirectory) 29 | 0.0 30 | else 31 | file.length().toDouble() / (1 shl 20) 32 | holder.filename.text = file.name 33 | holder.fileSize.text = holder.fileSize.context.getString(R.string.image_chooser_filesize_mib, size) 34 | if(file.isFile) { 35 | holder.view.setOnClickListener { 36 | val result = Intent() 37 | result.putExtra("path", file.path) 38 | activity.setResult(Activity.RESULT_OK, result) 39 | activity.finish() 40 | } 41 | } 42 | } 43 | 44 | class ImageFilesViewHolder(val view: View) : RecyclerView.ViewHolder(view) { 45 | val filename = view.findViewById(R.id.filename)!! 46 | val fileSize = view.findViewById(R.id.file_size)!! 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/LicenseActivity.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.app.AlertDialog 4 | import android.app.ListActivity 5 | import android.content.Intent 6 | import android.content.res.XmlResourceParser 7 | import android.net.Uri 8 | import android.os.Bundle 9 | import android.preference.Preference 10 | import android.text.method.ScrollingMovementMethod 11 | import android.view.View 12 | import android.view.ViewGroup 13 | import android.widget.ArrayAdapter 14 | import android.widget.ImageView 15 | import android.widget.ListView 16 | import android.widget.TextView 17 | import java.io.InputStreamReader 18 | 19 | class LicenseActivity : ListActivity() { 20 | private val TAG = "LicenseActivity" 21 | 22 | private var prefLayout = -1 23 | 24 | private var mList: ListView? = null 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.activity_licenses) 29 | 30 | // HAX 31 | prefLayout = Preference(this).layoutResource 32 | 33 | val licenseList: MutableList = mutableListOf() 34 | val xrp = resources.getXml(R.xml.licenses) 35 | while (xrp.eventType != XmlResourceParser.END_DOCUMENT) { 36 | xrp.next() 37 | if (xrp.eventType == XmlResourceParser.START_TAG && xrp.name == "license") { 38 | licenseList.add(License(xrp)) 39 | } 40 | } 41 | 42 | mList = findViewById(android.R.id.list) 43 | val licensesAdapter = LicenseArrayAdapter(licenseList) 44 | mList!!.adapter = licensesAdapter 45 | } 46 | 47 | override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) { 48 | val licenseTextLayout = layoutInflater.inflate(R.layout.dialog_license, null, false) 49 | val licenseTextView = licenseTextLayout.findViewById(R.id.textView) 50 | val lic = (l.adapter as LicenseArrayAdapter).getItem(position) 51 | licenseTextView.text = InputStreamReader(assets.open("licenses/${lic.file}")).readText() 52 | licenseTextView.movementMethod = ScrollingMovementMethod() 53 | AlertDialog.Builder(this@LicenseActivity) 54 | .setTitle(lic.name) 55 | .setView(licenseTextLayout) 56 | .setPositiveButton(R.string.licenses_upstream, { dialog, which -> 57 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(lic.url)) 58 | startActivity(intent) 59 | }) 60 | .show() 61 | } 62 | 63 | private inner class License(xrp: XmlResourceParser) { 64 | val name: String = xrp.getAttributeValue(null, "name") 65 | val type: String? = xrp.getAttributeValue(null, "type") 66 | val file: String? = xrp.getAttributeValue(null, "file") 67 | val url: String? = xrp.getAttributeValue(null, "url") 68 | 69 | var view: View? = null 70 | fun getView(parent: ViewGroup): View? { 71 | if (view == null) { 72 | // MOAR HAX 73 | // Abuse the Preference layout whose ID was retrieved earlier to create our View 74 | view = layoutInflater.inflate(prefLayout, parent, false) 75 | if (android.os.Build.VERSION.SDK_INT >= 24) 76 | (view!!.findViewById(android.R.id.icon_frame)).visibility = View.GONE 77 | (view!!.findViewById(android.R.id.icon)).visibility = View.GONE 78 | (view!!.findViewById(android.R.id.title)).text = name 79 | val summary = view!!.findViewById(android.R.id.summary) 80 | if (type != null) 81 | summary.text = type 82 | else 83 | summary.visibility = View.GONE 84 | (view!!.findViewById(android.R.id.widget_frame)).visibility = View.GONE 85 | } 86 | return view 87 | } 88 | } 89 | 90 | private inner class LicenseArrayAdapter(licenses: List) 91 | : ArrayAdapter(this, 0, licenses) { 92 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? { 93 | return getItem(position).getView(parent) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.AsyncTask 6 | import android.os.Bundle 7 | import android.util.Log 8 | import android.view.Menu 9 | import android.view.MenuItem 10 | import android.view.View 11 | import android.widget.Toast 12 | import eu.chainfire.libsuperuser.Shell 13 | 14 | class MainActivity : Activity() { 15 | private val TAG = "MainActivity" 16 | 17 | private var mPrefs: HostPreferenceFragment? = null 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContentView(R.layout.activity_main) 22 | 23 | mPrefs = fragmentManager.findFragmentById(R.id.prefs) as HostPreferenceFragment 24 | } 25 | 26 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 27 | menuInflater.inflate(R.menu.menu_main, menu) 28 | return super.onCreateOptionsMenu(menu) 29 | } 30 | 31 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 32 | when (item.itemId) { 33 | R.id.menu_licenses -> { 34 | val intent = Intent(this, LicenseActivity::class.java) 35 | startActivity(intent) 36 | } 37 | else -> return super.onOptionsItemSelected(item) 38 | } 39 | 40 | return true 41 | } 42 | 43 | override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { 44 | val appContext = applicationContext as UsbMountrApplication 45 | appContext.onActivityResult(requestCode, resultCode, resultData) 46 | } 47 | 48 | @Suppress("unused") 49 | fun onServeClicked(@Suppress("UNUSED_PARAMETER") v: View) { 50 | // Escape the file name to avoid bugs in the shell 51 | // Could use some finer filters but who cares 52 | val file = "(.)".toRegex().replace( 53 | mPrefs!!.preferenceManager.sharedPreferences 54 | .getString(mPrefs!!.SOURCE_KEY, ""), 55 | "\\\\$1") 56 | 57 | val ro = if (mPrefs!!.preferenceManager.sharedPreferences 58 | .getBoolean(mPrefs!!.RO_KEY, true)) "1" else "0" 59 | 60 | UsbScript().execute(file, ro, "1") 61 | } 62 | 63 | @Suppress("unused") 64 | fun onDisableClicked(@Suppress("UNUSED_PARAMETER") v: View) { 65 | UsbScript().execute("", "1", "0") 66 | } 67 | 68 | inner class UsbScript : AsyncTask() { 69 | override fun doInBackground(vararg params: String): Int { 70 | val usb = "/sys/class/android_usb/android0" 71 | val file = params[0] 72 | val ro = params[1] 73 | val enable = params[2] 74 | 75 | if (!(Shell.SU.run(arrayOf( 76 | "echo 0 > $usb/enable", 77 | // Try to append if the function is not already enabled (by ourselves most likely) 78 | "grep mass_storage $usb/functions > /dev/null || sed -e 's/$/,mass_storage/' $usb/functions | cat > $usb/functions", 79 | // If empty, set ourselves as the only function 80 | "[[ -z $(cat $usb/functions) ]] && echo mass_storage > $usb/functions", 81 | // Disable the feature if told to 82 | "[[ 0 == $enable ]] && sed -e 's/mass_storage//' $usb/functions | cat > $usb/functions", 83 | "echo disk > $usb/f_mass_storage/luns", 84 | "echo USBMountr > $usb/f_mass_storage/inquiry_string", 85 | "echo 1 > $usb/enable", 86 | "[[ -f $usb/f_mass_storage/luns ]] && echo > $usb/f_mass_storage/lun0/file", 87 | "[[ -f $usb/f_mass_storage/luns ]] && echo $ro > $usb/f_mass_storage/lun0/ro", 88 | "[[ -f $usb/f_mass_storage/luns ]] && echo $file > $usb/f_mass_storage/lun0/file", 89 | // Older kernels only support a single lun, cope with it 90 | "[[ ! -f $usb/f_mass_storage/luns ]] && echo > $usb/f_mass_storage/lun/file", 91 | "[[ ! -f $usb/f_mass_storage/luns ]] && echo $ro > $usb/f_mass_storage/lun/ro", 92 | "[[ ! -f $usb/f_mass_storage/luns ]] && echo $file > $usb/f_mass_storage/lun/file", 93 | "echo success" 94 | ))?.isEmpty() ?: true)) { 95 | if (enable != "0") { 96 | return R.string.host_success 97 | } else { 98 | return R.string.host_disable_success 99 | } 100 | } else { 101 | return R.string.host_noroot 102 | } 103 | } 104 | 105 | override fun onPostExecute(result: Int) { 106 | Toast.makeText(applicationContext, getString(result), Toast.LENGTH_SHORT).show() 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/RequestCodes.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | const val REQUEST_CODE_OPEN_FILE = 1 4 | const val REQUEST_CODE_IMPORT_FILE = 2 5 | -------------------------------------------------------------------------------- /app/src/main/java/streetwalrus/usbmountr/UsbMountrApplication.kt: -------------------------------------------------------------------------------- 1 | package streetwalrus.usbmountr 2 | 3 | import android.app.Application 4 | import android.content.Intent 5 | 6 | class UsbMountrApplication : Application() { 7 | val mActivityResultDispatcher: ActivityResultDispatcher = ActivityResultDispatcher() 8 | 9 | fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { 10 | mActivityResultDispatcher.onActivityResult(requestCode, resultCode, resultData) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_image_chooser.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_licenses.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 22 | 23 | 27 | 28 |