Showing posts with label Android code sample: storage. Show all posts
Showing posts with label Android code sample: storage. Show all posts

Saturday, March 9, 2019

Write image to External Storage with Permission Request at runtime, for Android 6+

It's a simple example to write image to External Storage, with targetSdkVersion = 28. It work as expected on devices running Android below 6, but fail on Android 6+! (Solution provided in second part below)



Java code:
package com.blogspot.android_er.androidwriteimage;

import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

    final String MyAlbum = "android-er";
    Bitmap bitmapSrc;
    TextView tvSavedInfo;

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

        tvSavedInfo = (TextView)findViewById(R.id.savedinfo);

        dispTestCase();

        //prepare dummy source of bitmap to save
        Drawable drawable = getDrawable(R.mipmap.ic_launcher);
        bitmapSrc = BitmapFactory.decodeResource(
                getApplicationContext().getResources(),
                R.mipmap.ic_launcher);

        ImageView ivImage;
        ivImage = findViewById(R.id.imageView1);
        ivImage.setImageDrawable(drawable);

        Button btnSave;
        btnSave = (Button)findViewById(R.id.buttonSave);
        btnSave.setOnClickListener(btnOnClickListener);

    }

    public File getPublicAlbumStorageDir(String albumName) {
    // Get the directory for the user's public pictures directory.
        File file = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), albumName);
        if (file.mkdirs()) {
            //if directory not exist
            Toast.makeText(getApplicationContext(),
                    file.getAbsolutePath() + " created",
                    Toast.LENGTH_LONG).show();
        }else{
            Toast.makeText(getApplicationContext(),
                    "Directory not created", Toast.LENGTH_LONG).show();
        }
        return file;
    }

    Button.OnClickListener btnOnClickListener = new Button.OnClickListener(){

        @Override
        public void onClick(View v) {

            tvSavedInfo.setText("");
            writeImage();

        }
    };

    private void writeImage(){
        //generate a unique file name from timestamp
        Date date = new Date();
        SimpleDateFormat simpleDateFormat =
                new SimpleDateFormat("yyMMdd-hhmmss-SSS");
        String fileName = "img" + simpleDateFormat.format(new Date()) + ".jpg";

        File dir = getPublicAlbumStorageDir(MyAlbum);
        File file = new File(dir, fileName);

        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            bitmapSrc.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
            outputStream.flush();
            outputStream.close();

            tvSavedInfo.setText("File saved: \n" + file.getAbsolutePath());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "FileNotFoundException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "IOException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }
    }

    private void dispTestCase(){
        String testCase;

        TextView tvTestCase;
        tvTestCase = (TextView)findViewById(R.id.testcase);
        testCase = "Tested on Android " + Build.VERSION.RELEASE + "\n";


        PackageManager packageManager = getPackageManager();
        String packageName = getPackageName();
        int targetSdkVersion;
        try {
            PackageInfo packageInfo =
                    packageManager.getPackageInfo(packageName, 0);

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

            testCase += "targetSdkVersion = " + targetSdkVersion;

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "NameNotFoundException: " + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }

        tvTestCase.setText(testCase);

    }

}


Layout XML:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/addr"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="android-er.blogspot.com"
        android:textSize="20dp"
        android:textStyle="italic"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Write Image to External Storage"
        android:textSize="26dp"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/addr" />

    <TextView
        android:id="@+id/testcase"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="26dp"
        android:textColor="#A00000"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title"/>

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/testcase" />
    <TextView
        android:id="@+id/savedinfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/imageView1"/>

    <Button
        android:id="@+id/buttonSave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click to save"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/savedinfo" />

</android.support.constraint.ConstraintLayout>


uses-permission of "android.permission.WRITE_EXTERNAL_STORAGE" is needed in manifest.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.android_er.androidwriteimage">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <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/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

reference:
- Android Developers > Docs > Guides > Save files on device storage


Request Permission at runtime:

The above code fail when install on device running Android 6+! Because start from Android 6, your apps have to request user approve permissions at runtime. The following example show how to check and request permission at runtime. You still have to declare uses-permission of "android.permission.WRITE_EXTERNAL_STORAGE" in manifest.

When run on devices below Android 6, permission will be granted at install time. When run on Android 6+, you have to check permission at runtime before perform the task and ask user for approval if need.



