This is base project for com.dedetok.wargasari,
com.dedetok.purwakaning, and com.dedetok.pitrapuja listed on Google Play. I tested on Android 7, 8,
9, 10, 11 and 13, I believe no bug or no any code that can caused your
privacy data leak. No guarantee if you use apk or other package
installer other then google play.
For com.dedetok.turuntirta and com.dedetok.bramara, I keep using MediaPlayer legacy. Source code:
https://dedetoknotes.blogspot.com/2024/04/android-java-source-code.htmlPut wargasari.mp3 in folder [Workspace]/Wargasari/app/src/main/res/raw/
build.gradle.kts (Module: app)
plugins {
id("com.android.application")
}
android {
namespace = "com.dedetok.wargasari"
compileSdk = 34
defaultConfig {
applicationId = "com.dedetok.wargasari"
minSdk = 24
targetSdk = 34
versionCode = 202404
versionName = "2024.04"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("com.google.android.gms:play-services-ads-lite:23.0.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// For exposing and controlling media sessions
implementation("androidx.media3:media3-session:1.3.0")
// For media playback using ExoPlayer
implementation("androidx.media3:media3-exoplayer:1.3.0")
// For HLS playback support with ExoPlayer
implementation("androidx.media3:media3-exoplayer-hls:1.3.0")
// UI unstable
//implementation("androidx.media3:media3-ui:1.3.0")
}
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">
<!-- For apps targeting Android 13 or higher & GMA SDK version 20.3.0 or lower -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- android:enableOnBackInvokedCallback="true" Back Pressed Target API level>33 -->
<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:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Wargasari"
tools:targetApi="31"
android:enableOnBackInvokedCallback="true"
>
<!-- android:configChanges="orientation|screenSize" keep state UI -->
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".myservice.DedetokMediaSessionService"
android:foregroundServiceType="mediaPlayback"
android:exported="true"
>
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
<!-- AD App ID: ca-app-pub-0220748109202708~2695747083 : AndroidManifest.xml -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713"/>
</application>
</manifest>
res -> values -> strings.xml
<resources>
<string name="app_name">Wargasari</string>
<string-array name="kidung">
<item>Ida ratu saking luhur |\nKawula nunas lugrane |\nMangda sampun titiang tandruh |\nMengayat bhatara mangkin |\nTitiang ngaturang pejati |\nCanang suci lan daksina |\nSami sampun puput |\nPretingkahing saji ||</item>
<item>Asep menyan majegau |\nCendana nuhur dewane |\nMangda ida gelis rawuh |\nMijil saking luhuring langit |\nSampun medabdaban sami |\nMaring giri meru reko |\nAncangan sadulur |\nSami pada ngiring ||</item>
<item>Bhetarane saking luhur |\nNgegenah ring ambarane |\nPenganggene sarwa murub |\nParekan sami mengiring |\nWidyadara widyadari |\nPada medudon dudonan |\nPrabhawa kumetug |\nAngliwer ring langit ||</item>
<item>Di bale manike luwung |\nMapanyengker ring telagane |\nKedangingin tunjung putih |\nTunjung abang tunjung putih |\nRing madyaning bale alit |\nIda bhatara mebawos |\nNganggit sekar jepun |\nSekar sane wangi ||</item>
<item>Ring bale emase parum |\nLinggih ida bhetarane |\nBale emas ngranyab murub |\nUpacara sarwa luwih |\nLeluhure sutra putih |\nIda bhatara mabawos |\nBawose di luhur |\nPacang turun gelis ||</item>
<item>Asep pejati wus katur |\nMendak ida bhetarane |\nPeneteg lan canang harum |\nCanang gantal canang sari |\nParekan pada menangkil |\nPedak sami nunas ica |\nNyadpada menyungsung |\nNgaturang pelinggih ||</item>
</string-array>
<array name="my_font_size">->
<item>18</item>
<item>24</item>
<item>32</item>
<item>42</item>
</array>
<string name="dialog_title">Exit Application</string>
<string name="dialog_message">Do you want to exit application?</string>
<string name="play_pause">Play/Pause</string>
<string name="zoom_in">Zoom In</string>
<string name="zoom_out">Zoom Out</string>
<string name="bait">Stanza</string>
<string name="play_repeat">Repeat play</string>
</resources>
res -> values -> ic_launcher_background.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
res -> Layout -> layout_stanza.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stanza_title"
android:textColor="#9C27B0"
/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stanza_content"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
res -> Layout -> activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
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"
android:orientation="vertical"
>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<androidx.appcompat.widget.AppCompatButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_zoom_in"
android:text="@string/zoom_in"
/>
<androidx.appcompat.widget.AppCompatButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/b_zoom_out"
android:text="@string/zoom_out"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_weight="1"
android:padding="10sp"
/>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center"
>
<androidx.appcompat.widget.AppCompatImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/play_button"
android:src="@android:drawable/ic_media_play"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/play_pause"
/>
<androidx.appcompat.widget.SwitchCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/switch_repeat"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/play_repeat"
android:text="@string/play_repeat"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
<!-- Sample AdMob unit ID: ca-app-pub-3940256099942544/6300978111 -->
<!-- AD Unit ID: ca-app-pub-0220748109202708/5649213488 : layout -->
<com.google.android.gms.ads.AdView
android:id="@+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:adSize="BANNER"
app:adUnitId="ca-app-pub-3940256099942544/6300978111"
android:layout_gravity="end|center"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
java -> StanzaContainer.java
package package com.dedetok.wargasari;
public class StanzaContainer {
public String stanzaTitle;
public String stanzaContent;
public StanzaContainer(String stanzaTitle, String stanzaContent) {
this.stanzaTitle = stanzaTitle;
this.stanzaContent = stanzaContent;
}
}
java -> MyAdapter.java
package com.dedetok.wargasari;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MyAdapter extends RecyclerView.Adapter {
ArrayList<StanzaContainer> myStanza;
int myUnitTextSize;
float myTextSize;
public MyAdapter(ArrayList<StanzaContainer> myStanza, int unit, float size) {
this.myStanza = myStanza;
this.myUnitTextSize = unit;
this.myTextSize = size;
}
@NonNull
@Override
public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.layout_stanza, parent, false);
return new MyAdapter.MyHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
StanzaContainer myTmpStanza = myStanza.get(position);
((MyHolder) holder).stanzaTitle.setText(myTmpStanza.stanzaTitle);
((MyHolder) holder).stanzaTitle.setTextSize(myUnitTextSize, myTextSize);
((MyHolder) holder).stanzaContent.setText(myTmpStanza.stanzaContent);
((MyHolder) holder).stanzaContent.setTextSize(myUnitTextSize, myTextSize);
}
@Override
public int getItemCount() {
return myStanza.size();
}
public void changeFontSize(int unit, float size) {
this.myUnitTextSize = unit;
this.myTextSize = size;
notifyDataSetChanged();
}
private class MyHolder extends RecyclerView.ViewHolder {
public AppCompatTextView stanzaTitle, stanzaContent;
public MyHolder(@NonNull View itemView) {
super(itemView);
stanzaTitle = itemView.findViewById(R.id.stanza_title);
stanzaContent = itemView.findViewById(R.id.stanza_content);
}
}
}
java -> com.dedetok.wargasari.myservice -> DedetokMediaSessionService.java
package com.dedetok.wargasari.myservice;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.session.MediaSession;
import androidx.media3.session.MediaSessionService;
/*
* MediaSessionService part - status ok
* ExoPlayer inside MediaSession
*/
public class DedetokMediaSessionService extends MediaSessionService {
/*
* MediaSession creates a default implementation of MediaSession.Callback that
* automatically handles all commands a MediaController sends to your player
*/
MediaSession myMediaSession = null;
/*
* Create your Player and MediaSession in the onCreate lifecycle event
*
*/
@Override
public void onCreate() {
super.onCreate();
ExoPlayer player = new ExoPlayer.Builder(this).build();
// Let system handle focus audio for incoming or outgoing call
player.setAudioAttributes(AudioAttributes.DEFAULT, true);
myMediaSession = new MediaSession.Builder(this, player).build();
// Log.e("dedetok","DedetokMediaSessionService onCreate"); // debug
}
/*
* The user dismissed the app from the recent tasks
*/
@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
Player player = myMediaSession.getPlayer();
if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 ) {
/*
* stop player if not ready || no media playing
* else play on background
*/
player.stop();
stopSelf();
// Log.e("dedetok", "onTaskRemoved stop and release resources"); // debug
}
}
/*
* Remember to release the player and media session in onDestroy
*/
@Override
public void onDestroy() {
// https://developer.android.com/media/media3/session/background-playback
myMediaSession.getPlayer().release();
myMediaSession.release();
myMediaSession = null;
super.onDestroy();
}
/*
* extends MediaSessionService
*/
@Nullable
@Override
public MediaSession onGetSession(@NonNull MediaSession.ControllerInfo controllerInfo) {
return myMediaSession;
}
}
java -> com.dedetok.wargasari.myservice -> MyMediaController.java
package com.dedetok.wargasari.myservice;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import com.dedetok.wargasari.R;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutionException;
/*
* MediaController client part
*/
public class MyMediaController {
String kidungName = "Wargasari";
Context appContext;
MediaController myMediaController = null;
// myMediaController.getApplicationLooper() Returns the Looper associated
// with the application thread that's used to access the player and on which
// player events are received.
MediaItem myMediaItem;
MyMediaControllerCallback myMediaControllerCallback;
public MyMediaController(Context appContext, MyMediaControllerCallback myMediaControllerCallback) {
this.appContext = appContext;
this.myMediaControllerCallback = myMediaControllerCallback;
createMediaController(appContext);
}
// user send play media
public void play() {
if (myMediaController!=null) {
// pause does not state player i.e. Player.STATE_READY
if (myMediaController.getPlaybackState()!=Player.STATE_READY) {
MediaMetadata myMediaMetadata = new MediaMetadata.Builder().
setTitle(kidungName).
setStation(kidungName).
setDisplayTitle(kidungName).
//setArtworkUri(myUri). // to set background icon may any size
build();
Uri myUri = Uri.parse("android.resource://com.my.package/" + R.raw.wargasari);
myMediaItem = new MediaItem.Builder().
setMediaMetadata(myMediaMetadata).
setUri(myUri).
build();
myMediaController.setMediaItem(myMediaItem); // addMediaItem(MediaItem myMediaItem)
myMediaController.prepare();
myMediaController.setPlayWhenReady(true);
} else {
//Log.e("dedetok", "MyMediaController start() direct play"); // debug
myMediaController.play();
}
}
}
// user call pause
// pause does not change onPlaybackStateChanged!
// Player.STATE_READY 3
public void pause() {
if (myMediaController!=null) {
myMediaController.pause();
}
}
/*
* user exit application, must stop player
* after stop() Player.STATE_IDLE / 1
* Player.STATE_IDLE but not trigger onIsPlayingChanged(boolean isPlaying) to false, BUG?
*/
public void stopPlayer() {
myMediaController.stop();
}
// 20240422
public void setRepeat(boolean booleanRepeat) {
if (myMediaController!=null) {
if (booleanRepeat) {
myMediaController.setRepeatMode(Player.REPEAT_MODE_ONE);
} else {
myMediaController.setRepeatMode(Player.REPEAT_MODE_OFF);
}
}
}
// call when exit application
public void releaseMediaSession() {
myMediaController.release();
}
/*
* ContextCompat.getMainExecutor() instead of MoreExecutors.directExecutor()
* in case you run your service in another process than the UI.
*/
private void createMediaController(Context appContext) {
//Log.e("dedetok","createMediaController step 1"); // debug
// https://developer.android.com/media/media3/session/connect-to-media-app#create-controller
// step 1 create SessionToken
SessionToken mySessionToken =
new SessionToken(appContext,
new ComponentName(appContext, DedetokMediaSessionService.class));
//Log.e("dedetok","createMediaController step 2"); // debug
// step 2 build MediaController using SessionToken
ListenableFuture<MediaController> myListenableFutureMediaController =
new MediaController.Builder(appContext, mySessionToken).buildAsync();
myListenableFutureMediaController.addListener(() -> {
// MediaController is available here with myListenableFutureMediaController.get()
try {
//Log.e("dedetok","try to get Media Controller"); // debug
myMediaController = myListenableFutureMediaController.get();
myMediaController.addListener(myPlayerListener);
} catch(ExecutionException | InterruptedException e) {
//Log.e("dedetok", e.getMessage()); // debug
}
}, MoreExecutors.directExecutor());
}
/*
* Playback listener 20240229
*/
Player.Listener myPlayerListener = new Player.Listener() {
@Override
public void onPlaybackStateChanged(int playbackState) {
Player.Listener.super.onPlaybackStateChanged(playbackState);
if (playbackState == Player.STATE_IDLE)
Log.e("dedetok", "Player.STATE_IDLE 1"); // debug
else if (playbackState == Player.STATE_BUFFERING)
Log.e("dedetok", "Player.STATE_BUFFERING 2"); // debug
else if (playbackState == Player.STATE_READY)
Log.e("dedetok", "Player.STATE_READY 3"); // debug
else if (playbackState == Player.STATE_ENDED)
Log.e("dedetok", "Player.STATE_ENDED 4"); // debug
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
Log.e("dedetok", "onIsPlayingChanged change to "+isPlaying); // debug
myMediaControllerCallback.isPlaying(isPlaying);
}
};
public interface MyMediaControllerCallback {
// from MyMediaController change player status
void isPlaying(boolean isPlaying);
}
}
java -> MyDataProvider.java
package com.dedetok.wargasari;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
import android.content.Context;
import android.content.res.TypedArray;
import com.dedetok.wargasari.myservice.MyMediaController;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyDataProvider implements MyMediaController.MyMediaControllerCallback {
MyMediaController myMediaController;
Context appContext;
MyDataProviderCallback myDataProviderCallback;
ExecutorService executor;
MyAdapter myAdapter;
boolean isPlaying;
public MyDataProvider(Context appContext) {
this.appContext = appContext;
this.myDataProviderCallback = (MyDataProvider.MyDataProviderCallback) appContext;
myMediaController = new MyMediaController(appContext, this);
}
/*
* async task to create recyclerview.adapter
* load array text into adapter
*/
public void loadKidungArrayAdapter(TypedArray availFontSize, int cTextSizeState, float defaultTextSize) {
if (executor == null) {
executor = Executors.newSingleThreadExecutor();
}
Runnable myRun = new Runnable() {
@Override
public void run() {
myAdapter = new MyAdapter(getTexts(), COMPLEX_UNIT_SP, availFontSize.getFloat(cTextSizeState, defaultTextSize));
myDataProviderCallback.setKidungArrayAdapter(myAdapter);
executor.shutdown();
executor = null;
}
};
executor.execute(myRun);
}
/*
* convert array string into arraylist from resources
*/
private ArrayList<StanzaContainer> getTexts() {
String[] kidung = appContext.getResources().getStringArray(R.array.kidung);
int numKidung = kidung.length;
ArrayList<StanzaContainer> myStanza = new ArrayList<>();
for (int i=0;i<numKidung; i++) {
int j=i+1;
String stanzaTitle = appContext.getResources().getString(R.string.bait)+" "+j;
StanzaContainer myTmpStanza = new StanzaContainer(stanzaTitle, kidung[i]);
myStanza.add(myTmpStanza);
}
//Log.i("array",Integer.toString(kidunglist.length));
return myStanza;
}
/*
* user play media
* send command play to media controller
*/
public void play() {
if (myMediaController!=null) {
//Log.e("dedetok", "MyDataProvider Playing"); // debug
// player does not instanly ready
myMediaController.play();
}
}
// user call pause
public void pause() {
if (myMediaController!=null) {
myMediaController.pause();
}
}
// player exit application must stop player
public void stopPlayer() {
myMediaController.stopPlayer();
}
// 20240422
public void setRepeat(boolean booleanRepeat) {
myMediaController.setRepeat(booleanRepeat);
}
// call when application destroy
public void releaseMediaSession() {
myMediaController.releaseMediaSession();
}
// user change font size
public void changeFontSize(float fSize) {
myAdapter.changeFontSize(COMPLEX_UNIT_SP,fSize);
}
/*
* implementation MyMediaControllerCallback START
*/
// from MyMediaController change player status
@Override
public void isPlaying(boolean isPlaying) {
this.isPlaying=isPlaying;
myDataProviderCallback.isPlaying(isPlaying);
}
/*
* implementation MyMediaControllerCallback START
*/
public interface MyDataProviderCallback {
// from MyDataProvider to update recyclerview
void setKidungArrayAdapter(MyAdapter myAdapter);
void isPlaying(boolean isPlaying);
}
}
java -> MainActivity.java
package com.dedetok.wargasari;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.view.View;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;
public class MainActivity extends AppCompatActivity implements
MyDataProvider.MyDataProviderCallback {
int cTextSizeState = 0;
TypedArray availFontSize;
float defaultTextSize = 18;
RecyclerView myRecyclerView;
MyDataProvider myDataProvider;
AppCompatImageButton bPlayPause;
/*
* ## activity life cycle 1 ##
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myDataProvider = new MyDataProvider(this);
// add version number from gradle into activity title
try {
PackageInfo myPackage = getPackageManager().getPackageInfo(getPackageName(),0);
String myTitle = getString(R.string.app_name)+" "+myPackage.versionName;
setTitle(myTitle);
} catch (PackageManager.NameNotFoundException e) {
// Do Nothing, pass process
}
// ads:adUnitId ca-app-pub-0220748109202708/5649213488 : AdView in Layout
// app id ca-app-pub-0220748109202708~2695747083 : AndroidManifest.xml
AdView mAdView = findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
// recyclerview
availFontSize = getResources().obtainTypedArray(R.array.my_font_size);
myRecyclerView = findViewById(R.id.my_recycler_view);
LinearLayoutManager myLinearLayout = new LinearLayoutManager(getApplicationContext());
myLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
myRecyclerView.setLayoutManager(myLinearLayout);
// adapter will created on async task
// button zoom text
AppCompatButton bZoomOut = findViewById(R.id.b_zoom_out);
bZoomOut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (cTextSizeState>0) {
cTextSizeState--;
if (myDataProvider!=null) {
myDataProvider.changeFontSize(availFontSize.getFloat(cTextSizeState, defaultTextSize));
}
}
}
});
AppCompatButton bZoomIn = findViewById(R.id.b_zoom_in);
bZoomIn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (cTextSizeState+1<availFontSize.length()) {
cTextSizeState++;
if (myDataProvider!=null) {
myDataProvider.changeFontSize(availFontSize.getFloat(cTextSizeState, defaultTextSize));
}
}
}
});
// onBackPressed() deprecated -> OnBackPressedCallback & getOnBackPressedDispatcher()
// dispatcher
OnBackPressedDispatcher myOnbackPressedDispatcher = getOnBackPressedDispatcher();
// callback on back key pressed
OnBackPressedCallback myBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
// Log.e("dedetok", "handleOnBackPressed()"); //debug
AlertDialog.Builder myAliertDialog = new AlertDialog.Builder(
MainActivity.this);
myAliertDialog.setTitle(R.string.dialog_title).
setMessage(R.string.dialog_message).
setIcon(android.R.drawable.ic_dialog_alert).
setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// finish() will execute onDestroy()
finish();
}
}).
setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// do nothing
}
}).
show();
}
};
// add callback to dispatcher
myOnbackPressedDispatcher.addCallback(this, myBackPressedCallback);
// 20240328
bPlayPause = findViewById(R.id.play_button);
bPlayPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (myDataProvider!=null) {
if (myDataProvider.isPlaying) {
myDataProvider.pause();
} else {
myDataProvider.play();
}
}
}
});
//20240422
SwitchCompat sRepeat = findViewById(R.id.switch_repeat);
sRepeat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean booleanRepeat) {
myDataProvider.setRepeat(booleanRepeat);
}
});
}
// ## activity life cycle 2 ##
// onStart()
@Override
protected void onStart() {
super.onStart();
// 20240329
// Android N (7.0) or latter MediaPlayer should create in OnStart
// TypedArray availFontSize, int cTextSizeState, float defaultTextSize)
// create Adapter in async task
myDataProvider.loadKidungArrayAdapter(availFontSize, cTextSizeState, defaultTextSize);
}
/*
* ## activity life cycle 3 ##
* activity process 3
* onResume()
* @Override onRestoreInstanceState(Bundle savedInstanceState) ?
*/
@Override
public void onResume() {
super.onResume();
}
/*
* ## activity life cycle 4 ##
* onPause()
* @Override onSaveInstanceState(Bundle outState) ?
*/
@Override
public void onPause() {
// do something before pause
//Log.e("dedetok","onPause"); // debug
super.onPause();
}
// ## activity life cycle 5 ##
@Override
protected void onStop() {
// 20240329
// Android N (7.0) or latter MediaPlayer should stop and release in OnStop
// do something before stop
//Log.e("dedetok","onStop"); // debug
super.onStop();
}
/*
* ## activity life cycle 6 ##
* clean up onDestroy()
*/
@Override
public void onDestroy() {
// Log.e("dedetok","onDestroy Activity"); // debug
// do something before destroy
//Log.e("dedetok","onDestroy"); // debug
// fix android 8, 9, 10, 11 to stop media and release resource
myDataProvider.stopPlayer();
myDataProvider.releaseMediaSession();
super.onDestroy();
}
/*
* impelementation MyDataProvider START
*/
// from MyDataProvider to update recyclerview
@Override
public void setKidungArrayAdapter(MyAdapter myAdapter) {
// can not direct update
myRecyclerView.post(new Runnable() {
@Override
public void run() {
myRecyclerView.setAdapter(myAdapter);
}
});
}
public void isPlaying(boolean isPlaying) {
bPlayPause.post(new Runnable() {
@Override
public void run() {
if (isPlaying) {
bPlayPause.setImageDrawable(getDrawable(android.R.drawable.ic_media_pause));
} else {
bPlayPause.setImageDrawable(getDrawable(android.R.drawable.ic_media_play));
}
}
});
}
/*
* impelementation MyDataProvider END
*/
}