📜 ⬆️ ⬇️

Develop sms blacklist for Android

Hello, dear habrasoobschestvu!

Based on the answers to my question , I publish this topic. “Antispam” is a somewhat loud name for the application made, since at this stage it represents only the black list of senders. However, in the future I plan to make really antispam with automatic filtering. The material below is designed for those who are at least a little familiar with developing for Android and have taken at least some steps to develop their own application, since I will not talk about creating an entire application from scratch, but only tell about the most interesting and important points. Who cares, welcome under cat.


Do you remember how it all started?


A small lyrical digression. It all started with the fact that I somewhere leaked my mobile phone number. I even suspect exactly where, because my friends who registered on the same site as me receive the same spam messages at the same time. But now is not about that. On Fridays, ads for various clubs began to come to me, and every week the flow of messages only increased. Since I became the owner of the phone based on the Android OS, I decided to make an application that will fight against this disgrace. Programmers never look for easy ways - I didn’t want to go to a mobile operator, and besides, I had long wanted to start developing for Android.
')

The architecture and layout of the application


The application consists of three parts:
  1. Actually a filter that receives and filters SMS messages;
  2. A database that stores the black list of senders and messages received from them;
  3. User interface.

How does all this work? The user adds to the black list of senders either from the "Inbox" or from the "Contacts". Turns on the filter and closes the application. When a message is received, the application analyzes its sender, and if it is in the black list, it places the message in the so-called repository, so that the user can later see the filtered messages. If the sender is “clean”, then the SMS will go to the Inbox.

First prototype


No sooner said than done. However, the implementation of the first prototype did not last a day. The fact is that I tried to catch an event about receiving a text message, and in the event handler for this event, delete from the Inbox folder all messages whose senders were blacklisted. The problem is that the owner of the phone would still hear a signal informing you that the SMS was received, and when opening the inbox, you would not see anything new there! And this is somehow not good. Therefore, I began to look for a way to intercept a message, even before it goes to the Inbox.

Interception of messages


Android OS is designed so that about such events as receiving SMS, turning on WI-FI, connecting the charging and the like, the system informs applications via broadcasting. More information about this mechanism and the whole architecture of Android can be read here (4 translations) . To create a recipient (listener) for such a system broadcast, you must:
  1. Create your own class inherited from BroadcastReceiver and overload the onReceive method;
  2. Register it in the system and specify which types of mailings we want to receive.


Creating a listener


public class SMSReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); Object[] pdus = (Object[]) bundle.get("pdus"); if (pdus.length == 0) { return; } Sms sms = Sms.fromPdus(pdus, context); SmsDatabase db = null; try { db = SmsDatabase.open(context); if (Filter.filter(context).isSpam(sms, db)) { abortBroadcast(); db.messages().save(sms); } } finally { if (db != null) db.close(); } } } 


The onReceive method works every time an SMS message arrives on the phone. You can extract all the necessary information from the intent parameter. Attention , according to the documentation, the class inherited from BroadcastReceiver is relevant only during the execution of the onReceive method. This means that the system can destroy an instance of the class as soon as the execution of the specified method ends. It also means that you should not store any information in non-static class fields.

In the first two lines, we extract the PDU information. Roughly speaking, it is SMS in "raw" form. After checking for emptiness, we are trying to extract information about the message using the static fromPdus () method in the sms class, which will be described later.

Then we use the Filter class to check if the sender of the newly received SMS is in the black list. If it is found that we are saving the message in the database and using the abortBroadcast () method, we interrupt the newsletter. This means that all recipients with a lower priority who are registered to receive SMS notification will not even know that such an event took place. We will set our recipient the highest priority (even higher than the recipient, which makes sounds and vibrates with the device) so as not to disturb the user in case of receiving a spam message. Read about priorities below.

In the previous version of the application, in the onReceive method, the database connection was opened twice: the first time in the Filter class when checking a message, and the second time directly when writing sms to the database. However, I refused such an approach and made the code “slightly more incorrect” from the point of view of the “prettiness” of the code, since the execution time of the onReceive method is limited to 10 seconds and it makes no sense to open two connections in a row. After all, if our method does not meet the allotted time, then Android will call the method of the next recipient and then the user will be informed about receiving SMS.

Registration of the listener