package com.blogspot.android_er.androidwriteimage;

import android.Manifest;
import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

    final String MyAlbum = "android-er";
    Bitmap bitmapSrc;
    TextView tvSavedInfo;

    Activity thisActivity;
    final int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;

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

        thisActivity = this;

        tvSavedInfo = (TextView)findViewById(R.id.savedinfo);

        dispTestCase();

        //prepare dummy source of bitmap to save
        Drawable drawable = getDrawable(R.mipmap.ic_launcher);
        bitmapSrc = BitmapFactory.decodeResource(
                getApplicationContext().getResources(),
                R.mipmap.ic_launcher);

        ImageView ivImage;
        ivImage = findViewById(R.id.imageView1);
        ivImage.setImageDrawable(drawable);

        Button btnSave;
        btnSave = (Button)findViewById(R.id.buttonSave);
        btnSave.setOnClickListener(btnOnClickListener);

    }

    public File getPublicAlbumStorageDir(String albumName) {
        // Get the directory for the user's public pictures directory.
        File file = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), albumName);
        if (file.mkdirs()) {
            //if directory not exist
            Toast.makeText(getApplicationContext(),
                    file.getAbsolutePath() + " created",
                    Toast.LENGTH_LONG).show();
        }else{
            Toast.makeText(getApplicationContext(),
                    "Directory not created", Toast.LENGTH_LONG).show();
        }
        return file;
    }

    Button.OnClickListener btnOnClickListener = new Button.OnClickListener(){

        @Override
        public void onClick(View v) {

            tvSavedInfo.setText("");

            if (ContextCompat.checkSelfPermission(thisActivity,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                // Permission is not granted
                // Should we show an explanation?
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        thisActivity,
                        Manifest.permission.WRITE_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(thisActivity,
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
                } else {
                    // No explanation needed; request the permission
                    ActivityCompat.requestPermissions(thisActivity,
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
    }
            }else{
                // permission granted
                writeImage();
            }

        }
    };

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {

        if(requestCode == MY_PERMISSIONS_REQUEST_WRITE_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();
            } else {
                // permission denied.
                Toast.makeText(getApplicationContext(),
                        "permission denied! Oh:(",
                        Toast.LENGTH_LONG).show();
            }
            return;
        }else{
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    private void writeImage(){
        //generate a unique file name from timestamp
        Date date = new Date();
        SimpleDateFormat simpleDateFormat =
                new SimpleDateFormat("yyMMdd-hhmmss-SSS");
        String fileName = "img" + simpleDateFormat.format(new Date()) + ".jpg";

        File dir = getPublicAlbumStorageDir(MyAlbum);
        File file = new File(dir, fileName);

        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            bitmapSrc.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
            outputStream.flush();
            outputStream.close();

            tvSavedInfo.setText("File saved: \n" + file.getAbsolutePath());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "FileNotFoundException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "IOException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }
    }

    private void dispTestCase(){
        String testCase;

        TextView tvTestCase;
        tvTestCase = (TextView)findViewById(R.id.testcase);
        testCase = "Tested on Android " + Build.VERSION.RELEASE + "\n";


        PackageManager packageManager = getPackageManager();
        String packageName = getPackageName();
        int targetSdkVersion;
        try {
            PackageInfo packageInfo =
                    packageManager.getPackageInfo(packageName, 0);

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

            testCase += "targetSdkVersion = " + targetSdkVersion;

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "NameNotFoundException: " + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }

        tvTestCase.setText(testCase);

    }

}


Layout XML file is same as above.

reference:
- Android Developers > Docs > Guides > Request App Permissions


Thursday, December 3, 2015

Open image, free draw something on bitmap, and save bitmap to ExternalStorage

Example to load image with intent of Intent.ACTION_PICK, free draw something on the image, and save the bitmap to storage.


I have a series example of "Something about processing images in Android", but have show how to save the result bitmap to SD Card. This example modify from one of the example "Detect touch and free draw on Bitmap", add the function to save the result bitmap to External Storage, with file name "test.jpg".


layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:orientation="vertical"
    tools:context="com.blogspot.android_er.androiddrawbitmap.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/loadimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Image" />

    <Button
        android:id="@+id/saveimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Image" />

    <ImageView
        android:id="@+id/result"
        android:scaleType="centerInside"
        android:adjustViewBounds="true"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/background_dark" />
</LinearLayout>


MainActivity.java
package com.blogspot.android_er.androiddrawbitmap;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    Button btnLoadImage, btnSaveImage;
    ImageView imageResult;

    final int RQS_IMAGE1 = 1;

    Uri source;
    Bitmap bitmapMaster;
    Canvas canvasMaster;

    int prvX, prvY;

    Paint paintDraw;

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

        btnLoadImage = (Button)findViewById(R.id.loadimage);
        btnSaveImage = (Button)findViewById(R.id.saveimage);
        imageResult = (ImageView)findViewById(R.id.result);

        paintDraw = new Paint();
        paintDraw.setStyle(Paint.Style.FILL);
        paintDraw.setColor(Color.WHITE);
        paintDraw.setStrokeWidth(10);

        btnLoadImage.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                Intent intent = new Intent(Intent.ACTION_PICK,
                        android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                startActivityForResult(intent, RQS_IMAGE1);
            }
        });

        btnSaveImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bitmapMaster != null){
                    saveBitmap(bitmapMaster);
                }
            }
        });

        imageResult.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                int action = event.getAction();
                int x = (int) event.getX();
                int y = (int) event.getY();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        prvX = x;
                        prvY = y;
                        drawOnProjectedBitMap((ImageView) v, bitmapMaster, prvX, prvY, x, y);
                        break;
                    case MotionEvent.ACTION_MOVE:
                        drawOnProjectedBitMap((ImageView) v, bitmapMaster, prvX, prvY, x, y);
                        prvX = x;
                        prvY = y;
                        break;
                    case MotionEvent.ACTION_UP:
                        drawOnProjectedBitMap((ImageView) v, bitmapMaster, prvX, prvY, x, y);
                        break;
                }
    /*
     * Return 'true' to indicate that the event have been consumed.
     * If auto-generated 'false', your code can detect ACTION_DOWN only,
     * cannot detect ACTION_MOVE and ACTION_UP.
     */
                return true;
            }
        });
    }

    /*
    Project position on ImageView to position on Bitmap draw on it
     */

    private void drawOnProjectedBitMap(ImageView iv, Bitmap bm,
                                       float x0, float y0, float x, float y){
        if(x<0 || y<0 || x > iv.getWidth() || y > iv.getHeight()){
            //outside ImageView
            return;
        }else{

            float ratioWidth = (float)bm.getWidth()/(float)iv.getWidth();
            float ratioHeight = (float)bm.getHeight()/(float)iv.getHeight();

            canvasMaster.drawLine(
                    x0 * ratioWidth,
                    y0 * ratioHeight,
                    x * ratioWidth,
                    y * ratioHeight,
                    paintDraw);
            imageResult.invalidate();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Bitmap tempBitmap;

        if(resultCode == RESULT_OK){
            switch (requestCode){
                case RQS_IMAGE1:
                    source = data.getData();

                    try {
                        //tempBitmap is Immutable bitmap,
                        //cannot be passed to Canvas constructor
                        tempBitmap = BitmapFactory.decodeStream(
                                getContentResolver().openInputStream(source));

                        Bitmap.Config config;
                        if(tempBitmap.getConfig() != null){
                            config = tempBitmap.getConfig();
                        }else{
                            config = Bitmap.Config.ARGB_8888;
                        }

                        //bitmapMaster is Mutable bitmap
                        bitmapMaster = Bitmap.createBitmap(
                                tempBitmap.getWidth(),
                                tempBitmap.getHeight(),
                                config);

                        canvasMaster = new Canvas(bitmapMaster);
                        canvasMaster.drawBitmap(tempBitmap, 0, 0, null);

                        imageResult.setImageBitmap(bitmapMaster);
                    } catch (FileNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    break;
            }
        }
    }

    private void saveBitmap(Bitmap bm){
        File file = Environment.getExternalStorageDirectory();
        File newFile = new File(file, "test.jpg");

        try {
            FileOutputStream fileOutputStream = new FileOutputStream(newFile);
            bm.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
            fileOutputStream.flush();
            fileOutputStream.close();
            Toast.makeText(MainActivity.this,
                    "Save Bitmap: " + fileOutputStream.toString(),
                    Toast.LENGTH_LONG).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this,
                    "Something wrong: " + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this,
                    "Something wrong: " + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }
    }
}


"android.permission.WRITE_EXTERNAL_STORAGE" is needed in AndroidManifest.xml. Also I add android:largeHeap="true" to require more heap for the app. But if you have a really big image, it still will closed, caused by Out Of Memory!
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.android_er.androiddrawbitmap">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:largeHeap="true"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>


download filesDownload the files (Android Studio Format) .

more: Something about processing images in Android

Saturday, July 25, 2015

Android read file "/proc/mounts"

Android example code to read "/proc/mounts", easy to be modified to read other text files.


com.example.android_proc_mounts.MainActivity.java
package com.example.android_proc_mounts;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.TextView;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class MainActivity extends ActionBarActivity {

    TextView info;
    String targetPath = "/proc/mounts";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        info = (TextView)findViewById(R.id.info);
        info.setText(ReadFile(targetPath));
    }

    private String ReadFile(String path){
        String result = "";

        File file = new File(path);
        if(file.exists()){
            result += path + ":\n"
                    + "======\n";
            Scanner scanner = null;
            try {
                scanner = new Scanner(file);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    result += line + "\n"
                            + "------\n";
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                result += e.toString();
            }

        }else{
            return (path + " NOT exists");
        }

        return result;
    }
}


