Hệ điều hành Android bảo vệ chính nó và thông tin riêng của người dùng bằng cách chạy các ứng dụng Android trên một môi trường ảo riêng (thuật ngữ gọi là Sandbox), môi trường ảo này có tài nguyên riêng, không đụng chạm gì tới tài nguyên của hệ điều hành, nếu ứng dụng muốn sử dụng tài nguyên bên ngoài Sandbox thì ứng dụng phải xin permission – quyền sử dụng. Tùy thuộc vào loại tài nguyên mà ứng dụng muốn truy cập, hệ điều hành sẽ cấp quyền sử dụng tự động hoặc sẽ phải hỏi ý kiến của người dùng thì mới được sử dụng.
Khai báo Permission
Tùy vào loại tài nguyên cần sử dụng mà ứng dụng sẽ phải khai báo permission cho phù hợp. Các permission sẽ được khai báo trong file AndroidManifest.xml.
Tùy vào loại dữ liệu có mức độ riêng tư đến mức nào mà hệ điều hành sẽ tự động cấp quyền hoặc phải xin ý kiến người dùng, chẳng hạn như quyền sử dụng đèn pin sẽ được cấp ngay, trong khi quyền truy cập danh sách số điện thoại liên lạc sẽ phải hỏi ý kiến người dùng… Cách người dùng cấp quyền cũng khác nhau theo từng phiên bản Android, chẳng hạn như đối với phiên bản Android 5.1 trở về trước thì người dùng sẽ cấp quyền trong quá trình cài đặt ứng dụng, còn ở phiên bản Android 6.0 trở lên thì người dùng sẽ cấp quyền khi ứng dụng đang chạy.
Xác định loại quyền cần dùng
Thông thường thì ứng dụng sẽ cần dùng đến các loại dữ liệu mà bản thân nó không thể tự tạo ra được, hay các hành động có thể làm ảnh hưởng đến hành vi của smartphone hoặc các ứng dụng khác. Chẳng hạn như quyền truy cập Internet, quyền sử dụng Camera, quyền tắt/bật Wifi…
Các quyền lại được chia làm nhiều cấp độ, trong đó 2 cấp độ cao nhất là bình thường (normal) và nguy hiểm (dangerous). Quyền bình thường là các quyền sử dụng tài nguyên mà ít có rủi ro đối với sự riêng tư của người dùng, loại quyền này sẽ được hệ điều hành tự động cấp. Dưới đây là danh sách các quyền bình thường có trong phiên bản API 23:
ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
Quyền nguy hiểm là quyền sử dụng các loại tài nguyên có liên quan đến sự riêng tư của người dùng hoặc có thể ảnh hưởng đến hệ điều hành và các ứng dụng khác. Loại quyền này cần được sự cho phép của người dùng. Dưới đây là danh sách các quyền nguy hiểm có trong phiên bản API 23:
READ_CALENDAR
WRITE_CALENDAR
CAMERA
READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
RECORD_AUDIO
READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
BODY_SENSORS
SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
Ứng dụng chỉ cần xin quyền để nó có thể sử dụng trực tiếp tài nguyên, nếu trong quá trình chạy mà ứng dụng có sử dụng dữ liệu lấy từ một ứng dụng khác thì chỉ có ứng dụng khác mới cần xin quyền.
Khai báo trong file AndroidManifest.xml
Để khai báo quyền thì chúng ta sử dụng thẻ <user-permissions>
trong thẻ <manifest>.
Ví dụ:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.phocode"> <uses-permission android:name="android.permission.SEND_SMS"/> <application ...> ... </application> </manifest>
Ở đây chúng ta xin quyền được gửi tin SMS. Đây là loại quyền nguy hiểm.
Xin quyền trong quá trình chạy
Đối với loại quyền nguy hiểm thì quá trình xin quyền sẽ khác ở từng phiên bản hệ điều hành và từng phiên bản SDK. Ví dụ:
- Nếu thiết bị chạy Android phiên bản 5.1 trở xuống và ứng dụng sử dụng API 22 trở xuống thì người dùng sẽ phải tự tay cấp quyền trong quá trình cài đặt ứng dụng, nếu không cấp thì ứng dụng sẽ không được cài đặt.
- Nếu thiết bị chạy Android 6.0 trở lên và ứng dụng sử dụng API 23 trở lên thì ứng dụng sẽ được cài nhưng khi chạy thì ứng dụng sẽ lần lượt xin từng quyền từ người dùng, người dùng có thể cấp quyền này, bỏ quyền kia và ứng dụng sẽ vẫn chạy nhưng giới hạn với những quyền không được cấp.
Kể từ phiên bản Android 6.0 (API 23), người dùng có thể lấy lại quyền của ứng dụng, ví dụ như chúng ta có một ứng dụng cần sử dụng camera, và hôm nay người dùng đã cho phép quyền sử dụng camera thì không có nghĩa là ngày hôm sau ứng dụng vẫn còn có quyền đó, do đó trước khi chạy chúng ta nên kiểm tra xem ứng dụng của chúng ta có quyền hay không đã.
Ví dụ
Chúng ta viết một ứng dụng cần có quyền đọc danh sách số điện thoại liên lạc.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.phocode.sharedpreferences"> <uses-permission android:name="android.permission.READ_CONTACTS"/> <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"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Trong file AndroidManifest.xml
chúng ta khai báo quyền trong thẻ <uses-permission>.
package com.phocode; import android.Manifest; import android.content.pm.PackageManager; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.widget.Toast; public class MainActivity extends Activity { private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, READ_CONTACTS_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case READ_CONTACTS_CODE: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(getApplicationContext(), "Contacts permission granted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getApplicationContext(), "Contacts permission denied", Toast.LENGTH_SHORT).show(); } return; } } } }
Trong file MainActivity.java
chúng ta thực hiện các công việc xin quyền đọc danh sách liên lạc.
private static final int READ_CONTACTS_CODE = 1;
Hằng số READ_CONTACTS_CODE
là một hằng số do chúng ta tự định nghĩa, hằng số này có tác dụng giống như ID để phân biệt các lần xin quyền.
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED)
Phương thức checkSelfPermission()
của lớp android.support.v4.ContextCompat
sẽ kiểm tra xem ứng dụng đã được cấp quyền đó rồi hay chưa,
lớp này được định nghĩa trong thư viện support v4. Chúng ta sẽ tìm hiểu về các thư viện support này sau.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, READ_CONTACTS_CODE);
Nếu quyền chưa được cấp thì chúng ta xin quyền bằng cách gọi phương thức ActivityCompat.requestPermission(),
phương thức này nhận vào đối tượng Activity
hiện tại, danh sách các quyền trong một mảng String
và ID mà chúng ta đã định nghĩa ở trên, kết quả trả về là PackageManager.PERMISSION_GRANTED
nếu được chấp nhận,
ngược lại là PackageManager.PERMISSION_DENIED.
@Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { ... }
Phương thức requestPermission()
sẽ hiển thị một hộp thoại xin cấp quyền cho người dùng, kết quả trả về sẽ được truyền vào lời gọi phương thức onRequestPermission(),
phương thức này nhận ID của quyền được xin, danh sách các quyền trong mảng permissions
và danh sách kết quả của từng quyền trong mảng grantResults.
switch (requestCode) { case READ_CONTACTS_CODE: { ... }
Câu lệnh switch()
sẽ thực thi từng câu lệnh tương ứng với từng ID.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(getApplicationContext(), "Contacts permission granted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getApplicationContext(), "Contacts permission denied", Toast.LENGTH_SHORT).show(); }
Ở đây chúng ta chỉ xin cấp 1 quyền là READ_CONTACTS,
do đó các mảng trả về chỉ có 1 phần tử, chúng ta kiểm tra xem kết quả xin quyền READ_CONTACTS
có thành công hay không bằng cách so sánh với hằng số PERMISSION_GRANTED.
Nếu có thì hiện một câu thông báo thành công, ngược lại thì báo thất bại.
Nếu ứng dụng đã từng bị từ chối cấp quyền bởi người dùng thì các lần xin quyền tiếp theo hộp thoại sẽ hiện một checkbox đề “Never ask again”, nếu người dùng check vào thì ứng dụng sẽ không bao giờ có thể xin quyền được nữa.