Android权限 - AppOps介绍

1.介绍
frameworks\base\core\java\android\app\AppOpsManager.java
frameworks\base\services\core\java\com\android\server\appop\AppOpsService.java

/**
 * API for interacting with "application operation" tracking.
 *
 * <p>This API is not generally intended for third party application developers; most
 * features are only available to system applications.
 */

就是应用程序操作管理。AppOpsManager是在API 19引入,即Android 4.3。且这个API不面向三方应用开发者;多数功能都只对系统应用可用。

AppOps虽然涵盖了App的权限管理,但是Google原生的设计并不仅仅是对“权限”的管理,而是对App的“动作”的管理。我们平时讲的权限管理多是针对具体的权限(App开发者在Manifest里申请的权限),而AppOps所管理的是所有可能涉及用户隐私和安全的操作,包括Turned on the screen, display toast等等,有些操作是不需要Manifest里申请权限的。

2.AppOpsManager介绍
(1).检测后的返回值

MODE_ALLOWED, // 0, 操作允许, Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is allowed to perform the given operation.
MODE_IGNORED, // 1, 操作不允许,但是checkOp不会抛出安全异常,表示忽略, Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is not allowed to perform the given operation,
                   // and this attempt should silently fail(it should not cause the app to crash).
MODE_ERRORED, // 2, 表示操作不允许,checkOp操作会抛出安全异常, Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: thegiven caller is not allowed to perform the given operation, 
                   // and this attempt should cause it to have a fatal error, typically a {@link SecurityException}.
MODE_DEFAULT, // 3, 默认行为,可能进一步检查权限, Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller should use its default security check. 
                   // This mode is not normally used; it should only be used with appop permissions, and callers must explicitly check for it and deal with it.
MODE_FOREGROUND, // 4, The only place you will this normally see this value is through {@link #unsafeCheckOpRaw}, which returns the actual raw mode of the op.

(2).相关常量
1).定义了一系列的OP:

......
public static final int OP_READ_CONTACTS = 4;
/** @hide */
@UnsupportedAppUsage
public static final int OP_WRITE_CONTACTS = 5;
/** @hide */
@UnsupportedAppUsage
public static final int OP_READ_CALL_LOG = 6;
/** @hide */
@UnsupportedAppUsage
public static final int OP_WRITE_CALL_LOG = 7;
/** @hide */
@UnsupportedAppUsage
public static final int OP_READ_CALENDAR = 8;
/** @hide */
@UnsupportedAppUsage
public static final int OP_WRITE_CALENDAR = 9;

public static final int _NUM_OP = 91;
......

2).定义了相对应的OP Name:

......
/** Allows an application to read the user's contacts data. */
public static final String OPSTR_READ_CONTACTS
		= "android:read_contacts";
/** Allows an application to write to the user's contacts data. */
public static final String OPSTR_WRITE_CONTACTS
		= "android:write_contacts";
/** Allows an application to read the user's call log. */
public static final String OPSTR_READ_CALL_LOG
		= "android:read_call_log";
/** Allows an application to write to the user's call log. */
public static final String OPSTR_WRITE_CALL_LOG
		= "android:write_call_log";
/** Allows an application to read the user's calendar data. */
public static final String OPSTR_READ_CALENDAR
		= "android:read_calendar";
/** Allows an application to write to the user's calendar data. */
public static final String OPSTR_WRITE_CALENDAR
		= "android:write_calendar";
......

3).定义了运行时权限及App Op权限:

// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
		// RUNTIME PERMISSIONS
		// Contacts
		OP_READ_CONTACTS,
		OP_WRITE_CONTACTS,
		OP_GET_ACCOUNTS,
		// Calendar
		OP_READ_CALENDAR,
		OP_WRITE_CALENDAR,
		// SMS
		OP_SEND_SMS,
		OP_RECEIVE_SMS,
		OP_READ_SMS,
		OP_RECEIVE_WAP_PUSH,
		OP_RECEIVE_MMS,
		OP_READ_CELL_BROADCASTS,
		// Storage
		OP_READ_EXTERNAL_STORAGE,
		OP_WRITE_EXTERNAL_STORAGE,
		OP_ACCESS_MEDIA_LOCATION,
		// Location
		OP_COARSE_LOCATION,
		OP_FINE_LOCATION,
		// Phone
		OP_READ_PHONE_STATE,
		OP_READ_PHONE_NUMBERS,
		OP_CALL_PHONE,
		OP_READ_CALL_LOG,
		OP_WRITE_CALL_LOG,
		OP_ADD_VOICEMAIL,
		OP_USE_SIP,
		OP_PROCESS_OUTGOING_CALLS,
		OP_ANSWER_PHONE_CALLS,
		OP_ACCEPT_HANDOVER,
		// Microphone
		OP_RECORD_AUDIO,
		// Camera
		OP_CAMERA,
		// Body sensors
		OP_BODY_SENSORS,
		// Activity recognition
		OP_ACTIVITY_RECOGNITION,
		// Aural
		OP_READ_MEDIA_AUDIO,
		OP_WRITE_MEDIA_AUDIO,
		// Visual
		OP_READ_MEDIA_VIDEO,
		OP_WRITE_MEDIA_VIDEO,
		OP_READ_MEDIA_IMAGES,
		OP_WRITE_MEDIA_IMAGES,

		// APPOP PERMISSIONS
		OP_ACCESS_NOTIFICATIONS,
		OP_SYSTEM_ALERT_WINDOW,
		OP_WRITE_SETTINGS,
		OP_REQUEST_INSTALL_PACKAGES,
		OP_START_FOREGROUND,
		OP_SMS_FINANCIAL_TRANSACTIONS,
};

4).定义了sOpToSwitch,及opToSwitch方法,通过数字得到相应的OP:

