I'm writing an application, which have to help me get all information about browser history, so I wrote a simple code:
public class WebHistory {
private Context context;
private Cursor cr;
public StringBuilder sb;
public WebHistory(Context c){
this.context = c;
}
public void takeHistory(){
cr = context.getContentResolver().query(Browser.BOOKMARKS_URI,Browser.HISTORY_PROJECTION, null, null, null);
cr.moveToFirst();
String title = "";
String date = "";
String visits = "";
String url = "";
String info = "";
if(cr.moveToFirst() && cr.getCount() > 0){
while(cr.isAfterLast() == false){
title = cr.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX);
date = cr.getString(Browser.HISTORY_PROJECTION_DATE_INDEX);
url = cr.getString(Browser.HISTORY_PROJECTION_URL_INDEX);
visits = cr.getString(Browser.HISTORY_PROJECTION_VISITS_INDEX);
info = title + " date: " + date + " url: " + url + " visits" + visits + "\n";
Toast.makeText(context, info, Toast.LENGTH_LONG).show();
cr.moveToNext();
}
}
}
}
Method takeHistory() helps me to take some data about browser history, but I need more functionality, like:
- HISTORY_PROJECTION_DATE_INDEX gives my only one date, and I need all dates (and also hours) when the user visited this page
- Browser.HISTORY_PROJECTION_VISITS_INDEX returns all visits which I made, but I want to divide this amount into gruops of visits which took place at the specified timestamp
Can anybody suggest how can I cull this information or recommend a tutorial, in which I can find necessary information? Thank you in advance for your advice.
You will need to start content observor and record all the changes that occur. I have done similar code. Start a content observor and in the onChange(); function, read the history that has changed since last time you read it(you can use shared preferences for that). And you need to do this all in a service
public void onChange(boolean selfChange) {
super.onChange(selfChange);
/**
* Get SharedPreferneces of the user
*/
SharedPreferences pref= myContext.getSharedPreferences("com.tpf.sbrowser",
Context.MODE_PRIVATE);
long wherelong = pref.getLong("Date", 0);
DatabaseManager db=new DatabaseManager(myContext,1);
String[] proj = new String[] { Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.URL, BookmarkColumns.DATE,};
String sel = Browser.BookmarkColumns.BOOKMARK + " = 0";
Cursor mCur = myContext.getContentResolver().query(
Browser.BOOKMARKS_URI, proj, sel, null, null);
Log.d("onChange", "cursorCount"+mCur.getCount());
mCur.moveToFirst();
String title = "";
String url = "";
long lastVisitedDate=0;
//You will need to create a database manager to manage your database and use its helper functions
DbMessage msg = new DbMessage(lastVisitedDate,url, title);
/**
* Start reading the user history and dump into database
*/
if(mCur.moveToFirst() && mCur.getCount() > 0) {
while (mCur.isAfterLast() == false) {
title =mCur.getString(0);
url = mCur.getString(1);
lastVisitedDate =mCur.getLong(2);
if ((lastVisitedDate>wherelong) && (!title.equals(url))) {
msg.set(lastVisitedDate, url, title);
db.InsertWithoutEnd(msg);
pref.edit().putBoolean("BrowserHistoryRead", true).commit();
pref.edit().putLong("Date", lastVisitedDate).commit();
myContext.updateTime(wherelong,lastVisitedDate);
wherelong=lastVisitedDate;
}
mCur.moveToNext();
}
}
mCur.close();
}
}
/**
* (non-Javadoc)
* #see android.app.Service#onDestroy()
*/
#Override
public void onDestroy() {
super.onDestroy();
getApplication().getContentResolver().unregisterContentObserver(
observer);
Toast.makeText(this, "Service destroyed ...", Toast.LENGTH_LONG).show();
state = 0;
}
}
Related
I am building an app , there is a requirement of notifying app when a new contact added or existing contact gets edited or removed. Although a lot of questions are already available those have answers as well. But my question is little bit different. I am using approach of Content Observer as mentioned below in the code
ContactChangeObserver contactChangeObserver = new ContactChangeObserver(this, new Handler());
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactChangeObserver);
and in ContactChangeObserver class I am overriding onChange() method as it was already recommended in many of the post. Code is mentioned below
public class ContactChangeObserver extends ContentObserver {
private Context mContext;
public ContactChangeObserver(Context context, Handler handler) {
super(handler);
mContext = context;
}
#Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
List<ContactsModel> listOfDBContact = new ChildTrackingDB().getAllContacts(ChildTrackingDB.getInstance(mContext));
List<ContactsModel> listOfCellPhoneContact = ContactUtility.readContactDirectoryOfPhone(mContext);
if (listOfCellPhoneContact.size() == listOfDBContact.size()) {
//this is edit case
} else if (listOfCellPhoneContact.size() > listOfDBContact.size()) {
//this is add case
}else {
//this is remove case
}
}
}
I am getting call back of onChange() as expected(in all cases of adding, removing and editing case). As mentioned in above example, I am overriding onChange() that has URI param. And when I get call back i also receive uri as well. my question is that can that uri be useful to only get that contact which got changed or added? The uri I am getting is
content://com.android.contacts
In my example code, if contact is edited and the device has let's suppose more than thousands contacts then it is a very time consuming to iterate over each contact.Or is there any better approach available for the problem.
I've faced a similar kind of problem. First of all the Uri which you are getting in onChange() method is vague. From my previous encounter I can tell, You won't be able to detect which contact got inserted, updated or deleted. So yes it becomes very time-consuming to detect which contact to get and perform Crud.
To answer your second question, I would suggest you to use Set instead of List. Here is a sample class which you might helpful. It's a linear operation and i've tested it with 2-3k+ data. And it performs well.
public class AddressBookObserver extends ContentObserver {
private static final String TAG = "AddressBookObserver";
private static final String FLAG_INSERT = "INSERT";
private static final String FLAG_DELETE = "DELETE";
private long lastTimeOfCall = 0L;
private long lastTimeOfUpdate = 0L;
private long threshold_time = 5000;
private WeakReference<Context> mContextWeakReference;
public AddressBookObserver(Handler handler, Context context) {
super(handler);
this.mContextWeakReference = new WeakReference<Context>(context);
}
#Override
public void onChange(boolean selfChange) {
onChange(selfChange, null);
}
#Override
public void onChange(boolean selfChange, Uri uri) {
Log.d(TAG, "onChange() Address Book Changed!");
lastTimeOfCall = System.currentTimeMillis();
Set<String> phoneBookSet = new HashSet<>();
Set<String> providerSet = new HashSet<>();
if (checkContactPermission() && (lastTimeOfCall - lastTimeOfUpdate > threshold_time) && General.getIsContactListImported(mContextWeakReference.get())) {
lastTimeOfUpdate = System.currentTimeMillis();
phoneBookSet.addAll(getOnlyPhoneNumbers());
providerSet.addAll(getProviderNumbers());
int bookCount = phoneBookSet.size();
int providerCount = providerSet.size();
Log.e(TAG, "onChange: bookCount: " + bookCount + " providerCount: " + providerCount);
if (bookCount > providerCount) {
Log.i(TAG, "onChange() Insert!");
phoneBookSet.removeAll(providerSet);
String val = phoneBookSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.w(TAG, "value to insert: " + val);
//DO Insert Operations
} else if (bookCount < providerCount) {
Log.i(TAG, "onChange() DELETE!");
providerSet.removeAll(phoneBookSet);
String val = providerSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.w(TAG, "value to delete: " + val);
//Do Delete Operations
} else {
Log.i(TAG, "onChange() UPDATE!");
Set<String> tempPhoneBookSet = new HashSet<>();
tempPhoneBookSet.addAll(phoneBookSet);
phoneBookSet.removeAll(providerSet);
String newData = phoneBookSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.e(TAG, "newData: " + newData);
providerSet.removeAll(tempPhoneBookSet);
String deleteData = providerSet.toString().replaceAll("[\\(\\)\\[\\]\\{\\}]", "");
Log.e(TAG, "deleteData: " + deleteData);
if (!newData.equals(deleteData)) {
//DO Update Operations
} else {
Log.i(TAG, "onChange() Nothing to update!");
}
}
} else if (!checkContactPermission()) {
Log.e(TAG, "onChange() Contact Permission not granted!");
} else {
Log.e(TAG, "onChange() Time threshold not reached Or Contacts not imported yet!");
}
}
private boolean checkContactPermission() {
return ContextCompat.checkSelfPermission(mContextWeakReference.get(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
}
/**
* Get only phone numbers from device's addressBook
*
* #return - Set of distinct phone numbers
*/
private Set<String> getOnlyPhoneNumbers() {
Log.d(TAG, "getOnlyPhoneNumbers()");
Cursor phones = mContextWeakReference.get().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " ASC");
Set<String> uniquePhoneContacts = new HashSet<>();
while (phones.moveToNext()) {
String name = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)).replaceAll("\\D", "");
String id = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID));
String photoUri = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.PHOTO_URI));
if (uniquePhoneContacts.add(id + "," + name + "," + photoUri + "," + phoneNumber)) {
Log.w(TAG, "Book#" + id + "," + name + "," + photoUri + "," + phoneNumber);
}
}
phones.close();
return uniquePhoneContacts;
}
/**
* Get all the Contacts from provider which are not deletable
*
* #return - All distinct phone numbers from app's provider
*/
private Set<String> getProviderNumbers() {
Log.d(TAG, "getProviderNumbers()");
Cursor phones = mContextWeakReference.get().getContentResolver().query(MyContactProvider.CONTENT_URI, null, PhoneContactController.COL_IS_DELETABLE + "=?",
new String[]{"0"}, MyContactProvider.COL_CONTACT_ID + " ASC");
Set<String> uniquePhoneContacts = new HashSet<>();
while (phones.moveToNext()) {
String id = phones.getString(phones.getColumnIndex(MyContactProvider.COL_CONTACT_ID));
String name = phones.getString(phones.getColumnIndex(MyContactProvider.COL_NAME));
String phoneNumber = phones.getString(phones.getColumnIndex(MyContactProvider.COL_CONTACT_NO));
String photoUri = phones.getString(phones.getColumnIndex(MyContactProvider.COL_PHOTO_URI));
if (uniquePhoneContacts.add(id + "," + name + "," + photoUri + "," + phoneNumber)) {
Log.w(TAG, "QueryProvider#" + id + "," + name + "," + photoUri + "," + phoneNumber);
}
}
phones.close();
return uniquePhoneContacts;
}
}
I hope this helps.
First and foremost, I found this answer particularly helpful. However, it made me wonder how one goes about finding such information.
I can't seem to figure out how to iterate all the messages in my inbox. My current solution uses Uri.parse("content://mms-sms/conversations") in which I give use "_id" and "ct_t". However, it seems I only find the three conversations in my phone despite having 30 msges (20 of them in the save conversation thread and the others divided between two other conversations). Would make sense for such a statement content://mms-sms/conversations. However, the other providers seem to deal only with SMS OR MMS. Isn't there a way to just iterate the entire list of messages in this fashion where I replace "content://mms-sms/conversations" with something else?
public boolean refresh() {
final String[] proj = new String[]{"_id","ct_t"};
cursor = cr.query(Uri.parse("content://mms-sms/conversations"),proj,null,null,null);
if(!(cursor.moveToFirst())) {
empty = true;
cursor.close();
return false;
}
return true;
}
I iterate the messages with a next function
public boolean next() {
if(empty) {
cursor.close();
return false;
}
msgCnt = msgCnt + 1;
Msg msg;
String msgData = cursor.getString(cursor.getColumnIndex("ct_t"));
if("application/cnd.wap.multipart.related".equals(msgData)) {
msg = ParseMMS(cursor.getString(cursor.getColumnIndex("_id")));
} else {
msg = ParseSMS(cursor.getString(cursor.getColumnIndex("_id")));
}
if(!(cursor.moveToNext())) {
empty = true;
cursor.close();
return false;
}
return true;
}
Well, what I am asking doesn't really seem possible.
For those just starting out on such tasks, it's advisable to learn about how content providers work in general. Each Uri value added to the query returns access to specific tables.
Spending some time looking at the different Telephony.Mmssms tables that one can access and it seems, from my testing, that the only table you can access is using "content://mms-sms/conversations as using "content://mms-sms" leads to a null cursor.
Such is life, and it doesn't really make sense to iterate the messages that way since the content and method of extracting the data differ greatly based on whether or not the msg is an SMS or MMS message. It makes sense to iterate and parse SMS and MMS messages separately and store the interesting data into the same class object type for one to manipulate how they would like at a later date.
Useful to such a topic would be the Telephony.Sms documentation. Which is where one can find a descriptions of the column index fields. You can find the same information for Telephony.Mms as well as the sub table Telephony.Mms.Part, with links to each of the base columns to describe the information.
With this being said, here is a solution to the question How can I iterate all the SMS/MMS messages in the phone? and here is the solution that worked for me.
public class Main extends AppCompatActivity {
//Not shown, Overrides, button to call IterateAll();
//implementations to follow
IterateAll();
public void ScanMMS();
public void ScanSMS();
public void ParseMMS(Msg msg);
public Bitmap getMmsImg(String id);
public String getMmsAddr(String id);
}
IterateAll() just calls the two different functions
IterateAll() {
ScanMMS();
ScanSMS();
}
ScanMMS() will iterate through the content://mms table extracting the data from each MMS.
public void ScanMMS() {
System.out.println("==============================ScanMMS()==============================");
//Initialize Box
Uri uri = Uri.parse("content://mms");
String[] proj = {"*"};
ContentResolver cr = getContentResolver();
Cursor c = cr.query(uri, proj, null, null, null);
if(c.moveToFirst()) {
do {
/*String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
System.out.println(str);*/
//System.out.println("--------------------MMS------------------");
Msg msg = new Msg(c.getString(c.getColumnIndex("_id")));
msg.setThread(c.getString(c.getColumnIndex("thread_id")));
msg.setDate(c.getString(c.getColumnIndex("date")));
msg.setAddr(getMmsAddr(msg.getID()));
ParseMMS(msg);
//System.out.println(msg);
} while (c.moveToNext());
}
c.close();
}
}
As one can see, a lot of the important MMS data is in this table, such as the date of the message, the message id and the thread id. You need to use that message ID to pull more information from MMS.
The MMS message is divided into smaller parts of data. Each part contains something different, like an image, or a text portion. You have to iterate each part as I do below.
public void ParseMMS(Msg msg) {
Uri uri = Uri.parse("content://mms/part");
String mmsId = "mid = " + msg.getID();
Cursor c = getContentResolver().query(uri, null, mmsId, null, null);
while(c.moveToNext()) {
/* String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
System.out.println(str);*/
String pid = c.getString(c.getColumnIndex("_id"));
String type = c.getString(c.getColumnIndex("ct"));
if ("text/plain".equals(type)) {
msg.setBody(msg.getBody() + c.getString(c.getColumnIndex("text")));
} else if (type.contains("image")) {
msg.setImg(getMmsImg(pid));
}
}
c.close();
return;
}
Each part as the mid field which corresponds to the id of the message found earlier. We search the MMS part library only for that mms id and then iterate the different parts found. ct or content_type as described in the documentation described what the part is, i.e. text, image, etc. I scan the type to see what to do with that part. If it's plain text, I add that text to the current message body (apparently there can be multiple text parts, but I haven't seen it, but I believe it) and if it's an image, than load the image into a bitmap. I imagine Bitmaps will be easy to send with java to my computer, but who knows, maybe want to just load it as a byte array.
Anyway, here is how one will get the image data from the MMS part.
public Bitmap getMmsImg(String id) {
Uri uri = Uri.parse("content://mms/part/" + id);
InputStream in = null;
Bitmap bitmap = null;
try {
in = getContentResolver().openInputStream(uri);
bitmap = BitmapFactory.decodeStream(in);
if(in != null)
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
You know, I'm not entirely sure how opening an input stream on the content resolver really works and how it is giving me just the image and not like all the other data, no clue, but it seems to work. I stole this one from some different sources while looking for solutions.
The MMS addresses aren't as straight forward to pull as they are for SMS, but here is how you can get them all. The only thing I haven't been able to do is figure out who the sender was. I'd love it if someone knew that.
public String getMmsAddr(String id) {
String sel = new String("msg_id=" + id);
String uriString = MessageFormat.format("content://mms/{0}/addr", id);
Uri uri = Uri.parse(uriString);
Cursor c = getContentResolver().query(uri, null, sel, null, null);
String name = "";
while (c.moveToNext()) {
/* String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
System.out.println(str);*/
String t = c.getString(c.getColumnIndex("address"));
if(!(t.contains("insert")))
name = name + t + " ";
}
c.close();
return name;
}
This was all just for MMS. The good news is that SMS is much simpler.
public void ScanSMS() {
System.out.println("==============================ScanSMS()==============================");
//Initialize Box
Uri uri = Uri.parse("content://sms");
String[] proj = {"*"};
ContentResolver cr = getContentResolver();
Cursor c = cr.query(uri,proj,null,null,null);
if(c.moveToFirst()) {
do {
String[] col = c.getColumnNames();
String str = "";
for(int i = 0; i < col.length; i++) {
str = str + col[i] + ": " + c.getString(i) + ", ";
}
//System.out.println(str);
System.out.println("--------------------SMS------------------");
Msg msg = new Msg(c.getString(c.getColumnIndex("_id")));
msg.setDate(c.getString(c.getColumnIndex("date")));
msg.setAddr(c.getString(c.getColumnIndex("Address")));
msg.setBody(c.getString(c.getColumnIndex("body")));
msg.setDirection(c.getString(c.getColumnIndex("type")));
msg.setContact(c.getString(c.getColumnIndex("person")));
System.out.println(msg);
} while (c.moveToNext());
}
c.close();
}
Here is my simple message structure so anyone may compile the above code quickly if wanted.
import android.graphics.Bitmap;
/**
* Created by rbenedict on 3/16/2016.
*/
//import java.util.Date;
public class Msg {
private String id;
private String t_id;
private String date;
private String dispDate;
private String addr;
private String contact;
private String direction;
private String body;
private Bitmap img;
private boolean bData;
//Date vdat;
public Msg(String ID) {
id = ID;
body = "";
}
public void setDate(String d) {
date = d;
dispDate = msToDate(date);
}
public void setThread(String d) { t_id = d; }
public void setAddr(String a) {
addr = a;
}
public void setContact(String c) {
if (c==null) {
contact = "Unknown";
} else {
contact = c;
}
}
public void setDirection(String d) {
if ("1".equals(d))
direction = "FROM: ";
else
direction = "TO: ";
}
public void setBody(String b) {
body = b;
}
public void setImg(Bitmap bm) {
img = bm;
if (bm != null)
bData = true;
else
bData = false;
}
public String getDate() {
return date;
}
public String getDispDate() {
return dispDate;
}
public String getThread() { return t_id; }
public String getID() { return id; }
public String getBody() { return body; }
public Bitmap getImg() { return img; }
public boolean hasData() { return bData; }
public String toString() {
String s = id + ". " + dispDate + " - " + direction + " " + contact + " " + addr + ": " + body;
if (bData)
s = s + "\nData: " + img;
return s;
}
public String msToDate(String mss) {
long time = Long.parseLong(mss,10);
long sec = ( time / 1000 ) % 60;
time = time / 60000;
long min = time % 60;
time = time / 60;
long hour = time % 24 - 5;
time = time / 24;
long day = time % 365;
time = time / 365;
long yr = time + 1970;
day = day - ( time / 4 );
long mo = getMonth(day);
day = getDay(day);
mss = String.valueOf(yr) + "/" + String.valueOf(mo) + "/" + String.valueOf(day) + " " + String.valueOf(hour) + ":" + String.valueOf(min) + ":" + String.valueOf(sec);
return mss;
}
public long getMonth(long day) {
long[] calendar = {31,28,31,30,31,30,31,31,30,31,30,31};
for(int i = 0; i < 12; i++) {
if(day < calendar[i]) {
return i + 1;
} else {
day = day - calendar[i];
}
}
return 1;
}
public long getDay(long day) {
long[] calendar = {31,28,31,30,31,30,31,31,30,31,30,31};
for(int i = 0; i < 12; i++) {
if(day < calendar[i]) {
return day;
} else {
day = day - calendar[i];
}
}
return day;
}
}
Some final comments and notes on this solution.
The person field seems to always be NULL and later I plan to implement a contact look up. I also haven't been able to identify who sent the MMS message.
I am not super familiar with java and I am still learning it. I am positive there is a data container (ArrayList) (Vector?) that could hold a user defined object. And if sortable by a specific field in the object (date), one could iterate that list and have a chronological order of all the message: both MMS/SMS and both sent/received.
Isn't there a way to just iterate the entire list of messages in this fashion where I replace "content://mms-sms/conversations" with something else?
It is possible to get all MMS and SMS messages in a single query using the content://mms-sms/complete-conversations URL. For some odd reason, there is no Uri field for this in the Telephony.MmsSms class, but it's been available since at least Froyo.
Using this single query will certainly be more efficient than querying the tables separately, and any sorting, grouping, or filtering that needs to be done will definitely be faster performed by the SQLite engine than by manipulating Java collections.
Please note that you must use a specific projection for this query. You cannot pass null or the * wildcard. Furthermore, it would be advisable to include MmsSms.TYPE_DISCRIMINATOR_COLUMN ("transport_type") in your projection - which will have a value of either "mms" or "sms" - to easily distinguish the message type.
The selection, selectionArgs, and orderBy arguments work as usual, and null can be passed for any or all of them.
I need to save the last visited webpage by user.
Evething works fine with my Galaxy S4 (5.0.1) with Chrome Browser.
However, on some phones I got nothing or very mixed results. From what I gathered, the biggest problem is with Browser URI itself. Some phones use Chrome as their main browser, some use something else.
I have three sources:
content://com.android.chrome.browser/bookmarks
content://com.sec.android.app.sbrowser/bookmarks
Browser.BOOKMARKS_URI
So right now I'm working on something like this:
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
h.removeCallbacks(this);
h.postDelayed(this, 500);
}
public void run() {
String[] proj = new String[] { Browser.BookmarkColumns.TITLE, Browser.BookmarkColumns.URL,Browser.BookmarkColumns.DATE };
String selection = Browser.BookmarkColumns.BOOKMARK + " = 0"; // 0 = history, 1 = bookmark
Cursor mCur = null;
try {
mCur = contentResolver.query(getURI(), proj, selection, null, null);
if(mCur != null && mCur.moveToLast()){
String title = "";
String url = "";
title = mCur.getString(mCur.getColumnIndex(Browser.BookmarkColumns.TITLE));
url = mCur.getString(mCur.getColumnIndex(Browser.BookmarkColumns.URL));
...
}
} catch (Exception e){
...
} finally {
if(mCur != null)
mCur.close();
}
}
private Uri getURI(){
Uri uri = Uri.parse("content://com.android.chrome.browser/bookmarks");
return uri;
}
Whet is the best way to provide correct URI?
onChange is triggered as content observer on browsers history.
So user can browse internet on Chrome and I still get history results from ASOP browser.
When I observe chrome directly, on some phones I get failed to find provider info, because there is no chrome installed.
What are other "popular" sources to search for browser history? I'd prefer to make this as bulletproof as possible.
try it: (run perfectly in android > 4.0 and 5 or 6.0 ); Anything, create a contentObserver array in FOREACH and add a list and record a different URI for each.
in service android (background):
HistoryObserver hObserver;
public void onCreate() {
hObserver = new HistoryObserver(new Handler(), this);
getApplicationContext().getContentResolver().registerContentObserver(Uri.parse("content://com.android.chrome.browser/history"), true, hObserver);
}
Class historicObserver:
import java.text.SimpleDateFormat;
import java.util.Calendar;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.provider.Browser;
import android.util.Log;
public class HistoryObserver extends ContentObserver {
public final String TAG = "HistoryObserver";
Context context;
public HistoryObserver(Handler handler, Context c) {
super(handler);
Log.d(TAG, "Creating new HistoryObserver");
context = c;
}
public HistoryObserver(Context c) {
super(null);
Log.d(TAG, "Creating a new HistoryObserver without a Handler");
context = c;
}
#Override
public boolean deliverSelfNotifications() {
Log.d(TAG, "delivering self notifications");
return true;
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.d(TAG, "onChange without uri: " + selfChange);
// onChange(selfChange, null);
}
#SuppressLint("NewApi")
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
Log.d(TAG, "onChange: " + selfChange + "\t " + uri.toString());
String[] proj = new String[] { Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.URL, Browser.BookmarkColumns.DATE };
String selection = Browser.BookmarkColumns.BOOKMARK + " = 0"; // 0 =
// history,
// 1 =
// bookmark
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Cursor mCursor = context.getContentResolver().query(Browser.BOOKMARKS_URI,
proj, selection, null, null);
// this.startManagingCursor(mCursor);
mCursor.moveToFirst();
int count = mCursor.getColumnCount();
String COUNT = String.valueOf(count);
Log.e("Browser sayac", COUNT);
String title = "";
String url = "";
String date = "";
if (mCursor.moveToFirst() && mCursor.getCount() > 0) {
while (mCursor.isAfterLast() == false) {
title = mCursor.getString(mCursor
.getColumnIndex(Browser.BookmarkColumns.TITLE));
url = mCursor.getString(mCursor
.getColumnIndex(Browser.BookmarkColumns.URL));
date = mCursor.getString(mCursor
.getColumnIndex(Browser.BookmarkColumns.DATE));
Long timestamp = Long.parseLong(date);
SimpleDateFormat dateFormat = new SimpleDateFormat(
"dd/MM/yyyy/HH:mm");
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
String finaldate = dateFormat.format(calendar.getTime());
String smsDate = finaldate.toString();
// Log.e("DTE", date);
Log.e("URL", title);
Log.e("TARIH", smsDate);
mCursor.moveToNext();
}
}
}
}
Getting nullpointer exception on running the doinbackground method.. This is my error![enter image description here][1]
this is my error :::
FAIAL EXCEPTION: AsyncIask #1
java.1ang.RuntimeException: An error occured while executing doInBa 45
ckground()
at android.os.AsyncIask$3.done(AsyncIask.java:278)
at java.uti1.concurrent.FutureIask$Sync.innerSetException(FutureIa 45
sk.java:273)
at java.uti1.concurrent.FutureIask.setException(FutureIask.java:12 45
4)
at java.uti1.concurrent.FutureIask$Sync.innerRun(FutureIask.java:3 45
07)
at java.uti1.concurrent.FutureTask.run(FutureIask.java:137)
at android.os.AsyncIask$Seria1Executor$1.run(AsyncIask.java:208)
at java.uti1.concurrent.ThreadPoo1Executor.runworker(IhreadPoo1Exe 45
cutor.java:1076)
at java.uti1.concurrent.IhreadPoo1Executor$Worker.run(IhreadPoo1Ex 45
ecutor.java:569)
at java.1ang.Ihread.run(Ihread.java:856)
Caused by: java.lang.NullPointerException
at com.examp1e.outgoingsms.OutgoingSmsListener$OutSmsLogger.doInBa 45
ckground(OutgoingSmsListener.java:45)
at com.examp1e.outgoingsms.OutgoingSmsListener$OutSmsLogger.doInBa 45
ckground(OutgoingSmsListener.java:1)
at android.os.AsyncIask$2.call(AsyncIask.java:264)
at java.uti1.concurrent.FutureIask$Sync.innerRun(FutureIask.java:3 45
05)
5 more
And this is my code..
public class OutgoingSmsListener extends BroadcastReceiver {
public Context context;
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
new OutSmsLogger(context).execute();
}
public class OutSmsLogger extends AsyncTask<Void, Void, Void> {
private final Uri SMS_URI = Uri.parse("content://sms");
private final String[] COLUMNS = new String[] {"date", "address", "body", "type"};
private static final String CONDITIONS = "type = 2 AND date > ";
private static final String ORDER = "date DESC";
private SharedPreferences prefs;
private long timeLastChecked;
private Cursor cursor;
public OutSmsLogger(Context context) {
this.prefs = context.getSharedPreferences("some_file_name", Context.MODE_PRIVATE);
}
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
timeLastChecked = prefs.getLong("time_last_checked", -1L);
ContentResolver cr = context.getContentResolver();
// get all sent SMS records from the date last checked, in descending order
cursor = cr.query(SMS_URI, COLUMNS, CONDITIONS + timeLastChecked, null, ORDER);
// if there are any new sent messages after the last time we checked
if (cursor.moveToNext()) {
Set<String> sentSms = new HashSet<String>();
timeLastChecked = cursor.getLong(cursor.getColumnIndex("date"));
do {
long date = cursor.getLong(cursor.getColumnIndex("date"));
String address = cursor.getString(cursor.getColumnIndex("address"));
String body = cursor.getString(cursor.getColumnIndex("body"));
String thisSms = date + "," + address + "," + body;
if (sentSms.contains(thisSms)) {
continue; // skip that thing
}
// else, add it to the set
sentSms.add(thisSms);
Log.d("Test", "date sent: " + date);
Log.d("Test", "target number: " + address);
Log.d("Test", "number of characters: " + body.length());
} while (cursor.moveToNext());
}
cursor.close();
Editor editor = prefs.edit();
editor.putLong("time_last_checked", timeLastChecked);
editor.commit();
return null;
}
}
}
Please help me to find a solution.
I had the same problem and it only showed up on some devices (or maybe I just always had some data before).
Problem is, that when you filter the result to the period where there is no data (for example you only want SMS for the last day and user didn't send any), you get null cursor. I find it strange and I believe on some devises you get properly initialized cursor, only it's empty, but on others you get null,so always check for null pointer before accessing cursor!
BTW "Problem solved" without providing the answer? Not cool dude!
I'm trying to query the data from the CallLog and insert in DB. For that, I've created a COntentObserver as inner class in a Service, and inside onChange() method, I call my method that goes to the specified URI and query the data that has changed.
But, lets say, I received a call, so the observer was notified. So, my method goes to the call log content provider, query and insert, but it is inserting two, three times the same register.
Here is the code of my service.
public class RatedCallsService extends Service
private Handler handler = new Handler();
private SQLiteDatabase db;
private OpenHelper helper;
private String theDate;
private String theMonth_;
private String theYear_;
private String theDay_;
public static boolean servReg = false;
class RatedCallsContentObserver extends ContentObserver {
public RatedCallsContentObserver(Handler h) {
super(h);
//helper = new OpenHelper(getApplicationContext());
//db = helper.getWritableDatabase();
}
#Override
public boolean deliverSelfNotifications() {
return true;
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.i(LOG_TAG, "Inside on Change. selfChange " + selfChange);
searchInsert();
}
}
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onCreate() {
servReg = true;
db = DataHandlerDB.createDB(this);
registerContentObserver();
}
#Override
public void onDestroy() {
super.onDestroy();
db.close();
this.getApplicationContext().getContentResolver().unregisterContentObserver(new RatedCallsContentObserver(handler));
}
private void searchInsert() {
Cursor cursor = getContentResolver().query(
android.provider.CallLog.Calls.CONTENT_URI, null, null, null,
android.provider.CallLog.Calls.DATE + " DESC ");
if (cursor.moveToFirst()) {
int numberColumnId = cursor
.getColumnIndex(android.provider.CallLog.Calls.NUMBER);
int durationId = cursor
.getColumnIndex(android.provider.CallLog.Calls.DURATION);
int contactNameId = cursor
.getColumnIndex(android.provider.CallLog.Calls.CACHED_NAME);
int numTypeId = cursor
.getColumnIndex(android.provider.CallLog.Calls.CACHED_NUMBER_TYPE);
int callTypeId = cursor
.getColumnIndex(android.provider.CallLog.Calls.TYPE);
Date dt = new Date();
int hours = dt.getHours();
int minutes = dt.getMinutes();
int seconds = dt.getSeconds();
String currTime = hours + ":" + minutes + ":" + seconds;
SimpleDateFormat dateFormat = new SimpleDateFormat("M/d/yyyy");
Date date = new Date();
cursor.moveToFirst();
String contactNumber = cursor.getString(numberColumnId);
String contactName = (null == cursor.getString(contactNameId) ? ""
: cursor.getString(contactNameId));
String duration = cursor.getString(durationId);
String numType = cursor.getString(numTypeId);
String callType = cursor.getString(callTypeId);
seconds = Integer.parseInt(duration);
theDate = dateFormat.format(date);
if (theDate.length() == 9) {
theMonth_ = theDate.substring(0, 1);
theDay_ = theDate.substring(2, 4);
theYear_ = theDate.substring(5, 9);
} else if (theDate.length() == 10) {
theMonth_ = theDate.substring(0, 2);
theDay_ = theDate.substring(3, 4);
theYear_ = theDate.substring(6, 10);
} else if (theDate.length() == 8) {
theMonth_ = theDate.substring(0, 1);
theDay_ = theDate.substring(2, 3);
theYear_ = theDate.substring(4, 8);
}
ContentValues values = new ContentValues();
ContentValues values2 = new ContentValues();
values.put("contact_id", 1);
values.put("contact_name", contactName);
values.put("number_type", numType);
values.put("contact_number", contactNumber);
values.put("duration", Utilities.convertTime(seconds));
values.put("date", dateFormat.format(date));
values.put("current_time", currTime);
values.put("cont", 1);
values.put("type", callType);
values2.put("month",
Utilities.monthName(Integer.parseInt(theMonth_)));
values2.put("duration", Utilities.convertTime(seconds));
values2.put("year", theYear_);
values2.put("month_num", Integer.parseInt(theMonth_));
if (!db.isOpen()) {
db = getApplicationContext()
.openOrCreateDatabase(
"/data/data/com.project.myapp/databases/myDb.db",
SQLiteDatabase.OPEN_READWRITE, null);
}
if (duration != "") {
if (Integer.parseInt(duration) != 0) {
String existingMonthDuration = DataHandlerDB
.selectMonthsDuration(theMonth_, theYear_, this);
Integer newMonthDuration;
if (existingMonthDuration != "") {
newMonthDuration = Integer
.parseInt(existingMonthDuration)
+ Integer.parseInt(duration);
values2.put("duration",
Utilities.convertTime(newMonthDuration));
db.update(DataHandlerDB.TABLE_NAME_3, values2,
"year = ?", new String[] { theYear_ });
} else {
db.insert(DataHandlerDB.TABLE_NAME_3, null, values2);
}
db.insert(DataHandlerDB.TABLE_NAME_2, null, values);
}
}
cursor.close();
}
}
public void registerContentObserver() {
this.getApplicationContext()
.getContentResolver()
.registerContentObserver(
android.provider.CallLog.Calls.CONTENT_URI, false,
new RatedCallsContentObserver(handler));
}
}
I've tried everything. unregistering the observer, etc. but nothing.
I selected the timestamp of the call from android.provider.CallLog.Calls.DATE and before I insert I check if there is some timestamp like that one, if there is I dont insert, if there isnt I insert the data. This values are unique, so never will have some like each other.
This happens to me when I suscribe tot he SMS content provider. I think is they way Android handles messages and calls that makes this behavior, I get the call 2 times whenever I send an SMS so I'm guessing is due to the message being put in the outbox table ? first, and then moved to the sent table. Perhaps something similar happens with the calls provider? Maybe this call is placed to a temporary table inside the provider and then once you receive it or miss it this call goes to the proper table (received/missed). What I do is, I check the Id of the message everytime my observer gets called and I keep the Id of the previous message so I can check If the observer is being calld due to the same ID I just handled.
does the selfChange vary ?
Hint. do not rely on this provider to monitor all your calls. Once android decides to terminate your application you will notice that your provider won't receive anymore calls. Try to schedule the attachment of the content observer every once in a while using AlarmManager.