currently i write an Provider and the TestCase for it. It went well so far, and now im try to test if the notification works as expected. Basicly i create an ContentObserver and do an insert on my Observer. All Inserts working fine already i only struggle with the notification issue. For that i use an CountDownlatch in my ProviderTestCase which gets count down when any #onChange Method gets invoked:
#SmallTest
public void testUriNotification() throws Exception
{
final TestContentObserver observer = new TestContentObserver(new Handler(), 1);
resolver.registerContentObserver(FavoritesContract.Favorites.CONTENT_URI,true,observer);
final ContentValues values = FavoritesContract.createFavorite(1);
final Uri uri = resolver.insert(FavoritesContract.Favorites.CONTENT_URI, values);
assertNotNull(uri);
observer.latch.await(5, TimeUnit.SECONDS);
assertThat(observer.latch.getCount(), Matchers.is(0L));
resolver.unregisterContentObserver(observer);
}
public static class TestContentObserver extends ContentObserver
{
private CountDownLatch latch;
public TestContentObserver(final Handler handler, final int countDown)
{
super(handler);
latch = new CountDownLatch(countDown);
}
#Override
public boolean deliverSelfNotifications()
{
return true;
}
#Override
public void onChange(final boolean selfChange)
{
latch.countDown();
super.onChange(selfChange);
}
#Override
public void onChange(final boolean selfChange, final Uri uri)
{
latch.countDown();
super.onChange(selfChange, uri);
}
The insert Method is like this:
#Override
public Uri insert(final Uri uri, final ContentValues values)
{
if (!insertAllowed(uri))
{
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
final SQLiteDatabase db = database.getWritableDatabase();
if (isFavoriteItem(uri))
{
final long id = db.insert("favorites", null, values);
return getUriForId(id, uri);
}
else if (isFavoriteList(uri))
{
final long id = db.insert("favorites", null, values);
return getUriForId(id, uri);
}
throw new IllegalArgumentException("No matching URI found for:" + uri);
}
private Uri getUriForId(long id, Uri uri)
{
Log.d(LOG_TAG,"getUriForId:"+id+" uri:"+uri);
if (id > 0)
{
final Uri itemUri = ContentUris.withAppendedId(uri, id);
if (!isInBatchMode())
{
// notify all listeners of changes and return itemUri:
Log.d(LOG_TAG, "Notify ContentResolver using :" + uri.toString());
getContext().
getContentResolver().
notifyChange(itemUri, null);
}
else
{
Log.d(LOG_TAG, "Cant notify ContentResolver, we are in Batch Mode for :" + uri.toString());
}
return itemUri;
}
return null;
}
private final boolean insertAllowed(final Uri uri)
{
if (uri == null)
{
return false;
}
return ALLOWED_TYPES.contains(URI_MATCHER.match(uri));
}
private final boolean isFavoriteList(final Uri uri)
{
if (uri == null)
{
return false;
}
return URI_MATCHER.match(uri) == FAVORITE_LIST;
}
private final boolean isFavoriteItem(final Uri uri)
{
if (uri == null)
{
return false;
}
return URI_MATCHER.match(uri) == FAVORITE_ID;
}
So, the CountDownlatch will never be count down. I see that in the getUriForId Method the URI looks good. The URI end up the /ID of the Insert.
Im not sure if i created some kind of Deadlock there because the Latch waits and the provider cant invoke the Observer method using notifyChange Method. So after 5 Seconds the test fails because the timeout happened.
What's happening is that the Handler you create is bound to the test thread, therefore you'll never receive a callback. You'll need to use Looper.getMainLooper to get a reference to the main (thread) Handler:
new Handler(Looper.getMainLooper());
Other recommendations, call observer.latch.await() before resolver.insert() to avoid a flaky test when the callback happens before your call to await().
Annotate this test with #MediumTest or #LargeTest. You should only use #SmallTest for unit tests.
Use assertTrue(observer.latch.getCount() <= 0) which is more readable in this case or if you want to stick to hamcrest Matchers, assertThat(observer.latch.getCount(), equalTo(0L));
Related
I am getting the Sent and Received SMS from almost devices. but i am not able to get it from specific device in specific model. Like samsungs S7, S8, S9. and some LG devices.
Strange is some S7 device works and some not.
Below is the way i am using.
Registering observer in Application class.
private void registerMessageObserver() {
String url = "content://sms/";
Uri uri = Uri.parse(url);
this.getApplicationContext()
.getContentResolver()
.registerContentObserver(uri, true,
new MessageObserver(new Handler(), this));
}
//Observer class
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
public class MessageObserver extends ContentObserver {
private static final String TAG = "MessageObserver";
Context moContext;
// Observe message state incoming or outgoing
public MessageObserver(Handler handler, Context foContext) {
super(handler);
moContext = foContext;
}
#Override
public boolean deliverSelfNotifications() {
Log.i(TAG, "deliverSelfNotifications Changed");
return true;
}
#Override
public void onChange(boolean selfChange) {
Log.i(TAG, "Message Changed");
super.onChange(selfChange);
Log.i(TAG, "Message Changed after super");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && moContext .checkCallingOrSelfPermission(Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED)
return;
String[] laColumns = new String[]{"_id", "type", "address", "body", "date", "person"};
Uri loUriAllMessages = Uri.parse("content://sms/");
Cursor loCursor = moContext.getContentResolver().query(loUriAllMessages, laColumns, null, null, "_id DESC");
if (loCursor != null) {
if (loCursor.moveToFirst()) {
int liMessageId = loCursor.getInt(loCursor.getColumnIndex("_id"));
String lsType = loCursor.getString(loCursor.getColumnIndex("type")).trim();
//Doing my stuff here
}
}
if (loCursor != null)
loCursor.close();
}
}
It work like charm as i said in most of devices. but some device did not call this even onChange did not calling.
I tried below way but it will not even call onChanged in any scenario.
private void registerMessageObserverSent() {
String url = "content://sms/sent";
Uri uri = Uri.parse(url);
this.getApplicationContext()
.getContentResolver()
.registerContentObserver(uri, true,
new MessageObserverSent(new Handler(), this));
}
private void registerMessageObserverOut() {
String url = "content://sms/out";
Uri uri = Uri.parse(url);
this.getApplicationContext()
.getContentResolver()
.registerContentObserver(uri, true,
new MessageObserverOut(new Handler(), this));
}
I tried Broadcast receiver but the receiver is only for the Received message.
Can anyone help how to resolve the issue for those devices.
The app is offline so no issue with play store policy. its for my personal use.
I want to read the sent message content from the mobile messages. I am not developing an sent sms reading application, but instead I want to read the recent last sent sms content form the mobile inbuilt sms app.
I want to read the sent sms from sent items and send some notification based on the keyword in the message.
I know we have to extend ContentObserver class and use ContentResolver.
Any idea is appreciated. Thank you!
This is my Observer class SMSObserver.java,
public class SMSObserver extends ContentObserver {
private String lastSmsId;
private Context c;
private String phoneNumber;
int type;
String lastID;
public SMSObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange,Uri uri) {
super.onChange(selfChange);
Uri uriSMSURI = Uri.parse("content://sms/out");
Cursor cur = c.getContentResolver().query(uriSMSURI, null, null, null, "date DESC LIMIT 1");
cur.moveToNext();
//String id = cur.getString(cur.getColumnIndex("_id"));
if( (type == 2 || type == 1) && (!lastID.contentEquals(cur.getString(cur.getColumnIndex("_id")))) ) {
String protocol = cur.getString(cur.getColumnIndex("protocol"));
lastID = cur.getString(cur.getColumnIndex("_id"));
// Message sent
if (protocol == null) {
Log.i("SMSStatus", "SMS Sent");
}
// Message receive
else {
Log.i("SMSStatus", "SMS received");
}
if (smsChecker(lastID)) {
String address = cur.getString(cur.getColumnIndex("address"));
// Optional: Check for a specific sender
if (address.equals(phoneNumber)) {
String message = cur.getString(cur.getColumnIndex("body"));
// Use message content for desired functionality
if(message.contains("Dinner")){
Toast.makeText(c,"Dinner offer for 2!!",Toast.LENGTH_LONG).show();
}
}
}
}
}
// Prevent duplicate results without overlooking legitimate duplicates
public boolean smsChecker(String smsId) {
boolean flagSMS = true;
if (smsId.equals(lastSmsId)) {
flagSMS = false;
}
else {
lastSmsId = smsId;
}
Log.d(lastSmsId ,"LastSmsId");
return flagSMS;
}
}
I'm registering the contentObserver in a class SentSMSTrackerService.java
public class SentSMSTrackerService extends Service {
#Override
public int onStartCommand(Intent intent, int flag, int startId) {
SMSObserver smsObserver = new SMSObserver(new Handler());
ContentResolver contentResolver = getApplicationContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse("content://sms/out"), true, smsObserver);
return START_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
I'm also giving the permission in AndroidManifest.xml
But,still i am not able to read the outgoing sms.
I have issue with both FIleObserver and ContentObserver not working in Android Marshmallow. I am using this thing for detecting changes that happening inside a folder. I set run time permissions for marshmallow. But after that also it shows no events. It works perfectly in other versions. Please help me to solve this problem.
First I tried Content Resolver inside Service for detect folder changes in background.
public class TestService extends Service {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
initial();
return START_STICKY;
}
public void initial(){
getContentResolver().registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
true,
new ContentObserver(new Handler()) {
#Override
public boolean deliverSelfNotifications() {
Log.d("hai", "deliverSelfNotifications");
return super.deliverSelfNotifications();
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
#Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.toString().matches(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/[0-9]+")) {
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri, new String[] {
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA
}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
final String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
// TODO: apply filter on the file name to ensure it's screen shot event
Log.d("file", "FILE CHANGE OCCURED " + fileName + " " + path);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
super.onChange(selfChange, uri);
}
}
);
}
}
And run time permissions as:
private void getPermission(){
boolean hasPermission = (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
if (!hasPermission) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_READ_STORAGE);
}
}
And received that permissions result in onRequestPermissionsResult.
This method didn't work for me. So I tried with FileObserver inside that service. That time also it works in all other platforms, but not Marshmallow.
This appears to be a bug in Marshmallow, see here.
You can only try working around it by polling for whatever information you need.
How well this will work for you depends on your use case. I found it usable for tracking download progress: start polling when the download starts, with a one-second interval, and stop when the download finishes.
If you expect very infrequent changes, you can try increasing the interval – at the cost of a potentially higher delay between changes happening and your app picking them up.
I am working on an android app and I need to increment the incoming missed calls.
I registered a ContentObserver. How can I check in the onChange method if the call is a missed call or not?
I have a contentobserver with the following code:
public class IncomingCall extend BroadcastReceiver
{
public void onReceive( final Context context, Intent intent)
{
String state= extras.getString(TelephonyManager.EXTRA_STATE);
if (TelephonyManager.EXTRA_STATE_IDLE.equals(state))
{
context.getApplicationContext().getContentResolver().registerContentObserver(android.provider.CallLog.Calls.CONTENT_URI, true, new CallContentObserver(new Handler(), context));
}
}
class CallContentObserver extends ContentObserver {
Context context;
public CallContentObserver(Handler handler, Context context) {
super(handler);
this.context = context;
}
#Override
public boolean deliverSelfNotifications() {
return true;
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
//flag for missed calls
how to check if last call is missed call?
Cursor c = context.getContentResolver().query(CallLog.Calls.CONTENT_URI,null,null, null, null);
int type = c.getColumnIndex(CallLog.Calls.TYPE);
while (c.moveToNext()) {
String callType = c.getString(type);
int dircode = Integer.parseInt(callType);
switch (dircode) {
case CallLog.Calls.MISSED_TYPE:
flag++;
}
break;
}
}
Isn't there another way to do this? (better than I did when checking the last call from CallLog database?
I would suggest to try the following code in order to determine 'if last call is missed call or not':
#Override
public void onChange(boolean selfChange, Uri uri) {
if (null == uri) {
onChange(selfChange);
return;
}
super.onChange(selfChange, uri);
final Cursor c = context.getContentResolver().query(uri,null,null, null, null);
final int type = c.getColumnIndex(CallLog.Calls.TYPE);
final int dircode = c.getInt(type);
switch (dircode) {
case CallLog.Calls.MISSED_TYPE:
flag++;
break;
}
}
It would be faster than checking all calls in case if You need to check only latest call. However, if where will be no uri, then provided way looks fine.
Another suggestion is to implement Service to do checking of missed calls count, so You will not have long processing in ContentObserver.
What I want to do is to have a Service with more than one ContentObserver registered and check which ContentObserver triggers onChange() to do specific reactions. I dont know if I just have to include a if/else inside the onchange(), or Overwrite it to each ContentObserver, in either cases I wouldnt know exactly how to do it. Thanks in advance for any help.
public class SmsObserverService extends Service {
private String BABAS = "babas";
#Override
public void onCreate() {
Handler handler = new Handler();
this.getContentResolver().registerContentObserver(Uri.parse("content://sms/"), true, new SmsObserver(handler));
//Second observer
this.getContentResolver().registerContentObserver(Uri.parse("CallLog.Calls.CONTENT_URI"), true, new SmsObserver(handler));
}
#Override
public IBinder onBind(Intent intent) {
// TODO Put your code here
return null;
}
public class SmsObserver extends ContentObserver{
public SmsObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
//Where I should check somehow which ContentObserver triggers the onChange
//This code to the sms log stuff, the call log part will be included
//when I find out how to check whic observer trigerred the onChange
Uri uri = Uri.parse("content://sms/");
Cursor cursor = getApplicationContext().getContentResolver().query( uri, null, null, null, null);
cursor.moveToNext();
String body = cursor.getString(cursor.getColumnIndex("body"));
String add = cursor.getString(cursor.getColumnIndex("address"));
String time = cursor.getString(cursor.getColumnIndex("date"));
String protocol = cursor.getString(cursor.getColumnIndex("protocol"));
if(protocol == null){
Toast.makeText(getApplicationContext(), "Enviada para: " +add + ", Hora: "+time +" - "+body, Toast.LENGTH_SHORT).show();
Log.i(BABAS, "Enviada para: "+add +" " +"Time: "+time +" - "+body);
}else{
Toast.makeText(getApplicationContext(), "Recebida de: "+add + ", Hora: "+time +" - "+body, Toast.LENGTH_SHORT).show();
Log.i(BABAS, "Recebida de: "+add +" " +"Time: "+time +" - "+body);
}
}
}
}
I'd simply extend ContentObserver and add whatever I am missing there.
mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
#Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
}
}
};
You can check uri with above method