layout/activity_main.xml
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>


Thursday, July 23, 2015

getExternalFilesDirs() and getExternalCacheDirs()

Start from Android 4.4 (API Level 19), methods getExternalFilesDirs(String type) and getExternalCacheDirs() were introduced.

File[] getExternalFilesDirs (String type) -
Returns absolute paths to application-specific directories on all external storage devices where the application can place persistent files it owns. These files are internal to the application, and not typically visible to the user as media.

File[] getExternalCacheDirs () -
Returns absolute paths to application-specific directories on all external storage devices where the application can place cache files it owns. These files are internal to the application, and not typically visible to the user as media.

Example code run on RedMi 2 (Android 4.4.4), WITH external SD Card:
both /storage/emulated/0/...(internal memory) and /storage/sdcard1/...(SD Card) are listed.


Run on Nexus 7 (Android 5.1.1) WITHOUT external SD Card:


Example code:
com.example.androidexternalstorage.MainActivity.java
package com.example.androidexternalstorage;

import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.ActionBarActivity;
import android.widget.TextView;

import java.io.File;

public class MainActivity extends ActionBarActivity {

    TextView info;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        info = (TextView)findViewById(R.id.info);

        File extFilesDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);

        String strInfo =
            "Environment.getRootDirectory(): \n"
            + Environment.getRootDirectory() + "\n"
            + "Environment.getExternalStorageDirectory(): \n"
            + Environment.getExternalStorageDirectory() + "\n";

        strInfo +=
            "\ngetExternalFilesDir(null): \n"
            + getExternalFilesDir(null) + "\n"
            + "getExternalFilesDir(Environment.DIRECTORY_PICTURES): \n"
            + getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "\n"
            + "getExternalFilesDir(Environment.DIRECTORY_MUSIC): \n"
            + getExternalFilesDir(Environment.DIRECTORY_MUSIC) + "\n";


        //API Level 19
        File[] externalFilesDirs = getExternalFilesDirs(null);
        strInfo += "\ngetExternalFilesDirs(null):\n";
        for(File f : externalFilesDirs){
            strInfo += f.getAbsolutePath() + "\n";
        }

        //API Level 19
        File[] externalCacheDirs = getExternalCacheDirs();
        strInfo += "\ngetExternalCacheDirs():\n";
        for(File f : externalCacheDirs){
            strInfo += f.getAbsolutePath() + "\n";
        }

        /*
        //API Level 21
        File[] externalMediaDirs = getExternalMediaDirs();
        strInfo += "\ngetExternalMediaDirs():\n";
        for(File f : externalMediaDirs){
            strInfo += f.getAbsolutePath() + "\n";
        }
        */

        info.setText(strInfo);
    }

}


