Showing posts with label Permission. Show all posts
Showing posts with label Permission. 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, April 27, 2017

Read Exif tag of JPG using ExifInterface(String filename), with Requesting Permissions at Run Time for Android 6.0 (API level 23) or higher.


Last post show how to Read Exif tag of JPG using ExifInterface(String filename), with Target Sdk Version to API 22. As mentioned, beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. It need extra handle of Requesting Permissions at Run Time.

This example add handle Requesting Permissions at Run Time, to make it Target Sdk Version API 25.



uses-permission of "android.permission.READ_EXTERNAL_STORAGE" is needed in AndroidManifest.xml, refer last post.

For the layout, refer to the example in Read Exif tag of JPG using ExifInterface(FileDescriptor).

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

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
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.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private static final int RQS_OPEN_IMAGE = 1;
    private static final int RQS_READ_EXTERNAL_STORAGE = 2;

    Button buttonOpen;
    TextView textUri;
    ImageView imageView;

    Uri targetUri = null;

    View.OnClickListener buttonOpenOnClickListener =
            new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    Intent intent = new Intent();
                    intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
                    intent.addCategory(Intent.CATEGORY_OPENABLE);
                    intent.setType("image/jpeg");
                    startActivityForResult(intent, RQS_OPEN_IMAGE);
                }

            };
    View.OnClickListener textUriOnClickListener =
            new View.OnClickListener(){

                @Override
                public void onClick(View v) {
                    if (targetUri != null){
                        Bitmap bm;
                        try {
                            bm = BitmapFactory.decodeStream(
                                    getContentResolver()
                                            .openInputStream(targetUri));
                            imageView.setImageBitmap(bm);
                        } catch (FileNotFoundException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }

            };

    View.OnClickListener imageOnClickListener =
            new View.OnClickListener(){

                @Override
                public void onClick(View view) {

                    if(CheckPermission_READ_EXTERNAL_STORAGE()){
                        showExif(targetUri);
                    }

                }
            };

    private boolean CheckPermission_READ_EXTERNAL_STORAGE() {
        // return true: have permission
        // return false: no permission
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    RQS_READ_EXTERNAL_STORAGE);

            return false;
        }else{
            return true;
        }
    }

    void showExif(Uri photoUri){

        if(photoUri != null){

            String photoPath = getRealPathFromURI(photoUri);

            try {
                /*
                ExifInterface (String filename) added in API level 5
                 */
                ExifInterface exifInterface = new ExifInterface(photoPath);

                String exif="Exif: ";
                exif += "\nIMAGE_LENGTH: " +
                        exifInterface.getAttribute(ExifInterface.TAG_IMAGE_LENGTH);
                exif += "\nIMAGE_WIDTH: " +
                        exifInterface.getAttribute(ExifInterface.TAG_IMAGE_WIDTH);
                exif += "\n DATETIME: " +
                        exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
                exif += "\n TAG_MAKE: " +
                        exifInterface.getAttribute(ExifInterface.TAG_MAKE);
                exif += "\n TAG_MODEL: " +
                        exifInterface.getAttribute(ExifInterface.TAG_MODEL);
                exif += "\n TAG_ORIENTATION: " +
                        exifInterface.getAttribute(ExifInterface.TAG_ORIENTATION);
                exif += "\n TAG_WHITE_BALANCE: " +
                        exifInterface.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
                exif += "\n TAG_FOCAL_LENGTH: " +
                        exifInterface.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
                exif += "\n TAG_FLASH: " +
                        exifInterface.getAttribute(ExifInterface.TAG_FLASH);
                exif += "\nGPS related:";
                exif += "\n TAG_GPS_DATESTAMP: " +
                        exifInterface.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
                exif += "\n TAG_GPS_TIMESTAMP: " +
                        exifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
                exif += "\n TAG_GPS_LATITUDE: " +
                        exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
                exif += "\n TAG_GPS_LATITUDE_REF: " +
                        exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
                exif += "\n TAG_GPS_LONGITUDE: " +
                        exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
                exif += "\n TAG_GPS_LONGITUDE_REF: " +
                        exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
                exif += "\n TAG_GPS_PROCESSING_METHOD: " +
                        exifInterface.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);

                Toast.makeText(getApplicationContext(),
                        exif,
                        Toast.LENGTH_LONG).show();


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

        }else{
            Toast.makeText(getApplicationContext(),
                    "photoUri == null",
                    Toast.LENGTH_LONG).show();
        }
    };

    /*
    This method getRealPathFromURI() is not coded by me,
    but I forgot where I copy it from.
     */
    private String getRealPathFromURI(Uri uri){
        String filePath = "";
        String wholeID = DocumentsContract.getDocumentId(uri);

        // Split at colon, use second item in the array
        String id = wholeID.split(":")[1];

        String[] column = { MediaStore.Images.Media.DATA };

        // where id is equal to
        String sel = MediaStore.Images.Media._ID + "=?";

        Cursor cursor = getApplicationContext().getContentResolver()
                .query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        column, sel, new String[]{ id }, null);

        int columnIndex = cursor.getColumnIndex(column[0]);

        if (cursor.moveToFirst()) {
            filePath = cursor.getString(columnIndex);
        }
        cursor.close();
        return filePath;
    }

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

        buttonOpen = (Button) findViewById(R.id.opendocument);
        buttonOpen.setOnClickListener(buttonOpenOnClickListener);

        textUri = (TextView) findViewById(R.id.texturi);
        textUri.setOnClickListener(textUriOnClickListener);

        imageView = (ImageView)findViewById(R.id.image);
        imageView.setOnClickListener(imageOnClickListener);
    }

    protected void onActivityResult(int requestCode,
                                    int resultCode, Intent data) {

        if (resultCode == Activity.RESULT_OK) {

            Uri dataUri = data.getData();

            if (requestCode == RQS_OPEN_IMAGE) {
                targetUri = dataUri;
                textUri.setText(dataUri.toString());
                imageView.setImageBitmap(null);
            }
        }

    }

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

        switch (requestCode) {
            case RQS_READ_EXTERNAL_STORAGE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    showExif(targetUri);
                } else {
                    Toast.makeText(this,
                            "permission denied!",
                            Toast.LENGTH_SHORT).show();
                }
                return;
            }
        }
    }
}



