Tuesday, March 5, 2024

Android java: behaviour state when configuration change (rotating mobile phone) Android N (7.0 / API 24)

After rotating mobilephone

  1. onConfigurationChanged
  2. onPause
  3. onSaveInstanceState
  4. onStop
  5. onDestroy

Following

  1. onStart
  2. onRestoreInstanceState
  3. onResume

NOTE: Important to keep state UI

  • Save any state in onSaveInstanceState
  • Restore InstanceState  in onRestoreInstanceState

To restore UI from InstanceState:

  • restore in onResume
  • if you use background service in OnStart, wait until it is finish. Easier way is UI handler -> handleMessage  

Friday, March 1, 2024

Source code Kramaning (com.dedetok.kramaning)

build.gradle.kts (Module: app)

plugins {
    id("com.android.application")
}

android {
    namespace = "com.dedetok.kramaning"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.dedetok.kramaning"
        minSdk = 24
        targetSdk = 34
        versionCode = 202401
        versionName = "2024.01"

        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")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    implementation ("com.google.android.gms:play-services-ads:22.6.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"/>

    <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.AppCompat.Light.NoActionBar"
        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>

        <!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
        <!-- AD App ID: ca-app-pub-0220748109202708~3978959687 -> AndroidManifest.xml -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-0220748109202708~3978959687"/>


    </application>

</manifest>

res -> values -> strings -> strings.xml

<resources>
    <string name="app_name">Kramaning</string>
    <string name="nav_open">Open</string>
    <string name="nav_close">Close</string>
    <string name="t_preparation">Preparation</string>
    <string name="t_trisandya">Trisandya</string>
    <string name="t_kramaning">Kramaning</string>
    <string name="t_metirta" translatable="false">Metirta &amp; Mebije</string>
</resources>

res -> values -> strings -> strings.xml (in)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Kramaning</string>
    <string name="nav_open">Buka</string>
    <string name="nav_close">Tutup</string>
    <string name="t_preparation">Persiapan</string>
    <string name="t_trisandya">Trisandya</string>
    <string name="t_kramaning">Kramaning</string>

</resources>

com.dedetok.kramaning.ScriptHindu.java

package com.dedetok.kramaning;

import java.util.ArrayList;

public class ScriptHindu {

    @SuppressWarnings("SpellCheckingInspection")
    private static final String[] textMantras = {
            "Om Ang Brahma Dupa Danta Ya Namah svaha||", // 0 dupa
            "Om Puspa Danta Ya Namah svaha||", // 1 flower
            "Om Waktra Sudayamam svaha||", // 2 mount
            "Om Sudayamam svaha, Om Ati Sudayamam svaha||", // 3 right hand
            "Om Prasada Stiti Sarira Suci Nirmalaya Namah svaha||", // 4 soul
            "Om Ang Namah, Om Ung Namah, Om Mang Namah||", // 5 pranayama
            "OM Bhur Bhubah Svah|\nTat savitur varenyam|\nBharga devasya dhimahi|\nDhiyo yo nah pracodayat||", // 6 traisandya 1
            "OM Narayana evedwam sarvam|\nYad butham Yac ca Bhavyam|\nNiskalanko niranjano nirvikalpo|\nNirakhyatah suddho deva eko|\nNarayana na dvitiyo asti kascit||", // 7 traisandya 2
            "OM Tvam siwah tvam mahadevah|\nIswarah paramesvarah|\nBrahma Visnuca rudrasca|\nPurusah parikirtitah||", // 8 traisandya 3
            "OM Papoham papokarmaham|\nPapatma papasambhavah|\nTrahi mam pundarikaksa|\nSabahyabyantarah sucih||", // 9 traisandya 4
            "OM Ksamasva mam mahadeva|\nSarvaprani hitangkara|\nMam moca sarva papebyah|\nPalayasva sada siva||", // 10 traisandya 5
            "OM Ksantavyah kayika dosah|\nKsantavyo vaciko mama|\nKsantavyo manasa dosah|\nTat pramadat ksamamsva mam||", // 11 traisandya 6
            "OM Canti Canti Canti||", // 12 traisandya 7
            "OM Atma tattvatma suddha ya mam svaha", // 13 kramaning 1
            "OM Adityah syah param jyoti|\nRakta teja namastute|\nSveta pangkaja madhyastha|\nBhaskara ya na namostute||\n\nOM Pranam ya bhaskaram dewam|\nSarwaklesa winasanam|\nParama ditya siwartam|\nBhukti mukti warapradam||\n\nOM Hrang hring sah parama ciwa raditya ya namah svaha||", // 14 kramaning 2
            "OM Namah deva ya adhista na ya|\nSarvavyapi nasiwaya|\nPadmasana eka pratisthaya|\nArdhanaresvar ya namah svaha||\n\nOM Brahma visnu isvara devam|\nJiwatmanan trilokanam|\nSarva jagat pratisthanam|\nSarva roga vinurcitam|\nSarva roga vinasamam|\nVigna desa vinasanam||\n\nOM Namah siva ya||", // 15 kramaning 3
            "OM Anugraha manoharam|\nDwva data nugrahakam|\nHyarcanam sarva pujanam|\nNamah sava nugrahakam||\n\nDeva devi maha sidhi|\nYajnangga nimalatmaka|\nLaksmi siddhisca dirgahayu|\nNirvigram suka vrdisca|\n\nOM Ggring anugahraha ya namah svaha|\nOM Gring anugraha ya namah svaha||\n\nOm Ayur verdi yaso verdi|\nVredi pradnya suka sriyam|\nDhrama santana verdisca|\nSantute sapta vrdayah||\n\nYata meru sthita devam|\nYawat gangga maha tale|\nCandrakau ganggane tawat|\nTattavatma wijayam bawat||\n\nOM Dhirgahayau tat astu svaha|\nOM Awignam astu tat astu svaha|\nOM Subahamastu tat astu svaha|\nOM Sukham bhavantu sruyam bhavantu purnam bhavantu||\n\nOm Sapta verdhi tat astu svaha|\nAntyestih parama pindam|\nAntyestih dewa mersittah|\nSwarvatih eka stnawa|\nSarwva dewa sukha pradadna|\nYa namo namah svaha||", // 16 kramaning 4
            "OM Maha deva devi suksma paramachintya ya nama svaha||", // 17 kramaning 5
            "Om ang brahma amrtha ya namah, Om ung wisnu amrtha ya namah, Om mang isvara amrtha ya namah||", // 18 tirta 1
            "Om Sarira paripurna ya namah, om ang ung mang sarira sudha, Pramantya ya namah, Om ung ksama sampurna ya namah||", // 19 tirta drink 2
            "Om Siva Amertha ya namah, om sadha siva amertha ya namah, Om parama siva amertha ya namah||", // 20 tirta wash face 3
            "Om çriyam bhawantu, Om sukam bhawantu, Om ksama sampurna ya namah svaha" // 21 Bije
    };

    private static final String[] titleMantrasID = {
            "Penyucian Dupa/Api", // 0 dupa
            "Penyucian Bunga", // 1 flower
            "Penyucian Mulut", // 2 mount
            "Penyucian Tangan", // 3 right hand
            "Penyucian Jiwa", // 4 soul
            "Pranayama", // 5 pranayama
            "Trisandya bait 1", // 6 traisandya 1
            "Trisandya bait 2", // 7 traisandya 2
            "Trisandya bait 3", // 8 traisandya 3
            "Trisandya bait 4", // 9 traisandya 4
            "Trisandya bait 5", // 10 traisandya 5
            "Trisandya bait 6", // 11 traisandya 6
            "Trisandya bait 7", // 12 traisandya 7
            "Kramaning bait 1", // 13 kramaning 1
            "Kramaning bait 2", // 14 kramaning 2
            "Kramaning bait 3", // 15 kramaning 3
            "Kramaning bait 4", // 16 kramaning 4
            "Kramaning bait 5", // 17 kramaning 5
            "Tirta dikepala", // 18 tirta 1
            "Tirta diminum", // 19 tirta drink 2
            "Tirta diraup", // 20 tirta wash face 3
            "Bije didahi, dtenggorokan &dimakan" // 21 Bije
    };

    private static final String[] titleMantrasEN = {
            "Penyucian Dupa/Api", // 0 dupa
            "Penyucian Bunga", // 1 flower
            "Penyucian Mulut", // 2 mount
            "Penyucian Tangan", // 3 right hand
            "Penyucian Jiwa", // 4 soul
            "Pranayama", // 5 pranayama
            "Trisandya bait 1", // 6 traisandya 1
            "Trisandya bait 2", // 7 traisandya 2
            "Trisandya bait 3", // 8 traisandya 3
            "Trisandya bait 4", // 9 traisandya 4
            "Trisandya bait 5", // 10 traisandya 5
            "Trisandya bait 6", // 11 traisandya 6
            "Trisandya bait 7", // 12 traisandya 7
            "Kramaning bait 1", // 13 kramaning 1
            "Kramaning bait 2", // 14 kramaning 2
            "Kramaning bait 3", // 15 kramaning 3
            "Kramaning bait 4", // 16 kramaning 4
            "Kramaning bait 5", // 17 kramaning 5
            "Tirta dikepala", // 18 tirta 1
            "Tirta diminum", // 19 tirta drink 2
            "Tirta diraup", // 20 tirta wash face 3
            "Bije didahi, dtenggorokan &dimakan" // 21 Bije
    };

    public static ArrayList<MantraContainer> getMantras(String myLang) {
        ArrayList<MantraContainer> tmpMantras= new ArrayList<MantraContainer>();
        for (int i=0;i<textMantras.length;i++) {
            MantraContainer tmpContainer = new MantraContainer();
            if (myLang.equalsIgnoreCase("id")) {
                tmpContainer.myTitle = titleMantrasID[i];
            } else {
                tmpContainer.myTitle = titleMantrasEN[i];
            }
            tmpContainer.myMantra = textMantras[i];
            tmpMantras.add(tmpContainer);
        }
        return tmpMantras;
    }
}

com.dedetok.kramaning.MantraContainer.java

package com.dedetok.kramaning;

public class MantraContainer {
    public String myTitle;
    public String myMantra;

}

com.dedetok.kramaning.MyAdapter.java

package com.dedetok.kramaning;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.appcompat.widget.AppCompatTextView;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter {

    private ArrayList<MantraContainer> myMantraAll;
    private ArrayList<MantraContainer> myMantraShow;

    public MyAdapter(String myLang) {
        super();
        this.myMantraAll = ScriptHindu.getMantras(myLang);
        this.myMantraShow = this.myMantraAll;

    }

    /*
     * mandatary extends RecyclerView.Adapter
     */
    @NonNull
    @Override
    public MyAdapter.MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View myView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.layout_viewitem, parent, false);
        return new MyAdapter.MyHolder (myView);
    }

    /*
     * mandatary extends RecyclerView.Adapter
     */
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        MantraContainer tmpMantraContainer = myMantraShow.get(position);
        ((MyHolder) holder).tvTitle.setText(tmpMantraContainer.myTitle);
        ((MyHolder) holder).tvMantra.setText(tmpMantraContainer.myMantra);
    }

    /*
     * mandatary extends RecyclerView.Adapter
     */
    @Override
    public int getItemCount() {
        return myMantraShow.size();
    }

    public void updateList(int myMin, int myMax) {
        myMantraShow = new ArrayList<MantraContainer>();
        for (int i=myMin;i<myMax;i++) {
            this.myMantraShow.add(this.myMantraAll.get(i));
        }
        notifyDataSetChanged();
    }

    private class MyHolder extends RecyclerView.ViewHolder {
        //public TextView tvTitle, tvMantra;
        public AppCompatTextView tvTitle, tvMantra;

        public MyHolder(@NonNull View itemView) {
            super(itemView);
            tvTitle = itemView.findViewById(R.id.tv_title);
            tvMantra = itemView.findViewById(R.id.tv_mantra);
            // unnecessary, use android:textSize="24sp"
            // if android sdk api<28 (android 8.0)
            /*
            if (Build.VERSION.SDK_INT<28) {
                tvTitle.setTextSize(24);
                tvMantra.setTextSize(24);
            }
            */
        }
    }
}