layout/activity_main.xml
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </ScrollView>


</LinearLayout>


Friday, December 26, 2014

Update "android.permission.READ_EXTERNAL_STORAGE" for KitKat

With re-visiting my old post "Convert between Uri and file path, and load Bitmap from", to load image from Uri form "content://media/external/images/media/xxxxx", real path in form of "/storage/emulated/0/Picture/..." and Uri in form of "file:///storage/emulated/0/Picture/...".

When run on HTC One X of Android 4.2.2, it can read the image from all three form, "content://media/external/images/media/xxxxx", "/storage/sdcard0/Pictures/Screenshots/..." and "file:///storage/sdcard0/Pictures/Screenshots/...".

When run on Nexus 7 of Android 4.4.4 KitKat, only the first Uri form of "content://media/external/images/media/xxxxx" can be display, and nothing display on the other two; "/storage/emulated/0/Pictures/Screenshots/..." and "file:///storage/emulated/0/Pictures/Screenshots/...".

To fix it, add permission of "android.permission.READ_EXTERNAL_STORAGE" in AndroidManifest.xml. Refer to the video below.


Monday, October 7, 2013

GridView example: load images to GridView from SD Card in background

Recall from the "old exercise of GridView", loading images to GridView from SD Card in onCreate() method running in main thread. Actually, it may take long time to finish, so it should be moved to background thread. This exercise move the file loading operation to AsyncTask running in background thread.

