zoukankan      html  css  js  c++  java
  • android 4.4删除短信

    android 4.4之后非默认的短信应用已经没有办法删除短信了。像以前那样用如下方法是不会没法删除短信的(即使在xml中配置了短信的读写权限),同时也不会有报错或其他提示。

    public void deleteSMS() {
            try {
                ContentResolver CR = getContentResolver();
                // Query SMS
                Uri uriSms = Uri.parse("content://sms/inbox");
                Cursor c = CR.query(uriSms, new String[] { "_id", "thread_id" },
                        null, null, null);
                if (null != c && c.moveToFirst()) {
                    do {
                        // Delete SMS
                        long threadId = c.getLong(1);
                        int result = CR.delete(Uri
                                .parse("content://sms/conversations/" + threadId),
                                null, null);
                        Log.d("deleteSMS", "threadId:: " + threadId + "  result::"
                                + result);
                    } while (c.moveToNext());
                }
            } catch (Exception e) {
                Log.d("deleteSMS", "Exception:: " + e);
            }
        }

    但通过打印可以看到上述代码的result是等于0的,即没有删除掉短信。

    这个是因为在:/frameworks/base/services/java/com/android/server/AppOpsService.java中android系统添加了权限检查的函数

    检查用户设定权限的函数是:checkOperation() 和 noteOperation(),区别是 checkOperation() 只是检查 Operation 的情况,noteOperation() 还会记录访问时间等信息,代码如下:

    @Override
    public int checkOperation(int code, int uid, String packageName) {
        verifyIncomingUid(uid);
        verifyIncomingOp(code);
        synchronized (this) {
            Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
            if (op == null) {
                return AppOpsManager.MODE_ALLOWED;
            }
            return op.mode;
        }
    }
    
    @Override
    public int noteOperation(int code, int uid, String packageName) {
        verifyIncomingUid(uid);
        verifyIncomingOp(code);
        synchronized (this) {
            Ops ops = getOpsLocked(uid, packageName, true);
            if (ops == null) {
                if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                        + " package " + packageName);
                return AppOpsManager.MODE_IGNORED;
            }
            Op op = getOpLocked(ops, code, true);
            if (op.duration == -1) {
                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
                        + " code " + code + " time=" + op.time + " duration=" + op.duration);
            }
            op.duration = 0;
            final int switchCode = AppOpsManager.opToSwitch(code);
            final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
            if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
                if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
                        + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
                op.rejectTime = System.currentTimeMillis();
                return switchOp.mode;
            }
            if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
                    + " package " + packageName);
            op.time = System.currentTimeMillis();
            op.rejectTime = 0;
            return AppOpsManager.MODE_ALLOWED;
        }
    }

    然后在MmsServiceBroker服务中可以找到如下代码就是对应用删除短信的权限进行检查

            @Override
            public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
                    throws RemoteException {
                mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
                        "Delete SMS/MMS message");
                if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
                        callingPkg) != AppOpsManager.MODE_ALLOWED) {
                    return false;
                }
                return getServiceGuarded().deleteStoredMessage(callingPkg, messageUri);
            }
    
            @Override
            public boolean deleteStoredConversation(String callingPkg, long conversationId)
                    throws RemoteException {
                mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Delete conversation");
                if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
                        callingPkg) != AppOpsManager.MODE_ALLOWED) {
                    return false;
                }
                return getServiceGuarded().deleteStoredConversation(callingPkg, conversationId);
            }

    不过幸运的是在AppOpsService.java中也提供了修改权限的接口:修改某个 App 的某项权限的函数是 setMode(),其中就是修改成员变量 mUidOps。mUidOps 是一个List 保存了某个package对应的所有权限的mode (允许,忽略),具体代码如下:

    @Override
    public void setMode(int code, int uid, String packageName, int mode) {
        verifyIncomingUid(uid);
        verifyIncomingOp(code);
        ArrayList<Callback> repCbs = null;
        code = AppOpsManager.opToSwitch(code);
        synchronized (this) {
            Op op = getOpLocked(code, uid, packageName, true);
            if (op != null) {
                if (op.mode != mode) {
                    op.mode = mode;
                    ArrayList<Callback> cbs = mOpModeWatchers.get(code);
                    if (cbs != null) {
                        if (repCbs == null) {
                            repCbs = new ArrayList<Callback>();
                        }
                        repCbs.addAll(cbs);
                    }
                    cbs = mPackageModeWatchers.get(packageName);
                    if (cbs != null) {
                        if (repCbs == null) {
                            repCbs = new ArrayList<Callback>();
                        }
                        repCbs.addAll(cbs);
                    }
                    if (mode == AppOpsManager.MODE_ALLOWED) {
                        // If going into the default mode, prune this op
                        // if there is nothing else interesting in it.
                        if (op.time == 0 && op.rejectTime == 0) {
                            Ops ops = getOpsLocked(uid, packageName, false);
                            if (ops != null) {
                                ops.remove(op.op);
                                if (ops.size() <= 0) {
                                    HashMap<String, Ops> pkgOps = mUidOps.get(uid);
                                    if (pkgOps != null) {
                                        pkgOps.remove(ops.packageName);
                                        if (pkgOps.size() <= 0) {
                                            mUidOps.remove(uid);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    scheduleWriteNowLocked();
                }
            }
        }
        if (repCbs != null) {
            for (int i=0; i<repCbs.size(); i++) {
                try {
                    repCbs.get(i).mCallback.opChanged(code, packageName);
                } catch (RemoteException e) {
                }
            }
        }
    }

    AppOpsManager 是一个管理类来和 AppOpsService 通信,两者关联起来的代码如下:

    /**
    * Common implementation of Context API, which provides the base
    * context object for Activity and other application components.
    */
    class ContextImpl extends Context {

    registerService(WINDOW_SERVICE, new ServiceFetcher() {
                    Display mDefaultDisplay;
                    public Object getService(ContextImpl ctx) {
                        Display display = ctx.mDisplay;
                        if (display == null) {
                            if (mDefaultDisplay == null) {
                                DisplayManager dm = (DisplayManager)ctx.getOuterContext().
                                        getSystemService(Context.DISPLAY_SERVICE);
                                mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
                            }
                            display = mDefaultDisplay;
                        }
                        return new WindowManagerImpl(display);
                    }});

    .....
    }

    他的函数实现比较简单,重点是把控制转移到 AppOpsService 就可以了。例如 noteOperation() 和 setMode() 在 AppOpsManager 里面调用他们的函数是 noteOp() 和 setMode(),代码如下:

    public int noteOp(int op, int uid, String packageName) {
        try {
            int mode = mService.noteOperation(op, uid, packageName);
            if (mode == MODE_ERRORED) {
                throw new SecurityException("Operation not allowed");
            }
            return mode;
        } catch (RemoteException e) {
        }
        return MODE_IGNORED;
    }
    
    public void setMode(int code, int uid, String packageName, int mode) {
        try {
            mService.setMode(code, uid, packageName, mode);
        } catch (RemoteException e) {
        }
    }

    OK,到这里我们应该就能有解决方法了,虽然接口没有公开,但我们在apk中利用反射来调用AppOpsManager,再利用setMode方法来给自己的应用打开权限,代码如下:

    public final class SmsWriteOpUtil {
        private static final int OP_WRITE_SMS = 15;
    
        public static boolean isWriteEnabled(Context context) {
            int uid = getUid(context);
            Object opRes = checkOp(context, OP_WRITE_SMS, uid);
    
            if (opRes instanceof Integer) {
                return (Integer) opRes == AppOpsManager.MODE_ALLOWED;
            }
            return false;
        }
    
        public static boolean setWriteEnabled(Context context, boolean enabled) {
            int uid = getUid(context);
            int mode = enabled ? AppOpsManager.MODE_ALLOWED
                    : AppOpsManager.MODE_IGNORED;
    
            return setMode(context, OP_WRITE_SMS, uid, mode);
        }
    
        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);
                args[1] = Integer.valueOf(uid);
                args[2] = context.getPackageName();
                Object result = checkOpMethod.invoke(appOpsManager, args);
    
                return result;
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        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 (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return false;
        }
    
        private static int getUid(Context context) {
            try {
                int uid = context.getPackageManager().getApplicationInfo(
                        context.getPackageName(), PackageManager.GET_SERVICES).uid;
    
                return uid;
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
                return 0;
            }
        }
    }

    使用起起来也很方便:

    if (!SmsWriteOpUtil.isWriteEnabled(getApplicationContext())) {
                SmsWriteOpUtil.setWriteEnabled(
                        getApplicationContext(), true);
    }
    deleteSMS();
    ......

    注意还别忘:

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

    android 4.4删除短信

  • 相关阅读:
    火狐插件火狐黑客插件将Firefox变成黑客工具的七个插件
    memcache安装环境:WINDOWS 7
    PHP正则表达式
    968. 监控二叉树 力扣(困难) dfs 官方说DP
    375. 猜数字大小 II 力扣(中等) 区间动态规划、记忆化搜索
    629. K个逆序对数组 力扣(困难) 区间动态规划
    剑指 Offer 51. 数组中的逆序对 力扣(困难) 巧用归并排序算法
    488. 祖玛游戏 力扣(困难) dfs
    16. 最接近的三数之和 力扣(中等) 双指针
    319. 灯泡开关 力扣(中等) 数论
  • 原文地址:https://www.cnblogs.com/android-blogs/p/4959167.html
Copyright © 2011-2022 走看看