com.dedetok.kramaning.FragmentMain.java

package com.dedetok.kramaning;

import android.os.Bundle;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class FragmentMain extends Fragment {

    private String localLang="id";

    private MyAdapter myAdapter;

    public FragmentMain(String localLang) {
        super(R.layout.layout_fragment_main);
        this.localLang = localLang;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        //recyclerview
        RecyclerView myRecyclerView = view.findViewById(R.id.my_recycler_view);
        LinearLayoutManager myLinearLayout = new LinearLayoutManager(getActivity().getApplicationContext());
        myLinearLayout.setOrientation(LinearLayoutManager.VERTICAL);
        myRecyclerView.setLayoutManager(myLinearLayout);
        myAdapter = new MyAdapter(localLang);
        myRecyclerView.setAdapter(myAdapter);

    }


    public void updateList(int mFirst, int mLast) {
        myAdapter.updateList(mFirst, mLast);
    }


}

com.dedetok.kramaning.MainActivity.java

package com.dedetok.kramaning;

import androidx.annotation.NonNull;

import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;

import android.os.Bundle;
import android.view.MenuItem;
import com.google.android.material.navigation.NavigationView;

import java.util.Locale;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdView;


public class MainActivity extends AppCompatActivity {

    private DrawerLayout myDrawerLayout;