Also introduce Reload button to demonstrate how to clear and reload the list.

Load images to GridView from SD Card


Here are some notes in my implementation:
  • In my trial experience, should not access ImageAdapter(extends BaseAdapter) from background thread such as doInBackground(). Doing so will conflict with ImageAdapter's getView() method, and generate IndexOutOfBoundsException occasionally. It whould be accessed in UI thread, so the clear(), add() and notifyDataSetChanged() have been moved to onPreExecute(), onProgressUpdate() and onPostExecute().
  • Cancel the AsyncTask if not need.
  • In my approach, always new another ImageAdapter when reloading; to prevent the list inside mixed with a invalid but still running AsyncTask.

package com.example.androidgridview;

import java.io.File;
import java.util.ArrayList;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   File[] files = targetDirector.listFiles();
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

  @Override
  public int getCount() {
   return itemList.size();
  }

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
   } else {
    imageView = (ImageView) convertView;
   }

   Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220,
     220);

   imageView.setImageBitmap(bm);
   return imageView;
  }

  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth,
     reqHeight);

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options);

   return bm;
  }

  public int calculateInSampleSize(

  BitmapFactory.Options options, int reqWidth, int reqHeight) {
   // Raw height and width of image
   final int height = options.outHeight;
   final int width = options.outWidth;
   int inSampleSize = 1;

   if (height > reqHeight || width > reqWidth) {
    if (width > height) {
     inSampleSize = Math.round((float) height
       / (float) reqHeight);
    } else {
     inSampleSize = Math.round((float) width / (float) reqWidth);
    }
   }

   return inSampleSize;
  }

 }

 ImageAdapter myImageAdapter;

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

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  /*
   * Move to asyncTaskLoadFiles String ExternalStorageDirectoryPath =
   * Environment .getExternalStorageDirectory() .getAbsolutePath();
   * 
   * String targetPath = ExternalStorageDirectoryPath + "/test/";
   * 
   * Toast.makeText(getApplicationContext(), targetPath,
   * Toast.LENGTH_LONG).show(); File targetDirector = new
   * File(targetPath);
   * 
   * File[] files = targetDirector.listFiles(); for (File file : files){
   * myImageAdapter.add(file.getAbsolutePath()); }
   */
  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String prompt = "remove " + (String) parent.getItemAtPosition(position);
   Toast.makeText(getApplicationContext(), prompt, Toast.LENGTH_SHORT)
     .show();
   
   myImageAdapter.remove(position);
   myImageAdapter.notifyDataSetChanged();

  }
 };

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <Button
        android:id="@+id/reload"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Reload"/>
    <GridView
        android:id="@+id/gridview"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:columnWidth="90dp"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:gravity="center"/>

</LinearLayout>


download filesDownload the files.

download filesDownload and try the APK.



Next:
- getView() to load images in AsyncTask


Friday, August 30, 2013

List files in directory with specified type

