The MMS portion of the Android SDK is largely not exposed, and consequently there is really one main 3rd party library that helps out with this. The sample project included in the library will not send MMS, however, a fork of the project does.
When I download the latter project, I can run the sample and send an MMS without having to set the application as the default application. However, when I integrate the code into my own application MMS will only send if I make my app the default messaging app. I have read that Android 4.4+ does not support MMS without being a default app, but why is it that the sample project I'm deriving my code from works without being default? The only difference I really notice is that the minimum SDK is 14 versus my project which is 18.
Here is the main code from my project:
Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hacknow2">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<application
android:name=".init.App"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".phase1.PermissionsActivity"/>
<activity android:name=".phase2.youtube.YoutubeActivity" />
<activity android:name=".phase2.posting.PostingActivity" />
<activity
android:name=".phase2.ui.main.SelectionActivity"
android:label="#string/title_activity_selection"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<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>
<activity android:name=".phase1.OpeningActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".phase3.mms.KitSmsSentReceiver"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver>
<receiver
android:name=".phase3.mms.KitMmsSentReceiver"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<receiver
android:name="com.klinker.android.send_message.MmsSentReceiver"
android:taskAffinity="com.klinker.android.messaging.MMS_SENT" />
<receiver
android:name="com.klinker.android.send_message.MmsReceivedReceiver"
android:taskAffinity="com.klinker.android.messaging.MMS_RECEIVED" />
<service android:name="com.android.mms.transaction.TransactionService" />
<service
android:name=".phase3.mms.HeadlessSmsSendService"
android:exported="true"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
</application>
</manifest>
MMSManager, singleton that manages sending MMS
public class MMSManager {
private static final String TAG = "MMSManager";
private static ThreadPoolExecutor mThreadManager;
private static MMSManager INSTANCE;
private static BlockingQueue<Runnable> decodeWorkQueue;
private static int NUMBER_OF_CORES =
Runtime.getRuntime().availableProcessors();
private Settings mSettings;
private Context mContext;
private MMSManager(Context c) {
mContext = c;
initSettings();
initLogging();
// A queue of Runnables
decodeWorkQueue = new LinkedBlockingQueue<Runnable>();
// setting the thread factory
mThreadManager = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES,
50, TimeUnit.MILLISECONDS, decodeWorkQueue);
BroadcastUtils.sendExplicitBroadcast(c, new Intent(), "test action");
}
//See https://stackoverflow.com/questions/14057273/android-singleton-with-global-context
private static synchronized MMSManager getSync() {
if (INSTANCE == null) INSTANCE = new MMSManager(App.get());
return INSTANCE;
}
public static MMSManager getInstance(Context c) {
if (INSTANCE == null) {
INSTANCE = getSync();
}
return INSTANCE;
}
private void initSettings() {
mSettings = Settings.get(mContext);
if (TextUtils.isEmpty(mSettings.getMmsc()) &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
initApns();
}
}
private void initApns() {
ApnUtils.initDefaultApns(mContext, new ApnUtils.OnApnFinishedListener() {
#Override
public void onFinished() {
mSettings = Settings.get(mContext, true);
}
});
}
private void initLogging() {
com.klinker.android.logger.Log.setDebug(true);
com.klinker.android.logger.Log.setPath("messenger_log.txt");
com.klinker.android.logger.Log.setLogListener(new OnLogListener() {
#Override
public void onLogged(String tag, String message) {
//logAdapter.addItem(tag + ": " + message);
android.util.Log.d("MMS_Manager " + tag, "onLogged: " + message);
}
});
}
//Not sure what exception might pop up but it's being handled anyway...
public void sendMMS(String phoneNumber, Bitmap bm) {
mThreadManager.execute(new Runnable() {
#Override
public void run() {
Log.d("ThreadPool/MMSManager", "Trying to send MMS.");
com.klinker.android.send_message.Settings sendSettings = new com.klinker.android.send_message.Settings();
sendSettings.setMmsc(mSettings.getMmsc());
sendSettings.setProxy(mSettings.getMmsProxy());
sendSettings.setPort(mSettings.getMmsPort());
sendSettings.setUseSystemSending(true);
Transaction transaction = new Transaction(mContext, sendSettings);
Message message = new Message(null, phoneNumber);
if (bm != null)
message.setImage(bm);
transaction.sendNewMessage(message, Transaction.NO_THREAD_ID);
}
});
}
}
The fragment that calls MMSManager methods, located in its parent, SelectionActivity:
public class SendDialog extends DialogFragment {
private static final int MY_PERMISSIONS_REQUEST_SEND_SMS = 1000;
private ArrayList<String> mPhoneNumbers;
private DMessage mMessage;
private OnFinished mCompletionListener;
private MMSManager mMMSManager;
private Bitmap mBitmap;
private static String[] mMediaTypes;
public SendDialog(ArrayList<String> phoneNumbers, DMessage m, OnFinished listener){
mPhoneNumbers = phoneNumbers;
mMessage = m;
mCompletionListener = listener;
}
public SendDialog(ArrayList<String> phoneNumbers, DMessage m, Bitmap b, OnFinished listener){
mPhoneNumbers = phoneNumbers;
mMessage = m;
mCompletionListener = listener;
mBitmap = b;
}
public interface OnFinished{
void complete(boolean success);
}
private String getCautionMessage(){
int numContacts = 0;
if(mPhoneNumbers!=null && mPhoneNumbers.size()>0) {
for (String number :
mPhoneNumbers) {
numContacts++;
}
}
return "Are you sure you'd like to send the highlighted post to " + numContacts + " people?";
}
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
mMediaTypes = getResources().getStringArray(R.array.postoptions);
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(getCautionMessage())
.setPositiveButton(R.string.OK, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
sendBulkTexts();
dismiss();
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mCompletionListener.complete(false);
}
});
// Create the AlertDialog object and return it
return builder.create();
}
private boolean isMMS(){
return mMessage.getMediaType().equals(mMediaTypes[2]);//mediaTypes[2] == "Image"
}
/**
* Converts phone number to only numbers.
* I.e. (XXX)-XXX-XXXX to XXXXXXXXXX
*/
private static String phoneNumberFormatter(String unformatted) {
return unformatted.replaceAll("\\D+", "");
}
private void sendBulkTexts(){
for (String number :
mPhoneNumbers) {
try {
String formattedNumber = phoneNumberFormatter(number);
if(!isMMS()) {
//Send a Youtube video or text
sendSMS(formattedNumber, mMessage.getBody());
}
else{
if(mMMSManager==null)//instantiate MMSManager and load bitmap
mMMSManager = MMSManager.getInstance(getActivity());
if(mBitmap!=null)
mMMSManager.sendMMS(formattedNumber, mBitmap);
}
}
catch(Exception e){
Toast.makeText(getActivity(), "Could not send text to " + number + ".", Toast.LENGTH_LONG).show();
Log.e("SendDialog", "sendBulkTexts: ", e);
mCompletionListener.complete(false);
}
}
Toast.makeText(getActivity(), "Finished sending texts.", Toast.LENGTH_LONG).show();
mCompletionListener.complete(true);
}
private void sendSMS(String phoneNumber, String message) throws Exception{
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNumber, null, message, null, null);
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_SEND_SMS: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
sendBulkTexts();
} else {
Toast.makeText(getActivity(),
"Permission for sending SMSs denied.", Toast.LENGTH_LONG).show();
mCompletionListener.complete(false);
return;
}
}
}
}
}
I added following lines after message is declared and it seems to work.
message.setFromAddress(Utils.getMyPhoneNumber(mContext));
message.setSave(false);
Uri uri = Uri.parse("content://mms-sms/conversations/");
message.setMessageUri(uri);
I'm not sure what the fork is doing but I suspect it is not using the system messaging libraries but using the Klinker libraries to send the message. That's what's allowing it to send messages without setting the app as default.
To get it to work without system libraries, you need to setSave as false. Then you need to provide a URI for it to work. The FromAddress may or may not be needed.
I have found the answer.
Apparently, in 2019 Google changed their rules such that you cannot add an app to the Google Play store that sends SMS/MMS directly from your app if it is not the default messaging app without some sort of strong justification. All you are allowed to do is create an intent with your SMS and MMS and pass it to the Android OS to use your default messaging app.
My recommendation is to avoid trying to write SMS/MMS using native APIs (unless you are absolutely confident you are a rare exception to Google Play's terms) and use a third-party service like Twilio to handle this for you.
Related
I am still new to Android development, but I’m creating an app that sendings an HTTP request when it receives a broadcast (from a barcode scan). I have tried to implement some other solutions that I read on here but haven't quite figured it out yet. It would be great if someone could help me get going in the right direction.
Essentially, the end goal is for the app to keep running in the background forever so that even if the app is not open and a barcode is scanned and sent to the app via broadcast signal, the HTTP request is still sent.
This is what I have so far:
MainActivity.java
public class MainActivity extends Activity implements CompoundButton.OnCheckedChangeListener {
public static final String BARCODE_BROADCAST = "...";
private final BarcodeReceiver barcodeReceiver = new BarcodeReceiver();
private TextView mTextView;
private String scannedBarcode;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.barcode);
}
#Override
public void onResume() {
super.onResume();
registerBarcodeScanner();
}
#Override
public void onPause() {
super.onPause();
unregisterBarcodeScanner();
// this code needs to keep the app running
Intent restartService = new Intent("RestartService");
this.startService(restartService);
sendBroadcast(restartService);
}
public void onDestroy() {
super.onDestroy();
// this code needs to keep the app running
Intent restartService = new Intent("RestartService");
this.startService(restartService);
sendBroadcast(restartService);
}
private void registerBarcodeScanner() {
registerReceiver(barcodeReceiver, new IntentFilter(BARCODE_BROADCAST));
}
private void unregisterBarcodeScanner() {
unregisterReceiver(barcodeReceiver);
}
private void displayBarcode() {
if (scannedBarcode == null) return;
String text = getString(R.string.barcode_scanned, scannedBarcode);
mTextView.setText(text);
/* SEND HTTP REQUEST */
}
private class BarcodeReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BARCODE_BROADCAST)) {
String barcode = intent.getStringExtra("Barcode");
if (barcode != null) {
scannedBarcode = barcode;
displayBarcode();
}
}
}
}
}
RestartService.java
public class RestartService extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, MainActivity.class));
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="..."
android:versionCode="1">
<uses-sdk android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
tools:replace="android:label">
<activity
android:name="...MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="...RestartService"
android:enabled="true"
android:exported="true"
android:label="RestartServiceWhenStopped"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="RestartService" />
</intent-filter>
</receiver>
</application>
</manifest>
Receiver works on all android versions from 4.2 upto 8.0. Even if app is removed from Recent Apps But if removed from Recent Apps in Android Oreo, it then never triggers receiver again.
my manifest.xml :
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".WatchMan"
android:enabled="true"
android:exported="true" />
<receiver
android:name=".Receiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
</application>
My receiver.java :
public class Receiver extends BroadcastReceiver
{
public String PhoneNumber = "UNKNOWN";
#Override
public void onReceive(Context context, Intent intent)
{
Log.d("RECEIVER :","CAPTURED THE EVENT.....");
try
{
PhoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
PhoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
context.startForegroundService(new Intent(context, WatchMan.class));
}
else
{
context.startService(new Intent(context, WatchMan.class));
}
}
catch (Exception e)
{
e.printStackTrace();
Log.e("RECEIVER EXCEPTION : ", "Exception is : ", e);
}
}
I want to know if i am doing any mistake in code? Android Developers Documentation asking to register receiver runtime using context. Then i searched for registering it in runtime on stackoverflow but looks no proper thread accepted as answer. How can make receiver to to be ready again, even if removed from recents of Android Oreo?
Thanking you in advance.
I have deleted unrelated posts. I am posting final answer as it may help others. #WIZARD help was thankful.
PHONE_STATE is implicit and will not be triggered on android Oreo or higher. So just place permissions in manifest like :
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".WatchMan"
android:enabled="true"
android:exported="true">
</service>
<service
android:name=".CatchNumbers"
android:enabled="true"
android:exported="true">
</service>
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
Register implicit receivers from foreground service :
public class WatchMan extends Service
{
NotificationManager mNotifyManager;
NotificationCompat.Builder mBuilder;
NotificationChannel notificationChannel;
String NOTIFICATION_CHANNEL_ID = "17";
private boolean running;
private BroadcastReceiver mCallBroadcastReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
String PhoneNumber = "UNKNOWN";
Log.d("RECEIVER : ","HERE HERE");
try
{
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if(state == null)
{
PhoneNumber = "UNKNOWN";
}
else if (state.equals(TelephonyManager.EXTRA_STATE_RINGING))
{
PhoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
Log.d("INCOMING ","Incoming number : "+PhoneNumber);
}
if(intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL"))
{
PhoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.d("OUTGOING ","Outgoing number : "+PhoneNumber);
}
if(!PhoneNumber.contentEquals("UNKNOWN"))
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
context.startForegroundService(new Intent(context, CatchNumbers.class));
}
else
{
context.startService(new Intent(context, CatchNumbers.class));
}
}
}
catch (Exception e)
{
e.printStackTrace();
Log.e("RECEIVER EXCEPTION : ", "Exception is : ", e);
}
}
};
public WatchMan() { }
#Override
public void onCreate()
{
super.onCreate();
mBuilder = new NotificationCompat.Builder(this, null);
IntentFilter filterstate = new IntentFilter();
filterstate.addAction("android.intent.action.NEW_OUTGOING_CALL");
filterstate.addAction("android.intent.action.PHONE_STATE");
this.registerReceiver(mCallBroadcastReceiver, filterstate);
Log.d("RECEIVER : ", "\nCreated....");
mNotifyManager = (NotificationManager) getApplicationContext().getSystemService(NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this, null);
mBuilder.setContentTitle("Insta Promo")
.setContentText("Insta Promo Is Up..")
.setTicker("Insta Promo Is Up..")
.setSmallIcon(R.drawable.ic_launcher_background)
.setPriority(Notification.PRIORITY_HIGH)
.setDefaults(Notification.DEFAULT_ALL)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setOngoing(true)
.setAutoCancel(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "My Notifications", NotificationManager.IMPORTANCE_HIGH);
// Configure the notification channel.
notificationChannel.setDescription("Channel description");
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setVibrationPattern(new long[]{0, 1000, 500, 1000});
notificationChannel.enableVibration(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
mNotifyManager.createNotificationChannel(notificationChannel);
}
running = true;
mBuilder.setChannelId(NOTIFICATION_CHANNEL_ID);
startForeground(17, mBuilder.build());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Log.d("RECEIVER : ", "\nOnStartCommand....");
new Thread(new Runnable()
{
public void run()
{
while(running)
{
try
{
Log.d("RECEIVER : ", "\nALIVE..");
Thread.sleep(10000);
}
catch (InterruptedException e)
{
Log.d("RECEIVER : ", "\nThread : InterruptedException in Receiver...");
Log.e("RECEIVER : ", "\nException is : ", e);
}
catch (Exception e)
{
Log.d("RECEIVER : ", "\nThread : Exception Error in Receiver...");
Log.e("RECEIVER : ", "\nException is : ", e);
}
}
}
}).start();
return START_STICKY;
}
#Override
public void onDestroy()
{
this.unregisterReceiver(mCallBroadcastReceiver);
running = true;
Log.d("RECEIVER : ", "\nDestroyed....");
Log.d("RECEIVER : ", "\nWill be created again....");
}
#Override
public IBinder onBind(Intent intent)
{
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public void onTaskRemoved(Intent rootIntent)
{
super.onTaskRemoved(rootIntent);
Log.d("SERVICE : ", "\nTask Removed....");
}
}
There are some intent actions like NEW_OUTGOING_CALL AND BOOT_COMPLETED which are excluded and can be implemented in receiver and placed in manifest like :
public class MyReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.d("INSTA_BOOT : ", "\nBOOT_COMPLETE_EVENT_OF_INSTA....");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
context.startForegroundService(new Intent(context, WatchMan.class));
}
else
{
context.startService(new Intent(context, WatchMan.class));
}
}
}
As i wanted to re-register or say want to restart foreground service on REBOOT OR BOOT-COMPLETE
CatchNumbers.java is a simple service which performs operation when receiver triggers perticular actions.
It works good on every restart and as android:excludeFromRecents="true" is not needed anymore as even if user removes it from recents on Oreo it will restart the service as it is STICKY. Hope it helps someone like me..!!
Based on the documentation for restrictions on implicit broadcast in Android 8, you cannot use implicit receivers in your manifest (although there are some exceptions, but phone state receiver is not among those exceptions)
You have to use foreground service and register your receiver in your foreground service instead of manifest
remove phone state receiver from manifest
register receiver in onCreate of Service:
#Override
public void onCreate() {
super.onCreate();
phoneStateReceiver = new PhoneStateReceiver();
registerReceiver(phoneStateReceiver, new IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED));
}
unregister in onDestroy:
#Override
public void onDestroy() {
unregisterReceiver(phoneStateReceiver);
super.onDestroy();
}
add a static method to your service to start service:
// start service even if your app is in stopped condition in android 8+
static void requestStart(#NonNull final Context context, #NonNull final String action){
final Context appContext = context.getApplicationContext();
Intent intent = new Intent(appContext, AppService.class);
intent.setAction(action);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// this is required to start the service if there is
// no foreground process in your app and your app is
// stopped in android 8 or above
appContext.startForegroundService(intent);
} else {
appContext.startService(intent);
}
}
start foreground in your onStartCommand
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(ACTION_START.equals(intent.getAction()))
startForeground(ID, notification);
else if(ACTION_STOP.equals(intent.getAction()))
stopForeground(true);
return START_STICKY;
}
I am fairly new to android. I am trying to send data from my tablet to my phone using a WearableListenerService. This part works well, I can see through logs that the data is sent. The problem is that I receive the data from the tablet in the Listener class and I have to transmit it to Mainactivity in order to update my Views. To do this I use a LocalBroadcaster and I implemented the onReceive method in my MainActivity. So when I give the order to send the data from the phone the onReceive gets called multiple time most of the time between 2 or 3 times and furthermore the activity is recreated because onCreate is triggered by this method (I don't know if this behavior is expected).
Here is the code:
DataLayerListenerService.java (Listener)
public class DataLayerListenerService extends WearableListenerService {
// Tag for Logcat
private static final String TAG = "DataLayerService";
private int notificationId = 001;
private String notif;
// Member for the Wear API handle
GoogleApiClient mGoogleApiClient;
#Override
public void onCreate() {
super.onCreate();
// Start the Wear API connection
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.build();
mGoogleApiClient.connect();
}
//#Override
public void onDataChanged(DataEventBuffer dataEvents) {
Log.v(TAG, "onDataChanged: " + dataEvents);
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
Log.e(TAG, "DataItem Changed: " + event.getDataItem().toString() + "\n"
+ DataMapItem.fromDataItem(event.getDataItem()).getDataMap());
String path = event.getDataItem().getUri().getPath();
switch (path) {
case DataLayerCommons.NOTIFICATION_PATH:
Log.v(TAG, "Data Changed for NOTIF_PATH: " + event.getDataItem().toString());
DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
notif = dataMapItem.getDataMap().getString(DataLayerCommons.NOTIFICATION_KEY);
Intent intent = new Intent(NOTIFICATION_RECEIVED);
intent.putExtra(NOTIFICATION_RECEIVED, notif);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
break;
case DataLayerCommons.COUNT_PATH:
Log.v(TAG, "Data Changed for COUNT_PATH: " + event.getDataItem() + "\n"
+ "Count data = " + DataMapItem.fromDataItem(event.getDataItem())
.getDataMap().getInt(DataLayerCommons.COUNT_KEY));
break;
default:
Log.v(TAG, "Data Changed for unrecognized path: " + path);
break;
}
} else if (event.getType() == DataEvent.TYPE_DELETED) {
Log.v(TAG, "DataItem Deleted: " + event.getDataItem().toString());
}
}
}
}
Main Activity
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
public static final String NOTIFICATION_RECEIVED = "NOTIFICATION_RECEIVED";
private String notif="";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG,"OnCreate");
setContentView(R.layout.main_activity);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter(NOTIFICATION_RECEIVED));
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "Got message!");
notif = intent.getStringExtra(NOTIFICATION_RECEIVED);
TextView warnView = findViewById(R.id.warningView);
warnView.setText(notif);
}
};
}
AndroidManifest.xml
<uses-feature android:name="android.hardware.type.watch" />
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/Theme.AppCompat">
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="false" />
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
<service android:name=".DataLayerListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data
android:host="*"
android:pathPrefix="/notification"
android:scheme="wear" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data
android:host="*"
android:pathPrefix="/start-activity"
android:scheme="wear" />
</intent-filter>
</service>
<activity
android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.example.android.wearable.datalayer.EXAMPLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
Thanks for the help
I would suggest using Application class to store your Activity instead of BroadcastReceiver registering.
What I mean:
in Application class create a variable that stores your activity:
class MyApp extends Application {
public static MyActivity activity;
}
to link save into this variable current activity at onCreate and release while onDestroy.
Somehow:
public MyActivity extends Activity {
void onCreate() {
MyApp.activity = this;
}
void onDestroy() {
MyApp.activity = null;
}
void redraw() {
//redraw
}
}
inside the service do something like this:
class MyService extends WearableListenerService {
void onDataChanged() {
if (MyApp.activity != null) {
MyApp.activity.redraw()
}
}
}
do not forget to set application in the manifest:
<application
android:name=".MyApp"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/Theme.AppCompat">
I'm trying to read USSD response to get Sim balance amount etc, and I'm having issues, I have been reading many questions related to that on Stackoverflow, nothing has worked so far. Except for this that came close: Prevent USSD dialog and read USSD response?. by #HenBoy331
But i'm still having issues with it. My broadcast receiver doesn't get called too. I'm using 4.4.2
But it shows nothing. I can't seem to parse the message and get the balance.
I have a MainActivity to make the phone call, ReceiverActivity to implement broadcast receiver, USSDService class to get the USSD response.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this, USSDService.class));
dailNumber("100");
}
private void dailNumber(String code) {
String ussdCode = "*" + code + Uri.encode("#");
startActivity(new Intent("android.intent.action.CALL", Uri.parse("tel:" + ussdCode)));
}
}
RecieverActivity.java
public class RecieverActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter filter = new IntentFilter("com.times.ussd.action.REFRESH");
this.registerReceiver(new Receiver(), filter);
}
public class Receiver extends BroadcastReceiver {
private String TAG = "XXXX";
#Override
public void onReceive(Context context, Intent intent) {
String message = intent.getStringExtra("message");
Log.i(TAG, "Got message: " + message);
}
}
}
USSDService.java
public class USSDService extends AccessibilityService {
public String TAG = "XXXX";
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "onAccessibilityEvent");
AccessibilityNodeInfo source = event.getSource();
/* if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && !event.getClassName().equals("android.app.AlertDialog")) { // android.app.AlertDialog is the standard but not for all phones */
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && !String.valueOf(event.getClassName()).contains("AlertDialog")) {
return;
}
if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && (source == null || !source.getClassName().equals("android.widget.TextView"))) {
return;
}
if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && TextUtils.isEmpty(source.getText())) {
return;
}
List<CharSequence> eventText;
if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
eventText = event.getText();
} else {
eventText = Collections.singletonList(source.getText());
}
String text = processUSSDText(eventText);
if( TextUtils.isEmpty(text) ) return;
// Close dialog
performGlobalAction(GLOBAL_ACTION_BACK); // This works on 4.1+ only
Log.d(TAG, text);
// Handle USSD response here
Intent intent = new Intent("com.times.ussd.action.REFRESH");
intent.putExtra("message", text);
sendBroadcast(intent);
}
private String processUSSDText(List<CharSequence> eventText) {
for (CharSequence s : eventText) {
String text = String.valueOf(s);
// Return text if text is the expected ussd response
if( true ) {
return text;
}
}
return null;
}
#Override
public void onInterrupt() {
}
#Override
protected void onServiceConnected() {
super.onServiceConnected();
Log.d(TAG, "onServiceConnected");
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.flags = AccessibilityServiceInfo.DEFAULT;
info.packageNames = new String[]{"com.android.phone"};
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
setServiceInfo(info);
}
}
AndroidManifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dialussd">
<uses-permission android:name="android.permission.CALL_PHONE" />
<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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".services.USSDService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data android:name="android.accessibilityservice"
android:resource="#xml/config_service" />
</service>
<receiver android:name=".RecieverActivity$Receiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
Please, is there any way i'm implementing this wrongly, Or perhaps there is a new way to get USSD responses which works?
After the launch, change the settings manually
Setting->Accessibility Setting -> You can see a option 'your app name'. Turn it on. (This has to be done from as a part of application flow(not manual))
instead of using broadcast receiver use these two lines of code in
add this code in MainActivity
public static void setTextViewToModify(String Text) {
textView.setText(Text);}
and add this in service class with in onAccessibityEvent
MainActivity.setTextViewToModify(text);
I'm trying to get the RemoteControlClient set up so my app's music can be controlled by the widget that pops up on the lock screen (like SoundCloud, Google Play Music, and other music/video apps work). I'm not sure what's wrong with my code and why it isn't correctly hooking, but here's what I have so far...
A class called MusicService that tries to handle the updates to the RemoteControlClient
public class MusicService extends Service
{
public static final String ACTION_PLAY = "com.stfi.music.action.PLAY";
private RemoteController controller = null;
#Override
public void onCreate()
{
super.onCreate();
System.out.println("Creating the service.");
if(controller == null)
{
controller = new RemoteController();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
String action = intent.getAction();
System.out.println("Got an action of " + action);
/* Logic to get my Song cur */
controller.register(this);
controller.updateMetaData(cur);
return START_STICKY;
}
#Override
public void onDestroy()
{
super.onDestroy();
System.out.println("Destorying MusicService");
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
This uses a class I have called RemoteController which houses my RemoteControlClient.
public class RemoteController {
private RemoteControlClient remoteControlClient;
private Bitmap dummyAlbumArt;
public void register(Context context)
{
if (remoteControlClient == null)
{
System.out.println("Trying to register it.");
dummyAlbumArt = BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_album_art);
AudioManager audioManager = (AudioManager) context.getSystemService(context.AUDIO_SERVICE);
ComponentName myEventReceiver = new ComponentName(context.getPackageName(), MediaButtonReceiver.class.getName());
audioManager.registerMediaButtonEventReceiver(myEventReceiver);
// build the PendingIntent for the remote control client
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(myEventReceiver);
// create and register the remote control client
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context, 0, mediaButtonIntent, 0);
remoteControlClient = new RemoteControlClient(mediaPendingIntent);
remoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
| RemoteControlClient.FLAG_KEY_MEDIA_NEXT
| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
);
audioManager.registerRemoteControlClient(remoteControlClient);
}
}
/**
* Update the state of the remote control.
*/
public void updateState(boolean isPlaying)
{
if(remoteControlClient != null)
{
if (isPlaying)
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
}
else
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
}
}
}
/**
* Updates the state of the remote control to "stopped".
*/
public void stop()
{
if (remoteControlClient != null)
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
}
}
public void updateMetaData(Song song)
{
if (remoteControlClient != null && song != null)
{
System.out.println("Updating metadata");
MetadataEditor editor = remoteControlClient.editMetadata(true);
editor.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, dummyAlbumArt);
editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, (long)1000);
editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, "Artist");
editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, "Title");
editor.apply();
updateState(true);
}
}
/**
* Release the remote control.
*/
public void release() {
remoteControlClient = null;
}
}
Every time I want to update the widget, I call startService(new Intent(MusicService.ACTION_PLAY));. It looks like it correctly creates the service, and it always gets to the point where it says "Updating metadata", but for some reason when I lock my screen and unlock it, I don't see any widget on my lock screen.
Below is the important parts of my manifest as well, seeing as that could somehow cause the issue...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stfi"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="17" />
<application
android:hardwareAccelerated="true"
android:allowBackup="true"
android:icon="#drawable/stfi"
android:label="#string/app_name"
android:largeHeap="true"
android:theme="#style/MyActionBarTheme" >
<meta-data
android:name="android.app.default_searchable"
android:value=".activities.SearchActivity" />
<activity
android:name=".StartingToFeelIt"
android:configChanges="orientation|keyboardHidden"
android:label="#string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="#xml/searchable" />
</activity>
...other activities listed
<service
android:name=".helpers.MyNotificationService"
android:enabled="true"
android:label="MyNotificationServiceLabel" >
</service>
<service
android:name=".music.MusicService"
android:exported="false" >
<intent-filter>
<action android:name="com.stfi.music.action.PLAY" />
</intent-filter>
<intent-filter>
<action android:name="com.example.android.musicplayer.action.URL" />
<data android:scheme="http" />
</intent-filter>
</service>
<receiver
android:name=".music.MediaButtonReceiver"
android:exported="false" >
</receiver>
</application>
Right now my MediaButtonReceiver doesn't really do much of anything. I'm just trying to get the hooks set up. If you want, this is my MediaButtonReceiver class...
public class MediaButtonReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
System.out.println("Receiving something.");
if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON))
{
final KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event != null && event.getAction() == KeyEvent.ACTION_UP)
{
if (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)
{
System.out.println("You clicked pause.");
}
else if(event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY)
{
System.out.println("You clicked play.");
}
else if (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT)
{
System.out.println("You clicked next.");
}
else if (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS)
{
System.out.println("You clicked previous.");
}
}
}
}
}
if you can't see remoteControlClient on lock screen you must implement audio focus. You can look here