Tuesday, June 25, 2024

java 17: tutorial network socket

NOTE for socket:

  1. Socket can not be use to read and send at the same time
  2. Only one state can be use in socket, client state send - server state read or vise versa.
  3. At server side, maintain state read and send for each connection accepted by serversocket.

Swing JFrame to start server and client

package com.dedetok;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;


import javax.swing.BoxLayout;
import javax.swing.JButton;

public class Test extends JFrame {

    private static final long serialVersionUID = 1L;
    private JPanel contentPane;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    Test frame = new Test();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    MyServerThread myServerThread;
    ExecutorService executor;
    MyClient myClient=null;

    /**
     * Create the frame.
     */
    public Test() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));

        setContentPane(contentPane);
        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
       
        JButton btnStartServer = new JButton("Start Server");
        contentPane.add(btnStartServer);
        btnStartServer.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                //System.out.println("Test"); // debug
                // TODO Auto-generated method stub
                if (executor==null) {
                    executor = Executors.newSingleThreadExecutor();
                    try {
                        if (myServerThread==null) {
                            System.out.println("Server null, create new"); // debug
                            myServerThread = new MyServerThread();
                            executor.submit(myServerThread);
                        }
                        //System.out.println("create thread"); // debug
                        //System.out.println("run thread"); // debug
                        if (executor.isTerminated()) {
                            System.out.println("is terminated"); // debug
                           
                        }
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                   
                }
            }
           
        });
       
        JButton btnStartClient = new JButton("Start Client");
        contentPane.add(btnStartClient);
        btnStartClient.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                try {
                    /*
                    if (myClient== null) {
                        myClient = new MyClient();
                    }
                    myClient.sendCommand();
                    */

                    myClient = new MyClient();
                    myClient.sendCommand();
                   
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
           
        });
    }

}

Server part

package com.dedetok;


