Monday, January 4, 2021

Android read text file with request permission at runtime

Android example to read text file, with request permission at runtime.

To read file in Android, if your app is installed on device that runs Android 6.0 (API level 23) or higher, you must request the dangerous permissions at runtime. (ref: Android Developers : Request app permissions)

Also shown in the video, access to /proc/net filesystem is restricted on Android 10+

On devices that run Android 10 or higher, apps cannot access /proc/net, which includes information about a device's network state. Apps that need access to this information, such as VPNs, should use the NetworkStatsManager or ConnectivityManager class. (ref: Android Developers: Privacy changes in Android 10 > Restriction on access to /proc/net filesystem)

layout xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:orientation="horizontal"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/leftpanel"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="@color/black"
        android:layout_margin="5dp"
        android:padding="5dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:textStyle="bold"
            android:textColor="@color/white"
            android:text="android-er.blogspot.com"/>
        <TextView
            android:id="@+id/exercise"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:textColor="@color/white"
            android:text="Exercise: Read Text File"/>
        <TextView
            android:id="@+id/sysinfo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:textStyle="bold"
            android:textColor="@color/white"/>
        <TextView
            android:id="@+id/sdkinfo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:textStyle="bold"
            android:textColor="@color/white"/>

    </LinearLayout>

    <RelativeLayout
        android:id="@+id/rightpanel"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#C0C0C0"
        android:layout_margin="5dp"
        android:padding="5dp">
        <Button
            android:id="@+id/read"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="2dp"
            android:text="Read"
            android:layout_alignParentTop="true"/>
        <TextView
            android:id="@+id/filecontent"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="2dp"
            android:background="#808080"
            android:fontFamily="monospace"
            android:layout_below="@id/read"
            android:layout_above="@id/msg"/>
        <TextView
            android:id="@+id/msg"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="2dp"
            android:background="#404040"
            android:layout_gravity="bottom"
            android:layout_alignParentBottom="true"/>
    </RelativeLayout>
</LinearLayout>
Java code:
package android_er.blogspot.com.jexreadtextfile;

import android.Manifest;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    //Try different files
    //String targetFile = "/sdcard/Download/Test.txt";
    //String targetFile = "/proc/cpuinfo";
    //String targetFile = "/proc/meminfo";
    String targetFile = "/proc/net/arp";  //Restricted since Android 10

    Button btnRead;
    TextView tvFileContent;
    TextView tvMsg;

    final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;

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

        btnRead = findViewById(R.id.read);
        btnRead.setText(targetFile);
        tvFileContent = findViewById(R.id.filecontent);
        //make this TextView scrollable
        tvFileContent.setMovementMethod(new ScrollingMovementMethod());
        tvMsg = findViewById(R.id.msg);
        tvMsg.setText("Click on READ button");

        btnRead.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tvMsg.setText("READ button clicked");
                toReadFile();
            }
        });
    }


    /*
    Request Permission at Runtime, before read file.
     */
    void toReadFile(){
        if (ContextCompat.checkSelfPermission(this,
                    Manifest.permission.READ_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                // Permission is not granted
                // Should we show an explanation?
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        this,
                        Manifest.permission.READ_EXTERNAL_STORAGE)) {
                    // Show an explanation to the user *asynchronously* -- don't block
                    // this thread waiting for the user's response! After the user
                    // sees the explanation, try again to request the permission.

                    //to simplify, call requestPermissions again
                    Toast.makeText(getApplicationContext(),
                            "shouldShowRequestPermissionRationale",
                            Toast.LENGTH_LONG).show();
                    ActivityCompat.requestPermissions(this,
                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                            PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
                } else {
                    // No explanation needed; request the permission
                    ActivityCompat.requestPermissions(this,
                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                            PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
    }
            }else{
                // permission granted
                readFile(targetFile);
            }
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode,
            @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        if(requestCode == PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE){
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted.
                Toast.makeText(getApplicationContext(),
                        "permission was granted, thx:)",
                        Toast.LENGTH_LONG).show();

                readFile(targetFile);
            } else {
                // permission denied.
                Toast.makeText(getApplicationContext(),
                        "permission denied! Oh:(",
                        Toast.LENGTH_LONG).show();
            }
            return;

        }else{
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    void readFile(String f){
        File file = new File(f);
        StringBuilder stringBuilder = new StringBuilder();

        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
                stringBuilder.append('\n');
            }
            bufferedReader.close();

            tvFileContent.setText(stringBuilder);
            tvMsg.setText("Done");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            tvMsg.setText(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            tvMsg.setText(e.getMessage());
        }
    }

    /* ==========================================
    display Exercise info
     */

    void dispExInfo(){
        TextView tvExercise = findViewById(R.id.exercise);
        TextView tvSysInfo = findViewById(R.id.sysinfo);
        TextView tvSdkInfo = findViewById(R.id.sdkinfo);

        tvExercise.append(" (Java)");

        String manufacturer = Build.MANUFACTURER;
        String model = Build.MODEL;
        String release = Build.VERSION.RELEASE;

        tvSysInfo.setText(
                manufacturer + "\n"
                        + model + "\n"
                        + "Android: " + release + "\n");

        PackageManager packageManager = getPackageManager();
        String packageName = getPackageName();
        int targetSdkVersion, minSdkVersion;
        int versionCode;
        String versionName;

        try {
            PackageInfo packageInfo =
                    packageManager.getPackageInfo(packageName, 0);

            ApplicationInfo applicationInfo = packageInfo.applicationInfo;
            targetSdkVersion = applicationInfo.targetSdkVersion;
            minSdkVersion = applicationInfo.minSdkVersion;

            tvSdkInfo.setText("targetSdkVersion = " + targetSdkVersion + "\n"
                    + "minSdkVersion = " + minSdkVersion);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "NameNotFoundException: " + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }
    }
}

