2022 May 08.
2022 May 02.
注
ファイル読み込みパーミッション取得ルーチンを省いているので、端末のアプリ設定で権限許可する必要あり
端末
build.gradle(Module: app)
dependenciesはデフォルトのまま
plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' } android { compileSdk 32 defaultConfig { applicationId "net.sytes.rokkosan.mygeturifrompathstring" minSdk 30 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } } dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
AndroidManifest.xml
"READ_EXTERNAL_STORAGE"権限を許可する
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.sytes.rokkosan.mygeturifrompathstring"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MyGetUriFromPathString"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
strings.xml
<resources> <string name="app_name">MyGetUriFromPathString</string> <string name="display_app_name">Play Audio Randomly</string> <string name="button_open_filer2select_dir">Select Directory</string> <string name="button_play">Play</string> <string name="no_dirs">No DirectoriesFiler</string> <string name="dir_exists">Dir of uri exists.</string> <string name="dir_not_exists">Dir of uri does not exist.</string> <string name="not_find">Could not find</string> </resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textViewDir" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/textViewFoundFile" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textViewFoundFile" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/textViewDebugInfo" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewDir" /> <TextView android:id="@+id/textViewDebugInfo" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/buttonOpenFiler" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewFoundFile" /> <Button android:id="@+id/buttonOpenFiler" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/buttonPlay" app:layout_constraintTop_toBottomOf="@id/textViewDebugInfo" android:text="@string/button_open_filer2select_dir" /> <Button android:id="@+id/buttonPlay" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toRightOf="@id/buttonOpenFiler" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewDebugInfo" android:text="@string/button_play" /> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
package net.sytes.rokkosan.mygeturifrompathstring /* 2022 May 03. 2022 May 01. Ryuichi Hashimoto. */ import android.content.ContentUris import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.widget.Button import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import java.util.* import java.util.concurrent.TimeUnit class MainActivity : AppCompatActivity() { private lateinit var textViewDir: TextView private lateinit var textViewFoundFile: TextView private lateinit var textViewDebugInfo: TextView private var selectedDirUri: Uri? = null private var selectedDirUriReadable: String = "" private val fileLauncherGetDir = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> // 結果を受け取るルーチン if (result.resultCode == RESULT_OK) { // succeeded. // get uri. selectedDirUri = result.data?.data // uri冒頭の"content://com.android.externalstorage.documents/tree"を削除し、 // エンコードされた /(スラッシュ)を戻す。 // プライマリ・ストレージの時に付く"/primary/"をファイルシステムに合わせて // "/emulated/0/"に置き換える。 selectedDirUriReadable = selectedDirUri.toString().replace( "content://com.android.externalstorage.documents/tree/", "/" ). replace("%3A", "/" ).replace("%2F", "/"). replace(Regex("^/primary/"),"/emulated/0/") textViewDir.text = selectedDirUriReadable // uri冒頭の"content://com.android.externalstorage.documents/tree"は削除されている。 // プライマリ・ストレージの時は"/emulated/0/..."となる。 // SDカードの時は"/xxxx-xxxx/...."となる。 } else { // failed textViewDir.text = getString(R.string.no_dirs) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) textViewDir = findViewById<TextView>(R.id.textViewDir) textViewFoundFile = findViewById<TextView>(R.id.textViewFoundFile) textViewDebugInfo = findViewById<TextView>(R.id.textViewDebugInfo) // アクションバーに表示する文字列を設定 supportActionBar?.title = getString(R.string.display_app_name) findViewById<Button>(R.id.buttonOpenFiler).setOnClickListener { openFilePickerGetUri() } findViewById<Button>(R.id.buttonPlay).setOnClickListener { // collect media files val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Audio.Media.getContentUri( MediaStore.VOLUME_EXTERNAL ) } else { MediaStore.Audio.Media.EXTERNAL_CONTENT_URI } val projection = arrayOf( MediaStore.Audio.Media._ID, "_data" ) // 1分間以上のAudioを指定する val selection = "${MediaStore.Audio.Media.DURATION} >= ?" val selectionArgs = arrayOf( TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES).toString() ) val uris: MutableList<Uri> = mutableListOf<Uri>() val resolver = applicationContext.contentResolver val query = resolver.query( collection, //uriの種類 projection, //取得する項目 nullは全部 selection, //フィルター条件 nullはフィルタリング無し selectionArgs, //フィルター用のパラメータ null //並べ替え。nullは並べ替えしない ) query?.use { cursor -> val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID) val pathColumn = cursor.getColumnIndexOrThrow("_data") while (cursor.moveToNext()) { val id = cursor.getLong(idColumn) val queriedUri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id ) val pathOfUri = cursor.getString(pathColumn) // pathOfUriの文字列を調べてディレクトリ内のメディアファイルを選択する // プライマリ・ストレージのファイルパスは"/storage/emulated/0/..."となっている // SDカードのファイルパスは"/storage/xxxx-xxxx/..."となっている val keyStr: String = selectedDirUriReadable if (pathOfUri.contains(keyStr)) { uris.add(queriedUri) } } } query?.close() if (uris.size > 0) { val playUri = uris[getRandomNum(uris.size)] // play audio val audioIntent = Intent() audioIntent.action = Intent.ACTION_VIEW audioIntent.setDataAndType(playUri, "audio/*") startActivity(audioIntent) // get filepath of playUri val proj = arrayOf("_data") val cursor = this.getContentResolver(). query(playUri, proj, null, null, null) if (cursor == null) { textViewFoundFile.text = getString(R.string.not_find) } else { val pathColumn = cursor.getColumnIndexOrThrow("_data") cursor.moveToFirst() textViewFoundFile.text = cursor.getString(pathColumn).replace( Regex("^/storage/"), "/" ) cursor.close() } } } } // 0以上、maxNum未満の範囲でランダムな数を1つ返す private fun getRandomNum(maxNum: Int): Int { val random = Random() return random.nextInt(maxNum) } fun openFilePickerGetUri() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION ) } fileLauncherGetDir.launch(intent) } }