import java.io.IOException;
import java.io.InputStream;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServerThread implements Runnable {

    ServerSocket myServerSocket;
    //InetAddress myServerInetAddress;
    int myServerPort = 54321;
    
    public MyServerThread() throws IOException {
        myServerSocket = new ServerSocket(myServerPort);
    }
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {

            Socket myClientSocket;
            try {
               
                myClientSocket = myServerSocket.accept();
               
                /*
                InetAddress inetAddress = myClientSocket.getInetAddress();
                System.out.println("receive from client: "+inetAddress.toString()); // debug
               
                InputStream myInputStream = myClientSocket.getInputStream();
                ObjectInputStream ois = new ObjectInputStream(myInputStream);
               
                String sClient;

                sClient = (String) ois.readObject();

                System.out.println("From Client: "+sClient); // debug
                sClient = "Echo: "+sClient;

                OutputStream myOutputStream = myClientSocket.getOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(myOutputStream);
                oos.writeObject(sClient);
                oos.flush();


                //ois.close();
                //oos.close();
                myClientSocket.close();
                */
               
                ClientHandler clientHandler = new ClientHandler(myClientSocket);
                clientHandler.run();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    class ClientHandler implements Runnable {
        Socket socket;
        ClientHandler(Socket socket) {
            this.socket = socket;
        }
       
        @Override
        public void run() {
            // TODO Auto-generated method stub
            InetAddress inetAddress = socket.getInetAddress();
            System.out.println("receive from client: "+inetAddress.toString()); // debug
           
            InputStream myInputStream;
            try {
                myInputStream = socket.getInputStream();
                ObjectInputStream ois = new ObjectInputStream(myInputStream);
                OutputStream myOutputStream = socket.getOutputStream();

                ObjectOutputStream oos = new ObjectOutputStream(myOutputStream);
           
                boolean loopMsg = true;

                while (loopMsg) {
                    String sClient = (String) ois.readObject();
                    if (sClient.equals("END")) {
                        loopMsg = false;
                    }
                    System.out.println("From Client: "+sClient); // debug
                    sClient = "Echo: "+sClient;
    
                    oos.writeObject(sClient);
                    oos.flush();
                   
                }


            //ois.close();
            //oos.close();
                socket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
       
    }

}

Client part

package com.dedetok;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class MyClient {

    ExecutorService executor;
    boolean isRun = false;
    
    public MyClient() throws IOException {
        executor  = Executors.newSingleThreadExecutor();

    }
    
    public void sendCommand() {
        if (!isRun) {
            executor.submit(myRun);
            isRun = true;
        }

    }
    
    int i=0;
    String str = "Hello ";
    
    Runnable myRun = new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            //System.out.println("Running thread client"); // debug
            try {

                /*
                System.out.println("create socket"); // debug
                Socket mySocket = new Socket("127.0.0.1", 54321);
               
                OutputStream myOutputStream = mySocket.getOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(myOutputStream);

               
                    System.out.println("send "+i); // debug

                        oos.writeObject("END");
                    oos.writeObject(str+1);
                    oos.flush();
                   
                    InputStream myInputStream = mySocket.getInputStream();
                    ObjectInputStream ois = new ObjectInputStream(myInputStream);
                    System.out.println("receive "+i); // debug
                    String rStr = (String) ois.readObject();
                    System.out.println(rStr); // debug
                   
                   
                System.out.println("end client"); // debug
                //mySocket.close();
               
                //ois.close();
                //oos.close();
                mySocket.close();
                i++;
                */
               
                System.out.println("create socket"); // debug
                Socket mySocket = new Socket("127.0.0.1", 54321);
               
                OutputStream myOutputStream = mySocket.getOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(myOutputStream);

                InputStream myInputStream = mySocket.getInputStream();
                ObjectInputStream ois = new ObjectInputStream(myInputStream);
               
                while (i<10) {
                    String str = "send "+i;
                    System.out.println(str); // debug

                    if (i==9)
                        str="END";
                    oos.writeObject(str);                       
                    oos.flush();
                   

                    System.out.println("receive "+i); // debug
                    String rStr = (String) ois.readObject();
                    System.out.println(rStr); // debug
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    //mySocket.close();
                   
                    //ois.close();
                    //oos.close();
                   
                    i++;
                }
                mySocket.close();
            } catch (UnknownHostException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            isRun = false;
        }
       
    };
}



Monday, June 3, 2024

Android java: request multiple permission READ_EXTERNAL_STORAGE & WRITE_EXTERNAL_STORAGE

Minimum API 24 (Android 7.0) 

Note:

  • Beginning with Android 4.4 (API level 19) it's no longer necessary for your app to request the WRITE_EXTERNAL_STORAGE permission to write to its own application-specific directories on external storage, which are provided by getExternalFilesDir(). However, the permission is required for API level 18 and lower. Example:
    File extDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
  • If the permission is a runtime permission or special permission, and if your app is installed on a device that runs Android 6.0 (API level 23) or higher, you must request the runtime permission or special permission yourself.
  • On devices that run Android 4.4 (API level 19) and higher, your app can interact with a documents provider, including external storage volumes and cloud-based storage, using the Storage Access Framework. This framework allows users to interact with a system picker to choose a documents provider and select specific documents and other files for your app to create, open, or modify.
  • To support media file access on devices that run Android 9 (API level 28) or lower, declare the READ_EXTERNAL_STORAGE permission and set the maxSdkVersion to 28. See reference 1.
  • If your app targets Android 10 (API level 29) or lower, you can temporarily opt out of scoped storage in your production app. If you target Android 10, however, you need to set the value of requestLegacyExternalStorage to true in your app's manifest file.

AndroidManifest.xml

...
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="28"
        />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28"
        />
...
    <!-- This attribute is "false" by default on apps targeting Android 10. -->
    <!-- android:requestLegacyExternalStorage="true" -->
    <application
        android:requestLegacyExternalStorage="true"
...

MainActivity.java

...
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);

        // android:maxSdkVersion="28" =  Build.VERSION_CODES.P
        if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
            checkMyPermission();
        }
...    int MY_CODE_REQUEST = 123;
    /*
     * 1. check permission
     */
    private void checkMyPermission() {
        // Build.VERSION.SDK_INT >= 23
        // https://riptutorial.com/android/example/23932/multiple-runtime-permissions-from-same-permission-groups
        ArrayList<String> sPermissionRequest = new ArrayList<>();
        int myRead = ContextCompat.checkSelfPermission(getApplicationContext()
                , READ_EXTERNAL_STORAGE);
        int myWrite = ContextCompat.checkSelfPermission(getApplicationContext()
                , WRITE_EXTERNAL_STORAGE);
        if (myRead != PackageManager.PERMISSION_GRANTED) {
            sPermissionRequest.add(getString(R.string.s_permission_read_storage));
        }
        if (myWrite != PackageManager.PERMISSION_GRANTED) {
            sPermissionRequest.add(getString(R.string.s_permission_write_storage));
        }

        if (sPermissionRequest.size()>0) {
            // NOT Granted
            String[] sPermissions = new String[sPermissionRequest.size()];
            for (int i=0;i<sPermissionRequest.size();i++) {
                sPermissions[i]=sPermissionRequest.get(i);
            }
            //sLog.e("dedetok", "sPermissionRequest.size()>0 size "+sPermissions.length); // debug
            requestPermissions(sPermissions, MY_CODE_REQUEST);
        }
    }

    /*
     * 2. receive permission status
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode==MY_CODE_REQUEST) {

            if (permissions.length>0) {
                ArrayList<String> myPermission = new ArrayList<>();
                for (int i=0;i<permissions.length;i++) {
                    //Log.e("dedetok", permissions[i]+" "+grantResults[i]); // debug
                    if (permissions[i].equals(getString(R.string.s_permission_read_storage)) &&
                            grantResults[i]!=PackageManager.PERMISSION_GRANTED) {
                        myPermission.add(READ_EXTERNAL_STORAGE);
                    }
                    if (permissions[i].equals(getString(R.string.s_permission_write_storage)) &&
                            grantResults[i]!=PackageManager.PERMISSION_GRANTED) {
                        myPermission.add(WRITE_EXTERNAL_STORAGE);
                    }
                }
                // Not granted, Request permissions on runtime

                String[] sRequestPermission = new String[myPermission.size()];
                for (int i=0;i<myPermission.size();i++) {
                    sRequestPermission[i] = myPermission.get(i);
                }

                //Log.e("dedetok", "requestCode==MY_CODE_REQUEST "+sRequestPermission.length); // debug
                requestPermissionLauncher.launch(sRequestPermission);
            }
        }
    }

    /*
     * 3. callback user permission
     * Register the permissions callback, which handles the user's response to the
     * system permissions dialog. Save the return value, an instance of
     * ActivityResultLauncher, as an instance variable.
     */
    private final ActivityResultLauncher<String[] > requestPermissionLauncher =
            registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions()
                    , new ActivityResultCallback() {
                        @Override
                        public void onActivityResult(Object objMap) {
                            //Log.e("dedetok", "onActivityResult "+o.toString()); // debug
                            if (objMap instanceof Map) {
                                StringBuilder mySB = new StringBuilder();
                                Map<String,Boolean> myMapPermission = (Map<String,Boolean>) objMap;
                                myMapPermission.forEach((permissionKey, permissionBoolean)->{
                                    //Log.e("dedetok", permissionKey+" "+READ_EXTERNAL_STORAGE+" "+permissionBoolean); // debug
                                    if (permissionKey.equals(READ_EXTERNAL_STORAGE) && !permissionBoolean) {
                                        mySB.append(READ_EXTERNAL_STORAGE+" ");
                                    }
                                    if (permissionKey.equals(WRITE_EXTERNAL_STORAGE) && !permissionBoolean) {
                                        mySB.append(WRITE_EXTERNAL_STORAGE+" ");
                                    }
                                });
                                //Log.e("dedetok", "mySB "+mySB.toString()); // debug
                                if (mySB.length()>0) {
                                    updateStatus(mySB.toString());
                                }
                            }
                        }
                    });

 References:

  1. https://developer.android.com/training/data-storage/shared/documents-files
  2. https://developer.android.com/training/permissions/declaring
  3. https://developer.android.com/guide/topics/manifest/uses-permission-element
  4. https://stackoverflow.com/questions/3093365/how-can-i-check-the-system-version-of-android
  5. https://developer.android.com/training/data-storage/use-cases