uses-permission of "android.permission.READ_EXTERNAL_STORAGE" is needed in AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android_er.blogspot.com.jexreadtextfile">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.JExReadTextFile">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
</manifest>


Kotlin version, basically auto-converted by Android Studio.
package android_er.blogspot.com.kexreadtextfile

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.io.*


class MainActivity : AppCompatActivity() {
    //Try different files
    //var targetFile = "/sdcard/Download/Test.txt";
    var targetFile = "/proc/cpuinfo";
    //var targetFile = "/proc/meminfo";
    //var targetFile = "/proc/net/arp" //Restricted since Android 10
    var btnRead: Button? = null
    var tvFileContent: TextView? = null
    var tvMsg: TextView? = null
    val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        dispExInfo()
        btnRead = findViewById(R.id.read)
        btnRead!!.setText(targetFile)
        tvFileContent = findViewById(R.id.filecontent)
        //make this TextView scrollable
        tvFileContent!!.setMovementMethod(ScrollingMovementMethod())
        tvMsg = findViewById(R.id.msg)
        tvMsg!!.setText("Click on READ button")
        btnRead!!.setOnClickListener(View.OnClickListener {
            tvMsg!!.setText("READ button clicked")
            toReadFile()
        })
    }

    /*
    Request Permission at Runtime, before read file.
     */
    fun toReadFile() {
        if (ContextCompat.checkSelfPermission(this,
                        Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            // Permission is not granted
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(
                            this,
                            Manifest.permission.READ_EXTERNAL_STORAGE)) {
                // Show an explanation to the user *asynchronously* -- don't block
                // this thread waiting for the user's response! After the user
                // sees the explanation, try again to request the permission.

                //to simplify, call requestPermissions again
                Toast.makeText(applicationContext,
                        "shouldShowRequestPermissionRationale",
                        Toast.LENGTH_LONG).show()
                ActivityCompat.requestPermissions(this,
                        arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
                        PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
            } else {
                // No explanation needed; request the permission
                ActivityCompat.requestPermissions(this,
                        arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
                        PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
            }
        } else {
            // permission granted
            readFile(targetFile)
        }
    }

    override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<String>,
            grantResults: IntArray) {
        if (requestCode == PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
            if (grantResults.size > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted.
                Toast.makeText(applicationContext,
                        "permission was granted, thx:)",
                        Toast.LENGTH_LONG).show()
                readFile(targetFile)
            } else {
                // permission denied.
                Toast.makeText(applicationContext,
                        "permission denied! Oh:(",
                        Toast.LENGTH_LONG).show()
            }
            return
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }

    fun readFile(f: String?) {
        val file = File(f)
        val stringBuilder = StringBuilder()
        try {
            val bufferedReader = BufferedReader(FileReader(file))
            var line: String?
            while (bufferedReader.readLine().also { line = it } != null) {
                stringBuilder.append(line)
                stringBuilder.append('\n')
            }
            bufferedReader.close()
            tvFileContent!!.text = stringBuilder
            tvMsg!!.text = "Done"
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
            tvMsg!!.text = e.message
        } catch (e: IOException) {
            e.printStackTrace()
            tvMsg!!.text = e.message
        }
    }

    /* ==========================================
    display Exercise info
     */
    fun dispExInfo() {
        val tvExercise = findViewById<TextView>(R.id.exercise)
        val tvSysInfo = findViewById<TextView>(R.id.sysinfo)
        val tvSdkInfo = findViewById<TextView>(R.id.sdkinfo)
        tvExercise.append(" (Kotlin)")
        val manufacturer = Build.MANUFACTURER
        val model = Build.MODEL
        val release = Build.VERSION.RELEASE
        tvSysInfo.text = """
             $manufacturer
             $model
             Android: $release
             
             """.trimIndent()
        val packageManager = packageManager
        val packageName = packageName
        val targetSdkVersion: Int
        val minSdkVersion: Int
        var versionCode: Int
        var versionName: String
        try {
            val packageInfo = packageManager.getPackageInfo(packageName, 0)
            val applicationInfo = packageInfo.applicationInfo
            targetSdkVersion = applicationInfo.targetSdkVersion
            minSdkVersion = applicationInfo.minSdkVersion
            tvSdkInfo.text = """
                targetSdkVersion = $targetSdkVersion
                minSdkVersion = $minSdkVersion
                """.trimIndent()
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
            Toast.makeText(applicationContext,
                    "NameNotFoundException: " + e.message,
                    Toast.LENGTH_LONG).show()
        }
    }
}