In part one, we saw how to create the bare bones of an SMS app. That is to say, we created a utility that could send and receive messages but that wasn't exactly in a state to replace your current messaging app any time soon. For starters, the app needed to be open in order to receive messages. And you could only message yourself…
So yes, we have a ways to go. Let's sort it out, shall we? And along the way, we'll learn a number of different skills that can be useful for creating a wide range of different apps!
Receiving messages in the background
The biggest requirement of any SMS app, is probably that it should be able to alert you about getting new messages. And if you have to have the app open for that to happen, then that kind of defeats the point.
Fortunately, we already created a broadcast receiver that can do this and that receiver will be able to hang around and listen out for our messages in the background by default. Problem is, at the moment the broadcast receiver doesn't actually do anything to alert us to the new messages – it just updates our list.
So the first thing we need to do, is to launch MainActivity.java whenever a message is intercepted. To do that, you just need to stick something in the onReceive of the broadcast receiver (which we called SmsBroadcastReceiver). A simple toast message will demonstrate that this works:
Toast.makeText(context, "Message Received!", Toast.LENGTH_SHORT).show();
To open the main app and show the messages, we just need to use startActivity. Except we need to add a flag in order to do this from a class other than an activity. Like so:
Intent i = new Intent(context, MainActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i);
This tells Android that a new task is being started – meaning that the user will be brought out of what they are currently doing. The problem is that if you use this code as it is, you'll end up creating multiple instances of your activity, which is pretty bad practice. The easy way to solve this problem is in the manifest, where you just need to add the following line to the main activity:
android:launchMode="singleInstance"
This will prevent multiple versions of the same activity being created and thus we won't need to worry about checking whether or not the activity is at the front. Except we kind of do still, just so that we can decide whether to update our inbox (if the activity is already at the front, then startActivity won't refresh our inbox otherwise). Do this by creating a static boolean called active in your MainActivity.Java and then setting it to 'true' onStart and 'false' onStop:
static boolean active = false; @Override public void onStart() { super.onStart(); active = true; inst = this; } @Override public void onStop() { super.onStop(); active = false; }
Finally, you can then use the following code in your broadcast receiver:
if (MainActivity.active) { MainActivity inst = MainActivity.instance(); inst.updateInbox(smsMessageStr); } else { Intent i = new Intent(context, MainActivity.class); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); }
Now your broadcast receiver will listen out for messages and if the app is running, then it will use the usual updateInbox function to refresh the messages. If the app is not running however, then it will be launched and the inbox will automatically be updated.
Finally, we also need to ensure that our broadcaster receiver starts up as soon as the phone boots. This is something we once again do in the Manifest with:
<action android:name="android.intent.action.BOOT_COMPLETED"/>
And don't forget to add the following permission for your app:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
Now, whenever you get an incoming text message, your app will launch and show it to the user; even if they haven't opened your app recently. As soon as the phone is ready to go, so is your app!
This means users will see the message in the inbox and can decide to respond if they so wish. This isn't really the best practice though, as most people don't want to be torn away from whatever they're doing to be forcibly shown your app. Not handy if they're using Google Maps while they're driving for instance!
Instead then, you might choose to use a transparent activity that only obscures part of the screen (and dismisses itself), you might use a dialog or you might send a push notification. Now that you know how to handle intercepting messages in the background, you can play around with how you want to deal with them.
Using broadcast receivers like this is something that will likely come in very handy and you can also use them to set alarms, to listen for other notifications or even to launch apps when the phone is plugged in…
Becoming the default SMS app
While your app is now receiving your messages automatically, it still isn't doing its full job as an SMS utility. Right now, whichever SMS app you've previously been using will still be doing the same thing and your app won't be an option when other apps use SMS intents. In other words, you're not the default app; and as yet, you don't have the option of being it either.
The good news is that all we need to do to change this is ensure we have all the right intent filters in our Manifest file. As many of you will know, an intent is a means of two apps communicating with one another and sending instructions. In Google's grand vision for Android, the users experience seamless switching between dedicated apps for different services without feeling as though they're loading separate 'programs'. Hence the push toward a consistent Material Design that would ensure a similar design language across utilities.
We need to ensure our app offers all of the functionality that users might expect from their primary SMS tool
Once we add the right intents then, we ensure our app can be chosen as the default messaging service. The only problem is that we also need to ensure our app offers all of the functionality that users might expect from their primary SMS tool. We need to support MMS for example, otherwise the device will be left with no default means of opening multimedia messages. We also need to create a service for quick replies. Hoi!
But it's okay, we can get through this if we stick together…
First, we need to create a new broadcaster that we're going to use to receive MMS messages. Unfortunately, receiving MMS is a whole other thing and so we don't want to go into that right now. Luckily, what we can do, is to create a kind of 'place holder' that will act as a faux MMS receiver in the meantime.
Create your new broadcast receiver just like you created the last one and then populate it like so:
package com.nqr.smsapp; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public class MMSBroadcastReceiver extends BroadcastReceiver { public static final String SMS_BUNDLE = "pdus"; @Override public void onReceive(Context context, Intent intent) { throw new UnsupportedOperationException("Coming soon!"); } }
What you have here is a broadcast receiver that essentially does nothing… But to be fair, the only people who use MMS these days are our parents (or so I believe). So this will be fine for a lot of people but if you were making a fully fledged SMS app from this, I would recommend coming back and implementing this properly.
Likewise, a fully-functional messaging app should also offer the 'Quick Reply' option so that users can reject calls with a message or respond to incoming messages without opening the main app. This will require a service to handle that but fortunately, we can just create an empty service again to trick Android.
If you were creating a different kind of app, then you might want to use a service to handle calculations or other operations in the background when your app was closed.
A service is something that runs silently in the background and doesn't need to be actively in use by the user. This means we can keep our app open and ready to receive messages, while our user gets on with other things. We can start services from activities, from broadcast receivers and from other services. First though, we need to create one. So make a new class and call it QuickResponseService. Because it is a service that allows us to respond quickly… (although this does sound a little like emergency breakdown cover…)
We're going to add some callback methods seeing as services have their own life cycles – just like activities – and you need these for things to work properly:
import android.app.Service; import android.content.Intent; import android.os.IBinder; public class QuickResponseService extends Service { @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onDestroy() { super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startID) { return super.onStartCommand(intent,flags,startID); } }
onCreate and onDestroy should be fairly self explanatory. onStartCommand meanwhile is what we call in order to start our service. Here we pass the intent so that we can pass information to the service. Luckily, we don't need to worry about all that because we're not actually going to be using our service.
But if you were creating a different kind of app, then you might want to use a service to handle calculations or other operations in the background when your app was closed.
Finally, with all these new classes, we're now ready to update our manifest in order to make it a suitable candidate as 'default SMS app'. Simply add all the following permissions and intent filters to your Manifest:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://ift.tt/nIICcg" package="com.nqr.smsapp"> <uses-permission android:name="android.permission.WRITE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:launchMode="singleInstance"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SENDTO"/> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="sms" /> <data android:scheme="smsto" /> <data android:scheme="mms" /> <data android:scheme="mmsto" /> </intent-filter> </activity> <receiver android:name=".SmsBroadcastReceiver" android:exported="true" android:permission="android.permission.BROADCAST_SMS"> <intent-filter android:priority="999" > <action android:name="android.provider.Telephony.SMS_RECEIVED" /> <action android:name="android.provider.Telephony.SMS_DELIVER" /> <action android:name="android.provider.Telephony.SMS_DELIVER_ACTION" /> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> <receiver android:name=".MMSBroadcastReceiver" android:exported="true" android:permission="android.permission.BROADCAST_WAP_PUSH"> <intent-filter android:priority="999" > <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" /> <data android:mimeType="application/vnd.wap.mms-message" /> </intent-filter> </receiver> <service android:name=".QuickResponseService" android:enabled="true" android:exported="true" android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"> <intent-filter> <category android:name="android.intent.category.DEFAULT" /> <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" /> <data android:scheme="sms" /> <data android:scheme="smsto" /> <data android:scheme="mms" /> <data android:scheme="mmsto" /> </intent-filter> </service> </application> </manifest>
This now tells Android that your app is capable of doing everything an SMS app needs to be able to do and if you've done everything right, then you should now see it as an option when you head into settings to change your default messaging app!
One more thing you need to do though: disable the relevant features when your app is not the default messaging app. Otherwise, it will continue to pop up whenever someone gets a new message, even when they chose to use another service! You can do this simply by querying Telephony.Sms.getDefaultSmsPackage() to see if yours is currently set to default.
Tidying up
Now the app almost works like any other SMS app and the parts that are missing should be easy enough for you to figure out.
All that's really left is to tidy things up a little. For example, most SMS apps don't show all your messages in one screen but rather group them by sender. This is something you can easily do by editing your refreshSMSInbox with the following:
do { String str = "SMS From: " + smsInboxCursor.getString(indexAddress) + "\n" + smsInboxCursor.getString(indexBody) + "\n"; if (smsInboxCursor.getString(indexAddress).equals("PHONE NUMBER HERE")) { arrayAdapter.add(str); } } while (smsInboxCursor.moveToNext());
You could simply scan through the messages and use each new sender to generate a list of contacts in a separate activity (creating a new entry only when the sender isn't already added to the list). From there, show a filtered inbox of only messages from that chosen number in its own thread. It would then be very simple to make the recipient of the message be the person in that thread. And of course you could add a FAB (floating action button) to handle new messages where the number is input manually, probably from the main screen showing the different contacts.
Of course most messaging apps also tend to tell you the name of the person messaging you. Using the following function (which relies on ContactsContract), you can easily get the sender's name rather than just showing their phone number:
public static String getContactName(Context context, String phoneNo) { ContentResolver cr = context.getContentResolver(); Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNo)); Cursor cursor = cr.query(uri, new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); if (cursor == null) { return phoneNo; } String Name = phoneNo; if (cursor.moveToFirst()) { Name = cursor.getString(cursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME)); } if (cursor != null && !cursor.isClosed()) { cursor.close(); } return Name; } }
Now you can show the name of the sender, rather than just lots of numbers. Be sure to make sure you get permission for READ_CONTACTS though, as this is separate from what we have gotten so far.
Note that you can also use this to get the contact's photo ID this way and thereby display the contact image next to the contact name and number too – which would result in a much nicer looking UI. And this would be especially true if you were to display the messages on cards using RecyclerView , as we have discussed in the past.
Closing comments
There's a little work left there for you to do but with that, you should now understand everything necessary to create your own, fully functional SMS app. Get to work on adding those final touches and be sure to share your creations in the comments section. Like I said though, there's no reason this has to become a typical SMS app just yet and you could always choose to make it into something else, whether that's an automatically responding AI or an SMS back-up tool. Get creative!
from Android Authority http://ift.tt/2dOwfgm
via IFTTT
Aucun commentaire:
Enregistrer un commentaire