The example List all 'jpg' files in given directoty:

 private File[] getJpgFiles(File f){
  File[] files = f.listFiles(new FilenameFilter(){

   @Override
   public boolean accept(File dir, String filename) {
    return filename.toLowerCase().endsWith(".jpg");
   }});
  
  return files;
 }


Thursday, August 1, 2013

Convert between Uri and file path, and load Bitmap from.

What return from Android build-in Gallery App is Uri, in the form of "content://media/...". This exercise demonstrate how to convert the Uri to real file path, in the form of "/storage/sdcard0/...". and convert back to Uri, in the form of "file:///storage/sdcard0/...".  All the three form refer to the same file.

And also demonstrate how to load bitmap from the Uri(s) and file path, then display in ImageView.

Convert between Uri and file path, and load Bitmap from.


package com.example.androiduri;

import java.io.File;
import java.io.FileNotFoundException;

import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.support.v4.content.CursorLoader;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class MainActivity extends Activity {

 Button btnSelFile;
 TextView text1, text2, text3, note;
 ImageView image;
 
 Uri orgUri, uriFromPath;
 String convertedPath;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  btnSelFile = (Button)findViewById(R.id.selfile);
  text1 = (TextView)findViewById(R.id.text1);
  text2 = (TextView)findViewById(R.id.text2);
  text3 = (TextView)findViewById(R.id.text3);
  note = (TextView)findViewById(R.id.note);
  image = (ImageView)findViewById(R.id.image);
  
  btnSelFile.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    Intent intent = new Intent(Intent.ACTION_PICK, 
      Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, 0);
   }});
  
  text1.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    image.setImageBitmap(null);
    note.setText("by Returned Uri");
    
    try {
     Bitmap bm = BitmapFactory.decodeStream(
       getContentResolver().openInputStream(orgUri));
     image.setImageBitmap(bm); 
    } catch (FileNotFoundException e) {
     e.printStackTrace(); 
    }
   }});
  
  text2.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    image.setImageBitmap(null);
    note.setText("by Real Path");
    Bitmap bm = BitmapFactory.decodeFile(convertedPath);
    image.setImageBitmap(bm);
   }});
  
  text3.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    image.setImageBitmap(null);
    note.setText("by Back Uri");
    
    try {
     Bitmap bm = BitmapFactory.decodeStream(
       getContentResolver().openInputStream(uriFromPath));
     image.setImageBitmap(bm); 
    } catch (FileNotFoundException e) {
     e.printStackTrace(); 
    }
   }});
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  
  if(resultCode == RESULT_OK){
   
   image.setImageBitmap(null);
   
   //Uri return from external activity
   orgUri = data.getData();
   text1.setText("Returned Uri: " + orgUri.toString() + "\n");
   
   //path converted from Uri
   convertedPath = getRealPathFromURI(orgUri);
   text2.setText("Real Path: " + convertedPath + "\n");
   
   //Uri convert back again from path
   uriFromPath = Uri.fromFile(new File(convertedPath));
   text3.setText("Back Uri: " + uriFromPath.toString() + "\n");
  }
  
 }

 public String getRealPathFromURI(Uri contentUri) {
  String[] proj = { MediaStore.Images.Media.DATA };
  
  //This method was deprecated in API level 11
  //Cursor cursor = managedQuery(contentUri, proj, null, null, null);
  
  CursorLoader cursorLoader = new CursorLoader(
            this, 
            contentUri, proj, null, null, null);        
  Cursor cursor = cursorLoader.loadInBackground();
  
  int column_index = 
    cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
  cursor.moveToFirst();
  return cursor.getString(column_index); 
 }
 
}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="android-er.blogspot.com" />
    <Button 
        android:id="@+id/selfile"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Select File" />
    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/text2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/text3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/note"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>


Please notice that the code haven't handle resize on the bitmap. If you load with a large picture, OutOfMemoryError will be caused.

download filesDownload the files.

Related: Load picture with Intent.ACTION_GET_CONTENT


Update@2014-12-27: Not work on KitKat now!

Permission of "android.permission.READ_EXTERNAL_STORAGE" have to be added in AndroidManifest.xml. Refer to Update "android.permission.READ_EXTERNAL_STORAGE" for KitKat.