Android Kitkat (API 19) - How to Write Messages in Sms Content Provider, Without Sending Them, from Non-Default App

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.

Write received message to SMS provider ( API level 19+ )

Thanks to Mike M. for the help. i got help from this answer - sms-doesnt-save-on-kitkat-4-4-already-set-as-default-messaging-app from SO and this post - kitkat-sms-mms-supports .

here is what i have done :

to write the sms into the sms provider of android system i used content provider. and passed value to it. code snippet is :

ContentValues values = new ContentValues();
values.put(Telephony.Sms.ADDRESS, msg.getDisplayOriginatingAddress());
values.put(Telephony.Sms.BODY, msg.getMessageBody());
context.getApplicationContext().getContentResolver().insert(Telephony.Sms.Sent.CONTENT_URI, values);

this code will save the received sms into the system sms provider and even after your ap is uninstalled other sms app can read it. keep in mind that you need to be the default sms app to do this operation. and you have to provide WRITE_SMS permission in manifest. i have targeted kitkat and versions after it. for previous versions you have to some part of code differently.

the whole SmsReceiver class after completion is :

public class SmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {

Object[] pdusObj = (Object[]) bundle.get("pdus");

SmsMessage[] messages = new SmsMessage[pdusObj.length];

for (int i = 0; i < messages.length; i++) {
String format = bundle.getString("format");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
messages[i] = SmsMessage.createFromPdu((byte[]) pdusObj[i], format);
} else {
messages[i] = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
}
}
for (SmsMessage msg : messages) {
Log.i("log", "display msg body : " + msg.getDisplayMessageBody() + "originating address : " + msg.getDisplayOriginatingAddress() + " get message body : " + msg.getMessageBody());
ContentValues values = new ContentValues();
values.put(Telephony.Sms.ADDRESS, msg.getDisplayOriginatingAddress());
values.put(Telephony.Sms.BODY, msg.getMessageBody());
context.getApplicationContext().getContentResolver().insert(Telephony.Sms.Sent.CONTENT_URI, values);
}

}
}
}

Sent SMS message is automatically written to the content provider while it shouldn't in Cyanogenmod KitKat

I don't know why, but when I added sent and delivered pending intent new sms is not automatically added to content provider. This solved my problem

PendingIntent sentPI = PendingIntent.getBroadcast(this, 0,
new Intent(SENT), 0);

PendingIntent deliveredPI = PendingIntent.getBroadcast(this, 0,
new Intent(DELIVERED), 0);

sms.sendTextMessage(phoneNo, null, singlePart, sentPI, deliveredPI);

Code from this question: Trouble with sendMultipartText in android

Do I need to manually insert new messages in content://sms/inbox?

Do all new messages automatically go in content://sms/inbox or do we have to update it manually ?

No, those messages will not be written to the inbox automatically. The default app is responsible for saving all incoming SMS to the Provider. Though there is no requirement for the default app to do so, if it doesn't save them there, then other apps that query the Provider to read existing messages just won't see them. This would explain why your inbuilt SMS app doesn't see the new messages received while yours is the default.

The only message writes that are handled automatically by the system are for outgoing SMS sent by non-default apps. All other inserts, updates, and deletes are left up to the default app.



Is it necessary to forward new messages to other apps listening for new SMS received?

Not specifically, as it were. Any non-default app that is interested in getting messages as they arrive should be listening for the SMS_RECEIVED broadcast, and the system will send that automatically. Such apps have direct access to the messages, since they're attached to the broadcast Intents as extras.

Beyond that, pretty much the only other (practical) way for non-defaults to read messages, or to be notified of changes, is through the Provider, so as long as you're saving the messages there, such apps are covered, as well.

Do I need to write into SMS Provider when set as default SMS app on Android?

If I were the default app on device, how do I insert the SMS?

You can do it the same way you'd add to any ContentProvider; by calling insert() on the ContentResolver with the Telephony.Sms.Sent.CONTENT_URI and a ContentValues object with the relevant data.

Where do I call this insert, is there some method that needs to be overloaded?

You don't need to overload anything. Simply make the appropriate insert() call upon receiving the result from the sent PendingIntents that would be passed as the fourth parameter in your example sendMultipartTextMessage() call. Depending on the result, you might need to insert the message with type STATUS_FAILED.

And when SMS is received, in the receiver class, do I write the SMS into internal SMS database as well?

I'm not sure what you mean by internal SMS database, but the default app is responsible for writing all incoming messages to the Provider, which you would do in the same way as described above, but with the Telephony.Sms.Inbox.CONTENT_URI. If your app has its own separate database, you can write to that as well, using the standard database API.



Related Topics



Leave a reply



Submit