private static int[] sOpToSwitch = new int[] {
		OP_COARSE_LOCATION,                 // COARSE_LOCATION
		OP_COARSE_LOCATION,                 // FINE_LOCATION
		OP_COARSE_LOCATION,                 // GPS
		OP_VIBRATE,                         // VIBRATE
		OP_READ_CONTACTS,                   // READ_CONTACTS
		OP_WRITE_CONTACTS,                  // WRITE_CONTACTS
		OP_READ_CALL_LOG,                   // READ_CALL_LOG
		OP_WRITE_CALL_LOG,                  // WRITE_CALL_LOG
		OP_READ_CALENDAR,                   // READ_CALENDAR
		OP_WRITE_CALENDAR,                  // WRITE_CALENDAR
		OP_COARSE_LOCATION,                 // WIFI_SCAN
		OP_POST_NOTIFICATION,               // POST_NOTIFICATION
		OP_COARSE_LOCATION,                 // NEIGHBORING_CELLS
		OP_CALL_PHONE,                      // CALL_PHONE
		OP_READ_SMS,                        // READ_SMS
		OP_WRITE_SMS,                       // WRITE_SMS
		OP_RECEIVE_SMS,                     // RECEIVE_SMS
		OP_RECEIVE_SMS,                     // RECEIVE_EMERGECY_SMS
		OP_RECEIVE_MMS,                     // RECEIVE_MMS
		OP_RECEIVE_WAP_PUSH,                // RECEIVE_WAP_PUSH
		OP_SEND_SMS,                        // SEND_SMS
		OP_READ_SMS,                        // READ_ICC_SMS
		OP_WRITE_SMS,                       // WRITE_ICC_SMS
		OP_WRITE_SETTINGS,                  // WRITE_SETTINGS
		OP_SYSTEM_ALERT_WINDOW,             // SYSTEM_ALERT_WINDOW
		OP_ACCESS_NOTIFICATIONS,            // ACCESS_NOTIFICATIONS
		OP_CAMERA,                          // CAMERA
		OP_RECORD_AUDIO,                    // RECORD_AUDIO
		OP_PLAY_AUDIO,                      // PLAY_AUDIO
		OP_READ_CLIPBOARD,                  // READ_CLIPBOARD
		OP_WRITE_CLIPBOARD,                 // WRITE_CLIPBOARD
		OP_TAKE_MEDIA_BUTTONS,              // TAKE_MEDIA_BUTTONS
		OP_TAKE_AUDIO_FOCUS,                // TAKE_AUDIO_FOCUS
		OP_AUDIO_MASTER_VOLUME,             // AUDIO_MASTER_VOLUME
		OP_AUDIO_VOICE_VOLUME,              // AUDIO_VOICE_VOLUME
		OP_AUDIO_RING_VOLUME,               // AUDIO_RING_VOLUME
		OP_AUDIO_MEDIA_VOLUME,              // AUDIO_MEDIA_VOLUME
		OP_AUDIO_ALARM_VOLUME,              // AUDIO_ALARM_VOLUME
		OP_AUDIO_NOTIFICATION_VOLUME,       // AUDIO_NOTIFICATION_VOLUME
		OP_AUDIO_BLUETOOTH_VOLUME,          // AUDIO_BLUETOOTH_VOLUME
		OP_WAKE_LOCK,                       // WAKE_LOCK
		OP_COARSE_LOCATION,                 // MONITOR_LOCATION
		OP_COARSE_LOCATION,                 // MONITOR_HIGH_POWER_LOCATION
		OP_GET_USAGE_STATS,                 // GET_USAGE_STATS
		OP_MUTE_MICROPHONE,                 // MUTE_MICROPHONE
		OP_TOAST_WINDOW,                    // TOAST_WINDOW
		OP_PROJECT_MEDIA,                   // PROJECT_MEDIA
		OP_ACTIVATE_VPN,                    // ACTIVATE_VPN
		OP_WRITE_WALLPAPER,                 // WRITE_WALLPAPER
		OP_ASSIST_STRUCTURE,                // ASSIST_STRUCTURE
		OP_ASSIST_SCREENSHOT,               // ASSIST_SCREENSHOT
		OP_READ_PHONE_STATE,                // READ_PHONE_STATE
		OP_ADD_VOICEMAIL,                   // ADD_VOICEMAIL
		OP_USE_SIP,                         // USE_SIP
		OP_PROCESS_OUTGOING_CALLS,          // PROCESS_OUTGOING_CALLS
		OP_USE_FINGERPRINT,                 // USE_FINGERPRINT
		OP_BODY_SENSORS,                    // BODY_SENSORS
		OP_READ_CELL_BROADCASTS,            // READ_CELL_BROADCASTS
		OP_MOCK_LOCATION,                   // MOCK_LOCATION
		OP_READ_EXTERNAL_STORAGE,           // READ_EXTERNAL_STORAGE
		OP_WRITE_EXTERNAL_STORAGE,          // WRITE_EXTERNAL_STORAGE
		OP_TURN_SCREEN_ON,                  // TURN_SCREEN_ON
		OP_GET_ACCOUNTS,                    // GET_ACCOUNTS
		OP_RUN_IN_BACKGROUND,               // RUN_IN_BACKGROUND
		OP_AUDIO_ACCESSIBILITY_VOLUME,      // AUDIO_ACCESSIBILITY_VOLUME
		OP_READ_PHONE_NUMBERS,              // READ_PHONE_NUMBERS
		OP_REQUEST_INSTALL_PACKAGES,        // REQUEST_INSTALL_PACKAGES
		OP_PICTURE_IN_PICTURE,              // ENTER_PICTURE_IN_PICTURE_ON_HIDE
		OP_INSTANT_APP_START_FOREGROUND,    // INSTANT_APP_START_FOREGROUND
		OP_ANSWER_PHONE_CALLS,              // ANSWER_PHONE_CALLS
		OP_RUN_ANY_IN_BACKGROUND,           // OP_RUN_ANY_IN_BACKGROUND
		OP_CHANGE_WIFI_STATE,               // OP_CHANGE_WIFI_STATE
		OP_REQUEST_DELETE_PACKAGES,         // OP_REQUEST_DELETE_PACKAGES
		OP_BIND_ACCESSIBILITY_SERVICE,      // OP_BIND_ACCESSIBILITY_SERVICE
		OP_ACCEPT_HANDOVER,                 // ACCEPT_HANDOVER
		OP_MANAGE_IPSEC_TUNNELS,            // MANAGE_IPSEC_HANDOVERS
		OP_START_FOREGROUND,                // START_FOREGROUND
		OP_COARSE_LOCATION,                 // BLUETOOTH_SCAN
		OP_USE_BIOMETRIC,                   // BIOMETRIC
		OP_ACTIVITY_RECOGNITION,            // ACTIVITY_RECOGNITION
		OP_SMS_FINANCIAL_TRANSACTIONS,      // SMS_FINANCIAL_TRANSACTIONS
		OP_READ_MEDIA_AUDIO,                // READ_MEDIA_AUDIO
		OP_WRITE_MEDIA_AUDIO,               // WRITE_MEDIA_AUDIO
		OP_READ_MEDIA_VIDEO,                // READ_MEDIA_VIDEO
		OP_WRITE_MEDIA_VIDEO,               // WRITE_MEDIA_VIDEO
		OP_READ_MEDIA_IMAGES,               // READ_MEDIA_IMAGES
		OP_WRITE_MEDIA_IMAGES,              // WRITE_MEDIA_IMAGES
		OP_LEGACY_STORAGE,                  // LEGACY_STORAGE
		OP_ACCESS_ACCESSIBILITY,            // ACCESS_ACCESSIBILITY
		OP_READ_DEVICE_IDENTIFIERS,         // READ_DEVICE_IDENTIFIERS
		OP_ACCESS_MEDIA_LOCATION,           // ACCESS_MEDIA_LOCATION
};