    FragmentMain myFragmentMain;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // get locale language
        String langLocale = Locale.getDefault().getLanguage();
        //Log.e("dedetok", langLocale); //debug

        // TODO change admob id before publish, put back to edit
        // This CODE make slow down startup
        /*
        MobileAds.initialize(this, new OnInitializationCompleteListener() {
            @Override
            public void onInitializationComplete(@NonNull InitializationStatus initializationStatus) {

            }
        });
        */
        // AD Unit ID: ca-app-pub-0220748109202708/9185950681 -> AdView in Layout
        // AD App ID: ca-app-pub-0220748109202708~3978959687 -> AndroidManifest.xml

        AdView mAdView = findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder().build();
        mAdView.loadAd(adRequest);

        Toolbar myToolbar = findViewById(R.id.my_toolbar);
        setSupportActionBar(myToolbar);

        myDrawerLayout = findViewById(R.id.my_drawerlayout);
        ActionBarDrawerToggle myActionBarDrawerToggle = new ActionBarDrawerToggle(this, myDrawerLayout, myToolbar, R.string.nav_open, R.string.nav_close);
        myDrawerLayout.addDrawerListener(myActionBarDrawerToggle);
        myActionBarDrawerToggle.syncState();

        NavigationView myNavigationView = findViewById(R.id.my_navigationview);
        myNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                if (myFragmentMain==null) {
                    //Log.e("dedetok", "null create new"); //debug
                    myFragmentMain = new FragmentMain(langLocale);
                }
                //Log.e("dedetok", item.getTitle().toString()); //debug
                int idItem = item.getItemId();
                if (idItem == R.id.preparation) {
                    //Log.e("dedetok", "Preparation"); //debug
                    myFragmentMain.updateList(0,5);
                } else if (idItem == R.id.trisandya) {
                    myFragmentMain.updateList(6,12);
                } else if (idItem == R.id.kramaning) {
                    myFragmentMain.updateList(13,17);
                } else if (idItem == R.id.metirta) {
                    myFragmentMain.updateList(18,21);
                }
                // close drawer if open
                if (myDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                    myDrawerLayout.close();
                }
                return false;
            }
        });

        myFragmentMain = new FragmentMain(langLocale);
        getSupportFragmentManager().beginTransaction().
                add(R.id.my_fragmentcontainerview, myFragmentMain, null).
                commit();
        //Log.e("dedetok", "create fragment end"); // debug will execute after commit
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (myFragmentMain!=null) {
            myFragmentMain.updateList(0,5);
        }
        //Log.e("dedetok", Integer.toString(Build.VERSION.SDK_INT)); // debug version android 7.0 level api 24
    }
}