Friday, May 31, 2024

Privacy Policy for Event Presensi (com.dedetok.eventpresensi)


 


Sample Data (Copy to any spreadsheet and save as csv)

id_member name_member
100000000001 Rudolf Diesel
100000000002 Albert Einstein
100000000003 Alexander Graham Bell
100000000004 James Watt
100000000005 Alessandro Volta
100000000006 Thomas Alva Edison
100000000007 Samuel F. B. Morse
100000000008 Michael Faraday

Friday, May 24, 2024

Android Java: spinner in fragment

layout_fragment_me.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="match_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Fragment"
        android:textSize="38sp"
        />
    <Spinner
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/my_spinner"
        android:minHeight="50dp"
        />

    <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="HELLO BUTTON"
        />
</androidx.appcompat.widget.LinearLayoutCompat> 

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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >

    <androidx.fragment.app.FragmentContainerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/fragment_view_me"
        />
    <androidx.appcompat.widget.AppCompatTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Main Activity"
        android:textSize="30dp"
        />
</androidx.appcompat.widget.LinearLayoutCompat>

MyData.java

package com.dedetok.tutorialcustomdropdown;

public class MyData {
    public String name, phone;

    @Override
    public String toString() {
        return name+" "+phone;
    }
}

FragmentMe.java

package com.dedetok.tutorialcustomdropdown;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import java.util.ArrayList;

