Android KitKat (API 19) – How to write messages in SMS Content Provider, without sending them, from Non-Default App?

The SmsWriteOpUtils class uses reflection to access methods of the AppOpsManager Service in order to enable/disable a non-default SMS app’s write access to the SMS Provider in API Level 19 (KitKat). Once set, an app’s access mode will be retained until it is reset, or the app is uninstalled.

Enabling an app’s write access allows that app all of the standard methods of interaction with the SMS Provider, including insert() and delete().

Please note that this class does no API Level check, and that the WRITE_SMS permission is still required.

import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public final class SmsWriteOpUtils {
    private static final int WRITE_OP_CODE = 15;

    public static boolean isWriteEnabled(Context context) {
        int result = checkOp(context);
        return result == AppOpsManager.MODE_ALLOWED;
    }

    public static boolean setWriteEnabled(Context context, boolean enabled) {
        int mode = enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
        return setMode(context, mode);
    }

    private static int checkOp(Context context) {
        try {
            Method checkOpMethod = AppOpsManager.class.getMethod("checkOp",
                                                                 Integer.TYPE,
                                                                 Integer.TYPE,
                                                                 String.class);

            AppOpsManager appOpsManager =
                (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            int uid = context.getApplicationInfo().uid;
            String packageName = context.getPackageName();

            return checkOpMethod.invoke(appOpsManager, WRITE_OP_CODE, uid, packageName);
        }
        catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return -1;
    }

    private static boolean setMode(Context context, int mode) {
        try {
            Method setModeMethod = AppOpsManager.class.getMethod("setMode",
                                                                 Integer.TYPE,
                                                                 Integer.TYPE,
                                                                 String.class,
                                                                 Integer.TYPE);

            AppOpsManager appOpsManager =
                (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            int uid = context.getApplicationInfo().uid;
            String packageName = context.getPackageName();

            setModeMethod.invoke(appOpsManager, WRITE_OP_CODE, uid, packageName, mode);

            return true;
        }
        catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return false;
    }
}

Example usage:

boolean canWriteSms;

if(!SmsWriteOpUtils.isWriteEnabled(getApplicationContext())) {
    canWriteSms = SmsWriteOpUtils.setWriteEnabled(getApplicationContext(), true);
}
...

NB: For regular user apps, this works only on API Level 19 (KitKat). The hole was patched in later versions.

Leave a Comment