Wednesday, February 28, 2024

My Radio List Summary

Mengembalikan kejayaan stasiun radio tanpa batas 
 

Australia 10 channels/stations
Brazil 3 channels/stations
Brunei Darussalam 6 channels/stations
China 3 channels/stations
France 5 channels/stations
Germany 3 channels/stations
Greece 3 channels/stations
India 5 channels/stations
Indonesia 127 channels/stations
Italy 12 channels/stations
Japan 2 channels/stations
Korea, Republic of 3 channels/stations
Lao People's Democratic Republic 2 channels/stations
Libya 2 channels/stations
Malaysia 6 channels/stations
Morocco 2 channels/stations
Netherlands, Kingdom of the 2 channels/stations
Palestine, State of 1 channels/stations
Paraguay 11 channels/stations
Peru 4 channels/stations
Philippines 3 channels/stations
Poland 3 channels/stations
Russian Federation 1 channels/stations
Singapore 8 channels/stations
Slovakia 3 channels/stations
Switzerland 4 channels/stations
Thailand 5 channels/stations
Ukraine 3 channels/stations
United Kingdom of Great Britain and Northern Ireland 2 channels/stations
United States of America 12 channels/stations
Viet Nam 2 channels/stations






------------------------------------------------------------

"Lagi keluar kota siaran radio diluar jangkauan, pakai Radio Walkman. Dengan Radio Walkman, sepanjang masih ada sinyal HP, radio kesayangan tetap bisa didengar."