public static int opToSwitch(int op) {
	return sOpToSwitch[op];
}

5).定义了sOpToString,通过op得到其name:

private static String[] sOpToString = new String[]{
		OPSTR_COARSE_LOCATION,
		OPSTR_FINE_LOCATION,
		OPSTR_GPS,
		OPSTR_VIBRATE,
		OPSTR_READ_CONTACTS,
		OPSTR_WRITE_CONTACTS,
		OPSTR_READ_CALL_LOG,
		OPSTR_WRITE_CALL_LOG,
		OPSTR_READ_CALENDAR,
		OPSTR_WRITE_CALENDAR,
		OPSTR_WIFI_SCAN,
		OPSTR_POST_NOTIFICATION,
		OPSTR_NEIGHBORING_CELLS,
		OPSTR_CALL_PHONE,
		OPSTR_READ_SMS,
		OPSTR_WRITE_SMS,
		OPSTR_RECEIVE_SMS,
		OPSTR_RECEIVE_EMERGENCY_BROADCAST,
		OPSTR_RECEIVE_MMS,
		OPSTR_RECEIVE_WAP_PUSH,
		OPSTR_SEND_SMS,
		OPSTR_READ_ICC_SMS,
		OPSTR_WRITE_ICC_SMS,
		OPSTR_WRITE_SETTINGS,
		OPSTR_SYSTEM_ALERT_WINDOW,
		OPSTR_ACCESS_NOTIFICATIONS,
		OPSTR_CAMERA,
		OPSTR_RECORD_AUDIO,
		OPSTR_PLAY_AUDIO,
		OPSTR_READ_CLIPBOARD,
		OPSTR_WRITE_CLIPBOARD,
		OPSTR_TAKE_MEDIA_BUTTONS,
		OPSTR_TAKE_AUDIO_FOCUS,
		OPSTR_AUDIO_MASTER_VOLUME,
		OPSTR_AUDIO_VOICE_VOLUME,
		OPSTR_AUDIO_RING_VOLUME,
		OPSTR_AUDIO_MEDIA_VOLUME,
		OPSTR_AUDIO_ALARM_VOLUME,
		OPSTR_AUDIO_NOTIFICATION_VOLUME,
		OPSTR_AUDIO_BLUETOOTH_VOLUME,
		OPSTR_WAKE_LOCK,
		OPSTR_MONITOR_LOCATION,
		OPSTR_MONITOR_HIGH_POWER_LOCATION,
		OPSTR_GET_USAGE_STATS,
		OPSTR_MUTE_MICROPHONE,
		OPSTR_TOAST_WINDOW,
		OPSTR_PROJECT_MEDIA,
		OPSTR_ACTIVATE_VPN,
		OPSTR_WRITE_WALLPAPER,
		OPSTR_ASSIST_STRUCTURE,
		OPSTR_ASSIST_SCREENSHOT,
		OPSTR_READ_PHONE_STATE,
		OPSTR_ADD_VOICEMAIL,
		OPSTR_USE_SIP,
		OPSTR_PROCESS_OUTGOING_CALLS,
		OPSTR_USE_FINGERPRINT,
		OPSTR_BODY_SENSORS,
		OPSTR_READ_CELL_BROADCASTS,
		OPSTR_MOCK_LOCATION,
		OPSTR_READ_EXTERNAL_STORAGE,
		OPSTR_WRITE_EXTERNAL_STORAGE,
		OPSTR_TURN_SCREEN_ON,
		OPSTR_GET_ACCOUNTS,
		OPSTR_RUN_IN_BACKGROUND,
		OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
		OPSTR_READ_PHONE_NUMBERS,
		OPSTR_REQUEST_INSTALL_PACKAGES,
		OPSTR_PICTURE_IN_PICTURE,
		OPSTR_INSTANT_APP_START_FOREGROUND,
		OPSTR_ANSWER_PHONE_CALLS,
		OPSTR_RUN_ANY_IN_BACKGROUND,
		OPSTR_CHANGE_WIFI_STATE,
		OPSTR_REQUEST_DELETE_PACKAGES,
		OPSTR_BIND_ACCESSIBILITY_SERVICE,
		OPSTR_ACCEPT_HANDOVER,
		OPSTR_MANAGE_IPSEC_TUNNELS,
		OPSTR_START_FOREGROUND,
		OPSTR_BLUETOOTH_SCAN,
		OPSTR_USE_BIOMETRIC,
		OPSTR_ACTIVITY_RECOGNITION,
		OPSTR_SMS_FINANCIAL_TRANSACTIONS,
		OPSTR_READ_MEDIA_AUDIO,
		OPSTR_WRITE_MEDIA_AUDIO,
		OPSTR_READ_MEDIA_VIDEO,
		OPSTR_WRITE_MEDIA_VIDEO,
		OPSTR_READ_MEDIA_IMAGES,
		OPSTR_WRITE_MEDIA_IMAGES,
		OPSTR_LEGACY_STORAGE,
		OPSTR_ACCESS_ACCESSIBILITY,
		OPSTR_READ_DEVICE_IDENTIFIERS,
		OPSTR_ACCESS_MEDIA_LOCATION,
};