Remark:
When I prepare the screencast, it seem not work on Xiaomi Redmi 2 running Android 4.4.4. Refer to the video above.


Tuesday, May 3, 2016

Open mp3 using Intent.ACTION_OPEN_DOCUMENT, ACTION_GET_CONTENT and ACTION_PICK, with checking and requesting permission at runtime.

Android example to open mp3 using Intent.ACTION_OPEN_DOCUMENT, ACTION_GET_CONTENT and ACTION_PICK, with checking and requesting permission at runtime.

This video show how it run on Android Emulator of Android 6.0, Marshmallo. It can be noticed that in case of ACTION_OPEN_DOCUMENT, the mp3 can be found but not openned; I don't know why currently! And if run on Android 6.0, have to check and request permission at runtime, otherwise the mp3 cannot be played.


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

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {

    TextView info, state;
    Button btnOpenDocument, btnGetContent, btnPick;
    Button buttonPlay, buttonPause, buttonStop;
    SeekBar timeLine;
    LinearLayout timeFrame;
    TextView timePos, timeDur;

    final static int RQS_OPEN_DOCUMENT = 1;
    final static int RQS_GET_CONTENT = 2;
    final static int RQS_PICK = 3;
    final static int RQS_PERMISSION_READ_EXTERNAL_STORAGE = 4;

    MediaPlayer mediaPlayer;
    Uri audioFileUri = null;

    enum MP_State {
        Idle, Initialized, Prepared, Started, Paused,
        Stopped, PlaybackCompleted, End, Error, Preparing
    }

    MP_State mediaPlayerState;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnOpenDocument = (Button)findViewById(R.id.opendocument);
        btnGetContent = (Button)findViewById(R.id.getcontent);
        btnPick = (Button)findViewById(R.id.pick);
        btnOpenDocument.setOnClickListener(btnOpenDocumentOnClickListener);
        btnGetContent.setOnClickListener(btnGetContentOnClickListener);
        btnPick.setOnClickListener(btnPickOnClickListener);

        info = (TextView) findViewById(R.id.info);
        state = (TextView) findViewById(R.id.state);

        buttonPlay = (Button) findViewById(R.id.play);
        buttonPlay.setOnClickListener(buttonPlayOnClickListener);
        buttonPause = (Button) findViewById(R.id.pause);
        buttonPause.setOnClickListener(buttonPauseOnClickListener);
        buttonStop = (Button) findViewById(R.id.stop);
        buttonStop.setOnClickListener(buttonStopOnClickListener);

        //
        timeLine = (SeekBar) findViewById(R.id.seekbartimeline);
        timeFrame = (LinearLayout) findViewById(R.id.timeframe);
        timePos = (TextView) findViewById(R.id.pos);
        timeDur = (TextView) findViewById(R.id.dur);

        ScheduledExecutorService myScheduledExecutorService
                = Executors.newScheduledThreadPool(1);

        myScheduledExecutorService.scheduleWithFixedDelay(
                new Runnable() {
                    @Override
                    public void run() {
                        monitorHandler.sendMessage(monitorHandler.obtainMessage());
                    }
                },
                200, //initialDelay
                200, //delay
                TimeUnit.MILLISECONDS);

    }

    Handler monitorHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            mediaPlayerMonitor();
        }

    };

    private void mediaPlayerMonitor() {
        if (mediaPlayer == null) {
            timeLine.setVisibility(View.INVISIBLE);
            timeFrame.setVisibility(View.INVISIBLE);
        } else {
            if (mediaPlayer.isPlaying()) {
                timeLine.setVisibility(View.VISIBLE);
                timeFrame.setVisibility(View.VISIBLE);

                int mediaDuration = mediaPlayer.getDuration();
                int mediaPosition = mediaPlayer.getCurrentPosition();
                timeLine.setMax(mediaDuration);
                timeLine.setProgress(mediaPosition);
                timePos.setText(String.valueOf((float) mediaPosition / 1000) + "s");
                timeDur.setText(String.valueOf((float) mediaDuration / 1000) + "s");
            } else {
                timeLine.setVisibility(View.INVISIBLE);
                timeFrame.setVisibility(View.INVISIBLE);
            }
        }
    }

    MediaPlayer.OnErrorListener mediaPlayerOnErrorListener
            = new MediaPlayer.OnErrorListener() {

        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
            // TODO Auto-generated method stub

            mediaPlayerState = MP_State.Error;
            showMediaPlayerState();

            return false;
        }
    };


    private void cmdReset() {
        if (mediaPlayer == null) {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setOnErrorListener(mediaPlayerOnErrorListener);
        }
        mediaPlayer.reset();
        mediaPlayerState = MP_State.Idle;
        showMediaPlayerState();
    }

    private void cmdSetDataSource(Uri uri) {
        if (mediaPlayerState == MP_State.Idle) {
            try {
                mediaPlayer.setDataSource(MainActivity.this, uri);
                mediaPlayerState = MP_State.Initialized;
            } catch (IllegalArgumentException e) {
                Toast.makeText(MainActivity.this,
                        e.toString(), Toast.LENGTH_LONG).show();
                e.printStackTrace();
            } catch (IllegalStateException e) {
                Toast.makeText(MainActivity.this,
                        e.toString(), Toast.LENGTH_LONG).show();
                e.printStackTrace();
            } catch (IOException e) {
                Toast.makeText(MainActivity.this,
                        e.toString(), Toast.LENGTH_LONG).show();
                e.printStackTrace();
            }
        } else {
            Toast.makeText(MainActivity.this,
                    "Invalid State@cmdSetDataSource - skip",
                    Toast.LENGTH_LONG).show();
        }

        showMediaPlayerState();
    }

    private void cmdPrepare() {

        if (mediaPlayerState == MP_State.Initialized
                || mediaPlayerState == MP_State.Stopped) {
            try {
                mediaPlayer.prepare();
                mediaPlayerState = MP_State.Prepared;
            } catch (IllegalStateException e) {
                Toast.makeText(MainActivity.this,
                        e.toString(), Toast.LENGTH_LONG).show();
                e.printStackTrace();
            } catch (IOException e) {
                Toast.makeText(MainActivity.this,
                        e.toString(), Toast.LENGTH_LONG).show();
                e.printStackTrace();
            }
        } else {
            Toast.makeText(MainActivity.this,
                    "Invalid State@cmdPrepare() - skip",
                    Toast.LENGTH_LONG).show();
        }

        showMediaPlayerState();
    }

    private void cmdStart() {
        if (mediaPlayerState == MP_State.Prepared
                || mediaPlayerState == MP_State.Started
                || mediaPlayerState == MP_State.Paused
                || mediaPlayerState == MP_State.PlaybackCompleted) {
            mediaPlayer.start();
            mediaPlayerState = MP_State.Started;
        } else {
            Toast.makeText(MainActivity.this,
                    "Invalid State@cmdStart() - skip",
                    Toast.LENGTH_LONG).show();
        }

        showMediaPlayerState();
    }

    private void cmdPause() {
        if (mediaPlayerState == MP_State.Started
                || mediaPlayerState == MP_State.Paused) {
            mediaPlayer.pause();
            mediaPlayerState = MP_State.Paused;
        } else {
            Toast.makeText(MainActivity.this,
                    "Invalid State@cmdPause() - skip",
                    Toast.LENGTH_LONG).show();
        }
        showMediaPlayerState();
    }

    private void cmdStop() {

        if (mediaPlayerState == MP_State.Prepared
                || mediaPlayerState == MP_State.Started
                || mediaPlayerState == MP_State.Stopped
                || mediaPlayerState == MP_State.Paused
                || mediaPlayerState == MP_State.PlaybackCompleted) {
            mediaPlayer.stop();
            mediaPlayerState = MP_State.Stopped;
        } else {
            Toast.makeText(MainActivity.this,
                    "Invalid State@cmdStop() - skip",
                    Toast.LENGTH_LONG).show();
        }
        showMediaPlayerState();

    }

    private void showMediaPlayerState() {

        switch (mediaPlayerState) {
            case Idle:
                state.setText("Idle");
                break;
            case Initialized:
                state.setText("Initialized");
                break;
            case Prepared:
                state.setText("Prepared");
                break;
            case Started:
                state.setText("Started");
                break;
            case Paused:
                state.setText("Paused");
                break;
            case Stopped:
                state.setText("Stopped");
                break;
            case PlaybackCompleted:
                state.setText("PlaybackCompleted");
                break;
            case End:
                state.setText("End");
                break;
            case Error:
                state.setText("Error");
                break;
            case Preparing:
                state.setText("Preparing");
                break;
            default:
                state.setText("Unknown!");
        }
    }

    View.OnClickListener buttonPlayOnClickListener
            = new View.OnClickListener() {

        @Override
        public void onClick(View v) {

            if (audioFileUri == null) {
                Toast.makeText(MainActivity.this,
                        "No file selected",
                        Toast.LENGTH_LONG).show();
            } else {
                cmdPrepare();
                cmdStart();
            }

        }

    };

    View.OnClickListener buttonPauseOnClickListener
            = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            cmdPause();
        }

    };

    View.OnClickListener buttonStopOnClickListener
            = new View.OnClickListener() {

        @Override
        public void onClick(View v) {

            cmdStop();

        }

    };

    View.OnClickListener btnOpenDocumentOnClickListener = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType("audio/mp3");
            startActivityForResult(
                    Intent.createChooser(intent, "ACTION_OPEN_DOCUMENT"),
                    RQS_OPEN_DOCUMENT);
        }
    };

    View.OnClickListener btnGetContentOnClickListener = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            intent.setType("audio/mp3");
            intent.setAction(Intent.ACTION_GET_CONTENT);
            startActivityForResult(Intent.createChooser(
                    intent, "ACTION_GET_CONTENT"), RQS_GET_CONTENT);
        }
    };

    View.OnClickListener btnPickOnClickListener = new View.OnClickListener(){

        @Override
        public void onClick(View v) {
            Intent intent = new Intent(Intent.ACTION_PICK,
                    android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
            startActivityForResult(
                    Intent.createChooser(intent, "ACTION_PICK"),
                    RQS_PICK);
        }
    };

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if(requestCode == RQS_OPEN_DOCUMENT
                    || requestCode == RQS_GET_CONTENT
                    || requestCode == RQS_PICK){
                audioFileUri = data.getData();

                info.setText(audioFileUri.toString());

                //Check and request Permission at runtime
                if (ActivityCompat.checkSelfPermission(this,
                        Manifest.permission.READ_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {

                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                            RQS_PERMISSION_READ_EXTERNAL_STORAGE);

                    return;
                }

                cmdReset();
                cmdSetDataSource(audioFileUri);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case RQS_PERMISSION_READ_EXTERNAL_STORAGE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(MainActivity.this,
                            "permission was granted, :)",
                            Toast.LENGTH_LONG).show();

                    cmdReset();
                    cmdSetDataSource(audioFileUri);
                } else {
                    Toast.makeText(MainActivity.this,
                            "permission denied, ...:(",
                            Toast.LENGTH_LONG).show();
                }
                return;
            }
        }
    }
}


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:orientation="vertical"
    android:padding="16dp"
    tools:context="com.blogspot.android_er.androidplayer.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/opendocument"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="ACTION_OPEN_DOCUMENT" />

    <Button
        android:id="@+id/getcontent"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="ACTION_GET_CONTENT" />

    <Button
        android:id="@+id/pick"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="ACTION_PICK" />

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

    <Button
        android:id="@+id/play"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Play" />

    <Button
        android:id="@+id/pause"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Pause" />

    <Button
        android:id="@+id/stop"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Stop" />

    <TextView
        android:id="@+id/state"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

    <SeekBar
        android:id="@+id/seekbartimeline"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:visibility="invisible" />

    <LinearLayout
        android:id="@+id/timeframe"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:visibility="invisible">

        <TextView
            android:id="@+id/pos"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/dur"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="right" />
    </LinearLayout>