"Lagi ke negeri orang tapi lagi pengen radio home base. Dengan Radio Walkman, sepanjang masih ada sinyal HP, radio kesayangan tetap bisa didengar."

Peringatan: Radio Walkman menggunakan data selular. Bila memungkinkan gunakan internet yang unlimited (unmetered).

Do not hesitate to request your radio station in comments. Country City Radio_Station_Name

Wednesday, February 21, 2024

Android java: Tutorial Media3 ExoPlayer & MediaSessionService

Note: For personal reference using Media3 ExoPlayer & MediaSessionService in java

project: com.dedetok.tutorialmediaplayerbackgroundservice

build.gradle.kts (Module: app) 

dependencies {
...
    // For exposing and controlling media sessions
    implementation("androidx.media3:media3-session:1.2.1")
    // For media playback using ExoPlayer
    implementation("androidx.media3:media3-exoplayer:1.2.1")
    // For HLS playback support with ExoPlayer
    implementation("androidx.media3:media3-exoplayer-hls:1.2.1")
...
}

package com.dedetok.tutorialmediaplayerbackgroundservice.myservice.DedetokMediaSessionService.java

package com.dedetok.tutorialmediaplayerbackgroundservice.myservice;

import android.content.Intent;
import android.util.Log;
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;

/*
 * MediaSession 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() {
        super.onDestroy();
        myMediaSession.getPlayer().release();
        myMediaSession.release();
        myMediaSession = null;
    }

    /*
     * extends MediaSessionService
     */
    @Nullable
    @Override
    public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
        return myMediaSession;
    }
}

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">
    <!-- internet -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- foreground service -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <!-- target API 34 and above -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
