「正規表現」による(含有)検索条件と除外条件で曲名、アーティスト名、アルバム名、ファイルのパスを検索できるアンドロイド スマホのミュージック プレイヤー アプリ(プレイリスト機能無し)

 Githubで文字パターン「正規表現」で音楽ファイルを検索できるミュージック プレイヤー アプリの.apkファイルなどをパブリック ドメインで公開しております。

 マイクロソフトのBing検索エンジンで「github android-free-searchable-music-player」などで検索してみてください。

 残念ながらグーグル検索エンジンでは検索できません。


 (音楽を含む)音声ファイルを文字パターン「正規表現」による(含有)検索条件と除外条件で曲名、アーティスト名、アルバム名、ファイルのパスを検索できるアンドロイド スマホのミュージック プレイヤー アプリ(プレイリスト機能無し)を自作しました。


 検索条件、除外条件、曲名、アーティスト名、アルバム名、ファイルのパスの全角英大文字(Aなど)、全角英小文字(aなど)、半角英大文字(Aなど)を半角英小文字(aなど)に置換してから文字を比較しています。


 「正規表現」とは、プログラミング言語などで少しの違いが有りますが、大体、共通している、文字パターンの書き方です。

 例えば、「正規表現」で「revolution|abingdon|西川」は「『revolution』または『abingdon』または『西川』」を意味します。「正規表現」で半角縦線(|)は「または」を意味します。

 また、例えば、「正規表現」で「fall in love.+live」には「fall in love live ver」や「fall in love(live ver)」などが該当する物として検索に引っかかります。「正規表現」で「.+」は「1文字以上の任意の文字」を意味し、「.*」は「0文字以上の任意の文字」を意味し、「\.」は半角ドット自体である「.」を意味し、「\(」は半角括弧自体である「(」を意味します。



 前作のアンドロイド スマホのミュージック プレイヤー アプリ(プレイリスト機能無し)に、検索画面を追加し、一覧画面に対応するアクティビティを追加し、全体を微調整しました。



 2023年9月29日時点で、無駄な処理が有るかもしれませんし、通知の処理に自信が無いですし、権限の許可の確認などのコードを全ては網羅できていませんが、下記に書き残しておきます。


 AQUOS sense3、Pixel 7a、FireHD8第12世代2022年で動作を確認できました。


 アマゾンのタブレットのFireHD8Plus(第10世代)は約3年間使用していたためか、ソフトウェア的な不具合かハードウェア的な不具合で電源ボタンを押しても反応しない事が有ったため、工場出荷時の状態へ端末をリセットして正常動作するように成りましたが、売却しました。


 著者はアンドロイド11のAQUOS sense3しか持っていないため、アンドロイド13以上の場合の、権限の許可の確認などのコードを試す事ができません。

 また、権限の許可のダイアログが表示されませんでしたが、アンドロイド11のせいなのか、著者のプログラムのバグなのか、わかりません。


 広告、無いです。

 アプリ内課金、無いです。

 Bluetoothイヤホンで聴く事ができますし、一時停止や次の曲への移動や音量の増減などの操作もできます。

 電話が、かかってきた時に、当アプリを一時停止して、通話が終了したら、当アプリの音楽の再生を再開します。

 現在、再生している分と秒を表示します。

 バック グラウンド再生できます。

 有線イヤホンが抜けた時に音楽の再生を一時停止します。

 Bluetoothイヤホンで、31分以上バック グラウンド再生しましたが、問題無かったです。


 有線イヤホンのバック グラウンド再生は、スマホを再起動した直後は、正常に動作しますが、しばらくスマホを使用していると、スリープされてしまいました。もしかしたら、バック グラウンド処理の優先度の設定が必要なのかもしれませんが、現時点では不明です。



 ※下記のXMLファイルやKotlinのプログラムなどのコードをコピペする場合は、2文字の全角空白を4文字の半角空白に置換してください。


 また、Android StudioにJavaやKotlinなどのプログラムのコードをコピペして、「import android.R」が自動で追加されてしまったら、削除してください。

 「android.R」は、「R.layout.activity_main」や「R.id.◯◯◯」の「R」とは違います。

 そのため、「import android.R」が有ると、コンパイル エラーが発生してしまいます。


 Android StudioにJavaやKotlinなどのプログラムのコードをコピペすると、変数の名前が半角バッククォート記号(`)で囲まれる事が有ります。

 Kotlinでは変数の名前を半角バッククォート記号(`)で囲むと予約語(inやnullなど)や半角空白記号( )などを変数の名前にできるそうです。

 可能であれば、半角バッククォート記号(`)で囲まれた変数の名前は、半角バッククォート記号(`)で囲まずに済む名前に変更したほうが良いのでは、と個人的に思っております。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/build.gradle

――――――――――――――――――――

plugins {

  id 'com.android.application'

  id 'org.jetbrains.kotlin.android'

}


android {

  namespace 'eliphas1810.freesearchablemusicplayer'

  compileSdk 33


  defaultConfig {

    applicationId "eliphas1810.freesearchablemusicplayer"

    minSdk 24

    targetSdk 33

    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'


  implementation "androidx.media:media:1.6.0"


  testImplementation 'junit:junit:4.13.2'

  androidTestImplementation 'androidx.test.ext:junit:1.1.3'

  androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。



 ※build.gradleを変更後にAndroid Studioで「Sync now」をクリックしないと「androidx.media」パッケージが認識されません。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/AndroidManifest.xml

――――――――――――――――――――

<?xml version="1.0" encoding="utf-8"?>


<manifest

  xmlns:android="http://schemas.android.com/apk/res/android"

  xmlns:tools="http://schemas.android.com/tools"

>


  <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />


  <uses-permission android:name="android.permission.WAKE_LOCK" />


  <application

    android:allowBackup="true"

    android:dataExtractionRules="@xml/data_extraction_rules"

    android:fullBackupContent="@xml/backup_rules"

    android:icon="@mipmap/ic_launcher"

    android:label="@string/app_name"

    android:supportsRtl="true"

    android:theme="@style/Theme.FreeSearchableMusicPlayer"

    tools:targetApi="31"

  >


    <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>


    <activity

      android:name=".MusicList"

      android:parentActivityName=".MainActivity"

    >

      <meta-data

        android:name="android.support.PARENT_ACTIVITY"

        android:value=".MainActivity"

      />

    </activity>


    <activity

      android:name=".MusicDetail"

      android:parentActivityName=".MusicList"

    >

      <meta-data

        android:name="android.support.PARENT_ACTIVITY"

        android:value=".MusicList"

      />

    </activity>


    <service

      android:name=".MusicPlayerService"

      android:enabled="true"

      android:exported="true"

    >

      <intent-filter>

        <action android:name="android.media.browse.MediaBrowserService" />

      </intent-filter>

    </service>


  </application>

</manifest>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/res/values/strings.xml

――――――――――――――――――――

<resources>


  <string name="app_name">FreeSearchableMusicPlayer</string>


  <string name="minutes_unit_label">minutes</string>

  <string name="seconds_and_milli_seconds_separator">.</string>

  <string name="seconds_unit_label">seconds</string>


  <string name="main_inclusion_pattern_label">Inclusion Pattern "Regular Expression"</string>

  <string name="main_inclusion_pattern_init"></string>

  <string name="main_exclusion_pattern_label">Exclusion Pattern "Regular Expression"</string>

  <string name="main_exclusion_pattern_init"></string>

  <string name="main_search">Search</string>


  <string name="music_list_close">Close</string>


  <string name="music_detail_close">Close</string>

  <string name="music_detail_start">Play</string>

  <string name="music_detail_pause">Pause</string>

  <string name="music_detail_stop">Stop</string>

  <string name="music_detail_previous">Previous</string>

  <string name="music_detail_next">Next</string>

  <string name="music_detail_loop">Single Loop Mode</string>

  <string name="music_detail_random">Random Mode</string>


  <string name="no_music_file_list">Applicable music files are not found.</string>

  <string name="ok">OK</string>

  <string name="denied_read_media_audio">Now this application does not have the permission to read audio media files( including music files) by others applications. So this applicatiopn can not read music files and show music files. Please finish this application.</string>

  <string name="denied_read_external_storage">Now this application does not have the permission to read files( including music files) by others applications. So this applicatiopn can not read music files and show music files. Please finish this application.</string>


  <string name="main_inclusion_pattern_wrong">The inclusion is wrong as "regular expression".</string>

  <string name="main_exclusion_pattern_wrong">The exclusion is wrong as "regular expression".</string>


  <string name="notification_content_title">Notification title</string>

  <string name="notification_content_text">Notification text</string>

  <string name="notification_ticker">Notification ticker</string>


</resources>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/res/values-ja/strings.xml

――――――――――――――――――――

<resources>


  <string name="app_name">FreeSearchableMusicPlayer</string>


  <string name="minutes_unit_label">分</string>

  <string name="seconds_and_milli_seconds_separator">.</string>

  <string name="seconds_unit_label">秒</string>


  <string name="main_inclusion_pattern_label">検索条件の文字パターン「正規表現」</string>

  <string name="main_inclusion_pattern_init"></string>

  <string name="main_exclusion_pattern_label">除外条件の文字パターン「正規表現」</string>

  <string name="main_exclusion_pattern_init"></string>

  <string name="main_search">検索</string>


  <string name="music_list_close">閉じる</string>


  <string name="music_detail_close">閉じる</string>

  <string name="music_detail_start">再生</string>

  <string name="music_detail_pause">一時停止</string>

  <string name="music_detail_stop">停止</string>

  <string name="music_detail_previous">前の曲へ</string>

  <string name="music_detail_next">次の曲へ</string>

  <string name="music_detail_loop">1曲だけループ モード</string>

  <string name="music_detail_random">ランダム モード</string>


  <string name="no_music_file_list">条件に該当する音楽ファイルが見つかりませんでした。</string>

  <string name="ok">わかりました</string>

  <string name="denied_read_media_audio">当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無いので、当アプリは音楽ファイルを表示して再生できません。当アプリを終了してください。</string>

  <string name="denied_read_external_storage">音楽ファイルを含む当アプリ以外によるファイルを読み取る権限の許可が無いので、当アプリは音楽ファイルを表示して再生できません。当アプリを終了してください。</string>


  <string name="main_inclusion_pattern_wrong">検索条件が「正規表現」として間違っています。</string>

  <string name="main_exclusion_pattern_wrong">除外条件が「正規表現」として間違っています。</string>


  <string name="notification_content_title">通知の見出し</string>

  <string name="notification_content_text">通知文</string>

  <string name="notification_ticker">通知ティッカー</string>


</resources>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/res/values/colors.xml

――――――――――――――――――――

<?xml version="1.0" encoding="utf-8"?>


<resources>


  <color name="purple_200">#FFBB86FC</color>

  <color name="purple_500">#FF6200EE</color>

  <color name="purple_700">#FF3700B3</color>

  <color name="teal_200">#FF03DAC5</color>

  <color name="teal_700">#FF018786</color>

  <color name="black">#FF000000</color>

  <color name="white">#FFFFFFFF</color>


  <color name="gray">#FF808080</color><!-- #AARRGGBB。アルファ値のFF = 255は不透明。 -->

  <color name="silver">#FFC0C0C0</color>

  <color name="lime">#FF00FF00</color>


</resources>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/res/layout/activity_main.xml

――――――――――――――――――――

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout

  xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:orientation="vertical"

>


  <TextView

    android:id="@+id/mainInclusionPatternLabel"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/main_inclusion_pattern_label"

    android:layout_gravity="center"

  />


  <EditText

    android:id="@+id/mainInclusionPattern"

    android:layout_width="match_parent"

    android:layout_height="50dp"

    android:ems="10"

    android:inputType="text"

    android:text="@string/main_inclusion_pattern_init"

  />


  <TextView

    android:id="@+id/mainExclusionPatternLabel"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/main_exclusion_pattern_label"

    android:layout_gravity="center"

  />


  <EditText

    android:id="@+id/mainExclusionPattern"

    android:layout_width="match_parent"

    android:layout_height="50dp"

    android:ems="10"

    android:inputType="text"

    android:text="@string/main_exclusion_pattern_init"

  />


  <Button

    android:id="@+id/mainSearch"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/main_search"

    android:layout_gravity="center"

  />


</LinearLayout>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。



 ※レイアウトXMLファイルのEditTextタグのandroid:text属性によるテキストボックスの初期表示値が結果的にゼロ文字に成る場合、Android Studioは赤文字で警告を表示しますが、無視しても問題は無かったです。


 ※レイアウトXMLファイルのEditTextタグのandroid:layout_height属性にwrap_contentを指定すると、テキストボックスの表示に問題が有りましたし、Android Studioは48dp以上を指定するように赤文字で警告を表示するので、50dpなどと指定します。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/res/layout/music_list.xml

――――――――――――――――――――

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout

  xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:orientation="vertical"

>


  <Button

    android:id="@+id/musicListClose"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_list_close"

    android:layout_gravity="center"

  />


  <ListView

    android:id="@+id/musicList"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

  />


</LinearLayout>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/res/layout/music_list_row.xml

――――――――――――――――――――

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout

  xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:orientation="vertical"

>


  <TextView

    android:id="@+id/musicListMusicTitle"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicListArtistName"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicListAlbumTitle"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicListMusicFilePath"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicListMusicDuration"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


</LinearLayout>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/res/layout/music_detail.xml

――――――――――――――――――――

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout

  xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:orientation="vertical"

>


  <Button

    android:id="@+id/musicDetailClose"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_close"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicDetailMusicTitle"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicDetailArtistName"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicDetailAlbumTitle"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicDetailMusicFilePath"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <TextView

    android:id="@+id/musicDetailMusicDuration"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <SeekBar

    android:id="@+id/musicDetailMusicCurrentDurationSeekBar"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

  />


  <TextView

    android:id="@+id/musicDetailMusicCurrentDuration"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_gravity="center"

  />


  <Button

    android:id="@+id/musicDetailStart"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_start"

    android:layout_gravity="center"

  />


  <Button

    android:id="@+id/musicDetailPause"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_pause"

    android:layout_gravity="center"

  />


  <Button

    android:id="@+id/musicDetailStop"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_stop"

    android:layout_gravity="center"

  />


  <Button

    android:id="@+id/musicDetailPrevious"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_previous"

    android:layout_gravity="center"

  />


  <Button

    android:id="@+id/musicDetailNext"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_next"

    android:layout_gravity="center"

  />


  <Button

    android:id="@+id/musicDetailLoop"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_loop"

    android:layout_gravity="center"

  />


  <Button

    android:id="@+id/musicDetailRandom"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="@string/music_detail_random"

    android:layout_gravity="center"

  />


</LinearLayout>

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/java/eliphas1810/freesearchablemusicplayer/MainActivity.kt

――――――――――――――――――――

package eliphas1810.freesearchablemusicplayer


import android.Manifest

import android.app.AlertDialog

import android.content.Intent

import android.content.pm.PackageManager

import android.os.Build

import android.os.Bundle

import android.widget.Button

import android.widget.EditText

import android.widget.Toast

import androidx.appcompat.app.AppCompatActivity


//Public Domain

//

//メイン画面と1対1対応のアクティビティ

class MainActivity : AppCompatActivity() {



  companion object {


    //メイン画面のアクティビティから、音楽ファイル一覧画面のアクティビティへ送信する情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val INCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.INCLUSION_PATTERN"

    const val EXCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.EXCLUSION_PATTERN"


    //アンドロイド アプリ開発者が管理する場合の、権限の許可のリクエストコードは、アンドロイド アプリ開発者の責任で重複させない事

    private const val READ_MEDIA_AUDIO_REQUEST_CODE = 1

    private const val READ_EXTERNAL_STORAGE_REQUEST_CODE = 2

  }



  //権限の許可の要求の結果が出た時に呼ばれます。

  override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {

    try {

      super.onRequestPermissionsResult(requestCode, permissions, grantResults)


      //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可の有無が選択された場合

      if (requestCode == READ_MEDIA_AUDIO_REQUEST_CODE) {


        //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可された場合

        if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {


          return


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可されなかった場合

        } else {


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了

          AlertDialog.Builder(this)

            .setMessage(getString(R.string.denied_read_media_audio))

            .setPositiveButton(getString(R.string.ok)) { _, _ ->

            }

            .create()

            .show()


          return

        }

      }


      //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可の有無が選択された場合

      if (requestCode == READ_EXTERNAL_STORAGE_REQUEST_CODE) {


        //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可された場合

        if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {


          return


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限を許可されなかった場合

        } else {


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了

          AlertDialog.Builder(this)

            .setMessage(getString(R.string.denied_read_external_storage))

            .setPositiveButton(getString(R.string.ok)) { _, _ ->

            }

            .create()

            .show()


          return

        }

      }


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //メモリー上に作成される時にのみ呼ばれます。

  override fun onCreate(savedInstanceState: Bundle?) {

    try {

      super.onCreate(savedInstanceState)

      setContentView(R.layout.activity_main)



      //検索ボタンが押された時の処理

      findViewById<Button>(R.id.mainSearch).setOnClickListener { view ->

        try {


          var inclusionPattern: String = findViewById<EditText>(R.id.mainInclusionPattern)?.text?.toString() ?: "";

          if (inclusionPattern != "") {

            //検索条件の「正規表現」が正しいか検査

            try {

              Regex(inclusionPattern)

            } catch (exception: Exception) {

              Toast.makeText(view.context.applicationContext, getString(R.string.main_inclusion_pattern_wrong) + exception.message, Toast.LENGTH_LONG).show()

              return@setOnClickListener

            }

          }


          var exclusionPattern: String = findViewById<EditText>(R.id.mainExclusionPattern)?.text?.toString() ?: "";

          if (exclusionPattern != "") {

            //除外条件の「正規表現」が正しいか検査

            try {

              Regex(exclusionPattern)

            } catch (exception: Exception) {

              Toast.makeText(view.context.applicationContext, getString(R.string.main_exclusion_pattern_wrong) + exception.message, Toast.LENGTH_LONG).show()

              return@setOnClickListener

            }

          }


          //音楽ファイルの一覧画面へ遷移

          val intent = Intent(this, MusicList::class.java)

          intent.putExtra(INCLUSION_PATTERN_KEY, inclusionPattern)

          intent.putExtra(EXCLUSION_PATTERN_KEY, exclusionPattern)

          startActivity(intent)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //権限の許可の確認


      //当アプリ以外によるファイルを読み取る権限の許可が無い場合

      if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {


        //アンドロイド ティラミス以上の場合

        //アンドロイド13以上の場合

        if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) {


          //アンドロイド13以降、Manifest.permission.READ_MEDIA_AUDIOが存在


          //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無い場合

          if (checkSelfPermission(Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_DENIED) {


            //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可ダイアログで「今後、表示しない」を未選択の場合

            if (shouldShowRequestPermissionRationale(Manifest.permission.READ_MEDIA_AUDIO)) {


              //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可ダイアログを表示して、onRequestPermissionsResultで選択結果を受け取る

              requestPermissions(arrayOf(Manifest.permission.READ_MEDIA_AUDIO), READ_MEDIA_AUDIO_REQUEST_CODE)


              //一旦、当処理は終了

              return


              //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可ダイアログで「今後、表示しない」を選択中の場合

            } else {


              //当アプリ以外による音楽を含む音声メディア ファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了


              AlertDialog.Builder(this)

                .setMessage(getString(R.string.denied_read_media_audio))

                .setPositiveButton(getString(R.string.ok)) { _, _ ->

                }

                .create()

                .show()


              return

            }

          }


          //アンドロイド12以下の場合

        } else {


          //当アプリ以外によるファイルを読み取る権限の許可ダイアログで「今後、表示しない」を未選択の場合

          if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {


            //当アプリ以外によるファイルを読み取る権限の許可ダイアログを表示して、onRequestPermissionsResultで選択結果を受け取る

            requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), READ_EXTERNAL_STORAGE_REQUEST_CODE)


            //一旦、当処理は終了

            return


            //当アプリ以外によるファイルを読み取る権限の許可ダイアログで「今後、表示しない」を選択中の場合

          } else {


            //当アプリ以外によるファイルを読み取る権限の許可が無いので、当アプリを実行できない事を説明して、処理を終了

            AlertDialog.Builder(this)

              .setMessage(getString(R.string.denied_read_external_storage))

              .setPositiveButton(getString(R.string.ok)) { _, _ ->

              }

              .create()

              .show()


            return

          }

        }

      }



    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。

 eliphas1810/freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。

 eliphas1810.freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/java/eliphas1810/freesearchablemusicplayer/MusicList.kt

――――――――――――――――――――

package eliphas1810.freesearchablemusicplayer


import android.content.Context

import android.content.Intent

import android.os.*

import android.os.Parcelable.Creator

import android.provider.MediaStore

import android.view.LayoutInflater

import android.view.View

import android.view.ViewGroup

import android.widget.*

import android.widget.AdapterView.OnItemClickListener

import androidx.appcompat.app.AppCompatActivity


//Public Domain


//音楽ファイルの情報をひとまとめにした物

//

//Parcelableはアクティビティとサービス間で送信し合う事ができる情報

//

data class MusicInfo(

  var id: Long?,

  var musicTitle: String?,

  var artistName: String?,

  var albumTitle: String?,

  var filePath: String?,

  var duration: Int? //音楽の所要時間

) : Parcelable {


  constructor(parcel: Parcel) : this(0, null, null, null, null, 0) {

    id = parcel.readLong()

    musicTitle = parcel.readString()

    artistName = parcel.readString()

    albumTitle = parcel.readString()

    filePath = parcel.readString()

    duration = parcel.readInt()

  }


  companion object {


    @field: JvmField

    val CREATOR: Creator<MusicInfo?> = object : Creator<MusicInfo?> {

      override fun createFromParcel(parcel: Parcel): MusicInfo? {

        return MusicInfo(parcel)

      }


      override fun newArray(size: Int): Array<MusicInfo?> {

        return arrayOfNulls<MusicInfo>(size)

      }

    }

  }


  override fun writeToParcel(parcel: Parcel, flags: Int) { //コンストラクタでParcelから取得する順番と同じ順番でParcelに書き込む必要が有ります。

    parcel.writeLong(id ?: 0) //idがnullの場合はゼロ

    parcel.writeString(musicTitle ?: "null") //曲名がnullの場合は「null」という文字

    parcel.writeString(artistName ?: "null")

    parcel.writeString(albumTitle ?: "null")

    parcel.writeString(filePath ?: "null")

    parcel.writeInt(duration ?: 0)

  }


  //Parcel.describeContents()は普通はゼロを返すように実装

  override fun describeContents(): Int {

    return 0

  }

}



//音楽ファイルの一覧画面で、Listの各件の内容をListViewの各行に設定する物

private class Adapter(context: Context, list: List<MusicInfo>) : ArrayAdapter<MusicInfo>(context, R.layout.music_list_row, list) {


  //例えば、「1分02.003秒」という形式に音楽の総再生時間を編集

  fun convertMusicDurationToText(musicDuration: Int) : String {


    val minutes = musicDuration / (1000 * 60)

    val seconds = (musicDuration % (1000 * 60)) / 1000

    val milliSeconds = musicDuration % 1000


    var durationText = ""

    durationText = durationText + minutes

    durationText = durationText + context.getString(R.string.minutes_unit_label)

    durationText = durationText + seconds

    durationText = durationText + context.getString(R.string.seconds_and_milli_seconds_separator)

    durationText = durationText + "%03d".format(milliSeconds)

    durationText = durationText + context.getString(R.string.seconds_unit_label)

    return durationText

  }


  //音楽ファイルの一覧画面で、Listの各件の内容をListViewの各行に設定

  override fun getView(position: Int, view: View?, viewGroup: ViewGroup): View {


    var view: View? = view


    try {


      if (view == null) {

        view = LayoutInflater.from(context).inflate(R.layout.music_list_row, viewGroup, false)

      }


      val musicInfo = getItem(position)


      view?.findViewById<TextView>(R.id.musicListMusicTitle)?.text = musicInfo?.musicTitle

      view?.findViewById<TextView>(R.id.musicListArtistName)?.text = musicInfo?.artistName

      view?.findViewById<TextView>(R.id.musicListAlbumTitle)?.text = musicInfo?.albumTitle

      view?.findViewById<TextView>(R.id.musicListMusicFilePath)?.text = musicInfo?.filePath

      view?.findViewById<TextView>(R.id.musicListMusicDuration)?.text = convertMusicDurationToText(musicInfo?.duration ?: 0)


    } catch (exception: Exception) {

      Toast.makeText(view?.context?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }


    return view!!

  }

}



//音楽ファイル一覧画面と1対1対応のアクティビティ

class MusicList : AppCompatActivity() {


  companion object {


    //メイン画面のアクティビティから、音楽ファイル一覧画面のアクティビティへ送信する情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val INCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.INCLUSION_PATTERN"

    const val EXCLUSION_PATTERN_KEY = "eliphas1810.freesearchablemusicplayer.EXCLUSION_PATTERN"



    //音楽ファイル一覧画面のアクティビティから、音楽ファイル詳細画面のアクティビティへ送信する情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val MUSIC_INFO_LIST_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_LIST"

    const val MUSIC_INFO_INDEX_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_INDEX"

  }



  private var musicInfoList: MutableList<MusicInfo>? = null



  //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換

  private fun halfWidthLowerCase(string : String) : String {


    if (string == null) {

      return ""

    }


    return string

      .replace("a", "a")

      .replace("b", "b")

      .replace("c", "c")

      .replace("d", "d")

      .replace("e", "e")

      .replace("f", "f")

      .replace("g", "g")

      .replace("h", "h")

      .replace("i", "i")

      .replace("j", "j")

      .replace("k", "k")

      .replace("l", "l")

      .replace("m", "m")

      .replace("n", "n")

      .replace("o", "o")

      .replace("p", "p")

      .replace("q", "q")

      .replace("r", "r")

      .replace("s", "s")

      .replace("t", "t")

      .replace("u", "u")

      .replace("v", "v")

      .replace("w", "w")

      .replace("x", "x")

      .replace("y", "y")

      .replace("z", "z")

      .replace("A", "A")

      .replace("B", "B")

      .replace("C", "C")

      .replace("D", "D")

      .replace("E", "E")

      .replace("F", "F")

      .replace("G", "G")

      .replace("H", "H")

      .replace("I", "I")

      .replace("J", "J")

      .replace("K", "K")

      .replace("L", "L")

      .replace("M", "M")

      .replace("N", "N")

      .replace("O", "O")

      .replace("P", "P")

      .replace("Q", "Q")

      .replace("R", "R")

      .replace("S", "S")

      .replace("T", "T")

      .replace("U", "U")

      .replace("V", "V")

      .replace("W", "W")

      .replace("X", "X")

      .replace("Y", "Y")

      .replace("Z", "Z")

      .lowercase()

  }



  //メモリー上に作成される時にのみ呼ばれます。

  override fun onCreate(savedInstanceState: Bundle?) {

    try {

      super.onCreate(savedInstanceState)

      setContentView(R.layout.music_list)



      //音楽ファイル一覧画面の閉じるボタンが押された時の処理

      findViewById<Button>(R.id.musicListClose).setOnClickListener { view ->

        try {


          //前の画面へ戻る

          finish()


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      if (musicInfoList != null && 1 <= (musicInfoList?.size ?: 0)) {

        musicInfoList?.clear()

      }


      musicInfoList = mutableListOf<MusicInfo>()



      //前の画面から、検索条件の文字パターン「正規表現」を取得

      var inclusionPattern = getIntent()?.getStringExtra(INCLUSION_PATTERN_KEY) ?: ""


      inclusionPattern = halfWidthLowerCase(inclusionPattern) //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換


      var inclusionRegex : Regex? = null

      if (inclusionPattern != "") {

        inclusionRegex = Regex(inclusionPattern)

      }



      //前の画面から、除外条件の文字パターン「正規表現」を取得

      var exclusionPattern = getIntent()?.getStringExtra(EXCLUSION_PATTERN_KEY) ?: ""


      exclusionPattern = halfWidthLowerCase(exclusionPattern) //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換


      var exclusionRegex : Regex? = null

      if (exclusionPattern != "") {

        exclusionRegex = Regex(exclusionPattern)

      }



      //当アプリ以外によるファイルの取得先

      val externalContentUri =

        if (Build.VERSION_CODES.Q <= Build.VERSION.SDK_INT) { //アンドロイド10(Q)以上の場合

          MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)

        } else {

          MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

        }


      getContentResolver().query(

        externalContentUri, //当アプリ以外によるファイルの取得先

        arrayOf( //SQLのSELECTに相当

          MediaStore.Audio.Media._ID, //android.media.MediaPlayerへの曲の指定に必要なID

          MediaStore.Audio.Media.TITLE, //音楽ファイルの曲名を取得

          MediaStore.Audio.Media.ARTIST, //音楽ファイルのアーティスト名を取得

          MediaStore.Audio.Media.ALBUM, //音楽ファイルのアルバム名を取得

          MediaStore.Audio.Media.DATA, //音楽ファイルのパスを取得

          MediaStore.Audio.Media.DURATION //音楽ファイルの総再生時間を取得

        ),

        "${MediaStore.Audio.Media.IS_MUSIC} != 0", //SQLのWHEREに相当。音楽ファイルに限定して一覧検索。

        null, //SQLのWHEREの?への指定パラメーターに相当

        "${MediaStore.Audio.Media.TITLE} ASC" //SQLのORDER BYに相当

      )?.use { cursor ->


        val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)

        val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)

        val artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)

        val albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)

        val filePathIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)

        val durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)


        while (cursor.moveToNext()) {


          var id = cursor.getLong(idIndex)

          var title = cursor.getString(titleIndex)

          var artist = cursor.getString(artistIndex)

          var album = cursor.getString(albumIndex)

          var filePath = cursor.getString(filePathIndex)

          var duration = cursor.getInt(durationIndex)



          var musicInfoTsv = title + "\t" + artist + "\t" + album + "\t" + filePath


          musicInfoTsv = halfWidthLowerCase(musicInfoTsv) //全角の英大文字、半角の英大文字、全角の英小文字を半角の英小文字に置換


          //検索条件が未指定の場合か、検索条件が含まれている場合

          if (inclusionRegex == null || inclusionRegex.containsMatchIn(musicInfoTsv)) {


            //除外条件が未指定の場合か、除外条件が含まれていない場合

            if (exclusionRegex == null || exclusionRegex.containsMatchIn(musicInfoTsv) == false) {


              musicInfoList?.add(MusicInfo(id, title, artist, album, filePath, duration))

            }

          }

        }

      }


      //音楽ファイルが見つからない場合

      if ((musicInfoList?.size ?: 0) <= 0) {

        Toast.makeText(this, getString(R.string.no_music_file_list), Toast.LENGTH_LONG).show()

      }


      //音楽ファイルの一覧画面で、ListViewの各行の内容を設定する物を指定

      val listView = findViewById<ListView>(R.id.musicList)

      listView.adapter = Adapter(this, musicInfoList ?: mutableListOf<MusicInfo>())


      //音楽ファイルの一覧画面で、ListViewの各行が押された時の処理

      listView.onItemClickListener = OnItemClickListener { adapterView, view, position, id ->

        try {


          //音楽ファイルの詳細画面へ遷移

          val intent = Intent(this, MusicDetail::class.java)

          intent.putExtra(MUSIC_INFO_LIST_KEY, ArrayList(musicInfoList))

          intent.putExtra(MUSIC_INFO_INDEX_KEY, position)

          startActivity(intent)


        } catch (exception: Exception) {

          Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //メモリーから破棄される時にのみ呼ばれます。

  override fun onDestroy() {

    try {


      musicInfoList?.clear()

      musicInfoList = null


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {

      super.onDestroy()

    }

  }

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。

 eliphas1810/freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。

 eliphas1810.freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/java/eliphas1810/freesearchablemusicplayer/MusicDetail.kt

――――――――――――――――――――

package eliphas1810.freesearchablemusicplayer



import android.content.*

import android.os.*

import android.widget.Button

import android.widget.SeekBar

import android.widget.TextView

import android.widget.Toast

import androidx.appcompat.app.AppCompatActivity


//Public Domain

//

//音楽ファイル詳細画面と1対1対応のアクティビティ

class MusicDetail : AppCompatActivity() {



  companion object {


    //音楽ファイル一覧画面のアクティビティから、音楽ファイル詳細画面のアクティビティへ送信する情報の名前

    //当アクティビティから、音楽を再生したりするサービスへ送信する情報の名前

    //サービスからの通知情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val MUSIC_INFO_LIST_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_LIST"

    const val MUSIC_INFO_INDEX_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_INDEX"

    const val LOOP_MUSIC_KEY = "eliphas1810.freesearchablemusicplayer.LOOP_MUSIC"

    const val RANDOM_MUSIC_KEY = "eliphas1810.freesearchablemusicplayer.RANDOM_MUSIC"

    const val CURRENT_MUSIC_DURATION_KEY = "eliphas1810.freesearchablemusicplayer.CURRENT_MUSIC_DURATION"



    //音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促される通知の名前

    const val UPDATE_MUSIC_INFO_KEY = "eliphas1810.freesearchablemusicplayer.UPDATE_MUSIC_INFO"



    //音楽ファイルの詳細画面に表示する現在の再生時間の更新を促される通知の名前

    const val UPDATE_MUSIC_CURRENT_DURATION_KEY = "eliphas1810.freesearchablemusicplayer.UPDATE_MUSIC_CURRENT_DURATION"



    //当アクティビティから、音楽を再生したりするサービスへの送信情報のコードは、アンドロイド アプリ開発者の責任で重複させない事

    const val START_MUSIC_MESSAGE = 1

    const val PAUSE_MUSIC_MESSAGE = 2

    const val STOP_MUSIC_MESSAGE = 3

    const val PREVIOUS_MUSIC_MESSAGE = 4

    const val NEXT_MUSIC_MESSAGE = 5

    const val SEEK_MUSIC_MESSAGE = 6

    const val LOOP_MUSIC_MESSAGE = 7

    const val RANDOM_MUSIC_MESSAGE = 8

    const val REQUEST_MUSIC_INFO_MESSAGE = 9

  }



  var musicInfoList: ArrayList<MusicInfo>? = null

  var musicInfoIndex = 0

  var loop = false

  var random = false

  var currentMusicDuration = 0


  var musicCurrentDurationChanging = false


  var connectingWithService: Boolean = false


  private var serviceConnection: ServiceConnection? = null


  var messenger: Messenger? = null


  private var musicInfoUpdateBroadcastReceiver: BroadcastReceiver? = null


  private var musicCurrentDurationUpdateBroadcastReceiver: BroadcastReceiver? = null



  //例えば、「1分2.003秒」といった形式に音楽の再生時間を編集

  fun convertMusicDurationToText(musicDuration: Int) : String {


    val minutes = musicDuration / (1000 * 60)

    val seconds = (musicDuration % (1000 * 60)) / 1000

    val milliSeconds = musicDuration % 1000


    var musicDurationText = ""

    musicDurationText = musicDurationText + minutes

    musicDurationText = musicDurationText + getString(R.string.minutes_unit_label)

    musicDurationText = musicDurationText + seconds

    musicDurationText = musicDurationText + getString(R.string.seconds_and_milli_seconds_separator)

    musicDurationText = musicDurationText + "%03d".format(milliSeconds)

    musicDurationText = musicDurationText + getString(R.string.seconds_unit_label)

    return musicDurationText

  }



  //メモリー上に作成される時にのみ呼ばれます。

  override fun onCreate(savedInstanceState: Bundle?) {

    try {

      super.onCreate(savedInstanceState)

      setContentView(R.layout.music_detail)



      if (musicInfoList != null && 1 <= (musicInfoList?.size ?: 0)) {

        musicInfoList?.clear()

      }


      musicInfoList = ArrayList(listOf())



      serviceConnection = object: ServiceConnection {

        override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {

          messenger = Messenger(iBinder)

          connectingWithService = true

        }

        override fun onServiceDisconnected(componentName: ComponentName) {

          messenger = null

          connectingWithService = false

        }

      }



      //アンドロイド8(オレオ)以上の場合

      if (Build.VERSION_CODES.O <= Build.VERSION.SDK_INT) {


        //アンドロイド アプリのバックグラウンド処理の部分である「サービス」と呼ばれる物を起動

        //

        //音楽を再生する「サービス」を起動

        //

        //音楽を再生する「サービス」がまだ無い場合は新規作成されます。

        //

        startForegroundService(Intent(applicationContext, MusicPlayerService::class.java))


      } else {


        //アンドロイド アプリのバックグラウンド処理の部分である「サービス」と呼ばれる物を起動

        //

        //音楽を再生する「サービス」を起動

        //

        //音楽を再生する「サービス」がまだ無い場合は新規作成されます。

        //

        startService(Intent(applicationContext, MusicPlayerService::class.java))

      }



      //アンドロイド アプリのバックグラウンド処理の部分である「サービス」と呼ばれる物へ接続

      //

      //音楽を再生する「サービス」へ接続

      //

      //音楽を再生する「サービス」がまだ無い場合は新規作成されます。

      //

      bindService(Intent(applicationContext, MusicPlayerService::class.java), serviceConnection!!, Context.BIND_AUTO_CREATE)



      //サービスからの、音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知を受け取ります。

      musicInfoUpdateBroadcastReceiver = object : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {

          try {


            val bundle = intent?.extras


            //サービスからの通知情報から、音楽ファイルの一覧の情報を受け取ります。


            musicInfoList = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合


              bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY, MusicInfo::class.java)


            } else {


              bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY)

            }



            if ((musicInfoList?.size ?: 0) <= 0) {

              return

            }



            //サービスからの通知情報から、選択中のゼロから始まる音楽ファイルの番号を受け取ります。

            musicInfoIndex = bundle?.getInt(MUSIC_INFO_INDEX_KEY) ?: 0



            //サービスからの通知情報から、ループ再生するか否かを受け取ります。

            loop = bundle?.getBoolean(LOOP_MUSIC_KEY) ?: false



            //サービスからの通知情報から、ループ再生するか否かを受け取ります。

            random = bundle?.getBoolean(RANDOM_MUSIC_KEY) ?: false



            //音楽ファイルの詳細画面の内容を更新


            val musicInfo = musicInfoList?.get(musicInfoIndex)


            findViewById<TextView>(R.id.musicDetailMusicTitle)?.text = musicInfo?.musicTitle

            findViewById<TextView>(R.id.musicDetailArtistName)?.text = musicInfo?.artistName

            findViewById<TextView>(R.id.musicDetailAlbumTitle)?.text = musicInfo?.albumTitle

            findViewById<TextView>(R.id.musicDetailMusicFilePath)?.text = musicInfo?.filePath

            findViewById<TextView>(R.id.musicDetailMusicDuration)?.text = convertMusicDurationToText(musicInfo?.duration ?: 0)


            if (loop) {

              findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.white, theme))

              findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

            } else {

              findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.silver, theme))

              findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

            }



            if (random) {

              findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.white, theme))

              findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

            } else {

              findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.silver, theme))

              findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

            }



            //音楽ファイルの詳細画面の、現在の再生時間のシーク バーが動かされていない場合

            if (musicCurrentDurationChanging == false) {


              //サービスからの通知情報から、現在の再生時間を受け取ります。

              currentMusicDuration = bundle?.getInt(CURRENT_MUSIC_DURATION_KEY) ?: 0


              //音楽ファイルの詳細画面の、現在の再生時間のシーク バーを更新

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).max = musicInfo?.duration ?: 0

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


              //音楽ファイルの詳細画面の、現在の再生時間を更新

              findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)

            }


          } catch (exception: Exception) {

            Toast.makeText(context?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

            throw exception

          }

        }

      }



      registerReceiver(musicInfoUpdateBroadcastReceiver, IntentFilter(UPDATE_MUSIC_INFO_KEY))



      //サービスからの、音楽ファイルの詳細画面に表示する現在の再生時間の更新を促す通知を受け取ります。

      musicCurrentDurationUpdateBroadcastReceiver = object : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {

          try {


            //音楽ファイルの詳細画面の、現在の再生時間のシーク バーが動かされていない場合

            if (musicCurrentDurationChanging == false) {


              val bundle = intent?.extras


              //サービスからの通知情報から、音楽ファイルの一覧の情報を受け取ります。


              musicInfoList = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合

                bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY, MusicInfo::class.java)

              } else {

                bundle?.getParcelableArrayList(MUSIC_INFO_LIST_KEY)

              }


              //サービスからの通知情報から、選択中のゼロから始まる音楽ファイルの番号を受け取ります。

              musicInfoIndex = bundle?.getInt(MUSIC_INFO_INDEX_KEY) ?: 0


              val musicInfo = musicInfoList?.get(musicInfoIndex)


              //サービスからの通知情報から、現在の再生時間を受け取ります。

              currentMusicDuration = bundle?.getInt(CURRENT_MUSIC_DURATION_KEY) ?: 0


              //音楽ファイルの詳細画面の、現在の再生時間のシーク バーを更新

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).max = musicInfo?.duration ?: 0

              findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


              //音楽ファイルの詳細画面の、現在の再生時間を更新

              findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)

            }


          } catch (exception: Exception) {

            Toast.makeText(context?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

            throw exception

          }

        }

      }



      registerReceiver(musicCurrentDurationUpdateBroadcastReceiver, IntentFilter(UPDATE_MUSIC_CURRENT_DURATION_KEY))



      //前の画面から音楽ファイルの一覧を取得


      musicInfoList = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合

        ArrayList(intent.getParcelableArrayListExtra(MUSIC_INFO_LIST_KEY, MusicInfo::class.java))

      } else {

        ArrayList(intent.getParcelableArrayListExtra(MUSIC_INFO_LIST_KEY))

      }



      //前の画面から選択されたゼロから始まる音楽ファイルの番号を取得

      musicInfoIndex = intent.getIntExtra(MUSIC_INFO_INDEX_KEY, 0)



      val musicInfo = musicInfoList?.get(musicInfoIndex)



      //画面の1回目の表示時の処理

      findViewById<TextView>(R.id.musicDetailMusicTitle)?.text = musicInfo?.musicTitle

      findViewById<TextView>(R.id.musicDetailArtistName)?.text = musicInfo?.artistName

      findViewById<TextView>(R.id.musicDetailAlbumTitle)?.text = musicInfo?.albumTitle

      findViewById<TextView>(R.id.musicDetailMusicFilePath)?.text = musicInfo?.filePath

      findViewById<TextView>(R.id.musicDetailMusicDuration)?.text = convertMusicDurationToText(musicInfo?.duration ?: 0)


      findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


      findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).max = musicInfo?.duration ?: 0

      findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


      findViewById<Button>(R.id.musicDetailLoop).setTextColor(resources.getColor(R.color.silver, theme))

      findViewById<Button>(R.id.musicDetailLoop).setBackgroundColor(resources.getColor(R.color.gray, theme))


      findViewById<Button>(R.id.musicDetailRandom).setTextColor(resources.getColor(R.color.silver, theme))

      findViewById<Button>(R.id.musicDetailRandom).setBackgroundColor(resources.getColor(R.color.gray, theme))



      //閉じるボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailClose).setOnClickListener { view ->

        try {


          //音楽を再生する「サービス」に、音楽を停止するようにメッセージを送信


          val bundle = Bundle()

          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, STOP_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          //音楽を再生する「サービス」を終了させます。

          stopService(Intent(applicationContext, MusicPlayerService::class.java))


          //前の画面へ戻る

          finish()


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //音楽ファイルの詳細画面の、現在の再生時間のシーク バーが操作された時の処理

      findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener {

        override fun onStartTrackingTouch(seekBar: SeekBar) {


          musicCurrentDurationChanging = true


          //音楽を再生する「サービス」に、音楽を一時停止するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, PAUSE_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)

        }


        override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromTouch: Boolean) {

          currentMusicDuration = progress

          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)

        }


        override fun onStopTrackingTouch(seekBar: SeekBar) {


          //音楽を再生する「サービス」に、音楽の再生開始時間を指定して、再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, SEEK_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          musicCurrentDurationChanging = false

        }

      })



      //再生ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailStart).setOnClickListener { view ->

        try {


          //音楽を再生する「サービス」に、音楽を再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, START_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //一時停止ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailPause).setOnClickListener { view ->

        try {


          //音楽を再生する「サービス」に、音楽を一時停止するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, PAUSE_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //停止ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailStop).setOnClickListener { view ->

        try {


          currentMusicDuration = 0


          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


          findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


          //音楽を再生する「サービス」に、音楽を停止するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, STOP_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「前の曲へ戻る」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailPrevious).setOnClickListener { view ->

        try {


          currentMusicDuration = 0


          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


          findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


          //音楽を再生する「サービス」に、前の曲へ戻って再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, PREVIOUS_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「次の曲へ進む」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailNext).setOnClickListener { view ->

        try {


          currentMusicDuration = 0


          findViewById<TextView>(R.id.musicDetailMusicCurrentDuration)?.text = convertMusicDurationToText(currentMusicDuration)


          findViewById<SeekBar>(R.id.musicDetailMusicCurrentDurationSeekBar).progress = currentMusicDuration


          //音楽を再生する「サービス」に、次の曲へ進んで再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, NEXT_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「ループ モード」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailLoop).setOnClickListener { view ->

        try {


          loop = !loop


          //音楽を再生する「サービス」に、ループ再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, LOOP_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          if (loop) {

            findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.white, theme))

            findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

          } else {

            findViewById<Button>(R.id.musicDetailLoop)?.setTextColor(resources.getColor(R.color.silver, theme))

            findViewById<Button>(R.id.musicDetailLoop)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

          }


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //「ランダム モード」ボタンが押された時の処理

      findViewById<Button>(R.id.musicDetailRandom).setOnClickListener { view ->

        try {


          random = !random


          //音楽を再生する「サービス」に、ランダム再生するようにメッセージを送信


          val bundle = Bundle()


          bundle.putParcelableArrayList(MUSIC_INFO_LIST_KEY, musicInfoList)

          bundle.putInt(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

          bundle.putBoolean(LOOP_MUSIC_KEY, loop)

          bundle.putBoolean(RANDOM_MUSIC_KEY, random)

          bundle.putInt(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


          val message: Message = Message.obtain(null, RANDOM_MUSIC_MESSAGE)


          message.data = bundle


          messenger?.send(message)


          if (random) {

            findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.white, theme))

            findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.lime, theme))

          } else {

            findViewById<Button>(R.id.musicDetailRandom)?.setTextColor(resources.getColor(R.color.silver, theme))

            findViewById<Button>(R.id.musicDetailRandom)?.setBackgroundColor(resources.getColor(R.color.gray, theme))

          }


        } catch (exception: Exception) {

          Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

          throw exception

        }

      }



      //音楽を再生する「サービス」に、音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知をするようにメッセージを送信


      val message: Message = Message.obtain(null, REQUEST_MUSIC_INFO_MESSAGE)


      messenger?.send(message)



    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  override fun onResume() {

    try {

      super.onResume()



      //音楽を再生する「サービス」に、音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知をするようにメッセージを送信


      val message: Message = Message.obtain(null, REQUEST_MUSIC_INFO_MESSAGE)


      messenger?.send(message)


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //メモリーから破棄される時にのみ呼ばれます。

  override fun onDestroy() {

    try {


      musicInfoList?.clear()

      musicInfoList = null


      unregisterReceiver(musicInfoUpdateBroadcastReceiver)

      musicInfoUpdateBroadcastReceiver = null


      unregisterReceiver(musicCurrentDurationUpdateBroadcastReceiver)

      musicCurrentDurationUpdateBroadcastReceiver = null


      messenger = null


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {

      super.onDestroy()

    }

  }

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。

 eliphas1810/freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。

 eliphas1810.freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。





/home/◯◯◯/AndroidStudioProjects/FreeSearchableMusicPlayer/app/src/main/java/eliphas1810/freesearchablemusicplayer/MusicPlayerService.kt

――――――――――――――――――――

package eliphas1810.freesearchablemusicplayer



import android.app.*

import android.content.*

import android.media.AudioAttributes

import android.media.AudioManager

import android.media.MediaPlayer

import android.net.Uri

import android.os.*

import android.provider.MediaStore

import android.support.v4.media.MediaBrowserCompat

import android.support.v4.media.session.MediaSessionCompat

import android.support.v4.media.session.PlaybackStateCompat

import android.view.KeyEvent

import android.widget.Toast

import androidx.media.AudioAttributesCompat

import androidx.media.AudioFocusRequestCompat

import androidx.media.AudioManagerCompat

import androidx.media.MediaBrowserServiceCompat

import androidx.media.session.MediaButtonReceiver

import java.util.concurrent.Executors

import java.util.concurrent.ScheduledExecutorService

import java.util.concurrent.TimeUnit


//Public Domain


//アクティビティからサービスへの送信情報を受け取る物


class MusicPlayerActivityHandler(

  var musicPlayerService: MusicPlayerService?

) : Handler(Looper.getMainLooper()) {


  override fun handleMessage(message: Message) {

    try {


      if (message.what == MusicPlayerService.REQUEST_MUSIC_INFO_MESSAGE) {

        musicPlayerService?.updateMusicInfo()

        return

      }


      val bundle = message.data


      //アクティビティからサービスへの送信情報から、音楽ファイルの一覧の情報を受け取ります。


      musicPlayerService?.musicInfoList = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合

        bundle.getParcelableArrayList(MusicPlayerService.MUSIC_INFO_LIST_KEY, MusicInfo::class.java)

      } else {

        bundle.getParcelableArrayList(MusicPlayerService.MUSIC_INFO_LIST_KEY)


      }


      //アクティビティからサービスへの送信情報から、選択中のゼロから始まる音楽ファイルの番号を受け取ります。

      musicPlayerService?.musicInfoIndex = bundle.getInt(MusicPlayerService.MUSIC_INFO_INDEX_KEY)


      //アクティビティからサービスへの送信情報から、ループ再生するか否かを受け取ります。

      musicPlayerService?.loop = bundle.getBoolean(MusicPlayerService.LOOP_MUSIC_KEY)


      //アクティビティからサービスへの送信情報から、ランダム再生するか否かを受け取ります。

      musicPlayerService?.random = bundle.getBoolean(MusicPlayerService.RANDOM_MUSIC_KEY)


      //アクティビティからサービスへの送信情報から、音楽の再生開始時間を受け取ります。

      musicPlayerService?.currentMusicDuration = bundle.getInt(MusicPlayerService.CURRENT_MUSIC_DURATION_KEY)


      if (message.what == MusicPlayerService.START_MUSIC_MESSAGE) {

        musicPlayerService?.start()

        return

      }


      if (message.what == MusicPlayerService.PAUSE_MUSIC_MESSAGE) {

        musicPlayerService?.pause()

        return

      }


      if (message.what == MusicPlayerService.STOP_MUSIC_MESSAGE) {

        musicPlayerService?.stop()

        return

      }


      if (message.what == MusicPlayerService.PREVIOUS_MUSIC_MESSAGE) {

        musicPlayerService?.previous()

        return

      }


      if (message.what == MusicPlayerService.NEXT_MUSIC_MESSAGE) {

        musicPlayerService?.next()

        return

      }


      if (message.what == MusicPlayerService.SEEK_MUSIC_MESSAGE) {

        musicPlayerService?.seek()

        return

      }


    } catch (exception: Exception) {

      Toast.makeText(musicPlayerService?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {

      super.handleMessage(message)

    }

  }

}



//事故で意図せず有線イヤホンが抜けた場合などに音楽の再生を一時停止する物

//

//有線イヤホンや無線イヤホンなどからスマホやタブレットの内蔵スピーカーへ音声出力先が戻る通知を受け取る物

//

//アンドロイド システムからの通知を受け取ります。

//

private class AudioBecomingNoisyBroadcastReceiver(

  var musicPlayerService: MusicPlayerService?

) : BroadcastReceiver() {


  //事故で意図せず有線イヤホンが抜けた場合などの処理

  //

  //有線イヤホンや無線イヤホンなどからスマホやタブレットの内蔵スピーカーへ音声出力先が戻る通知を受け取った場合の処理

  //

  //アンドロイド システムからの通知を受け取った場合の処理

  //

  override fun onReceive(context: Context?, intent: Intent?) {

    try {


      //音楽の再生を一時停止

      musicPlayerService?.pause()


    } catch (exception: Exception) {

      Toast.makeText(musicPlayerService?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }

}



//アンドロイド アプリのバックグラウンド処理の部分である「サービス」と呼ばれる物

//

//音楽を再生する「サービス」

//

//Bluetoothイヤホンの物理ボタンなどの「メディア ボタン」との接続である「メディア セッション」に対応する場合は、「サービス」はServiceクラスではなくMediaBrowserServiceCompatクラスを継承

//

class MusicPlayerService : MediaBrowserServiceCompat(), MediaPlayer.OnCompletionListener {



  companion object {



    //アクティビティから、音楽を再生したりする当サービスへ送信する情報の名前

    //当サービスから、アクティビティへ通知する情報の名前

    //

    //重複を避けるため、「パッケージ名 + 情報の名前」

    //

    const val MUSIC_INFO_LIST_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_LIST"

    const val MUSIC_INFO_INDEX_KEY = "eliphas1810.freesearchablemusicplayer.MUSIC_INFO_INDEX"

    const val LOOP_MUSIC_KEY = "eliphas1810.freesearchablemusicplayer.LOOP_MUSIC"

    const val RANDOM_MUSIC_KEY = "eliphas1810.freesearchablemusicplayer.RANDOM_MUSIC"

    const val CURRENT_MUSIC_DURATION_KEY = "eliphas1810.freesearchablemusicplayer.CURRENT_MUSIC_DURATION"



    //アクティビティから、音楽を再生したりする当サービスへの送信情報のコードは、アンドロイド アプリ開発者の責任で重複させない事

    const val START_MUSIC_MESSAGE = 1

    const val PAUSE_MUSIC_MESSAGE = 2

    const val STOP_MUSIC_MESSAGE = 3

    const val PREVIOUS_MUSIC_MESSAGE = 4

    const val NEXT_MUSIC_MESSAGE = 5

    const val SEEK_MUSIC_MESSAGE = 6

    //const val LOOP_MUSIC_MESSAGE = 7

    //const val RANDOM_MUSIC_MESSAGE = 8

    const val REQUEST_MUSIC_INFO_MESSAGE = 9



    //音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知の名前

    const val UPDATE_MUSIC_INFO_KEY = "eliphas1810.freesearchablemusicplayer.UPDATE_MUSIC_INFO"



    //音楽ファイルの詳細画面に表示する現在の再生時間の更新を促す通知の名前

    const val UPDATE_MUSIC_CURRENT_DURATION_KEY = "eliphas1810.freesearchablemusicplayer.UPDATE_MUSIC_CURRENT_DURATION"



    //メディア セッションで、当サービスへの接続は許可するが、何も情報を返さない場合のメディア ルートID。

    //

    //当サービス内だけの、アプリ開発者独自の値

    //

    private const val EMPTY_MEDIA_ROOT_ID = "eliphas1810.freesearchablemusicplayer.EMPTY_MEDIA_ROOT_ID"

    private const val CHANNEL_ID = "eliphas1810.freesearchablemusicplayer.CHANNEL_ID"

  }



  private var messenger: Messenger? = null


  private var mediaPlayer: MediaPlayer? = null


  var musicInfoList: ArrayList<MusicInfo>? = null

  var musicInfoIndex = 0


  var loop = false

  var random = false


  var currentMusicDuration = 0


  var starting = false

  var pausing = false

  var stopping = false



  //当アプリ以外によるファイルの取得先

  var externalContentUri: Uri? = null


  private var audioBecomingNoisyBroadcastReceiver: AudioBecomingNoisyBroadcastReceiver? = null


  var audioManager: AudioManager? = null


  var audioFocusRequestCompat: AudioFocusRequestCompat? = null


  var mediaSessionCompat: MediaSessionCompat? = null


  var scheduledExecutorService: ScheduledExecutorService? = null



  //音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知をします。

  fun updateMusicInfo() {


    val intent = Intent(UPDATE_MUSIC_INFO_KEY)


    intent.putParcelableArrayListExtra(MUSIC_INFO_LIST_KEY, musicInfoList)

    intent.putExtra(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

    intent.putExtra(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)

    intent.putExtra(LOOP_MUSIC_KEY, loop)

    intent.putExtra(RANDOM_MUSIC_KEY, random)


    baseContext.sendBroadcast(intent)

  }



  //音楽ファイルの詳細画面に表示する現在の再生時間の更新を促す通知をします。

  fun updateMusicCuurentDuration() {


    currentMusicDuration = mediaPlayer?.currentPosition ?: 0


    val intent = Intent(UPDATE_MUSIC_CURRENT_DURATION_KEY)


    intent.putParcelableArrayListExtra(MUSIC_INFO_LIST_KEY, musicInfoList)

    intent.putExtra(MUSIC_INFO_INDEX_KEY, musicInfoIndex)

    intent.putExtra(CURRENT_MUSIC_DURATION_KEY, currentMusicDuration)


    baseContext.sendBroadcast(intent)

  }



  //音楽を再生

  fun start() {


    //オーディオ フォーカス(Audio Focus)を得られない場合

    if (AudioManagerCompat.requestAudioFocus(audioManager!!, audioFocusRequestCompat!!) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {


      //音楽を再生せず終了

      return

    }


    val musicInfo = musicInfoList?.get(musicInfoIndex)

    val mediaStoreId = musicInfo?.id

    val uri = ContentUris.withAppendedId(externalContentUri!!, mediaStoreId!!)


    if (starting) {


      mediaPlayer?.stop()


      stopping = true

      starting = false

      pausing = false


      currentMusicDuration = 0


      mediaPlayer?.reset()

      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else if (pausing) {


      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else if (stopping) {


      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      stopping = false

      pausing = false


    } else {


      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false

    }

  }



  //音楽の再生を一時停止

  fun pause() {


    if (starting) {


      mediaPlayer?.pause()

      pausing = true

      starting = false

      stopping = false

    }

  }



  //音楽の再生を停止

  fun stop() {


    //得ていたオーディオ フォーカス(Audio Focus)を放棄

    AudioManagerCompat.abandonAudioFocusRequest(audioManager!!, audioFocusRequestCompat!!)


    if (starting || pausing) {


      mediaPlayer?.stop()


      stopping = true

      starting = false

      pausing = false


      currentMusicDuration = 0

    }

  }



  //前の曲へ戻って再生

  fun previous() {


    if (loop == false) {


      val musicInfoCount = musicInfoList?.size ?: 0


      if (random) {

        musicInfoIndex = (0..(musicInfoCount - 1)).random()

      } else {

        musicInfoIndex = musicInfoIndex - 1

        if (musicInfoIndex <= -1) {

          musicInfoIndex = musicInfoCount - 1

        }

      }

    }


    //音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知をします。

    updateMusicInfo()


    //オーディオ フォーカス(Audio Focus)を得られない場合

    if (AudioManagerCompat.requestAudioFocus(audioManager!!, audioFocusRequestCompat!!) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {


      //音楽を再生せず終了

      return

    }


    val musicInfo = musicInfoList?.get(musicInfoIndex)

    val mediaStoreId = musicInfo?.id

    val uri = ContentUris.withAppendedId(externalContentUri!!, mediaStoreId!!)


    if (starting || pausing) {


      mediaPlayer?.stop()


      stopping = true

      starting = false

      pausing = false


      currentMusicDuration = 0


      mediaPlayer?.reset()

      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else if (stopping) {


      mediaPlayer?.reset()

      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else {


      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false

    }

  }



  //次の曲へ進んで再生

  fun next() {


    if (loop == false) {


      val musicInfoCount = musicInfoList?.size ?: 0


      if (random) {

        musicInfoIndex = (0..(musicInfoCount - 1)).random()

      } else {

        musicInfoIndex = musicInfoIndex + 1

        if (musicInfoCount <= musicInfoIndex) {

          musicInfoIndex = 0

        }

      }

    }


    //音楽ファイルの詳細画面に表示する音楽ファイルの情報の更新を促す通知をします。

    updateMusicInfo()


    //オーディオ フォーカス(Audio Focus)を得られない場合

    if (AudioManagerCompat.requestAudioFocus(audioManager!!, audioFocusRequestCompat!!) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {


      //音楽を再生せず終了

      return

    }


    val musicInfo = musicInfoList?.get(musicInfoIndex)

    val mediaStoreId = musicInfo?.id

    val uri = ContentUris.withAppendedId(externalContentUri!!, mediaStoreId!!)


    if (starting || pausing) {


      mediaPlayer?.stop()


      stopping = true

      starting = false

      pausing = false


      currentMusicDuration = 0


      mediaPlayer?.reset()

      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else if (stopping) {


      mediaPlayer?.reset()

      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else {


      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false

    }

  }



  //音楽の再生開始時間を指定して再生

  fun seek() {


    //オーディオ フォーカス(Audio Focus)を得られない場合

    if (AudioManagerCompat.requestAudioFocus(audioManager!!, audioFocusRequestCompat!!) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {


      //音楽を再生せず終了

      return

    }


    val musicInfo = musicInfoList?.get(musicInfoIndex)

    val mediaStoreId = musicInfo?.id

    val uri = ContentUris.withAppendedId(externalContentUri!!, mediaStoreId!!)


    if (starting) {


      mediaPlayer?.stop()


      stopping = true

      starting = false

      pausing = false


      mediaPlayer?.reset()

      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.seekTo(currentMusicDuration)

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else if (pausing) {


      mediaPlayer?.seekTo(currentMusicDuration)

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false


    } else if (stopping) {


      mediaPlayer?.prepare()

      mediaPlayer?.seekTo(currentMusicDuration)

      mediaPlayer?.start()


      starting = true

      stopping = false

      pausing = false


    } else {


      mediaPlayer?.setDataSource(this, uri)

      mediaPlayer?.prepare()

      mediaPlayer?.seekTo(currentMusicDuration)

      mediaPlayer?.start()


      starting = true

      pausing = false

      stopping = false

    }

  }



  //再生中の音楽が再生終了した場合を処理

  override fun onCompletion(mediaPlayer: MediaPlayer) {

    try {


      //次の音楽を再生

      next()


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //メモリー上に作成される時にのみ呼ばれます。

  override fun onCreate() {

    try {

      super.onCreate()



      //アンドロイド8(オレオ)以上の場合

      if (Build.VERSION_CODES.O <= Build.VERSION.SDK_INT) {


        val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager


        val notificationChannel = NotificationChannel(CHANNEL_ID, getText(R.string.notification_content_title), NotificationManager.IMPORTANCE_LOW)


        notificationManager.createNotificationChannel(notificationChannel)


        val notificationBuilder = Notification.Builder(applicationContext, CHANNEL_ID)


        notificationBuilder.setContentTitle(getText(R.string.notification_content_title))

        notificationBuilder.setContentText(getText(R.string.notification_content_text))

        notificationBuilder.setTicker(getText(R.string.notification_ticker))


        val notification = notificationBuilder.build()


        //startForeground()を実行しないと、アンドロイド システムに強制終了されてしまいます。

        //

        //startForeground()には、通知(Notification)が必要です。

        //

        startForeground(1, notification)

      }


      mediaPlayer = MediaPlayer()


      if (musicInfoList != null && 1 <= (musicInfoList?.size ?: 0)) {

        musicInfoList?.clear()

      }

      musicInfoList = ArrayList(listOf())


      //当アプリ以外によるファイルの取得先


      externalContentUri =

        if (Build.VERSION_CODES.Q <= Build.VERSION.SDK_INT) { //アンドロイド10(Q)以上の場合

          MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)

        } else {

          MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

        }


      //再生中の音楽の再生終了を受け取る物を設定

      mediaPlayer?.setOnCompletionListener(this)


      //音楽再生中、画面がスリープされても、CPUがスリープされないようにします。

      mediaPlayer?.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK)


      //アンドロイド システムの音楽の音量の設定を適用

      mediaPlayer?.setAudioAttributes(

        AudioAttributes.Builder()

          .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)

          .setUsage(AudioAttributes.USAGE_MEDIA)

          .build()

      )


      //事故で意図せず有線イヤホンが抜けた場合などの通知を受け取る物を設定

      //

      //有線イヤホンや無線イヤホンなどからスマホやタブレットの内蔵スピーカーへ音声出力先が戻る通知を受け取る物を設定

      //

      audioBecomingNoisyBroadcastReceiver = AudioBecomingNoisyBroadcastReceiver(this)

      registerReceiver(audioBecomingNoisyBroadcastReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))


      //電話アプリやYoutubeアプリといった他の音声を再生するアプリによってオーディオ フォーカス(Audio Focus)が失われた場合の処理


      audioManager = getSystemService(AudioManager::class.java)


      val audioAttributesCompatBuilder = AudioAttributesCompat.Builder()


      audioAttributesCompatBuilder.setUsage(AudioAttributesCompat.USAGE_MEDIA)

      audioAttributesCompatBuilder.setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)


      val audioFocusRequestCompatBuilder = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)


      audioFocusRequestCompatBuilder.setAudioAttributes(audioAttributesCompatBuilder.build())


      audioFocusRequestCompatBuilder.setOnAudioFocusChangeListener { focusChange ->


        //電話が、かかってきて、通話が終了した時などの場合

        //

        //オーディオ フォーカス(Audio Focus)が戻ってきた場合

        if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {


          //音楽の再生を再開

          start()


          //電話が、かかってきている時や、通話中などの場合

          //

          //オーディオ フォーカス(Audio Focus)が一時的に失われた場合

        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {


          //音楽の再生を一時停止

          pause()


          //Youtubeアプリなどの音声を再生するアプリを起動した場合

          //

          //オーディオ フォーカス(Audio Focus)が失われた場合

        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {


          //音楽の再生を停止

          stop()

        }

      }


      audioFocusRequestCompat = audioFocusRequestCompatBuilder.build()


      //Bluetoothイヤホンの物理ボタンなどの「メディア ボタン」との接続である「メディア セッション」の処理


      mediaSessionCompat = MediaSessionCompat(

        this,

        MusicPlayerService::class.java.name

      )


      val playbackStateCompatBuilder = PlaybackStateCompat.Builder()

        .setActions(

          PlaybackStateCompat.ACTION_PLAY

              or PlaybackStateCompat.ACTION_PAUSE

              or PlaybackStateCompat.ACTION_PLAY_PAUSE

              or PlaybackStateCompat.ACTION_STOP

              or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS

              or PlaybackStateCompat.ACTION_SKIP_TO_NEXT

        )


      mediaSessionCompat?.setPlaybackState(playbackStateCompatBuilder?.build())


      mediaSessionCompat?.setCallback(object : MediaSessionCompat.Callback() {

        override fun onMediaButtonEvent(intent: Intent): Boolean {


          var keyEvent = if (Build.VERSION_CODES.TIRAMISU <= Build.VERSION.SDK_INT) { //アンドロイド13(ティラミス)以上の場合

            intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)

          } else {

            intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)

          }


          if (keyEvent == null) {

            return false

          }


          //ACTION_DOWNとACTION_UPの二重でonMediaButtonEventが呼ばれるので、ACTION_DOWNの場合だけ処理して、処理の重複を回避

          if (keyEvent.action != KeyEvent.ACTION_DOWN) {

            return false

          }


          if (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {

            start()

            return true

          }


          if (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {

            pause()

            return true

          }


          if (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { //再生と一時停止が同一の物理ボタンの場合


            if (starting) {

              pause()

              return true

            }


            if (pausing || stopping) {

              start()

            }


            return true

          }


          if (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_STOP) {

            stop()

            return true

          }


          if (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {

            previous()

            return true

          }


          if (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_NEXT) {

            next()

            return true

          }


          return super.onMediaButtonEvent(intent)

        }

      })


      sessionToken = mediaSessionCompat?.sessionToken


      mediaSessionCompat?.isActive = true


      MediaButtonReceiver.handleIntent(

        mediaSessionCompat,

        Intent(

          applicationContext,

          MusicPlayerService::class.java

        )

      )


      //1秒おきに、音楽ファイルの詳細画面に表示する現在の再生時間の更新を促す通知をします。

      scheduledExecutorService = Executors.newSingleThreadScheduledExecutor()

      scheduledExecutorService?.scheduleAtFixedRate(

        {

          try {


            if (starting) {


              //音楽ファイルの詳細画面に表示する現在の再生時間の更新を促す通知をします。

              updateMusicCuurentDuration()

            }


          } catch (exception: Exception) {

            Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

            throw exception

          }

        },

        1, //1回目までの時間間隔の時間数

        1, //1回目以降の時間間隔の時間数

        TimeUnit.SECONDS //時間の単位。秒。

      )


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    }

  }



  //「サービス」が起動される時に呼ばれます。

  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

    try {

    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {

      return START_NOT_STICKY

    }

  }



  //「サービス」がバインドされる時に呼ばれます。

  override fun onBind(intent: Intent): IBinder? {

    try {


      messenger = Messenger(MusicPlayerActivityHandler(this))


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {


      return messenger?.binder

    }

  }



  override fun onGetRoot(

    clientPackageName: String,

    clientUid: Int,

    rootHints: Bundle?

  ): BrowserRoot {

    return BrowserRoot(EMPTY_MEDIA_ROOT_ID, null)

  }



  override fun onLoadChildren(

    parentMediaId: String,

    result: Result<List<MediaBrowserCompat.MediaItem>>

  ) {

    result.sendResult(null) //メディア セッションで、当サービスへの接続は許可するが、何も情報を返しません。

    return

  }



  //メモリーから破棄される時にのみ呼ばれます。

  override fun onDestroy() {

    try {


      scheduledExecutorService?.shutdownNow()

      scheduledExecutorService = null


      messenger = null


      if (mediaPlayer?.isPlaying() ?: false) {

        mediaPlayer?.stop()

      }

      mediaPlayer?.reset()

      mediaPlayer?.release()

      mediaPlayer = null


      musicInfoList?.clear()

      musicInfoList = null


      externalContentUri = null


      unregisterReceiver(audioBecomingNoisyBroadcastReceiver)

      audioBecomingNoisyBroadcastReceiver?.musicPlayerService = null

      audioBecomingNoisyBroadcastReceiver = null


      AudioManagerCompat.abandonAudioFocusRequest(audioManager!!, audioFocusRequestCompat!!)

      audioManager = null

      audioFocusRequestCompat = null


      mediaSessionCompat?.release()

      mediaSessionCompat = null


    } catch (exception: Exception) {

      Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()

      throw exception

    } finally {

      super.onDestroy()

    }

  }

}

――――――――――――――――――――

 ◯◯◯はLinux Mintのユーザー名です。

 FreeSearchableMusicPlayerは著者が付けたAndroid Studioのプロジェクトの名前です。

 eliphas1810/freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。

 eliphas1810.freesearchablemusicplayerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。

  • Xで共有
  • Facebookで共有
  • はてなブックマークでブックマーク

作者を応援しよう!

ハートをクリックで、簡単に応援の気持ちを伝えられます。(ログインが必要です)

応援したユーザー

応援すると応援コメントも書けます

新規登録で充実の読書を

マイページ
読書の状況から作品を自動で分類して簡単に管理できる
小説の未読話数がひと目でわかり前回の続きから読める
フォローしたユーザーの活動を追える
通知
小説の更新や作者の新作の情報を受け取れる
閲覧履歴
以前読んだ小説が一覧で見つけやすい
新規ユーザー登録無料

アカウントをお持ちの方はログイン

カクヨムで可能な読書体験をくわしく知る