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.
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.
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));
How to implement SIP protocol in Android ?
there is any SDK or library available to implement it easily into Android?
Here is a third party Library with sample code. You can use this, I have used it and it works fine.
Android 2.3 or higher provides API for SIP.
Refer this link for SIP in Android
also you can see DEMO project for SIP from Sample
update:
Android SDK Samples on github.
SipDemo1, SipDemo2
Search for SipDemo project in samples for android 4.0.3 SDK version(API level -15)
I have been investigated this sort of problem for a long time and found out that SipManager and SipProfile are unfortunatelly poor and extremelly buggy.
So I found a Linphone library. There is a link for their wiki. I implemented it in my project using maven:
repositories {
...
maven { "https://linphone.org/maven_repository/"}
}
Also there is a sample of using it on gitlab: link here, it's pretty fresh, for now :)
If the link would crash, I just copy/paste the most important part of how to use linphone's core:
public class LinphoneService extends Service {
private static final String START_LINPHONE_LOGS = " ==== Device information dump ====";
// Keep a static reference to the Service so we can access it from anywhere in the app
private static LinphoneService sInstance;
private Handler mHandler;
private Timer mTimer;
private Core mCore;
private CoreListenerStub mCoreListener;
public static boolean isReady() {
return sInstance != null;
}
public static LinphoneService getInstance() {
return sInstance;
}
public static Core getCore() {
return sInstance.mCore;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
// The first call to liblinphone SDK MUST BE to a Factory method
// So let's enable the library debug logs & log collection
String basePath = getFilesDir().getAbsolutePath();
Factory.instance().setLogCollectionPath(basePath);
Factory.instance().enableLogCollection(LogCollectionState.Enabled);
Factory.instance().setDebugMode(true, getString(R.string.app_name));
// Dump some useful information about the device we're running on
Log.i(START_LINPHONE_LOGS);
dumpDeviceInformation();
dumpInstalledLinphoneInformation();
mHandler = new Handler();
// This will be our main Core listener, it will change activities depending on events
mCoreListener = new CoreListenerStub() {
#Override
public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
Toast.makeText(LinphoneService.this, message, Toast.LENGTH_SHORT).show();
if (state == Call.State.IncomingReceived) {
Toast.makeText(LinphoneService.this, "Incoming call received, answering it automatically", Toast.LENGTH_LONG).show();
// For this sample we will automatically answer incoming calls
CallParams params = getCore().createCallParams(call);
params.enableVideo(true);
call.acceptWithParams(params);
} else if (state == Call.State.Connected) {
// This stats means the call has been established, let's start the call activity
Intent intent = new Intent(LinphoneService.this, CallActivity.class);
// As it is the Service that is starting the activity, we have to give this flag
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
};
try {
// Let's copy some RAW resources to the device
// The default config file must only be installed once (the first time)
copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc");
// The factory config is used to override any other setting, let's copy it each time
copyFromPackage(R.raw.linphonerc_factory, "linphonerc");
} catch (IOException ioe) {
Log.e(ioe);
}
// Create the Core and add our listener
mCore = Factory.instance()
.createCore(basePath + "/.linphonerc", basePath + "/linphonerc", this);
mCore.addListener(mCoreListener);
// Core is ready to be configured
configureCore();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
// If our Service is already running, no need to continue
if (sInstance != null) {
return START_STICKY;
}
// Our Service has been started, we can keep our reference on it
// From now one the Launcher will be able to call onServiceReady()
sInstance = this;
// Core must be started after being created and configured
mCore.start();
// We also MUST call the iterate() method of the Core on a regular basis
TimerTask lTask =
new TimerTask() {
#Override
public void run() {
mHandler.post(
new Runnable() {
#Override
public void run() {
if (mCore != null) {
mCore.iterate();
}
}
});
}
};
mTimer = new Timer("Linphone scheduler");
mTimer.schedule(lTask, 0, 20);
return START_STICKY;
}
#Override
public void onDestroy() {
mCore.removeListener(mCoreListener);
mTimer.cancel();
mCore.stop();
// A stopped Core can be started again
// To ensure resources are freed, we must ensure it will be garbage collected
mCore = null;
// Don't forget to free the singleton as well
sInstance = null;
super.onDestroy();
}
#Override
public void onTaskRemoved(Intent rootIntent) {
// For this sample we will kill the Service at the same time we kill the app
stopSelf();
super.onTaskRemoved(rootIntent);
}
private void configureCore() {
// We will create a directory for user signed certificates if needed
String basePath = getFilesDir().getAbsolutePath();
String userCerts = basePath + "/user-certs";
File f = new File(userCerts);
if (!f.exists()) {
if (!f.mkdir()) {
Log.e(userCerts + " can't be created.");
}
}
mCore.setUserCertificatesPath(userCerts);
}
private void dumpDeviceInformation() {
StringBuilder sb = new StringBuilder();
sb.append("DEVICE=").append(Build.DEVICE).append("\n");
sb.append("MODEL=").append(Build.MODEL).append("\n");
sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n");
sb.append("SDK=").append(Build.VERSION.SDK_INT).append("\n");
sb.append("Supported ABIs=");
for (String abi : Version.getCpuAbis()) {
sb.append(abi).append(", ");
}
sb.append("\n");
Log.i(sb.toString());
}
private void dumpInstalledLinphoneInformation() {
PackageInfo info = null;
try {
info = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (PackageManager.NameNotFoundException nnfe) {
Log.e(nnfe);
}
if (info != null) {
Log.i(
"[Service] Linphone version is ",
info.versionName + " (" + info.versionCode + ")");
} else {
Log.i("[Service] Linphone version is unknown");
}
}
private void copyIfNotExist(int ressourceId, String target) throws IOException {
File lFileToCopy = new File(target);
if (!lFileToCopy.exists()) {
copyFromPackage(ressourceId, lFileToCopy.getName());
}
}
private void copyFromPackage(int ressourceId, String target) throws IOException {
FileOutputStream lOutputStream = openFileOutput(target, 0);
InputStream lInputStream = getResources().openRawResource(ressourceId);
int readByte;
byte[] buff = new byte[8048];
while ((readByte = lInputStream.read(buff)) != -1) {
lOutputStream.write(buff, 0, readByte);
}
lOutputStream.flush();
lOutputStream.close();
lInputStream.close();
}
}
I hope, that will help somebody, because I spend a lot of time trying to find it!
I used by this library:
https://www.mizu-voip.com/Software/SIPSDK/AndroidSIPSDK.aspx
it is very easy.
also i add button for answer the call:
mysipclient.Accept(mysipclient.GetLine());
I know that there are a few Questions here on SO relating to this, but none of them helped me to get this working - capture SMS that being sent.
I am using Android 2.2 (FROYO) on a Samsung phone (if that matters somehow).
I've searched a lot for this on Stackoverflow and realized that I need ContentObserver for my request. I'm using Service instead of Activity, so I've registered that ContentObserver in my Service class, and it looks like this:
public class SMSSending extends Service {
private class MyContentObserver extends ContentObserver {
public MyContentObserver() {
super(null);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Uri uriSMSURI = Uri.parse("content://sms/sent");
Cursor cur = getBaseContext().getContentResolver().query(uriSMSURI, null, null, null, null);
cur.moveToNext();
String content = cur.getString(cur.getColumnIndex("body"));
Toast.makeText(getApplicationContext(), "SOME TEXT", Toast.LENGTH_LONG).show();
}
#Override
public boolean deliverSelfNotifications() {
return false;
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
MyContentObserver contentObserver = new MyContentObserver();
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse("content://sms/sent"),true, contentObserver);
Toast.makeText(getApplicationContext(), "SERVICE CREATED", Toast.LENGTH_LONG).show();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void onStart(Intent intent, int startid) {
Toast.makeText(getApplicationContext(), "SERVICE STARTED", Toast.LENGTH_LONG).show();
}
}
As you can see I've put Toast in few places so I could see if this is working at all - and unfortunately none of this notifications appear. Also, i tried with putting some code for LogCat but nothing happens.
I've also tried to put Uri uriSMSURI = Uri.parse("content://sms"); instead of content://sms/sent
but the application simply doesn't do anything.
Of course, I have permissions in Manifest:
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
What am i missing?
Fortunately, I've managed to work it out, but by using totally different approach. Maybe is this going to help someone in future..
Instead of using ContentObserver (which I still don't know why didn't work) I've created new Thread and started it after my service has been created and started. So it looks like this:
...
final Uri CONTENT_URI = Uri.parse("content://sms/sent");
...
public void onStart(Intent intent, int startid) {
Go();
}
private void Go(){
new Thread(new Runnable() {
public void run() {
try {
while(true){
Cursor cursor = getContentResolver().query(CONTENT_URI, null, null, null, null);
if(cursor.moveToFirst()){
text = cursor.getString(cursor.getColumnIndexOrThrow("body")).toString();
if(!text.equalsIgnoreCase(actual)){
previous = text;
//do what you need..
}
}
Thread.sleep(60000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
It's working absolutely stable, even better than with using ContentObserver, having in mind that lot of people had problems with it, something like this and some other..
I have defined the following service with an observer of messages sent. The problem is that when sending a message, I sense that is called 3 times onChange method of contentobserver. ¿Someone know tell me why?
Thanks
public class DSMSService extends Service {
private static final String CONTENT_SMS = "content://sms";
private class MyContentObserver extends ContentObserver {
ContentValues values = new ContentValues();
int threadId;
public MyContentObserver() {
super(null);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.v(TAG, "****************************** SMS change detected *************************************");
Log.v(TAG, "Notification on SMS observer");
// save the message to the SD card here
Uri uriSMSURI = Uri.parse("content://sms");
Cursor cur = getBaseContext().getContentResolver().query(uriSMSURI, null, null, null, null);
// this will make it point to the first record, which is the last SMS sent
cur.moveToNext();
String content = cur.getString(cur.getColumnIndex("body"));
Log.v(TAG, "content: " + content);
}
#Override
public boolean deliverSelfNotifications() {
return false;
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
Log.v(TAG, "starting........");
MyContentObserver contentObserver = new MyContentObserver();
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse("content://sms"),true, contentObserver);
DAO = new DAOaBlackList(getBaseContext());
}
#Override
public void onDestroy() {
Log.d(TAG, "stopping........");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "Received start id " + startId + ": " + intent);
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
#Override
public void onStart(Intent intent, int startid) {
Log.v(TAG, "onStart........");
}
}
What you want to do is check for the _id of the last item in the content://sms/sent uri inside onChange. You need to store the previous _id (maybe in a static global variable) and compare it to the _id of the last item (cursor.moveToLast())of the cursor after you query for content://sms/sent. If the _id is the same, you can choose to ignore the call to onChange. This multiple calls to onChange I believe is due to the sms being moved from folder to folder during sending - outbox, sent items, some other "invisible folder" (which we can't know exactly what, as this particular feature REALLY REALLY needs proper documentation). As you cannot listen to a more specific Uri than content://sms/sent you'll have to implement this checking for _id everytime you want to detect an sms being sent.
If the previous _id is different from the one in your static global variable, then you have an sms being sent.
You have kept the Observer for the SMS database through URI. so whenever message is being send the database is updated and 3 of the column of that table is getting updated. so it will notify the observer for each of them. so it is being called for as many times as table data is updated.