6).列出了每个OP对应的权限,如果没有,则为null:

private static String[] sOpPerms = new String[] {
		android.Manifest.permission.ACCESS_COARSE_LOCATION,
		android.Manifest.permission.ACCESS_FINE_LOCATION,
		null,
		android.Manifest.permission.VIBRATE,
		android.Manifest.permission.READ_CONTACTS,
		android.Manifest.permission.WRITE_CONTACTS,
		android.Manifest.permission.READ_CALL_LOG,
		android.Manifest.permission.WRITE_CALL_LOG,
		android.Manifest.permission.READ_CALENDAR,
		android.Manifest.permission.WRITE_CALENDAR,
		android.Manifest.permission.ACCESS_WIFI_STATE,
		null, // no permission required for notifications
		null, // neighboring cells shares the coarse location perm
		android.Manifest.permission.CALL_PHONE,
		android.Manifest.permission.READ_SMS,
		null, // no permission required for writing sms
		android.Manifest.permission.RECEIVE_SMS,
		android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
		android.Manifest.permission.RECEIVE_MMS,
		android.Manifest.permission.RECEIVE_WAP_PUSH,
		android.Manifest.permission.SEND_SMS,
		android.Manifest.permission.READ_SMS,
		null, // no permission required for writing icc sms
		android.Manifest.permission.WRITE_SETTINGS,
		android.Manifest.permission.SYSTEM_ALERT_WINDOW,
		android.Manifest.permission.ACCESS_NOTIFICATIONS,
		android.Manifest.permission.CAMERA,
		android.Manifest.permission.RECORD_AUDIO,
		null, // no permission for playing audio
		null, // no permission for reading clipboard
		null, // no permission for writing clipboard
		null, // no permission for taking media buttons
		null, // no permission for taking audio focus
		null, // no permission for changing master volume
		null, // no permission for changing voice volume
		null, // no permission for changing ring volume
		null, // no permission for changing media volume
		null, // no permission for changing alarm volume
		null, // no permission for changing notification volume
		null, // no permission for changing bluetooth volume
		android.Manifest.permission.WAKE_LOCK,
		null, // no permission for generic location monitoring
		null, // no permission for high power location monitoring
		android.Manifest.permission.PACKAGE_USAGE_STATS,
		null, // no permission for muting/unmuting microphone
		null, // no permission for displaying toasts
		null, // no permission for projecting media
		null, // no permission for activating vpn
		null, // no permission for supporting wallpaper
		null, // no permission for receiving assist structure
		null, // no permission for receiving assist screenshot
		Manifest.permission.READ_PHONE_STATE,
		Manifest.permission.ADD_VOICEMAIL,
		Manifest.permission.USE_SIP,
		Manifest.permission.PROCESS_OUTGOING_CALLS,
		Manifest.permission.USE_FINGERPRINT,
		Manifest.permission.BODY_SENSORS,
		Manifest.permission.READ_CELL_BROADCASTS,
		null,
		Manifest.permission.READ_EXTERNAL_STORAGE,
		Manifest.permission.WRITE_EXTERNAL_STORAGE,
		null, // no permission for turning the screen on
		Manifest.permission.GET_ACCOUNTS,
		null, // no permission for running in background
		null, // no permission for changing accessibility volume
		Manifest.permission.READ_PHONE_NUMBERS,
		Manifest.permission.REQUEST_INSTALL_PACKAGES,
		null, // no permission for entering picture-in-picture on hide
		Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
		Manifest.permission.ANSWER_PHONE_CALLS,
		null, // no permission for OP_RUN_ANY_IN_BACKGROUND
		Manifest.permission.CHANGE_WIFI_STATE,
		Manifest.permission.REQUEST_DELETE_PACKAGES,
		Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
		Manifest.permission.ACCEPT_HANDOVER,
		null, // no permission for OP_MANAGE_IPSEC_TUNNELS
		Manifest.permission.FOREGROUND_SERVICE,
		null, // no permission for OP_BLUETOOTH_SCAN
		Manifest.permission.USE_BIOMETRIC,
		Manifest.permission.ACTIVITY_RECOGNITION,
		Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
		null,
		null, // no permission for OP_WRITE_MEDIA_AUDIO
		null,
		null, // no permission for OP_WRITE_MEDIA_VIDEO
		null,
		null, // no permission for OP_WRITE_MEDIA_IMAGES
		null, // no permission for OP_LEGACY_STORAGE
		null, // no permission for OP_ACCESS_ACCESSIBILITY
		null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
		Manifest.permission.ACCESS_MEDIA_LOCATION,
};

