使用 ContentResolver.delete()时, 来看看流程.
在 ContentResolver 来进行删除时, 会调用 ContentProvider 的方法. 也就说明了. 使用 ContentProvider 时, 实际的数据操作在 ContentProvider, 而 ContentProvider 仅仅相当于一个接口.
- ContentResolver.java
public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable String where,
@Nullable String[] selectionArgs) {
...
try {
long startTime = SystemClock.uptimeMillis();
int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);// to ContentProvider
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
return rowsDeleted;
}catch{}
...
}
}
其向 ContentProvider 传入了四个参数:
callingPkg: 使用者包名uri: uri 路径
selection: 条件
selectionArgs: 条件参数
进入ContentProvider.delete, 可以发现, 其调用了 SQLiteContentProvider. 所以, 其实他们也就是对数据库进行了处理, 也就是其封装类.
- ContentProvider.java
public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
validateIncomingUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.delete(uri, selection, selectionArgs);//SQLiteContentProvider.java
} finally {
setCallingPackage(original);
}
}
看其中, if语句:
- ContentProvider.java
if (enforceWritePermission(callingPkg, uri, null) !=AppOpsManager.MODE_ALLOWED) {
return 0;
}
// enforceWritePermission 为false时, 那么返回为 "零".
//如果未被删除, 那么返回值为 "零", 且不会报错.
也就是说: 如果权限的验证返回值不为 : AppOpsManager.MODE_ALLOWED, 就得不到我们想要的结果. 那么我们仅仅分析权限这个问题, 看起需要满足什么样的条件才能保证我们得到正确的返回值.
权限的验证会到达:
- ContentProvider.java
protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken) throws SecurityException {
...
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getReadPermission();
if (componentPerm != null) {
final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
if (mode == MODE_ALLOWED) {
return MODE_ALLOWED;
} else {
missingPerm = componentPerm;
strongestMode = Math.max(strongestMode, mode);
}
}
// track if unprotected read is allowed; any denied
// <path-permission> below removes this ability
boolean allowDefaultRead = (componentPerm == null);
final PathPermission[] pps = getPathPermissions();
if (pps != null) {
final String path = uri.getPath();
for (PathPermission pp : pps) {
final String pathPerm = pp.getReadPermission();
if (pathPerm != null && pp.match(path)) {
final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, callerToken);
if (mode == MODE_ALLOWED) {
return MODE_ALLOWED;
}
...
}
}
}
...
}
...
}
从这里发现, 在验证权限时, 还需要做一步操作. 这个方法会将传进来的 "读写权限", "包名", IBinder对象传入, 返回一个mode.
final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
- ContentProvider.java
private int checkPermissionAndAppOp(String permission, String callingPkg,
IBinder callerToken) {
...
final int permOp = AppOpsManager.permissionToOpCode(permission);
if (permOp != AppOpsManager.OP_NONE) {
return mTransport.mAppOpsManager.noteProxyOp(permOp, callingPkg);
}
return MODE_ALLOWED;
}
这个方法传进去了三个参数:
permission: "读" 或 "写" 的权限.
callingPkg: 从 ContentResolver 就开始了传入.
IBInder: null
先看
final int permOp = AppOpsManager.permissionToOpCode(permission);
- AppOpsManager.java
public static int permissionToOpCode(String permission) {
Integer boxedOpCode = sRuntimePermToOp.get(permission);
return boxedOpCode != null ? boxedOpCode : OP_NONE;
}
//这里的 sRuntimePermToOp 是一个Map对象, 它们其实是 int sOpPerms[] - 数组角标的key - value.
而 OP_NONE =-1, 表示不在数组中. 如果在数组中, 那么:
- ContentProvider.java
if (permOp != AppOpsManager.OP_NONE) {
return mTransport.mAppOpsManager.noteProxyOp(permOp, callingPkg);
}
那么此时就可以分析:
mTransport.mAppOpsManager.noteProxyOp(permOp, callingPkg);
- AppOpsManager.java
public int noteProxyOp(int op, String proxiedPackageName) {
int mode = noteProxyOpNoThrow(op, proxiedPackageName);
if (mode == MODE_ERRORED) {
throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+ " from uid " + Process.myUid() + " or calling package "
+ proxiedPackageName + " from uid " + Binder.getCallingUid()
+ " not allowed to perform " + sOpNames[op]);
}
return mode;
}
public int noteProxyOpNoThrow(int op, String proxiedPackageName) {
try {
return mService.noteProxyOperation(op, mContext.getOpPackageName(),
Binder.getCallingUid(), proxiedPackageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
刚进入 AppOpsManager, 才仅仅做了两次跳转. 直接到 AppOpsService 中去处理了.
- AppOpsService.java
public int noteProxyOperation(int code, String proxyPackageName,
int proxiedUid, String proxiedPackageName) {
verifyIncomingOp(code); /验证权限是否正确.
final int proxyUid = Binder.getCallingUid();
//通过uid来判断其所属的进程.
/*
private static String resolvePackageName(int uid, String packageName) {
if (uid == 0) {
return "root";
} else if (uid == Process.SHELL_UID) {
return "com.android.shell";
} else if (uid == Process.SYSTEM_UID && packageName == null) {
return "android";
}
return packageName;
}
*/
//可以看出, 其返回的有四类值.
//当然需要注意的是 proxyPackageName 这个并不是我们自己应用的包名.
String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
...
final int proxyMode = noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, -1, null);
if (proxyMode != AppOpsManager.MODE_ALLOWED || Binder.getCallingUid() == proxiedUid) {
return proxyMode;
}
String resolveProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
if (resolveProxiedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
proxyMode, resolveProxyPackageName);
}
> code : int permOp = AppOpsManager.permissionToOpCode(permission); 返回值> proxyPackageName: 代理包名, 即 AppOpsManager 的包名
> proxiedUid: Binder.getCallingUid(), 在AppOpsManager中被调用.
> proxiedPackageName: ContentResolver的包名
如果满足这个条件:
if (proxyMode != AppOpsManager.MODE_ALLOWED || Binder.getCallingUid() == proxiedUid) {
return proxyMode;
}
那么, 返回值不为: AppOpsManager.MODE_ALLOWED. 这就表示 ContentResovle.delte的返回值为零, 得不到我们想要的结果.
其实, 从这里就可以得到上面的总结:
> ContentResolve.delete 的操作, 其实调用了ContentProvider.delete, 并最终访问了数据库. 说明它们其实就是对数据库进行操作.
> ContentResolve, ContentProvider 实际是对数据库操作的封装.
> ContentResolve, ContentProvider 在进行"增", "删", "改", "查"时, 会进行权限的验证.
如果该应用走的是
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
proxyMode, resolveProxyPackageName);
AppOpsService.java
> code: 权限数组的的值, 如果为 MODE_ALLOWED, 即表示有权限.
> uid: proxyUid, 在 AppOpsManager中使用 Binder.getCallingUid获得.
> packageName: resolvePackageName(proxiedUid, proxiedPackageName) 的返回值. 这里的 proxiedPackageName, 是用户应用apk的包名
> proxyUid: noteOperationUnchecked(code, proxyUid,resolveProxyPackageName, -1, null)的返回值.
> proxyPackageName: resolvePackageName(proxyUid, proxyPackageName)的返回值. 这里是AppOpsManager中所调用Binder.getCallingPkg()所获得的包名.
其实从这里就基本可以确认了:
> 权限的验证的流程从 ContentProvider -> AppOpsManager -> AppOpsService.
> 权限的验证会最终到AppOpsService.noteOperationUnchecked.
-----------------------------------------------------------------------------
那么应用的权限是如何被赋予的呢?
以短信应用为例. 如果用户短信应用不是默认的短信应用, 那么其在使用 ContentProvider 的"增", "删", "改", "查" 的操作时, 无法做出正确的响应.
设置短信应用为默认的短信应用的方法为 SmsApplication.setDefaultApplication().
public static void setDefaultApplication(String packageName, Context context) {
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
if (!tm.isSmsCapable()) {
// No phone, no SMS
return;
}
final int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
setDefaultApplicationInternal(packageName, context, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
这个类没有提过外部接口, 可以使用反射机制来调用该方法, 为我们自己的应用设置成默认应用.
从 SmsApplication 的设置默认 SMS 的操作:
# SmsApplication.java
public static void setDefaultApplication(String packageName, Context context) {
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
if (!tm.isSmsCapable()) {
// No phone, no SMS
return;
}
final int userId = getIncomingUserId(context);
final long token = Binder.clearCallingIdentity();
try {
setDefaultApplicationInternal(packageName, context, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
}
// 设置默认短信应用的实际方法在: setDefaultApplicationInternal().
> packageName: 传进来的默认包名.
// 这个方法主要切换新旧的"默认包" 来切换使用默认新的应用包名.
private static void setDefaultApplicationInternal(String packageName, Context context, int userId) {
// Get old package name
String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
...
if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
// No change
return;
}
// We only make the change if the new package is valid
PackageManager packageManager = context.getPackageManager();
Collection<SmsApplicationData> applications = getApplicationCollection(context);
SmsApplicationData oldAppData = oldPackageName != null ?
getApplicationForPackage(applications, oldPackageName) : null;
SmsApplicationData applicationData = getApplicationForPackage(applications, packageName);
if (applicationData != null) {
// Ignore OP_WRITE_SMS for the previously configured default SMS app.
AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
if (oldPackageName != null) {
try {
PackageInfo info = packageManager.getPackageInfo(oldPackageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
//设置 MODE_IGNORED, 表明 oldpackage 将不具备处理短信的功能.
appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
oldPackageName, AppOpsManager.MODE_IGNORED);
} catch (NameNotFoundException e) {
Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
}
}
//将传进来的 package name 作为新的默认程序来进行处理.
// Update the secure setting.
Settings.Secure.putStringForUser(context.getContentResolver(),
Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
userId);
// Configure this as the preferred activity for SENDTO sms/mms intents
configurePreferredActivity(packageManager, new ComponentName(
applicationData.mPackageName, applicationData.mSendToClass), userId);
// 设置 AppOpsManager.MODE_ALLOWED 权限.
// Allow OP_WRITE_SMS for the newly configured default SMS app.
appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
// Assign permission to special system apps
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
PHONE_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
BLUETOOTH_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
MMS_SERVICE_PACKAGE_NAME);
assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
TELEPHONY_PROVIDER_PACKAGE_NAME);
// Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
// apps, all of them should be able to write to telephony provider.
// This is to allow the proxy package permission check in telephony provider
// to pass.
assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
...
//在移除 oldpacakge的作为默认的设置
if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) {
// Notify the old sms app that it's no longer the default
final Intent oldAppIntent =
new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
final ComponentName component = new ComponentName(oldAppData.mPackageName,
oldAppData.mSmsAppChangedReceiverClass);
oldAppIntent.setComponent(component);
oldAppIntent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, false);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName);
}
context.sendBroadcast(oldAppIntent);
}
// Notify the new sms app that it's now the default (if the new sms app has a receiver
// to handle the changed default sms intent).
if (applicationData.mSmsAppChangedReceiverClass != null) {
final Intent intent =
new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
final ComponentName component = new ComponentName(applicationData.mPackageName,
applicationData.mSmsAppChangedReceiverClass);
intent.setComponent(component);
intent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, true);
context.sendBroadcast(intent);
}
MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED,
applicationData.mPackageName);
}
}
从这个方法可以看出:
> 系统在设置默认短信应用时, 需要冠以权限, 和设置默认短信两部操作.
> 如果之前有默认短信应用, 先将其应用去掉权限, 并从广播中移除. 然后在将新的短信应用设置成默认的应用并冠以权限.
这里可以思考一个问题. 为自制的短信应用设置成默认短信应用. 则可以对 SmsApplication.setDefaultApplication 进行重写.
由于 SmsApplication 没有对外提供接口, 可考虑使用 反射机制来处理.
public static void setAsDefaultApp(String packageName, Context ctx) {
try {
Class<?> systemClass = Class.forName("com.android.internal.telephony.SmsApplication");
Method myMethod = systemClass.getMethod("setDefaultApplication", String.class, Context.class);
Method[] allMethod = systemClass.getDeclaredMethods();
Constructor[] cons = systemClass.getDeclaredConstructors();
cons[0].getName();
cons[0].setAccessible(true);
Object Tmngr = cons[0].newInstance();
for (Method m : allMethod) {
if (m.getName().toUpperCase().equalsIgnoreCase("setDefaultApplication".toUpperCase())) {
Object o[] = new Object[2];
o[0] = packageName;
o[1] = ctx.getApplicationContext();
try {
m.setAccessible(true);
m.invoke(Tmngr, o);
} catch (Exception e1) {
Log.e("test", "-->setAsDefaultApp-->setDefaultApplication:" + e1.getMessage());
}
}
}
// Settings.Secure.putString(ctx.getContentResolver(), Settings.Secure.SMS_DEFAULT_APPLICATION, packageName);
// myMethod.invoke(null,packageName, ctx);
} catch (Exception e) {
}
}
这样就可以使自制短信应用具有 默认短信应用的全新, 可以和系统自带的短信应用做"增", "删", "改", "查"等需要权限的处理了.
-----------------------------------------------------------------------------
当然, 还有一种方式, 就是为该应用设置权限: AppOpsManager.MODE_ALLOWED.
SmsApplication.setDefaultApplicationInternal
appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
这个代码就是为应用设置了该权限.
先找到该方法的实现:
- AppOpsManager.java
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
实际上, 该方法的具体实现在 AppOpsService, 具体的源码就不贴出来了. 因为现在 可以考虑设置权限的问题了. 依然使用反射机制
public static final int OP_WRITE_SMS = 15;
> code=15;
private static boolean setMode(Context context, int code, int uid, int mode) {
AppOpsManager appOpsManager = (AppOpsManager) context
.getSystemService(Context.APP_OPS_SERVICE);
Class appOpsManagerClass = appOpsManager.getClass();
try {
Class[] types = new Class[4];
types[0] = Integer.TYPE;
types[1] = Integer.TYPE;
types[2] = String.class;
types[3] = Integer.TYPE;
Method setModeMethod = appOpsManagerClass.getMethod("setMode",
types);
Object[] args = new Object[4];
args[0] = Integer.valueOf(code);
args[1] = Integer.valueOf(uid);
args[2] = context.getPackageName();
args[3] = Integer.valueOf(mode);
setModeMethod.invoke(appOpsManager, args);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
这样就在进行删除操作的时候, 进行上述两种操作, 就可以付上其相应的权限.
现在再进一步完善代码, 进行一次容错处理. 如果应用 "已经是默认短信应用" 或者 "已经设置了默认权限" .
首先针对第一个容错处理: 已经是默认短信应用
- SmsApplication.java
public static boolean isDefaultSmsApplication(Context context, String packageName) {
if (packageName == null) {
return false;
}
final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
|| BLUETOOTH_PACKAGE_NAME.equals(packageName)) {
return true;
}
return false;
}
只要反射该方法, 就可以了.
-----------------------------------------------------------------------------
额外指出, 这个方法的说明是: 是否需要向数据库写入 sms. 如果不向数据库写入数据, 那么 sms 将不会显示在界面上. 自行思考其用处.
- SmsApplication.java
/**
* Returns whether need to write the SMS message to SMS database for this package.
* <p>
* Caller must pass in the correct user context if calling from a singleton service.
*/
public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
if (SmsManager.getDefault().getAutoPersisting()) {
return true;
}
if((packageName != null) && (packageName.equals("com.ape.saletracker") || packageName.equals("com.saleTrackPK"))){
return false;
}
return !isDefaultSmsApplication(context, packageName);
}
----------------------------------------------------------------------------
再来对第二种方式进行容错处理: "已经设置了默认权限".
AppOpsManager.java
public int checkOp(String op, int uid, 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();
}
}
这里的两个方法的处理方式, 一模一样, 只是传入的的参数类型不同.
- SmsApplication.java
int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid, packageName);
根据上面这个来仿写:
private static Object checkOp(Context context, int code, int uid) {
AppOpsManager appOpsManager = (AppOpsManager) context
.getSystemService(Context.APP_OPS_SERVICE);
Class appOpsManagerClass = appOpsManager.getClass();
try {
Class[] types = new Class[3];
types[0] = Integer.TYPE;
types[1] = Integer.TYPE;
types[2] = String.class;
Method checkOpMethod = appOpsManagerClass.getMethod("checkOp",
types);
Object[] args = new Object[3];
args[0] = Integer.valueOf(code);//public static final int OP_WRITE_SMS = 15;
args[1] = Integer.valueOf(uid);
args[2] = context.getPackageName();
Object result = checkOpMethod.invoke(appOpsManager, args);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}