</LinearLayout>


uses-permission of "android.permission.READ_EXTERNAL_STORAGE" is needed in AndroidManifest.xml.


download filesDownload the files .

Related:
Open mp4 using Intent.ACTION_OPEN_DOCUMENT, ACTION_GET_CONTENT and ACTION_PICK, and play in VideoView.

Friday, April 22, 2016

Requesting Permissions of Manifest.permission.ACCESS_FINE_LOCATION at Run Time

Refer to last example of "Get my Last Known Location, by calling LocationServices.FusedLocationApi.getLastLocation()" in Android Studio, you will be prompted with "code should explicitly check to see if permission is available (with 'checkPermission') or explicitly handle a potential 'SecurityException'" on the code "LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient)".


Last example handle 'SecurityException' with try/catch. This example show another approach to check if permission is available with 'checkPermission', then call ActivityCompat.requestPermissions() if need, and handle the user answer in onRequestPermissionsResult().

reference: Android Developers - Requesting Permissions at Run Time




notice for sending location to Android Emulator: I have to open another app, Google Maps, to monitor location, otherwise my example cannot get the updated location.

Edit MainActivity.java from last post.
package com.blogspot.android_er.androidgetlastlocation;

import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;

public class MainActivity extends AppCompatActivity
        implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    Button btnGetLastLocation;
    TextView textLastLocation;

    GoogleApiClient mGoogleApiClient;
    Location mLastLocation;

    static final int MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnGetLastLocation = (Button) findViewById(R.id.getlastlocation);
        btnGetLastLocation.setOnClickListener(btnGetLastLocationOnClickListener);
        textLastLocation = (TextView) findViewById(R.id.lastlocation);

        // Create an instance of GoogleAPIClient.
        if (mGoogleApiClient == null) {
            mGoogleApiClient = new GoogleApiClient.Builder(this)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .addApi(LocationServices.API)
                    .build();
        }
    }

    View.OnClickListener btnGetLastLocationOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            if (mGoogleApiClient != null) {
                if (mGoogleApiClient.isConnected()) {
                    getMyLocation();
                } else {
                    Toast.makeText(MainActivity.this,
                            "!mGoogleApiClient.isConnected()", Toast.LENGTH_LONG).show();
                }
            } else {
                Toast.makeText(MainActivity.this,
                        "mGoogleApiClient == null", Toast.LENGTH_LONG).show();
            }
        }
    };

    /*
    // Handle 'SecurityException' with try/catch
    private void getMyLocation(){
        try{
            //code should explicitly check to see if permission is available
            //(with 'checkPermission') or explicitly handle a potential 'SecurityException'
            //
            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
            if (mLastLocation != null) {
                textLastLocation.setText(
                        String.valueOf(mLastLocation.getLatitude()) + "\n"
                                + String.valueOf(mLastLocation.getLongitude()));
                Toast.makeText(MainActivity.this,
                        String.valueOf(mLastLocation.getLatitude()) + "\n"
                                + String.valueOf(mLastLocation.getLongitude()),
                        Toast.LENGTH_LONG).show();
            }else{
                Toast.makeText(MainActivity.this,
                        "mLastLocation == null",
                        Toast.LENGTH_LONG).show();
            }
        } catch (SecurityException e){
            Toast.makeText(MainActivity.this,
                    "SecurityException:\n" + e.toString(),
                    Toast.LENGTH_LONG).show();
        }
    }
    */


    //------------------------------------------------------------------------------
    //ref: Requesting Permissions at Run Time
    //http://developer.android.com/training/permissions/requesting.html
    //------------------------------------------------------------------------------
    private void getMyLocation() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED
            && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.


            //------------------------------------------------------------------------------
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);

            return;
        }
        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        if (mLastLocation != null) {
            textLastLocation.setText(
                    String.valueOf(mLastLocation.getLatitude()) + "\n"
                            + String.valueOf(mLastLocation.getLongitude()));
            Toast.makeText(MainActivity.this,
                    String.valueOf(mLastLocation.getLatitude()) + "\n"
                            + String.valueOf(mLastLocation.getLongitude()),
                    Toast.LENGTH_LONG).show();
        }else{
            Toast.makeText(MainActivity.this,
                    "mLastLocation == null",
                    Toast.LENGTH_LONG).show();
        }
    }

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

        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(MainActivity.this,
                            "permission was granted, :)",
                            Toast.LENGTH_LONG).show();
                    getMyLocation();

                } else {
                    Toast.makeText(MainActivity.this,
                            "permission denied, ...:(",
                            Toast.LENGTH_LONG).show();
                }
                return;
            }

            // other 'case' lines to check for other
            // permissions this app might request
        }
    }

    @Override
    protected void onStart() {
        mGoogleApiClient.connect();
        super.onStart();
    }

    @Override
    protected void onStop() {
        mGoogleApiClient.disconnect();
        super.onStop();
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        getMyLocation();
    }

    @Override
    public void onConnectionSuspended(int i) {
        Toast.makeText(MainActivity.this,
                "onConnectionSuspended: " + String.valueOf(i),
                Toast.LENGTH_LONG).show();
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Toast.makeText(MainActivity.this,
                "onConnectionFailed: \n" + connectionResult.toString(),
                Toast.LENGTH_LONG).show();
    }
}


Related:
Request Location Updates with LocationListener.onLocationChanged()