7).给出了默认操作,通过opToDefaultMode方法得到默认值:

/**
 * This specifies the default mode for each operation.
 */
private static int[] sOpDefaultMode = new int[] {
		AppOpsManager.MODE_ALLOWED, // COARSE_LOCATION
		AppOpsManager.MODE_ALLOWED, // FINE_LOCATION
		AppOpsManager.MODE_ALLOWED, // GPS
		AppOpsManager.MODE_ALLOWED, // VIBRATE
		AppOpsManager.MODE_ALLOWED, // READ_CONTACTS
		AppOpsManager.MODE_ALLOWED, // WRITE_CONTACTS
		AppOpsManager.MODE_ALLOWED, // READ_CALL_LOG
		AppOpsManager.MODE_ALLOWED, // WRITE_CALL_LOG
		AppOpsManager.MODE_ALLOWED, // READ_CALENDAR
		AppOpsManager.MODE_ALLOWED, // WRITE_CALENDAR
		AppOpsManager.MODE_ALLOWED, // WIFI_SCAN
		AppOpsManager.MODE_ALLOWED, // POST_NOTIFICATION
		AppOpsManager.MODE_ALLOWED, // NEIGHBORING_CELLS
		AppOpsManager.MODE_ALLOWED, // CALL_PHONE
		AppOpsManager.MODE_ALLOWED, // READ_SMS
		AppOpsManager.MODE_IGNORED, // WRITE_SMS
		AppOpsManager.MODE_ALLOWED, // RECEIVE_SMS
		AppOpsManager.MODE_ALLOWED, // RECEIVE_EMERGENCY_BROADCAST
		AppOpsManager.MODE_ALLOWED, // RECEIVE_MMS
		AppOpsManager.MODE_ALLOWED, // RECEIVE_WAP_PUSH
		AppOpsManager.MODE_ALLOWED, // SEND_SMS
		AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
		AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
		AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
		getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
		AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
		AppOpsManager.MODE_ALLOWED, // CAMERA
		AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
		AppOpsManager.MODE_ALLOWED, // PLAY_AUDIO
		AppOpsManager.MODE_ALLOWED, // READ_CLIPBOARD
		AppOpsManager.MODE_ALLOWED, // WRITE_CLIPBOARD
		AppOpsManager.MODE_ALLOWED, // TAKE_MEDIA_BUTTONS
		AppOpsManager.MODE_ALLOWED, // TAKE_AUDIO_FOCUS
		AppOpsManager.MODE_ALLOWED, // AUDIO_MASTER_VOLUME
		AppOpsManager.MODE_ALLOWED, // AUDIO_VOICE_VOLUME
		AppOpsManager.MODE_ALLOWED, // AUDIO_RING_VOLUME
		AppOpsManager.MODE_ALLOWED, // AUDIO_MEDIA_VOLUME
		AppOpsManager.MODE_ALLOWED, // AUDIO_ALARM_VOLUME
		AppOpsManager.MODE_ALLOWED, // AUDIO_NOTIFICATION_VOLUME
		AppOpsManager.MODE_ALLOWED, // AUDIO_BLUETOOTH_VOLUME
		AppOpsManager.MODE_ALLOWED, // WAKE_LOCK
		AppOpsManager.MODE_ALLOWED, // MONITOR_LOCATION
		AppOpsManager.MODE_ALLOWED, // MONITOR_HIGH_POWER_LOCATION
		AppOpsManager.MODE_DEFAULT, // GET_USAGE_STATS
		AppOpsManager.MODE_ALLOWED, // MUTE_MICROPHONE
		AppOpsManager.MODE_ALLOWED, // TOAST_WINDOW
		AppOpsManager.MODE_IGNORED, // PROJECT_MEDIA
		AppOpsManager.MODE_IGNORED, // ACTIVATE_VPN
		AppOpsManager.MODE_ALLOWED, // WRITE_WALLPAPER
		AppOpsManager.MODE_ALLOWED, // ASSIST_STRUCTURE
		AppOpsManager.MODE_ALLOWED, // ASSIST_SCREENSHOT
		AppOpsManager.MODE_ALLOWED, // READ_PHONE_STATE
		AppOpsManager.MODE_ALLOWED, // ADD_VOICEMAIL
		AppOpsManager.MODE_ALLOWED, // USE_SIP
		AppOpsManager.MODE_ALLOWED, // PROCESS_OUTGOING_CALLS
		AppOpsManager.MODE_ALLOWED, // USE_FINGERPRINT
		AppOpsManager.MODE_ALLOWED, // BODY_SENSORS
		AppOpsManager.MODE_ALLOWED, // READ_CELL_BROADCASTS
		AppOpsManager.MODE_ERRORED, // MOCK_LOCATION
		AppOpsManager.MODE_ALLOWED, // READ_EXTERNAL_STORAGE
		AppOpsManager.MODE_ALLOWED, // WRITE_EXTERNAL_STORAGE
		AppOpsManager.MODE_ALLOWED, // TURN_SCREEN_ON
		AppOpsManager.MODE_ALLOWED, // GET_ACCOUNTS
		AppOpsManager.MODE_ALLOWED, // RUN_IN_BACKGROUND
		AppOpsManager.MODE_ALLOWED, // AUDIO_ACCESSIBILITY_VOLUME
		AppOpsManager.MODE_ALLOWED, // READ_PHONE_NUMBERS
		AppOpsManager.MODE_DEFAULT, // REQUEST_INSTALL_PACKAGES
		AppOpsManager.MODE_ALLOWED, // PICTURE_IN_PICTURE
		AppOpsManager.MODE_DEFAULT, // INSTANT_APP_START_FOREGROUND
		AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
		AppOpsManager.MODE_ALLOWED, // RUN_ANY_IN_BACKGROUND
		AppOpsManager.MODE_ALLOWED, // CHANGE_WIFI_STATE
		AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES
		AppOpsManager.MODE_ALLOWED, // BIND_ACCESSIBILITY_SERVICE
		AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER
		AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS
		AppOpsManager.MODE_ALLOWED, // START_FOREGROUND
		AppOpsManager.MODE_ALLOWED, // BLUETOOTH_SCAN
		AppOpsManager.MODE_ALLOWED, // USE_BIOMETRIC
		AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION
		AppOpsManager.MODE_DEFAULT, // SMS_FINANCIAL_TRANSACTIONS
		AppOpsManager.MODE_ALLOWED, // READ_MEDIA_AUDIO
		AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_AUDIO
		AppOpsManager.MODE_ALLOWED, // READ_MEDIA_VIDEO
		AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_VIDEO
		AppOpsManager.MODE_ALLOWED, // READ_MEDIA_IMAGES
		AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_IMAGES
		AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
		AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
		AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
		AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION
};