public class FragmentMe extends Fragment {

    ArrayList<MyData> arrayList = new ArrayList<>();

    public FragmentMe() {
        super(R.layout.layout_fragment_me);
        Log.e("dedetok", "Create Fragment ME"); // debug
    }

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

        init();
        Log.e("dedetok", "size "+arrayList.size()); // debug
        Spinner mySpinner = view.findViewById(R.id.my_spinner);
        ArrayAdapter<MyData> arrayAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, arrayList);
        Log.e("dedetok", "setAdapter"); // debug
        mySpinner.setAdapter(arrayAdapter);
        mySpinner.setOnItemSelectedListener(mySpinnerListener);

    }

    AdapterView.OnItemSelectedListener mySpinnerListener =
            new AdapterView.OnItemSelectedListener() {

                @Override
                public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                    if (parent.getItemAtPosition(pos) instanceof MyData) {
                        MyData myData = (MyData) parent.getItemAtPosition(pos);
                        // TODO
                    }
                }

                @Override
                public void onNothingSelected(AdapterView<?> adapterView) {
                    // Do nothing
                }
            };
    private void init() {
        MyData myData = new MyData();
        myData.name = "aaaa";
        myData.phone = "1111";
        arrayList.add(myData);
        myData = new MyData();
        myData.name = "bbbb";
        myData.phone = "2222";
        arrayList.add(myData);
        myData = new MyData();
        myData.name = "ccccc";
        myData.phone = "2233322";
        arrayList.add(myData);
        myData = new MyData();
        myData.name = "4ddddd";
        myData.phone = "444444";
        arrayList.add(myData);
    }
}

MainActivity.java

package com.dedetok.tutorialcustomdropdown;

import android.os.Bundle;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.FragmentTransaction;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        setContentView(R.layout.activity_main);
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();
        FragmentMe fragment = new FragmentMe();
        transaction.setReorderingAllowed(true);
        transaction.add(R.id.fragment_view_me, fragment);
        transaction.commit();
    }
}

Wednesday, May 15, 2024

Android Java: open activity for result to pick up / choose a file in Shared Folder - Download /storage/emulated/0/Download

Note:

Moving from startActivityForResult to ActivityResultLauncher to pickup file in Shared Folder - Download /storage/emulated/0/Download

Minimum Android 7 API 24 Targeted SDK Android 24 API 34

build.gradle.kts (Module :app)

plugins {
    alias(libs.plugins.android.application)
}

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

    defaultConfig {
        applicationId = "com.dedetok.tutorialsharedfolder"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        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(libs.appcompat)
    implementation(libs.material)
    implementation(libs.activity)
    implementation(libs.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.ext.junit)
    androidTestImplementation(libs.espresso.core)
}

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_EXTERNAL_STORAGE"
        android:maxSdkVersion="29"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="29"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

    <application
        android:requestLegacyExternalStorage="true"
        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.TutorialSharedFolder"
        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>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

package com.dedetok.tutorialsharedfolder;

