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/


No comments:

Post a Comment