We wrote a listener. It remains to register his system. This can be done in two ways:
  1. Programmatically using the registerReceiver () method. In this case, the recipient will live only as long as the component that registered it is alive (as a rule, this is the Activity);
  2. Using AndroidManifest. In this case, the recipient will live, even if the application is closed, and moreover, even if the phone is restarted!


Obviously, the 2nd option is more acceptable. Let's see how it can be implemented:
 <application android:label="@string/app_name" android:icon="@drawable/app_icon"> …………….    ………………….. <receiver android:name=".SMSReceiver" android:enabled="false"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver> </application> 


Everything is very simple here. Specify the recipient's class name (android: name), and then use the intent-filter tag to specify the priority (android: priority, 1000 is the maximum value; for the standard recipient that vibrates and makes sounds, priority 999) and which events we subscribe to ( android.provider.Telephony.SMS_RECEIVED).

Turning the listener on and off


It should be said that the recipient is off by default. From this it follows that the user has the ability to turn the filter on and off by activating and deactivating the recipient of the SMS. In the Filter class, there are corresponding on (), off () and enabled () methods for this.

The Filter class, by the way, is a singleton class, since we must have one filter for the entire application, despite the fact that it depends on the context (Context). In the SMSReceiver class, you could see how a filter instance is accessed through the static filter () method, which accepts an instance of the Context class as a parameter.

 private static Filter _filter; public static Filter filter(Context context) { if (_filter == null || !_filter._context.equals(context)) { _filter = new Filter(context); } return _filter; } 


I am by no means arguing that such a implementation is the only true one. If you can offer a better solution, then I will consider it with pleasure.

In the Filter class constructor, initialize the necessary work data:
 private final ComponentName componentName; private final PackageManager packageManager; private Context _context; private Filter(Context context) { _context = context; String packageName = context.getPackageName(); String receiverComponent = packageName + ".SMSReceiver"; componentName = new ComponentName(packageName, receiverComponent); packageManager = context.getPackageManager(); } 


componentName is the full name of the application component, in this case, the recipient, which includes the package name and class name (SMSReceiver).
packageManager - it is clear from the title that this is a class for managing application components.

Consider a method that includes a filter:
 public void on() { if (!enabled()) { packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); } else { Util.showMessage(_context.getString(R.string.alreadyStarted), _context); } } 


It's simple. If the component is already enabled (enabled () is a self-written method, we will consider it a bit later), then we inform the user about it, but if it is disabled, we enable it. The static class Util is self-written and includes various auxiliary functions. In this case, the showMessage method uses the standard Toast class to display messages on the screen.

The off () method, which disables the filter, is completely similar to the on () method, except that the PackageManager.COMPONENT_ENABLED_STATE_DISABLED flag is used, and if the filter is already turned off, a corresponding message is displayed.

The method that checks the state of the filter is even simpler:
 public boolean enabled() { int enabled = packageManager.getComponentEnabledSetting(componentName); return (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED); } 


Check for lice


In the Filter class, there is another method that has not been described. This is the isSpam () method, which, in fact, performs the main task of the application. The method is extremely simple. It extracts messages from the sender from the SMS and tries to find it in the database. If there is one, the message is considered spam.

 public boolean isSpam(Sms sms, SmsDatabase db) { Sender sender = sms.sender(); return db.senders().exists(sender); } 


Sms class


Sms class has already been mentioned twice. It would be necessary to tell about it in more detail. This class serves as a representation of SMS messages in the application. It contains the following fields:

 private String _body; private Sender _sender; private long _timestamp; 


where _body is the message body, _sender is the sender of the message _timestamp is the time when the message was received as a UNIX timestamp.

Sender class is not standard for Android. It is handwritten and can store the sender's phone, and also, if necessary, extract the sender's name from the phone book. We will not consider it in detail.

We already know that the Sms class has a static fromPdus () method that retrieves message information from the PDU. Let's look at its code:
 public static Sms fromPdus(Object[] pdus, Context context) { Sms result = new Sms(); for (int i = 0; i < pdus.length; i++) { SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdus[i]); result._body += sms.getMessageBody(); } SmsMessage first = SmsMessage.createFromPdu((byte[]) pdus[0]); result._sender = new Sender(first.getOriginatingAddress(), context); result._timestamp = first.getTimestampMillis(); return result; } 