(3).AppOpsManager相关暴露方法

// 构造方法
AppOpsManager(Context context, IAppOpsService service) {
	mContext = context;
	mService = service;
}
// Do a quick check for whether an application might be able to perform an operation.
public int checkOp(int op, int uid, String packageName)
// Make note of an application performing an operation.
public int noteOp(int op, int uid, String packageName)
// Report that an application has started executing a long-running operation.
public int startOp(int op, int uid, String packageName)
// Do a quick check to validate if a package name belongs to a UID.
public void checkPackage(int uid, @NonNull String packageName)
// Report that an application is no longer performing an operation that had previously been started with {@link #startOp(int, int, String)}. 
public void finishOp(int op, int uid, String packageName)

3.AppOpsService介绍
(1)AppOpsService构造函数

public AppOpsService(File storagePath, Handler handler) {
	LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
	mFile = new AtomicFile(storagePath, "appops"); //data/system/appops.xml
	mHandler = handler; //handler为AMS的MainHandler
	mConstants = new Constants(mHandler);
	readState(); //用于读取appops.xml持久的信息
}

在ActivityManagerService中进行初始化:mAppOpsService = mInjector.getAppOpsService(new File(systemDir, “appops.xml”), mHandler);
PS:Injector类中:

public AppOpsService getAppOpsService(File file, Handler handler) {
	return new AppOpsService(file, handler);
}

(2).内部类

static final class UidState {
    public final int uid;

	public int state = UID_STATE_CACHED;
	public int pendingState = UID_STATE_CACHED;
	public long pendingStateCommitTime;

	public int startNesting;
	public ArrayMap<String, Ops> pkgOps;
	public SparseIntArray opModes;

	// true indicates there is an interested observer, false there isn't but it has such an op
	public SparseBooleanArray foregroundOps;
	public boolean hasForegroundWatchers;
	......
}
final static class Ops extends SparseArray<Op> {
	final String packageName;
	final UidState uidState;
	final boolean isPrivileged;
	......
}
final static class Op {
	int op;
	boolean running;
	final UidState uidState;
	final @NonNull String packageName;

	private @Mode int mode;
	private @Nullable LongSparseLongArray mAccessTimes;
	private @Nullable LongSparseLongArray mRejectTimes;
	private @Nullable LongSparseLongArray mDurations;
	private @Nullable LongSparseLongArray mProxyUids;
	private @Nullable LongSparseArray<String> mProxyPackageNames;

	int startNesting;
	long startRealtime;
	......
}

Ops大概的数据结构,大概分为三个级别
(1). SparseArray mUidStates,用于存放uid和对应的uid下的状态,用UidState变量代表;
(2). UidState,一个uid对应一个UidState;一个uid可以对应多个package,每个package下面都有一个Ops代表一组操作;
(3). 每个Ops下又有多个op;
另外每个UidState下还有一组opModes,分别保存code和mode的对应管理;不过这个值来自于非pkg下的uid.

(3).相关函数说明
readState():解析pkg及uid标签

......
if (tagName.equals("pkg")) {
	readPackage(parser);
} else if (tagName.equals("uid")) {
	readUidOps(parser);
}
......

readPackage()的主要作用: 解析pkg标签下的uid标签

String pkgName = parser.getAttributeValue(null, "n");
 ......
if (tagName.equals("uid")) {
	readUid(parser, pkgName);
} else {
	Slog.w(TAG, "Unknown element under <pkg>: "
			+ parser.getName());
	XmlUtils.skipCurrentTag(parser);
}
......

readUid()的主要作用:得到应用的Privileged属性,解析uid标签下的op等标签

private void readUid(XmlPullParser parser, String pkgName)
		throws NumberFormatException, XmlPullParserException, IOException {
	int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
	final UidState uidState = getUidStateLocked(uid, true);
	String isPrivilegedString = parser.getAttributeValue(null, "p");
	boolean isPrivileged = false;
	if (isPrivilegedString == null) {
		try {
			IPackageManager packageManager = ActivityThread.getPackageManager();
			if (packageManager != null) {
				ApplicationInfo appInfo = ActivityThread.getPackageManager()
						.getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid));
				if (appInfo != null) {
					isPrivileged = (appInfo.privateFlags
							& ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
				}
			} else {
				// Could not load data, don't add to cache so it will be loaded later.
				return;
			}
		} catch (RemoteException e) {
			Slog.w(TAG, "Could not contact PackageManager", e);
		}
	} else {
		isPrivileged = Boolean.parseBoolean(isPrivilegedString);
	}
	int outerDepth = parser.getDepth();
	int type;
	while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
			&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
		if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
			continue;
		}
		String tagName = parser.getName();
		if (tagName.equals("op")) {
			readOp(parser, uidState, pkgName, isPrivileged);
		} else {
			Slog.w(TAG, "Unknown element under <pkg>: "
					+ parser.getName());
			XmlUtils.skipCurrentTag(parser);
		}
	}
	uidState.evalForegroundOps(mOpModeWatchers);
}

