Monday 23 November 2015

Runtime Permissions in 6.0 Android MarshMallow

Recently i was studying about 6.0 runtime permissions and found it very interesting from both developer point of view as well as user.

On the basis of that i've developed a small demo which i am sharing and will help us understanding 6.0 runtime permissions in a better way.

So earlier we use to define permissions in the manifest and then simply use that feature in code. It is same for pre-marshmallow Devices but for Marshmallow and above we need to check for permissions first i.e if their are granted by user or not in order to perform certain functionalities.

 For example if i want to perform a call feature in 6.0 then i need to ask for permission from user before using it. So if user allows/grant that permission then only i'll be able to perform that functionality ahead, else i can't. So lets look into code how this works :

 1. MainActivity.java

public class MainActivity extends AppCompatActivity {

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

    private void prepareViews() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        ((Button)findViewById(R.id.permission_btn)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // here check whether app is runnung on 23 or greater then check for permission else perform normal functionality
                if (Build.VERSION.SDK_INT >= 23) {
                    checkPermission();
                } else {
                    Toast.makeText(MainActivity.this, "Device is below 6.0 do your work", Toast.LENGTH_LONG).show();
                }
            }
        });
        ((Button)findViewById(R.id.write_permission_btn)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT >= 23) {
                    checkCallPermission();
                } else {
                    Toast.makeText(MainActivity.this, "Device is below 6.0 do your work", Toast.LENGTH_LONG).show();
                }
            }
        });

    }


    private void checkPermission(){
        if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            // This condition checks whether user has earlier denied the permission or not just by clicking on deny in the permission dialog.
            //Remember not on the never ask check box then deny in the permission dialog
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_COARSE_LOCATION)) {
                /**
                 * Show an explanation to user why this permission in needed by us. Here using alert dialog to show the permission use.
                 */
                new AlertDialog.Builder(this)
                        .setTitle("Permission Required")
                        .setMessage("This permission was denied earlier by you. This permission is required to access your location for app accuracy purposes.So, in order to use this feature please allow this permission by clickking ok.")
                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                                        1);
                            }
                        })
                        .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .show();

            } else {
                // Just ask for the permission for first time. This block will come into play when user is trying to use feature which requires permission grant.
                //So for the first time user will be into this else block. So just ask for the permission you need by showing default permission dialog
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},1);
            }
        }else {
            // If permission is already granted by user then we will be into this else block. So do whatever is required here
            Toast.makeText(this,"Permission Aleardy granted",Toast.LENGTH_LONG).show();
        }
    }
    private void checkCallPermission(){
        if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.CALL_PHONE)) {
                /**
                 *
                 */
                new AlertDialog.Builder(this)
                        .setTitle("Permission Required")
                        .setMessage("This permission was denied earlier by you. This permission is required to call from app .So, in order to use this feature please allow this permission by clicking ok.")
                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[]{Manifest.permission.CALL_PHONE},
                                        2);
                            }
                        })
                        .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .show();

            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.CALL_PHONE},2);
            }
        }else {
            Toast.makeText(this,"Permission Aleardy granted",Toast.LENGTH_LONG).show();
        }
    }

    /**
     *  This method will be invoked when user allows or deny's a permission from the permission dialog so take actions accordingly.
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case 1:
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission was granted, Do the Location -related task you need to do.
                    Toast.makeText(this, "COARSE Permission granted", Toast.LENGTH_LONG).show();
                } else {
                    // This block will be called when user has denied the permission. There will be two possibilities.
                    //1. User has simple denied the permission by clicking on the deny button the permission dialog.
                    //2. User has ticked the never show dialog and denied the permission from the permission dialog.
                    String permission = permissions[0];
                    boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
                    if (!showRationale) {
                        //We will be in this block when User has ticked the never show dialog and denied the permission from the permission dialog
                        //Here we can not request the permission again if user has denied the permission with never ask option enabled.
                        //Only way is to show a imfo dialog and ask user to grant the permission from settings.
                        Toast.makeText(this, "COARSE Permission Denied with never show options", Toast.LENGTH_LONG).show();
                    } else {
                        //We will be in this block when User has denied the permission from the permission dialog and not clicked on the never show again checkbox.
                        //We can show a explaination why we need this permission and request permission again.
                        Toast.makeText(this, "COARSE Permission Denied", Toast.LENGTH_LONG).show();
                        //permission denied,Disable the functionality that depends on this permission.
                    }

                }
                break;
            case 2:
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "Call Permission granted", Toast.LENGTH_LONG).show();
                } else {
                    String permission = permissions[0];
                    boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
                    if (!showRationale) {
                        Toast.makeText(this, "Call Permission Denied with never show options", Toast.LENGTH_LONG).show();
                    } else {
                        Toast.makeText(this, "Call Permission Denied", Toast.LENGTH_LONG).show();
                    }
                    break;
                }
        }
    }
}

2. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
 >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Check Permission for Location"
        android:id="@+id/permission_btn"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_below="@+id/permission_btn"
        android:layout_height="wrap_content"
        android:text="call phone Check Permission for Location"
        android:id="@+id/write_permission_btn"
        />
</RelativeLayout> 

3. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.kamalvaid.marshmallowfeatures" >


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

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

</manifest>

Github Link :- https://github.com/CammyKamal/BlogTutorials/tree/master/MarshMallowFeatures

Happy coding!!

 References : http://developer.android.com/index.html

Tuesday 17 November 2015

Loading images in a GridView or Listview from drawables at Runtime using Imageloader



You can use this code where you want to get the dynamic resource id (i.e in int). I Mostly used in Adapters (for gridview, listview).

ImageArray for dummy purpose

String[] imagesArray={"image1","image2","image3","image4","image5","image6"};
   

First Declare your imageloader

ImageLoader mImageloader;


Define your DisplayOptions as per requirement

mImageOptions = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.ic_launcher)
        .showImageForEmptyUri(R.drawable.ic_launcher)
        .showImageOnFail(R.drawable.ic_launcher)
        .cacheInMemory(true)
        .cacheOnDisk(true)
        .considerExifParams(true)
        .bitmapConfig(Bitmap.Config.RGB_565)
        .build();


Define config as per requirement   

   
 ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(mContext)
        .threadPriority(Thread.NORM_PRIORITY - 2)
        .denyCacheImageMultipleSizesInMemory()
        .diskCacheFileNameGenerator(new Md5FileNameGenerator())
        .diskCacheSize(50 * 1024 * 1024) // 50 Mb
        .tasksProcessingOrder(QueueProcessingType.LIFO)
        .writeDebugLogs() // Remove for release app
        .build();
        // Initialize ImageLoader with configuration.
        ImageLoader.getInstance().init(config);
        mImageloader=ImageLoader.getInstance();

        
Then Load your image into your imageview
mImageloader
        .displayImage(("drawable://" + getResourceId(imagesArray[index],"drawable","com.example.dummy", mContext)), YourImageView, mImageOptions, new SimpleImageLoadingListener() {
            @Override
            public void onLoadingStarted(String imageUri, View view) {
            }

            @Override
            public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
            }

            @Override
            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
            }
        }, new ImageLoadingProgressListener() {
            @Override
            public void onProgressUpdate(String imageUri, View view, int current, int total) {
            }
        });
        
        

Use this method to get the resource id
public  int getResourceId(String fileName, String resourceName, String packageName,Context mContext) 
    {
        try {
            return mContext.getResources().getIdentifier(fileName, resourceName, packageName);
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        } 
    }