At first glance, the method may seem complicated. However, it is not. The only difficulty is that one logical SMS message can be split into several physical ones. I think that in this respect I have not discovered America for you. This special effect is familiar to all who always write without spaces, to shove as much information as possible into one SMS and save their money.

Thus, we in a cycle collect the message body piece by piece, and then extract the necessary information from the very first SMS message. In general, with the Sms class you can finish it.

A little bit about the database


Blacklisted arrogant spammers, as well as the results of their unrecognized creativity are stored in a database. This is necessary so that the user can see what he saw there. After all, theoretically, the user can add to the blacklist and phone number from his contacts, and after reconciling with its owner, he may want to read the SMS received from him.

We were lucky, because in Android, SQLite goes straight out of the box. I implemented all the logic of working with the database using standard JAVA classes and SQL queries. According to the law of meanness, after I finished this part of the application, I read an ORM article for Android on Habré.

Working with the database I have built on the principle: opened the connection - worked with the data - closed the connection. This practice is especially good for SQLite, since this DBMS is file-based and blocking may occur during a read or write operation. Therefore, I try not to keep long-lived connections.

I wouldn’t like to consider all the wrapper methods for the database, as there are many of them, and the article is not rubber. In addition, they are all of the same type, so to understand the essence, we consider only the method that we saw in the onReceive method of the SMSReceiver class.

The record “db.messages (). Save (sms)” tells us that within the db connection we refer to the Messages table, which stores the filtered messages, and add a new sms message to it using the save () method. I repeat that all classes and methods are self-written.

 public void save(Sms sms) { if (!Preferences.get(_context).storeSms()) return; int maxCount = Preferences.get(_context).storedSmsAmount(); if (count() >= maxCount) trim(maxCount - 1); SQLiteStatement insert = _db.compileStatement( "INSERT INTO Messages(Phone, Timestamp, Body)" + "VALUES (?, ?, ?)"); insert.bindString(1, sms.sender().phone()); insert.bindLong(2, sms.timestamp()); insert.bindString(3, sms.body()); insert.execute(); } 


The first line of code we get access to the settings of the application and check the flag responsible for the user's desire to store the filtered messages. If the user refused to store, then just exit.

Then, again, from the settings, we retrieve the size of the filtered SMS storage set by the user. And if the current number of messages is larger than this size, we delete the old messages from the storage, taking into account the order in which they were received. That is, the first messages to be deleted will be deleted first. Classic lineup.

After these gestures, we form a request to insert data into the Messages table and, using typed bind () methods, bind the data to the generated query. At the end we execute the request.

Testing and debugging


It seems everything is ready. It remains a small step. The question is, how can we make sure that the application functions correctly? What does the recipient get at all, but does the database save something? I will not tell you about the Unit-testing of applications for Android, since this is a topic for a separate large topic. I'll tell you only about how to simulate receiving SMS on the emulator.

To do this, open any telnet client. Depending on my mood, I use either Putty or the standard client in Windows 7 (which you must first turn on). Connect to localhost, and specify the port displayed in the window title with the emulator as the port. As a rule, it is 5554. It goes without saying that in this case the emulator should be running. After reading the greeting, you can begin to enter commands.

There are a lot of teams. Their list can be obtained by typing the command “help”. With the help of these commands you can simulate many events that can occur with the phone. However, we are only interested in imitation of receiving SMS messages.

To simulate this, just type the command
 sms send _ _ 

where telephone_number is any sequence of digits that may begin with a “+” sign, and text_mouse is the content of the message, including spaces and other characters that we usually write in SMS.

In case of successful simulation, we will see the message “OK” in the telnet client terminal.

Conclusion


In this topic, I tried to reveal the main technical details of my application, pay attention to the pitfalls and some features of the Android platform. The latest version of the application can be downloaded for free from Google Play.

If you have any questions or you have any suggestions, I will be grateful if you write about it in the comments or in private messages. Constructive criticism is welcome. Thanks for attention!

PS: I am sorry that I did not insert screenshots. And so it turned out quite a lot of material, and I don’t know what needs to be explained from the above. If you do not agree with me, then I will definitely correct the topic.

Source: https://habr.com/ru/post/146453/


All Articles