readOp()的主要作用:解析op标签

private void readOp(XmlPullParser parser, @NonNull UidState uidState,
		@NonNull String pkgName, boolean isPrivileged) throws NumberFormatException,
		XmlPullParserException, IOException {
	Op op = new Op(uidState, pkgName,
			Integer.parseInt(parser.getAttributeValue(null, "n")));

	final int mode = XmlUtils.readIntAttribute(parser, "m",
			AppOpsManager.opToDefaultMode(op.op));
	op.mode = mode;

	int outerDepth = parser.getDepth();
	int type;
	while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
			&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
		if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
			continue;
		}
		String tagName = parser.getName();
		if (tagName.equals("st")) {
			final long key = XmlUtils.readLongAttribute(parser, "n");

			final int flags = AppOpsManager.extractFlagsFromKey(key);
			final int state = AppOpsManager.extractUidStateFromKey(key);

			final long accessTime = XmlUtils.readLongAttribute(parser, "t", 0);
			final long rejectTime = XmlUtils.readLongAttribute(parser, "r", 0);
			final long accessDuration = XmlUtils.readLongAttribute(parser, "d", 0);
			final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
			final int proxyUid = XmlUtils.readIntAttribute(parser, "pu", 0);

			if (accessTime > 0) {
				op.accessed(accessTime, proxyUid, proxyPkg, state, flags);
			}
			if (rejectTime > 0) {
				op.rejected(rejectTime, proxyUid, proxyPkg, state, flags);
			}
			if (accessDuration > 0) {
				op.running(accessTime, accessDuration, state, flags);
			}
		} else {
			Slog.w(TAG, "Unknown element under <op>: "
					+ parser.getName());
			XmlUtils.skipCurrentTag(parser);
		}
	}

	if (uidState.pkgOps == null) {
		uidState.pkgOps = new ArrayMap<>();
	}
	Ops ops = uidState.pkgOps.get(pkgName);
	if (ops == null) {
		ops = new Ops(pkgName, uidState, isPrivileged);
		uidState.pkgOps.put(pkgName, ops);
	}
	ops.put(op.op, op);
}

部分appops.xml中的内容:

<uid n="10066"> //n代表uid
<op n="0" m="1" /> //n代表code,m代表mode
<op n="15" m="0" />
<op n="87" m="0" />
<op n="89" m="0" />
</uid>

<pkg n="android">  // n代表name,即包名
<uid n="1000" p="true"> //n代表uid,p代表是否是预装在/system/priv-app/下(即isPrivileged)
<op n="0" /> // n代表code
<op n="3">
<st n="214748364801" t="1600740577341" d="75" /> //d代表间隔
</op>
<op n="8">
<st n="214748364808" t="1600740495469" pp="com.android.providers.calendar" pu="10054" /> //st代表state,n代表key,t代表时间,pp代表代理包名,pu代表代理uid
</op>
<op n="40">
<st n="214748364801" t="1600768490988" d="2" />
</op>
<op n="41">
<st n="214748364801" t="1600740532022" d="28588554" />
</op>
<op n="43">
<st n="214748364801" r="1600768491106" /> //r代表拒绝时间
</op>
</uid>
</pkg>

(4).一些重要方法介绍
public void checkPackage(int uid, @NonNull String packageName)
这个方法会在uid下创建对应的uidState和一个Ops返回给用户,创建成功返回说明uid和packagename是对应的上的,返回AppOpsManager.MODE_ALLOWED,否则返回AppOpsManager.MODE_ERRORED

public int noteOp(int op, int uid, String packageName)
noteOp 函数比checkOp函数额外多出的功能就是会创建对应的op,另外会更新一些op操作的时间用于统计信息

public int startOp(int op, int uid, String packageName)
表示一个长时间运行的操作,比noteOp增加了一个统计信息,放在一个叫starting的集合里可以dump到,调用finishOp结束操作。

(5).权限检测
1).checkOp

public int checkOp(@NonNull String op, int uid, @NonNull String packageName) {
	return checkOp(strOpToOp(op), uid, packageName);
}

public int checkOp(int op, int uid, String packageName) {
	try {
		int mode = mService.checkOperation(op, uid, packageName);
		if (mode == MODE_ERRORED) {
			throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
		}
		return mode;
	} catch (RemoteException e) {
		throw e.rethrowFromSystemServer();
	}
}

public int checkOperation(int code, int uid, String packageName) {
	return checkOperationInternal(code, uid, packageName, false /*raw*/);
}