...
<!-- omited -->
...
        <!-- android:foregroundServiceType="mediaPlayback" target API >=29 -->
        <!-- action android:name="android.media.browse.MediaBrowserService" backward compatible with client apps -->
        <!-- action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" Declare legacy support for voice actions -->
        <service
            android:name=".myservice.DedetokMediaSessionService"
            android:exported="true"
            android:foregroundServiceType="mediaPlayback"
            android:icon="@mipmap/ic_launcher"
            >
            <intent-filter>
                <action android:name="androidx.media3.session.MediaSessionService" />
                <action android:name="android.media.browse.MediaBrowserService" />
                <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
            </intent-filter>
        </service>
    </application>

</manifest>

package com.dedetok.tutorialmediaplayerbackgroundservice.RadioListItem.java

package com.dedetok.tutorialmediaplayerbackgroundservice;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

public class RadioListItem implements Parcelable {
    public String mCountry; //0
    public String mCity; //1
    public String mRadio; //2
    public String mIcon; //3
    public String mStreamURL; //4

    public RadioListItem() {
    }

    protected RadioListItem(Parcel in) {
        mCountry = in.readString();
        mCity = in.readString();
        mRadio = in.readString();
        mIcon = in.readString();
        mStreamURL = in.readString();
    }

    public static final Creator<RadioListItem> CREATOR = new Creator<RadioListItem>() {
        @Override
        public RadioListItem createFromParcel(Parcel in) {
            return new RadioListItem(in);
        }

        @Override
        public RadioListItem[] newArray(int size) {
            return new RadioListItem[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel parcel, int i) {
        parcel.writeString(mCountry);
        parcel.writeString(mCity);
        parcel.writeString(mRadio);
        parcel.writeString(mIcon);
        parcel.writeString(mStreamURL);
    }
}

package com.dedetok.tutorialmediaplayerbackgroundservice.MainActivity.java

package com.dedetok.tutorialmediaplayerbackgroundservice;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.dedetok.tutorialmediaplayerbackgroundservice.myservice.DedetokMediaSessionService;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutionException;

/*
 * MediaController
 * UI
 */
public class MainActivity extends AppCompatActivity {
    MediaController myMediaController = null;
    AppCompatButton bPlay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bPlay = findViewById(R.id.b_play);
        bPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // https://stackoverflow.com/questions/74035158/android-media3-session-controller-playback-not-starting
                // https://stackoverflow.com/questions/77039284/how-to-modify-the-auto-generated-media3-notification-java
                if (myMediaController!=null) {
                    Log.e("dedetok", "MediaController set MediaItem"); // debug
                    RadioListItem myRadio = new RadioListItem();
                    myRadio.mStreamURL = "https://stream-node0.rri.co.id/streaming/25/9125/voi.mp3";
                    myRadio.mRadio = "RRI VOI";
                    myRadio.mIcon = "https://www.rri.co.id/assets/v2/img/pro6.png";
                    String mRadioTitle = "Playing "+myRadio.mRadio;
                    Log.e("dedetok", "MediaController prepare and play"); // debug
                    Uri myUri = Uri.parse(myRadio.mIcon);
                    MediaMetadata myMediaMetadata = new MediaMetadata.Builder().
                            setTitle(myRadio.mRadio).
                            setStation(myRadio.mRadio).
                            setDisplayTitle(mRadioTitle).
                            //setArtworkUri(myUri). // to set background icon may any size
                            build();
                    //MediaItem myMediaItem = MediaItem.fromUri(myRadio.mStreamURL); // original
                    MediaItem myMediaItem = new MediaItem.Builder().
                            setMediaMetadata(myMediaMetadata).
                            setUri(Uri.parse(myRadio.mStreamURL)).
                            build();
                    myMediaController.setMediaItem(myMediaItem);
                    myMediaController.prepare();
                    myMediaController.setPlayWhenReady(true);
                    Log.e("dedetok", "MediaController prepare and play"); // debug
                }
            }
        });

    }

    /*
     * ## activity life cycle 2 ##
     * Android N (7.0) create Player
     */
    @Override
    protected void onStart() {
        super.onStart();

        // start services and get MediaController
        createMediaController();

    }


    /*
     * ## activity life cycle 5 ##
     * Android N (7.0) release Player
     */
    @Override
    protected void onStop() {
        super.onStop();
        // THIS WILL STOP BACKGROUND PLAYER
        //myMediaController.stop();
        //Log.e("dedetok", "onStaop stoping player"); // debug
    }


    /*
     * ContextCompat.getMainExecutor() instead of MoreExecutors.directExecutor()
     * in case you run your service in another process than the UI.
     */
    private void createMediaController() {
        // https://developer.android.com/media/media3/session/connect-to-media-app#create-controller
        // step 1 create SessionToken
        SessionToken mySessionToken =
                new SessionToken(this,
                        new ComponentName(this, DedetokMediaSessionService.class));
        // step 2 build MediaController using SessionToken
        ListenableFuture<MediaController> myListenableFutureMediaController =
                new MediaController.Builder(this, mySessionToken).buildAsync();
        myListenableFutureMediaController.addListener(() -> {
            // MediaController is available here with myListenableFutureMediaController.get()
            try {
                myMediaController = myListenableFutureMediaController.get();
                Log.e("dedetok", "get MediaController"); // debug
                if (myMediaController==null) {
                    Log.e("dedetok", "MediaController null"); // debug
                }
            } catch(ExecutionException e) {
                Log.e("dedetok", e.getMessage()); // debug
                e.printStackTrace(); // debug
            } catch(InterruptedException e) {
                Log.e("dedetok", e.getMessage()); // debug
                e.printStackTrace(); // debug
            }
        }, MoreExecutors.directExecutor());
    }

}



 

 

