12/08/2018, 12:05

Android 6.0 Marshmallow : The New Runtime Permission ( Part 2 )

Như mình đã giới thiệu ở Phần 1 https://viblo.asia/bui.huu.tuan/posts/AeJ1vO2PGkby , trong Phần 2 này mình sẽ hướng dẫn các bạn xử lí Runtime Permission một cách cụ thế. 1. Các Permission được tự động cấp phép Dưới đây là danh sách các Permission được tự động cấp phép lúc cài đặt và sẽ ...

requestpermission.jpg

Như mình đã giới thiệu ở Phần 1 https://viblo.asia/bui.huu.tuan/posts/AeJ1vO2PGkby , trong Phần 2 này mình sẽ hướng dẫn các bạn xử lí Runtime Permission một cách cụ thế.

1. Các Permission được tự động cấp phép

Dưới đây là danh sách các Permission được tự động cấp phép lúc cài đặt và sẽ không bị thu hồi. Chúng được gọi là Normal Permission (PROTECTION_NORMAL) :

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS

android.permission.ACCESS_NETWORK_STATE

android.permission.ACCESS_NOTIFICATION_POLICY

android.permission.ACCESS_WIFI_STATE

android.permission.ACCESS_WIMAX_STATE

android.permission.BLUETOOTH

android.permission.BLUETOOTH_ADMIN

android.permission.BROADCAST_STICKY

android.permission.CHANGE_NETWORK_STATE

android.permission.CHANGE_WIFI_MULTICAST_STATE

android.permission.CHANGE_WIFI_STATE

android.permission.CHANGE_WIMAX_STATE

android.permission.DISABLE_KEYGUARD

android.permission.EXPAND_STATUS_BAR

android.permission.FLASHLIGHT

android.permission.GET_ACCOUNTS

android.permission.GET_PACKAGE_SIZE

android.permission.INTERNET

android.permission.KILL_BACKGROUND_PROCESSES

android.permission.MODIFY_AUDIO_SETTINGS

android.permission.NFC

android.permission.READ_SYNC_SETTINGS

android.permission.READ_SYNC_STATS

android.permission.RECEIVE_BOOT_COMPLETED

android.permission.REORDER_TASKS

android.permission.REQUEST_INSTALL_PACKAGES

android.permission.SET_TIME_ZONE

android.permission.SET_WALLPAPER

android.permission.SET_WALLPAPER_HINTS

android.permission.SUBSCRIBED_FEEDS_READ

android.permission.TRANSMIT_IR

android.permission.USE_FINGERPRINT

android.permission.VIBRATE

android.permission.WAKE_LOCK

android.permission.WRITE_SYNC_SETTINGS

com.android.alarm.permission.SET_ALARM

com.android.launcher.permission.INSTALL_SHORTCUT

com.android.launcher.permission.UNINSTALL_SHORTCUT

Chỉ cần đặt chúng trong file AndroidManifest.xml như bạn vẫn làm trước đây vì những Permission trên sẽ không bị thu hồi như đã đề cập.

2. Sẵn sàng với Runtime Permission

Bây giờ là lúc làm cho Ứng dụng của bạn hỗ trợ Runtime Permisson một cách hoàn hảo. Đầu tiên hãy để compileSdkVersion và targetSdkVersion là 23.

android {
    compileSdkVersion 23
    ...

    defaultConfig {
        ...
        targetSdkVersion 23
        ...
    }

Ví dụ dưới đây, chúng ta muốn thêm một danh bạ :

 private static final String TAG = "Contacts";
 private void insertDummyContact() {
    // Two operations are needed to insert a new contact.
    ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(2);

    // First, set up a new raw contact.
    ContentProviderOperation.Builder op =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
    operations.add(op.build());

    // Next, set the name for the contact.
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
                    "__DUMMY CONTACT from runtime permissions sample");
    operations.add(op.build());

    // Apply the operations.
    ContentResolver resolver = getContentResolver();
    try {
        resolver.applyBatch(ContactsContract.AUTHORITY, operations);
    } catch (RemoteException e) {
        Log.d(TAG, "Could not add a new contact: " + e.getMessage());
    } catch (OperationApplicationException e) {
        Log.d(TAG, "Could not add a new contact: " + e.getMessage());
    }
}

Đoạn code trên yêu cầu Permission WRITE_CONTACTS. Nếu nó được gọi ra mà không được quyền cấp phép, ứng dụng sẽ bị crash.