// 最终调用到:
private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
			boolean raw) {
	if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
		return AppOpsManager.MODE_IGNORED;
	}

	boolean isPrivileged;

	try {
	    // 应用是否为priv-app
		isPrivileged = verifyAndGetIsPrivileged(uid, packageName);
	} catch (SecurityException e) {
		Slog.e(TAG, "checkOperation", e);
		return AppOpsManager.opToDefaultMode(code);
	}

	synchronized (this) {
		// 检查user多用户相关的权限
		if (isOpRestrictedLocked(uid, code, packageName, isPrivileged)) {
			return AppOpsManager.MODE_IGNORED;
		}
		// 根据code转换为op code,这里又进行了一个转换的操作
		code = AppOpsManager.opToSwitch(code);
		// 获取UidState
		UidState uidState = getUidStateLocked(uid, false);
		if (uidState != null && uidState.opModes != null
				&& uidState.opModes.indexOfKey(code) >= 0) {
			// 检查opModes下维护的code权限
			final int rawMode = uidState.opModes.get(code);
			return raw ? rawMode : uidState.evalMode(code, rawMode);
		}
		// 获取pkg下的Op
		Op op = getOpLocked(code, uid, packageName, false, false);
		if (op == null) {
		    // op不存在使用默认规则
			return AppOpsManager.opToDefaultMode(code);
		}
		// op 存在返回op下的mode
		return raw ? op.mode : op.evalMode();
	}
}

2).noteOp

public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
	return noteOp(strOpToOp(op), uid, packageName);
}

public int noteOp(int op, int uid, String packageName) {
	final int mode = noteOpNoThrow(op, uid, packageName);
	if (mode == MODE_ERRORED) {
		throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
	}
	return mode;
}

public int noteOpNoThrow(int op, int uid, String packageName) {
	try {
		return mService.noteOperation(op, uid, packageName);
	} catch (RemoteException e) {
		throw e.rethrowFromSystemServer();
	}
}

public int noteOperation(int code, int uid, String packageName) {
	final CheckOpsDelegate delegate;
	synchronized (this) {
		delegate = mCheckOpsDelegate;
	}
	if (delegate == null) {
		return noteOperationImpl(code, uid, packageName);
	}
	return delegate.noteOperation(code, uid, packageName,
			AppOpsService.this::noteOperationImpl);
}

private int noteOperationImpl(int code, int uid, String packageName) {
	verifyIncomingUid(uid);
	verifyIncomingOp(code);
	String resolvedPackageName = resolvePackageName(uid, packageName);
	if (resolvedPackageName == null) {
		return AppOpsManager.MODE_IGNORED;
	}
	return noteOperationUnchecked(code, uid, resolvedPackageName, Process.INVALID_UID, null,
			AppOpsManager.OP_FLAG_SELF);
}

private int noteOperationUnchecked(int code, int uid, String packageName,
		int proxyUid, String proxyPackageName, @OpFlags int flags) {
	boolean isPrivileged;
	try {
		isPrivileged = verifyAndGetIsPrivileged(uid, packageName);
	} catch (SecurityException e) {
		Slog.e(TAG, "noteOperation", e);
		return AppOpsManager.MODE_ERRORED;
	}

	synchronized (this) {
		final Ops ops = getOpsRawLocked(uid, packageName, isPrivileged, true /* edit */);
		if (ops == null) {
			scheduleOpNotedIfNeededLocked(code, uid, packageName,
					AppOpsManager.MODE_IGNORED);
			if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
					+ " package " + packageName);
			return AppOpsManager.MODE_ERRORED;
		}
		final Op op = getOpLocked(ops, code, true);
		if (isOpRestrictedLocked(uid, code, packageName, isPrivileged)) {
			scheduleOpNotedIfNeededLocked(code, uid, packageName,
					AppOpsManager.MODE_IGNORED);
			return AppOpsManager.MODE_IGNORED;
		}
		final UidState uidState = ops.uidState;
		if (op.running) {
			final OpEntry entry = new OpEntry(op.op, op.running, op.mode, op.mAccessTimes,
				op.mRejectTimes, op.mDurations, op.mProxyUids, op.mProxyPackageNames);
			Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
					+ " code " + code + " time=" + entry.getLastAccessTime(uidState.state,
					uidState.state, flags) + " duration=" + entry.getLastDuration(
							uidState.state, uidState.state, flags));
		}

		final int switchCode = AppOpsManager.opToSwitch(code);
		// If there is a non-default per UID policy (we set UID op mode only if
		// non-default) it takes over, otherwise use the per package policy.
		if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
			final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
			if (uidMode != AppOpsManager.MODE_ALLOWED) {
				if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
						+ switchCode + " (" + code + ") uid " + uid + " package "
						+ packageName);
				op.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
						uidState.state, flags);
				mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
						uidState.state, flags);
				scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode); 
				return uidMode;
			}
		} else {
			final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
			final int mode = switchOp.evalMode();
			if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
				if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
						+ switchCode + " (" + code + ") uid " + uid + " package "
						+ packageName);
				op.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
						uidState.state, flags);
				mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
						uidState.state, flags);
				scheduleOpNotedIfNeededLocked(code, uid, packageName, mode);
				return mode;
			}
		}
		if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
				+ " package " + packageName);
		op.accessed(System.currentTimeMillis(), proxyUid, proxyPackageName,
				uidState.state, flags);
		mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
				uidState.state, flags);
		scheduleOpNotedIfNeededLocked(code, uid, packageName,
				AppOpsManager.MODE_ALLOWED);
		return AppOpsManager.MODE_ALLOWED;
	}
}

注意:
(1).一般的op都会走noteOperation,但是OP_RECORD_AUDIO却是不走这里,而是走checkOperation;
因为对于AUDIO有专门的方法checkAudioOperation,最终调用到checkOperation。
(2).checkOperation与noteOperation的区别可以查看checkOp及noteOp的函数说明:checkOp只是check;noteOp不仅check还note。至于具体调用哪个,看需求。一般的runtime权限都会进行check,少数非runtime也会check;多数非runtime权限会进行note,少数runtime权限也会note;这个看代码中的需求。

4.一般来说我们修改AppOps的机会比较少;但是在过CTA权限的时候,涉及到蓝牙、WLAN权限开关以及适配Android M之前的应用权限弹框等,需要修改AppOps的checkOperation及noteOperation。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值