Sunday, February 11, 2024

Openssl s_client to verify SSL

Syntax general: openssl s_client [-connect host:port] [option]

 

Get and read openssl s_client output 

$ echo "Get HTTP/1.0" | openssl s_client google.com:443
CONNECTED(00000003)
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1                        <- verification chain 2 ok
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1                        <- verification chain 1 ok
depth=0 CN = *.google.com
verify return:1                        <- verification chain 0 ok
---
Certificate chain
 0 s:CN = *.google.com
   i:C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jan  9 06:25:08 2024 GMT; NotAfter: Apr  2 06:25:07 2024 GMT
 1 s:C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
   i:C = US, O = Google Trust Services LLC, CN = GTS Root R1
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Aug 13 00:00:42 2020 GMT; NotAfter: Sep 30 00:00:42 2027 GMT
 2 s:C = US, O = Google Trust Services LLC, CN = GTS Root R1
   i:C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
   a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 19 00:00:42 2020 GMT; NotAfter: Jan 28 00:00:42 2028 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
... <TRUNCATED> ...
-----END CERTIFICATE-----
subject=CN = *.google.com
issuer=C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 6833 bytes and written 396 bytes
Verification: OK                    <- handshake verification ok
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

Use openssl s_client to export certificate PEM into a file (output file: certfs.pem)

$ echo "Get HTTP/1.0" | openssl s_client -showcerts -connect google.com:443 </dev/null | sed -n -e '/-.BEGIN/,/-.END/ p' > certifs.pem
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.google.com
verify return:1
DONE

Get fingerprint SHA1 in byte

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin

Fingerprint SHA256 in byte

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256 -noout -in /dev/stdin

Fingerprint SHA256 in base64 encode

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com:443 | \
    openssl x509 -pubkey -noout | \
    openssl rsa -pubin -outform der | \
    openssl dgst -sha256 -binary | \
    openssl enc -base64 

To get service sertificates

$ echo "Get HTTP/1.0" | openssl s_client -connect google.com.com:443 -showcerts

For Let's Encrypt, here is official information about compatibility platforms (operating system, browser and java virtual machine,  <link>

Reference:

  • https://www.openssl.org/docs/man1.0.2/man1/openssl-s_client.html
  • https://www.baeldung.com/linux/ssl-certificates