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ẽ ...
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 :
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 :
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.
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.
Yeah. Xong