import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;

import android.provider.DocumentsContract;

public class MainActivity extends AppCompatActivity {

    /*
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="29"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="29"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
    <application
        android:requestLegacyExternalStorage="true"

    /storage/emulated/0/Download

    MediaStore.Downloads (/storage/emulated/0/Download) Android 10 (API level 29) and higher,
     */


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

        /*
         * check perrmission
         */
        boolean isPermitted;
        String myPermissionRequired = android.Manifest.permission.READ_EXTERNAL_STORAGE;
        isPermitted = myCheckPermission(this, myPermissionRequired);
        Log.e("dedetok", "READ_EXTERNAL_STORAGE: "+isPermitted);

        myPermissionRequired = android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
        isPermitted = myCheckPermission(this, myPermissionRequired);
        Log.e("dedetok", "WRITE_EXTERNAL_STORAGE: "+isPermitted);
        myPermissionRequired = Manifest.permission.MANAGE_EXTERNAL_STORAGE;
        isPermitted = myCheckPermission(this, myPermissionRequired);
        Log.e("dedetok", "MANAGE_EXTERNAL_STORAGE: "+isPermitted);

        /*
         * root Environment.getExternalStorageDirectory() -> /storage/emulated/0
         */
        // get root directory path NOT USED
        File fExternalStorage = Environment.getExternalStorageDirectory();
        String sFExternalStorage = fExternalStorage.getPath();
        //String fsFExternalStorage = sFExternalStorage+File.separator+sFilename;
        Log.e("dedetok", "Environment.getExternalStorageDirectory() "+sFExternalStorage); //debug

        /*
         * Access file in /storage/emulated/0/Download
         * Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) ->
         *          /storage/emulated/0/Download
         */
        // get Download directory path NOT USED IN PICK FILE BUT USED IN PRINT FILE
        File extDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        String dirPath = extDir.getPath();
        Log.e("dedetok", "getExternalStoragePublicDirectory "+dirPath); //debug

        /*
         * Select file in Download Folder
         */
        //getFileURI(); // OLD WAT Deprecated
        getFileURINew(); // New Way using system dialog to select file