Tất nhiên, bạn sẽ thêm Permission trên vào file AndroidManifest.xml như bạn vẫn làm trước đây :

<uses-permission android:name="android.permission.WRITE_CONTACTS"/>

Bước tiếp theo chúng ta sẽ viết 1 hàm để kiểm tra xem Permission đó có được cấp phép hay không. Nếu nó không được cấp phép, ta sẽ hiển thị một dialog yêu cầu người dùng cấp phép cho nó. Còn nếu nó đã được cấp phép rồi thì thực hiện follow tiếp theo như bình thường.

Permission được xếp theo các nhóm sau :

permgroup.png

Nếu bất cứ Permission nào nằm trong một nhóm được cấp phép, thì các Permission còn lại trong nhóm đó cũng sẽ được tự động cấp phép. Trong trường hợp này, WRITE_CONTACTS được cấp phép, có nghĩa là 2 Permission còn lại trong nhóm là READ_CONTACTS và GET_ACCOUNTS cũng sẽ được tự động cấp phép.

Code để kiểm tra và yêu cầu cấp phép Permission như sau :

    final private int REQUEST_CODE_ASK_PERMISSIONS = 123;

    private void insertDummyContactWrapper() {
    int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
    if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                REQUEST_CODE_ASK_PERMISSIONS);
        return;
    }
    insertDummyContact();
    }

Nếu Permission đã được cấp phép, hàm insertDummyContact() sẽ được gọi. Ngược lại, hàm requestPermissions sẽ được gọi và hiển thị một dialog như sau :

requestpermission.jpg

Bất kể người dùng chọn Deny hay Allow, hàm onRequestPermissionsResult sẽ luôn được gọi để thông báo kết qủa mà chúng ta có thể kiểm tra thông qua tham số thứ 3, grantResults, như sau :

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case REQUEST_CODE_ASK_PERMISSIONS:
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission Granted
                insertDummyContact();
            } else {
                // Permission Denied
                Toast.makeText(MainActivity.this, "WRITE_CONTACTS Denied", Toast.LENGTH_SHORT)
                        .show();
            }
            break;
        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    }

Đó là cách Runtime Permission hoạt động. Code khá đơn giarn, nhưng để sử dụng nó làm cho ứng dụng chạy một cách hoàn hảo, bạn phải xử lí tất cả trường hợp với cùng một phương thức như trên.

3. Xử lí "Never Ask Again"

Nếu người dùng từ chối cấp phép một Permission, khi khởi chạy lần thứ hai, người dùng sẽ có một lựa chọn "Never ask again" để tránh việc ứng dụng yêu cầu người dùng trong lần tiếp theo.

neveraskagain.jpg

Nếu người dùng chọn "Never Ask Again" trước khi ấn Deny, lần tiếp theo khi chúng ta gọi hàm requestPermissions, dialog sẽ không xuất hiện nữa, thay vì đó, nó sẽ ko làm gì hết.

Tuy nhiên nó cũng khá bất tiện nếu người dùng ko làm gì và ko có gì tương tác ngược trở lại. Trong trường hợp này bạn phải xử lí như sau. Trước khi gọi requestPermissions, chúng ta phải kiểm tra xem chúng ta có nên nói lý do tại sao ứng dụng cần được cấp phép Permission hay không, thông qua hàm shouldShowRequestPermissionRationale :

    final private int REQUEST_CODE_ASK_PERMISSIONS = 123;
    private void insertDummyContactWrapper() {
    int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
    if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {
                showMessageOKCancel("You need to allow access to Contacts",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                                        REQUEST_CODE_ASK_PERMISSIONS);
                            }
                        });
                return;
            }
        requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
                REQUEST_CODE_ASK_PERMISSIONS);
        return;
    }
    insertDummyContact();
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
    new AlertDialog.Builder(MainActivity.this)
            .setMessage(message)
            .setPositiveButton("OK", okListener)
            .setNegativeButton("Cancel", null)
            .create()
            .show();
    }

Kết qủa là dialog sẽ được hiển thị ra khi Permisssion yêu cầu lần đầu tiên và cũng được hiển thị ra nếu người dùng trước đó đã chọn Never ask again. Đối với các trường hợp sau đó, onRequestPermissionsResult sẽ được gọi với PERMISSION_DENIED, mà ko hiển thị bất cứ dialog yêu cầu Permission nào.

rationaledialog.jpg

Yeah. Xong             </div>
            
            <div class=

0