Những điều cần biết về Permission của Android

Với Marshmallow, một mô hình permission đã được bổ sung vào Android mà yêu cầu các Developer phải có cách tiếp cận khác để thực hiện xin cấp quyền permission trên Android. Trong bài viết này, chúng ta sẽ xem xét các cách thức để xử lý yêu cầu cấp quyền permission từ cả góc độ kỹ thuật và góc độ làm thế nào để cung cấp một trải nghiệm người dùng mượt mà.

Mô hình Runtime Permission

Hệ thống cấp quyền permission của Android là một trong những mối quan tâm an ninh lớn nhất kể từ khi tất cả các permission được yêu cầu khi cài đặt. Sau khi cài đặt, ứng dụng sẽ có thể truy cập tất cả mọi thứ đã được cấp quyền mà không cần thêm bất kỳ sự nhìn nhận nào từ phía người dùng cho biết chính xác những gì ứng dụng được phép truy cập.

Trong Android 6.0 Marshmallow, ứng dụng sẽ không được cấp bất kỳ permission nào vào lúc cài đặt. Thay vào đó, ứng dụng sẽ yêu cầu người dùng cho phép permission vào thời điểm chạy.

runtimepermission.jpg

Lưu ý rằng, dialog yêu cầu permission hiển thị ở trên là không chạy tự động. Developer phải tự gọi nó ra. Trong trường hợp này, developer đang cố gắng thực hiện một chức năng mà đòi hỏi phải được cung cấp một permission mà người dùng chưa cho phép, khi đó, chức năng này sẽ ném ra một Exception dẫn đến crash ứng dụng.

runtimepermissioncrash.jpg

Ngoài ra, người dùng còn có thể thu hồi các permission bất cứ lúc nào thông qua phần Setting ứng dụng.

permissionsrevoke.jpg

Các permission được cấp tự động

Có một số permission sẽ được cấp tự động vào thời điểm cài đặt và không thể thu hồi, đó là các Normal Permission (PROTECT_PERMISSION):

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ỉ đơn giản khai báo các quyền trên trong AndroidManifest.xml, các chức năng cần các quyền trên sẽ hoạt động tốt mà không cần thêm một sự cho phép nào từ người dùng.

Để ứng dụng hỗ trợ mô hình Runtime Permission

Để ứng dụng hỗ trợ mô hình Runtime Permission, đầu tiên, phải thiết lập compileSdkVersiontargetSdkVersion có giá trị 23.

    android {
        compileSdkVersion 23
        ...

        defaultConfig {
            ...
            targetSdkVersion 23
            ...
        }

Ví dụ, ứng dụng đang được xây dựng có chức năng thao tác với danh bạ của người dùng, được thực hiện trong phương thức sau:

    private void operateContact() {
        //insert a new contact
    }

Đoạn code trên cần được cung cấp permission WRITE_CONTACTS để thực hiện. Nếu phương thức trên được gọi mà không cung cấp permission thì ứng dụng sẽ bị crash.

Bước tiếp theo là thêm khai báo permission vào trong file AndroidManifest.xml tương tự như cách thức cũ.

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

Bước tiếp theo là phải tạo ra chức năng kiểm tra xem permission có được cung cấp hay không. Nếu chưa được cấp phép, một dialog sẽ được gọi ra để yêu cầu người dùng cung cấp permission này. Nếu permission đã được cấp phép, chức năng thao tác với danh bạ sẽ được thực hiện.

Các permission được phân vào các nhóm như sau:

permgroup.png

Nếu bất kỳ một permission nào trong cùng một nhóm được cấp phép, thì các permission khác trong cùng nhóm đó cũng sẽ được cấp phép. Tức là việc cấp phép sẽ được cung cấp theo nhóm. Chẳng hạn trong ví dụ này, WRITE_CONTACTS được cấp phép, đồng nghĩa với việc READ_CONTACTSGET_ACCOUNTS cũng được cấp phép.

Để kiểm tra và yêu cầu permission, sử dụng phương thức checkSelfPermissionrequestPermissions của Activity. Các phương thức này được sử dụng từ API level 23.

    final private int REQUEST_CODE_ASK_PERMISSIONS = 123;

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

Nếu permission Contact đã được cung cấp, phương thức operateContact sẽ được gọi. Nếu không, phương thức requestPermissions sẽ được thực hiện để gọi dialog yêu cầu người dùng cung cấp permission như sau:

requestpermission.jpg

Khi người dùng chọn Allow hoặc Deny, phương thức onRequestPermissionsResult của Activity sẽ được gọi để trả về kết quả thông qua tham số grantResults:

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

Xử lý "Never Ask Again"

Nếu người dùng từ chối cấp một permission và ngăn chặn ứng dụng xin quyền tiếp trong tương lai, thì sẽ lựa chọn "Never Ask Again".

neveraskagain.jpg

Nếu tuỳ chọn "Never Ask Again" được người dùng lựa chọn trước khi từ chối cấp quyền, thì lần tiếp theo gọi phương thức requestPermissions, dialog yêu cầu cung cấp permission sẽ không được hiển thị ra nữa, và sẽ không có gì được thực hiện.

Việc người dùng thực hiện một hành động nhưng không có gì tương tác trở lại là một điều không tốt trong trải nghiệm người dùng. Vì vậy, trước khi gọi phương thức requestPermissions, cần phải giải thích tới người dùng lý do ứng dụng cần permission thông qua phương thức shouldShowRequestPermissionRationale của Activity.

    final private int REQUEST_CODE_ASK_PERMISSIONS = 123;

    private void operateContactWrapper() {
        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 quả là dialog sau được hiển thị:

rationaledialog.jpg

Yêu cầu nhiều permission cùng lúc

Khi phát triển ứng dụng, sẽ có tính năng yêu cầu nhiều permission để thực hiện. Vì vậy, có thể yêu cầu nhiều permission cùng một lúc với cùng phương pháp như trên. Và đừng quên kiểm tra trường hợp người dùng lựa chọn "Never Ask Again".

Kết luận

  • Sử dụng mô hình Runtime Permission để hỗ trợ các vấn đề cấp thiết.
  • Đừng đặt targetSdkVersion là 23 khi ứng dụng của bạn chưa hỗ trợ Runtime Permission.