        /*
         * writing to Download Folder
         */
        String sFileOutput = extDir+File.separator+"helloworld.txt";
        File fOutput = new File(sFileOutput);
        try {
            PrintWriter myPW = new PrintWriter(fOutput);
            myPW.println("Hello World");
            myPW.println("this is a test");
            myPW.flush();
            myPW.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace(); // debug
        }


    }

    /*
     * self check permission function
     */
    private boolean myCheckPermission(Context appContext, String sPermission) {
        return (ContextCompat.checkSelfPermission(appContext, sPermission)
                == PackageManager.PERMISSION_GRANTED);
        // or
        // return (PermissionChecker.checkSelfPermission(appContext, sPermission)
        //        == PermissionChecker.PERMISSION_GRANTED);
    }

    ///////////// THIS IS PART OF DEPRECATED SECTION START /////////////
    /*
     * get result
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode,
                                    Intent resultData) {
        super.onActivityResult(requestCode, resultCode, resultData);
        if (requestCode == MY_ID_PICK_FILE
                && resultCode == Activity.RESULT_OK) {
            // The result data contains a URI for the document or directory that
            // the user selected.
            Uri uri;
            if (resultData != null) {
                uri = resultData.getData();
                // Perform operations on the document using its URI.
                Log.e("dedetok", "Result"); // debug
                if (uri!=null) {

                    Log.e("dedetok", "Result uri: "+uri.getPath()); // debug
                    openMyFile(uri);
                }
            }
        }

    }
    private static final int MY_ID_PICK_FILE = 1; // any number
    private static final String mimeTypeFilter = "text/csv";
    /*
     * https://developer.android.com/training/data-storage/shared/documents-files
     * https://stackoverflow.com/questions/71667342/how-to-allow-selection-of-csv-in-a-file-chooser-intent-in-android
     * Deprecated startActivityForResult
     */
    private void getFileURI() {
        String[] myMime = {"*/*",
                "text/plain",
                "application/csv",
                "application/vnd.ms-excel",
                "application/excel",
                "application/x-excel",
                "application/x-msexcel"
        };
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); // MUST
        intent.addCategory(Intent.CATEGORY_OPENABLE); // MUST
        intent.setType(mimeTypeFilter);

        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        intent.putExtra(Intent.EXTRA_MIME_TYPES, myMime);

        startActivityForResult(intent, MY_ID_PICK_FILE);
    }
    ///////////// THIS IS PART OF DEPRECATED SECTION END /////////////

    ///////////// THIS PART OF NEW WAY START /////////////
    /*
     * https://developer.android.com/training/basics/intents/result
     */
    private void getFileURINew() {
        String[] myMime = {"*/*",
                "text/plain",
                "application/csv",
                "application/vnd.ms-excel",
                "application/excel",
                "application/x-excel",
                "application/x-msexcel"
        };
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); // MUST
        intent.addCategory(Intent.CATEGORY_OPENABLE); // MUST
        intent.setType(mimeTypeFilter);

        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        intent.putExtra(Intent.EXTRA_MIME_TYPES, myMime);

        mGetContent.launch(intent);
    }
    // ActivityResultCallback
    ActivityResultCallback<ActivityResult> myActivityResult = new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult aResult) {
            if (aResult.getResultCode()==RESULT_OK) {
                Intent iData = aResult.getData();
                Uri uriData = iData.getData();
                Log.e("dedetok", "new "+uriData.getPath());

            }

        }
    };
    // ActivityResultContracts
    ActivityResultContracts.StartActivityForResult myARC = new ActivityResultContracts.StartActivityForResult();
    // ActivityResultLauncher
    ActivityResultLauncher<Intent> mGetContent = registerForActivityResult(
        myARC,
        myActivityResult);
    ///////////// THIS PART OF NEW WAY END /////////////

    ///////////// SUPPORTING FUNCTION /////////////
    /*
     * Open File after receive from system pick up
     */
    private void openMyFile(Uri uriMyFile) {
        //getFileURI(); // crash
        StringBuilder stringBuilder = new StringBuilder();
        if (isVirtualFile(uriMyFile)) {
            Log.e("dedetok","It is Virual"); // debug
            ContentResolver resolver = getContentResolver();

            String[] openableMimeTypes = resolver.getStreamTypes(uriMyFile, mimeTypeFilter);

            if (openableMimeTypes != null ||
                    openableMimeTypes.length > 0) {

                try {
                    InputStream inputStream = resolver
                            .openTypedAssetFileDescriptor(uriMyFile, openableMimeTypes[0], null)
                            .createInputStream();
                    BufferedReader reader = new BufferedReader(
                            new InputStreamReader(inputStream));
                    String line;
                    while ((line = reader.readLine()) != null) {
                        stringBuilder.append(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace(); // debug
                }
            }

        } else {
            try {
                InputStream inputStream =
                         getContentResolver().openInputStream(uriMyFile);
                BufferedReader reader = new BufferedReader(
                         new InputStreamReader(inputStream));
                String line;
                while ((line = reader.readLine()) != null) {
                    stringBuilder.append(line);
                }
            } catch (IOException e) {
                e.printStackTrace(); // debug
            }
        }
        Log.e("dedetok",stringBuilder.toString());
    }

    /*
     * check if file receive is virtual
     * Android 7.0 (API level 24) and higher
     *https://developer.android.com/training/data-storage/shared/documents-files
     */
    private boolean isVirtualFile(Uri uri) {
        if (!DocumentsContract.isDocumentUri(this, uri)) {
            return false;
        }

        Cursor cursor = getContentResolver().query(
                uri,
                new String[] { DocumentsContract.Document.COLUMN_FLAGS },
                null, null, null);

        int flags = 0;
        if (cursor.moveToFirst()) {
            flags = cursor.getInt(0);
        }
        cursor.close();

        return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
    }

}

References:

  • https://developer.android.com/training/basics/intents/result
  • https://devofandroid.blogspot.com/2022/09/get-result-from-another-activity-using.html
  • https://medium.com/@steves2001/moving-from-android-startactivityforresult-to-registerforactivityresult-76ca04044ff1
  • https://www.geeksforgeeks.org/how-to-use-activityforresultluncher-as-startactivityforresult-is-deprecated-in-android/