Custom SIP VoIP app keeps being closed by Android - android

I've developed a version of the native SIP Demo app provided by Google https://android.googlesource.com/platform/development/+/master/samples/SipDemo/
My Simple app registers with the SIP server and when a call invite is received on that SIP account a notification and Toast is displayed with the caller ID. I've not designed the app to run as a service or anything. After launching the activity the app registers with the SIP server and then I simple hit the home button and let the app run in the background, waiting for an incoming call.
The app functions correctly it's just that within ~ 12 - 24 hrs the app seems to have been stopped by the Android OS. I'm not sure if it's related to the native SIP stack or Android's resource manager shutting down the app but I've tried setting everything I can on the handset itself to prevent the app from being stopped like removing any battery saving settings etc.
Are there any tips to keep the app running in the background using the native SIP API?
My is code below:
CallerIDActivity.java -
package example.callerid;
//This app has been developed with a target API of 22 so as to avoid having to
//implement runtime permissions on the test handset with a higher API level.
import android.Manifest;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.sip.SipException;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.net.sip.SipRegistrationListener;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Random;
public class CallerIDactivity extends Activity {
public SipManager manager = null;
public SipProfile me = null;
public IncomingCallReceiver callReceiver;
public CallerIDactivity (){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createNotificationChannel();
}//end constructor
//define NotificationChannel method for use in the contructor above
//IncomingCallReciever will then use this channel to send notification of CID
public void createNotificationChannel(){
//Create the Notification but only on API 26+ as the
//notification class is new and not in the support lib
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
CharSequence name = "CHANNEL_NAME";
String description = "CID_NOTIFY";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel("CHANNEL_ID", name, importance);
channel.setDescription(description);
//Register the channel with the system: you can't change the importance
//or other notification behaviour after this
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if(notificationManager !=null) {
notificationManager.createNotificationChannel(channel);
}
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.callerid);
// Set up the intent filter. This will be used to trigger an
// IncomingCallReceiver when someone calls the SIP address used by this
// application.
IntentFilter filter = new IntentFilter();
filter.addAction("android.CallerID.INCOMING_CALL");
callReceiver = new IncomingCallReceiver();
this.registerReceiver(callReceiver, filter);
}//end onCreate
#Override
public void onStart() {
super.onStart();
// When we get back from the preference setting Activity, assume
// settings have changed, and re-login with new auth info.
initializeManager();
}
#Override
public void onDestroy() {
super.onDestroy();
closeLocalProfile();
if (callReceiver != null) {
this.unregisterReceiver(callReceiver);
Log.d("CallerID", "UnRegisteringReceiverOnDestroy ");
Toast toast = Toast.makeText(getApplicationContext(), "UnRegisteringReceiverOnDestroy", Toast.LENGTH_LONG);
toast.show();
}
}//end onDestroy
public void RegisterBtn(View callerid){
initializeManager();
Log.d("CallerIDactivity","RegisterBtn Press");
Toast toast = Toast.makeText(getApplicationContext(),"Registering", Toast.LENGTH_SHORT);
toast.show();
}
public void initializeManager() {
if(manager == null) {
manager = SipManager.newInstance(this);
}
initializeLocalProfile();
}
public void initializeLocalProfile() {
if (manager == null) {
return;
}
if (me != null) {
closeLocalProfile();
}
try {
SipProfile.Builder builder = new SipProfile.Builder("sipaccount", "my.sip.server");
builder.setPassword("pass");
me = builder.build();
Intent i = new Intent();
i.setAction("android.CallerID.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
manager.open(me,pi,null);
// This listener must be added AFTER manager.open is called,
// Otherwise the methods aren't guaranteed to fire.
manager.setRegistrationListener(me.getUriString(), new SipRegistrationListener() {
public void onRegistering(String localProfileUri) {
updateStatus("Registering with SIP Server...");
}
public void onRegistrationDone(String localProfileUri, long expiryTime) {
updateStatus("Ready");
}
public void onRegistrationFailed(String localProfileUri, int errorCode,
String errorMessage) {
updateStatus("Registration failed. Please check settings.");
}
});
}catch (ParseException pe) {
updateStatus("Connection Error.");
} catch (SipException se) {
updateStatus("Connection error.");
}
}
/**
* Closes out your local profile, freeing associated objects into memory
* and unregistering your device from the server.
*/
public void closeLocalProfile() {
if (manager == null) {
Log.d("closeLocalProfile", "manager == null");
return;
}
try {
if (me != null) {
manager.close(me.getUriString());
Toast toast = Toast.makeText(getApplicationContext(), "ClosingProfileURI"+" "+me.getUriString(), Toast.LENGTH_SHORT);
toast.show();
}
} catch (Exception ee) {
Toast toast = Toast.makeText(getApplicationContext(), ee.getMessage(), Toast.LENGTH_SHORT);
toast.show();
Log.d("onDestroy", "Failed to close local profile.", ee);
}
}
/**
* Updates the status box at the top of the UI with a messege of your choice.
* #param status The String to display in the status box.
*/
public void updateStatus(final String status) {
this.runOnUiThread(new Runnable() {
public void run() {
Toast toast = Toast.makeText(getApplicationContext(), status, Toast.LENGTH_SHORT);
toast.show();
}});
/* Be a good citizen. Make sure UI changes fire on the UI thread.
this.runOnUiThread(new Runnable() {
public void run() {
Log.i("allenssip",status);
TextView labelView = (TextView) findViewById(R.id.sipLabel);
labelView.setText(status);
}
});
*/
}//end updateStatus
}//end Class
IncomingCallReceiver.java -
package example.callerid;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.sip.*;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;
import android.widget.Toast;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Random;
/**
* Listens for incoming SIP calls, intercepts and hands them off to CallerIDactivity.
*/
public class IncomingCallReceiver extends BroadcastReceiver {
public Calendar calendar;
public SimpleDateFormat dateFormat;
public String date;
public SipManager mSipManager;
public SipSession mSipSesion;
public SipProfile peerProfile;
static private int notifyNum = 1;
/**
* Processes the incoming call, answers it, and hands it over to the
* CallerIDactivity.
* #param context The context under which the receiver is running.
* #param intent The intent being received.
*/
#Override
public void onReceive(Context context, Intent intent) {
mSipManager = SipManager.newInstance(context);
try {
mSipSesion = mSipManager.getSessionFor(intent);
} catch (SipException e) {
e.printStackTrace();
}
peerProfile = mSipSesion.getPeerProfile();
calendar = Calendar.getInstance();
dateFormat = new SimpleDateFormat("dd/MM/yyyy");
date = dateFormat.format(calendar.getTime());
Log.d("CallerID", "OnReceive");
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, "CHANNEL_ID")
.setSmallIcon(R.mipmap.ic_launcher_round)
.setContentTitle("CallerID" + " " + date)
.setContentText(peerProfile.getDisplayName())
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
if(notifyNum <= 9) {
notificationManager.notify(notifyNum++, mBuilder.build());
}else {notificationManager.notify(notifyNum, mBuilder.build());
notifyNum = 1;
}
int count = 0;
while (count <= 10) {
Toast toast = Toast.makeText(context, peerProfile.getDisplayName(), Toast.LENGTH_SHORT);
toast.show();
count++;
}//end while
}//end onReceive
}//end Class

Related

Phone manager/Battery optimizer app kills foreground service

-I am developing an application that records user location in a .csv file and uploads them to firebase storage and I've written a service to accomplish this. The service needs to run indefinitely until the user stops it (by unchecking a switch from the activity that launched the service). The service runs in foreground and after running for 15-20 minutes, the phone manager app gives "resource intensive app found" notification and kills the service. onDestroy() never gets called!. I return START_STICKY which is supposed to recreate the service and call onStartCommand() again but the service never gets created again!
I've seen some people write workarounds for this problem but they target specific android versions. My app targets android Pie and I haven't seen any "generic" solution to this problem yet. All solutions are either device/brand specific or API level specific. For now I'm testing the app on Huawei Y7 prime 2018 (Android O) and on my device, i got around this by manually disallowing the phone manager app to manage my application, this way the service does run indefinitely until the user kills it from the app. The nature of the application is such that i need minimum user engagement and i don't expect the users to go through the hassle of manually disabling my app from battery optimizer/phone manager app. My LocationService.java class is attached, if somebody could please provide either a better implementation of this service or some generic method to keep it alive, that'd be great. Even some hack to make sure that onDestroy() gets called before the system kills the application would work!
package com.example.bumps;
import android.Manifest;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class LocationService extends Service implements LocationListener {
private static final String CHANNEL_ID = "LocationServiceChannel";
private static final int ONGOING_NOTIFICATION_ID = 1;
public static final String PREFS_TAG = "PendingUploadPrefs";
private LocationManager locationManager;
DecimalFormat numberFormat = new DecimalFormat("#.000");
DecimalFormat coordinatesFormat = new DecimalFormat("#.00000");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
SimpleDateFormat sdfForDir = new SimpleDateFormat("dd-MM-yyyy_hh:mm:ss");
File locationDirectory;
String currentDataDirectory;
FirebaseStorage filesStorage;
String userUUID;
List<ResumeFileUpload> pendingFileUploads;
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor sharedPrefEditor;
private SOTWFormatter sotwFormatter;
#Override
public void onCreate() {
super.onCreate();
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
filesStorage = FirebaseStorage.getInstance();
pendingFileUploads = new ArrayList<>();
sharedPreferences = this.getSharedPreferences(PREFS_TAG, Context.MODE_PRIVATE);
sharedPrefEditor = sharedPreferences.edit();
sotwFormatter = new SOTWFormatter(this);
String timeStamp = sdfForDir.format(new Date());
currentDataDirectory = "RBD_" + timeStamp;
locationDirectory = new File(getExternalFilesDir(null), currentDataDirectory);
if (!locationDirectory.exists())
locationDirectory.mkdir();
}
#RequiresApi(api = Build.VERSION_CODES.O)
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//userUUID = intent.getStringExtra("userUUID");
userUUID = sharedPreferences.getString("userUUID", "UNKNOWN");
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0, 0, this);
}
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
createNotificationChannel();
Notification notification =
new Notification.Builder(this, CHANNEL_ID)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.bump)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
return START_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Location Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
try {
manager.createNotificationChannel(serviceChannel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
locationManager.removeUpdates(this);
}
File newFileLoc = writeLocationDataToFile(locationStringBuilder);
pendingFileUploads.add(new ResumeFileUpload(currentDataDirectory,
newFileLoc.getPath()));
uploadToStorage(newFileLoc);
/*if there are no pending files from some previous session, just save the ones
* that just got created*/
if (getPendingFileUploads() == null){
setList("PendingUploads", pendingFileUploads);
}else{
/*if there are pending uploads from some previous session, fetch them,
* update the list of pending uploads with and save for later upload*/
List<ResumeFileUpload> pUploads = getPendingFileUploads();
for (ResumeFileUpload resumeFileUpload: pendingFileUploads){
pUploads.add(resumeFileUpload);
}
setList("PendingUploads", pUploads);
}
}
private List<ResumeFileUpload> getPendingFileUploads(){
List<ResumeFileUpload> pendingUploads = null;
String serializedObject = sharedPreferences.getString("PendingUploads", null);
if (serializedObject != null){
Gson gson = new Gson();
Type type = new TypeToken<List<ResumeFileUpload>>(){}.getType();
pendingUploads = gson.fromJson(serializedObject, type);
}
return pendingUploads;
}
private boolean deleteFile(File file){
try {
Uri uri = Uri.fromFile(file);
File fdelete = new File(uri.getPath());
if (fdelete.exists()) {
if (fdelete.delete()) {
System.out.println("file Deleted :" + uri.getPath());
return true;
} else {
System.out.println("file not Deleted :" + uri.getPath());
return false;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private File writeLocationDataToFile(StringBuilder data) {
String fileName = "Location.csv";
String colNamesLoc = "Timestamp,Latitude,Longitude,Accuracy,DirectionOfMotion,Speed\n";
File newFile = new File(locationDirectory, fileName);
try {
FileWriter fileWriter = new FileWriter(newFile);
fileWriter.append(colNamesLoc);
fileWriter.append(data.toString());
fileWriter.flush();
fileWriter.close();
Toast.makeText(this, "Saved To " + getExternalFilesDir(null)
+ "/" + fileName, Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
}
return newFile;
}
private void uploadToStorage(final File newFile){
Uri uri = Uri.fromFile(new File(newFile.getPath()));
StorageReference storageReference = filesStorage.getReference()
.child(userUUID + "/" + currentDataDirectory + "/" +
uri.getLastPathSegment());
UploadTask uploadTask = storageReference.putFile(uri);
uploadTask.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
Toast.makeText(LocationService.this,
newFile.getName() + " Upload Failed!", Toast.LENGTH_SHORT).show();
}
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
deleteFile(newFile);
Toast.makeText(LocationService.this,
newFile.getName() + " Upload Successful!", Toast.LENGTH_SHORT).show();
}
});
}
/*Location Listener*/
#Override
public void onLocationChanged(Location location) {
if (location != null) {
String lat = coordinatesFormat.format(location.getLatitude());
String lng = coordinatesFormat.format(location.getLongitude());
String bearing = "0.0";
if (location.getBearing() != 0.0)
bearing = String.valueOf(sotwFormatter.format(location.getBearing()));
int speed = (int) location.getSpeed();
String currentSpeed = String.valueOf(speed * 3.6f) + " KM/h";
String accuracy = "0.0";
if (location.getAccuracy() != 0.0)
accuracy = coordinatesFormat.format(location.getAccuracy());
String timeStamp = simpleDateFormat.format(new Date());
String locationData = timeStamp + ", " + lat +
", " + lng + ", " + accuracy + ", " + bearing + ", "
+ currentSpeed;
locationStringBuilder.append(locationData);
locationStringBuilder.append("\n");
}
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onProviderDisabled(String provider) {
}
/*SharedPreferences code*/
public <T> void setList(String key, List<T> list) {
Gson gson = new Gson();
String json = gson.toJson(list);
set(key, json);
}
public void set(String key, String value) {
sharedPrefEditor.putString(key, value);
sharedPrefEditor.commit();
}
}

How to fix: Wrong 1st argument type. Found: 'com.harrysoft.androidbluetoothserial.demoapp.CommunicateViewModel', required: 'android.content.Context'

I'm trying to to build an app which has to run in the background. So for this I'm using the ForegroundService, but when I write "this" in the CommunicateViewModel class, it gets underlined and show me:
"Cannot resolve constructor
'Intent(com.harrysoft.androidbluetoothserial.demoapp.CommunicateViewModel,
java.lang.Class<com.harrysoft.androidbluetoothserial.demoapp.TimeService>)'"
and at the next this:
"Wrong 1st argument type. Found:
'com.harrysoft.androidbluetoothserial.demoapp.CommunicateViewModel',
required: 'android.content.Context' less... Inspection info:
startForegroundService (android.content.Context, Intent) in
ContextCompat cannot be applied to
(com.harrysoft.androidbluetoothserial.demoapp.CommunicateViewModel,
Intent)  "
How can I solve this problem?
CommunicateViewModel:
package com.harrysoft.androidbluetoothserial.demoapp;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.content.Intent;
import android.os.CountDownTimer;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.harrysoft.androidbluetoothserial.BluetoothManager;
import com.harrysoft.androidbluetoothserial.SimpleBluetoothDeviceInterface;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class CommunicateViewModel extends AndroidViewModel {
// A CompositeDisposable that keeps track of all of our asynchronous tasks
private CompositeDisposable compositeDisposable = new CompositeDisposable();
// Our BluetoothManager!
private BluetoothManager bluetoothManager;
// Our Bluetooth Device! When disconnected it is null, so make sure we know that we need to deal with it potentially being null
#Nullable
private SimpleBluetoothDeviceInterface deviceInterface;
// The messages feed that the activity sees
private MutableLiveData<String> messagesData = new MutableLiveData<>();
// The connection status that the activity sees
private MutableLiveData<ConnectionStatus> connectionStatusData = new MutableLiveData<>();
// The device name that the activity sees
private MutableLiveData<String> deviceNameData = new MutableLiveData<>();
// The message in the message box that the activity sees
private MutableLiveData<String> messageData = new MutableLiveData<>();
// Our modifiable record of the conversation
private StringBuilder messages = new StringBuilder();
// Our configuration
private String deviceName;
private String mac;
// A variable to help us not double-connect
private boolean connectionAttemptedOrMade = false;
// A variable to help us not setup twice
private boolean viewModelSetup = false;
// Called by the system, this is just a constructor that matches AndroidViewModel.
public CommunicateViewModel(#NonNull Application application) {
super(application);
}
// Called in the activity's onCreate(). Checks if it has been called before, and if not, sets up the data.
// Returns true if everything went okay, or false if there was an error and therefore the activity should finish.
public boolean setupViewModel(String deviceName, String mac) {
// Check we haven't already been called
if (!viewModelSetup) {
viewModelSetup = true;
// Setup our BluetoothManager
bluetoothManager = BluetoothManager.getInstance();
if (bluetoothManager == null) {
// Bluetooth unavailable on this device :( tell the user
toast(R.string.bluetooth_unavailable);
// Tell the activity there was an error and to close
return false;
}
// Remember the configuration
this.deviceName = deviceName;
this.mac = mac;
// Tell the activity the device name so it can set the title
deviceNameData.postValue(deviceName);
// Tell the activity we are disconnected.
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}
// If we got this far, nothing went wrong, so return true
return true;
}
// Called when the user presses the connect button
public void toconnect(){
Intent serviceIntent = new Intent(this, TimeService.class);
serviceIntent.putExtra("inputExtra", "this can be set invissable");
ContextCompat.startForegroundService(this, serviceIntent);
connect();
}
public void connect() {
// Check we are not already connecting or connected
if (!connectionAttemptedOrMade) {
// Connect asynchronously
compositeDisposable.add(bluetoothManager.openSerialDevice(mac)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(device -> onConnected(device.toSimpleDeviceInterface()), t -> {
toast(R.string.connection_failed);
connectionAttemptedOrMade = false;
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}));
// Remember that we made a connection attempt.
connectionAttemptedOrMade = true;
// Tell the activity that we are connecting.
connectionStatusData.postValue(ConnectionStatus.CONNECTING);
}
}
// Called when the user presses the disconnect button
public void disconnect() {
// Check we were connected
if (connectionAttemptedOrMade && deviceInterface != null) {
connectionAttemptedOrMade = false;
// Use the library to close the connection
bluetoothManager.closeDevice(deviceInterface);
// Set it to null so no one tries to use it
deviceInterface = null;
// Tell the activity we are disconnected
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}
}
// Called once the library connects a bluetooth device
private void onConnected(SimpleBluetoothDeviceInterface deviceInterface) {
this.deviceInterface = deviceInterface;
if (this.deviceInterface != null) {
// We have a device! Tell the activity we are connected.
connectionStatusData.postValue(ConnectionStatus.CONNECTED);
// Setup the listeners for the interface
this.deviceInterface.setListeners(this::onMessageReceived, this::onMessageSent, t -> toast(R.string.message_send_error));
// Tell the user we are connected.
toast(R.string.connected);
// Reset the conversation
messages = new StringBuilder();
messagesData.postValue(messages.toString());
} else {
// deviceInterface was null, so the connection failed
toast(R.string.connection_failed);
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}
getCurrentTime();
}
// Adds a received message to the conversation
private void onMessageReceived(String message) {
messages.append(deviceName).append(": ").append(message).append('\n');
messagesData.postValue(messages.toString());
}
// Adds a sent message to the conversation
private void onMessageSent(String message) {
// Add it to the conversation
messages.append(getApplication().getString(R.string.you_sent)).append(": ").append(message).append('\n');
messagesData.postValue(messages.toString());
// Reset the message box
messageData.postValue("");
}
// Send a message
public void sendMessage(String message) {
new CountDownTimer(1000, 1000) {
public void onTick(long millisUntilFinished) {
if (deviceInterface != null && !TextUtils.isEmpty(message)) {
Log.i("info", "sendMessage: send");
}
deviceInterface.sendMessage(message);
}
public void onFinish() {
disconnect();
timer();
}
}.start();
// Check we have a connected device and the message is not empty, then send the message -----------------
}
//---------------------------------------------------------------------------MY Code
//timer
public void timer(){
new CountDownTimer(28000, 1000) {
public void onTick(long millisUntilFinished) {
Log.i("INFO", "onTick: ");
}
public void onFinish() {
connect();
}
}.start();
}
// Get Time
public void getCurrentTime() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat mdformat = new SimpleDateFormat("HHmm");
SimpleDateFormat seconds = new SimpleDateFormat("ss");
String strSec = seconds.format(calendar.getTime());
String strDate = "Time" + mdformat.format(calendar.getTime());
//if ((strSec == "29") || (strSec == "59")) {
sendMessage(strDate);
//}
}
// Called when the activity finishes - clear up after ourselves.
#Override
protected void onCleared() {
// Dispose any asynchronous operations that are running
compositeDisposable.dispose();
// Shutdown bluetooth connections
bluetoothManager.close();
}
// Helper method to create toast messages.
private void toast(#StringRes int messageResource) { Toast.makeText(getApplication(), messageResource, Toast.LENGTH_LONG).show(); }
// Getter method for the activity to use.
public LiveData<String> getMessages() { return messagesData; }
// Getter method for the activity to use.
public LiveData<ConnectionStatus> getConnectionStatus() { return connectionStatusData; }
// Getter method for the activity to use.
public LiveData<String> getDeviceName() { return deviceNameData; }
// Getter method for the activity to use.
public LiveData<String> getMessage() { return messageData; }
// An enum that is passed to the activity to indicate the current connection status
enum ConnectionStatus {
DISCONNECTED,
CONNECTING,
CONNECTED
}
}
TimeService:
package com.harrysoft.androidbluetoothserial.demoapp;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.*;
import io.reactivex.annotations.Nullable;
import static com.harrysoft.androidbluetoothserial.demoapp.App.CHANNEL_ID;
public class TimeService extends Service {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Example Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_android)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
//do heavy work on a background thread
//stopSelf();
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
You have to pass a Context as the first argument of the Intent constructor. You can retrieved it thanks to the application object.
Intent serviceIntent = new Intent(getApplication().getApplicationContext(), TimeService.class);

WiFi P2P Add Local Service failure

I am trying to develope basic server app on Android using WiFi P2P and Network Service Discovery.
Unfortunatlly I stucked because I am still not able to add local service to my wifi p2p manager. Every time I am tring to run app onFailure method execute. Cannot find the reason.
import android.app.Activity;
import android.content.Context;
import android.content.IntentFilter;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.ActionListener;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
public class ServerMainActivity extends Activity
{
private IntentFilter mIntentFilter;
private WifiP2pManager mManager;
private WifiP2pManager.Channel mChannel;
private ServerBroadcastReceiver mReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server_main);
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
mReceiver = new ServerBroadcastReceiver(mManager, mChannel, this);
startRegistration();
}
/** register the BroadcastReceiver with the intent values to be matched */
#Override
public void onResume() {
super.onResume();
mReceiver = new ServerBroadcastReceiver(mManager, mChannel, this);
registerReceiver(mReceiver, mIntentFilter);
}
#Override
public void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.server_main, menu);
return super.onCreateOptionsMenu(menu);
}
private void startRegistration() {
mManager.clearLocalServices(mChannel, new ActionListener() {
#Override
public void onFailure(int arg0) {
Log.d("Server Main Activity", "cannot clear local services " + arg0);
}
#Override
public void onSuccess() {
// TODO Auto-generated method stub
Log.d("Server Main Activity", "local services cleared.");
// Create a string map containing information about your service.
Map<String,String> record = new HashMap<String,String>();
record.put("listenport", "0");
record.put("buddyname", "John Doe" + (int) (Math.random() * 1000));
record.put("available", "visible");
// Service information. Pass it an instance name, service type
// _protocol._transportlayer , and the map containing
// information other devices will want once they connect to this one.
WifiP2pDnsSdServiceInfo serviceInfo =
WifiP2pDnsSdServiceInfo.newInstance("_SynchroMusic", "_tcp", record);
// Add the local service, sending the service info, network channel,
// and listener that will be used to indicate success or failure of
// the request.
mManager.addLocalService(mChannel, serviceInfo, new ActionListener() {
#Override
public void onSuccess() {
// Command successful! Code isn't necessarily needed here,
// Unless you want to update the UI or add logging statements.
Log.d("Server Main Activity", "local servica added.");
}
#Override
public void onFailure(int arg0) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
Log.d("Server Main Activity", "faliture" + arg0);
}
});
}
});
}
}
Most of this code is based on official android developer tutorials, so I guess it should work. Running cyanogenmod 11 (Android 4.4.2) on HTC One X.
Here is my BroadcastReceiver class if needed:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.util.Log;
public class ServerBroadcastReceiver extends BroadcastReceiver {
public ServerBroadcastReceiver() {
}
private WifiP2pManager mManager;
private WifiP2pManager.Channel mChannel;
private ServerMainActivity mActivity;
public ServerBroadcastReceiver(WifiP2pManager manager, Channel channel,
ServerMainActivity activity) {
super();
this.mManager = manager;
this.mChannel = channel;
this.mActivity = activity;
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// Determine if Wifi P2P mode is enabled or not, alert
// the Activity.
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
//activity.setIsWifiP2pEnabled(true);
Log.d("ServerBroadcast", "Wifi on");
} else {
//activity.setIsWifiP2pEnabled(false);
Log.d("ServerBroadcast", "Wifi off");
}
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// The peer list has changed! We should probably do something about
// that.
Log.d("ServerBroadcast", "peers Changed action");
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
// Connection state changed! We should probably do something about
// that.
Log.d("ServerBroadcast", "Con Changed");
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
// DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager()
// .findFragmentById(R.id.frag_list);
//fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra(
/// WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));
Log.d("ServerBroadcast", "Dev Changed");
}
//throw new UnsupportedOperationException("Not yet implemented " + action);
else throw new UnsupportedOperationException("Not yet implemented " + action);
}
}
When line
//throw new UnsupportedOperationException("Not yet implemented " + action);
is not commented it throws an error
Not yet implementedandroid.net.wifi.p2p.STATE_CHANGED
You must wait for WIFI_P2P_STATE_ENABLED and then register your service.

Show notification from background started service

So i'm using asmack library to listen for incoming xmpp packets. I've got the service implemented. It's started, bound, a method is run and then it's unbound. The service creates a PacketListener to get incoming messages, and displays a notification when a new message comes in. This all works while the app is the active activity, but doesn't work when it's fully closed (swiped away in the window list) and it's just the service running, the PacketListener doesn't fire. It fires as soon as the app is reopened though, firing off all notifications. I'm looking to get those notifications fired as they come in, instead of the service being suspended, if you will. The code for the service is here:
package com.jayseeofficial.xmpp.service;
import java.io.File;
import java.util.ArrayList;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.widget.Toast;
import com.jayseeofficial.xmpp.MainActivity;
import com.jayseeofficial.xmpp.Program;
import com.jayseeofficial.xmpp.R;
import com.jayseeofficial.xmpp.objects.Contact;
import com.jayseeofficial.xmpp.preferences.Preferences;
public class XMPPService extends Service {
private Connection conn = null;
private boolean loggedIn = false;
private final XMPPBinder binder = new XMPPBinder();
private Handler handler;
#Override
public IBinder onBind(Intent arg0) {
handler = new Handler(getMainLooper());
return binder;
}
public class XMPPBinder extends Binder {
public XMPPService getService() {
return XMPPService.this;
}
}
public void logIn() {
new LogInTask().execute();
}
public boolean isLoggedIn() {
return loggedIn;
}
private ConnectionConfiguration getConnectionConfiguration() {
ConnectionConfiguration config = new ConnectionConfiguration(
Preferences.Account.getServer(), Preferences.Account.getServerPort());
config.setSASLAuthenticationEnabled(true);
config.setCompressionEnabled(true);
config.setSecurityMode(SecurityMode.enabled);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
config.setTruststoreType("AndroidCAStore");
config.setKeystoreType("AndroidCAStore");
config.setTruststorePassword(null);
config.setTruststorePath(null);
} else {
config.setTruststoreType("BKS");
String path = System.getProperty("javax.net.ssl.trustStore");
if (path == null) {
path = System.getProperty("java.home") + File.separator + "etc" + File.separator
+ "security" + File.separator + "cacerts.bks";
}
config.setTruststorePath(path);
}
return config;
}
private void showNotification() {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setContentTitle("XMPP+").setContentText("running")
.setSmallIcon(R.drawable.speech_bubble);
Intent i = new Intent(this, MainActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(i);
// Assign the intent
mBuilder.setContentIntent(stackBuilder.getPendingIntent(9127,
PendingIntent.FLAG_UPDATE_CURRENT));
NotificationManager mgr = (NotificationManager) this
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification n = mBuilder.build();
n.flags |= Notification.FLAG_ONGOING_EVENT;
mgr.notify(1,n);
}
private void hideNotification() {
// TODO Implement hideNotification in XMPPService
}
private class LogInTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
try {
showNotification();
// Set up and log in
conn = new XMPPConnection(getConnectionConfiguration());
conn.connect();
conn.login(Preferences.Account.getUsername(), Preferences.Account.getPassword());
// Set up to receive messages
PacketFilter filter = new PacketTypeFilter(Message.class);
conn.addPacketListener(new XMPPMessagePacketListener(), filter);
conn.sendPacket(new Presence(Type.available));
final ArrayList<Contact> allContacts = new ArrayList<Contact>();
// Populate contact list
for (RosterEntry re : conn.getRoster().getEntries()) {
Log.d("Roster entry: ", re.getUser() + ": " + re.getName());
Contact c = new Contact();
if (re.getName() == null) {
c.setName(re.getUser());
} else {
c.setName(re.getName());
}
c.setAddress(re.getUser());
allContacts.add(c);
}
handler.post(new Runnable() {
#Override
public void run() {
Program.contacts.addAll(allContacts);
}
});
loggedIn = true;
} catch (XMPPException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void ignored) {
if (loggedIn) {
Toast.makeText(XMPPService.this, "Logged in successfully", Toast.LENGTH_LONG)
.show();
} else {
Toast.makeText(XMPPService.this, "Failed to log in", Toast.LENGTH_LONG).show();
}
}
}
private class XMPPMessagePacketListener implements PacketListener {
#Override
public void processPacket(Packet packet) {
Message m = (Message) packet;
if (m.getBody() != null) {
com.jayseeofficial.xmpp.objects.Message message = new com.jayseeofficial.xmpp.objects.Message();
message.setRecipient(Preferences.Account.getUsername() + "#"
+ Preferences.Account.getServer());
String fullSender = m.getFrom();
String address = fullSender.substring(0, fullSender.indexOf('/'));
message.setSender(address);
message.setContents(m.getBody());
Program.showMessageNotification(message, 9127);
}
}
}
}
and the code for the starting/binding the service is here:
public static void init() {
if (!initialized) {
// Call other portions of init code first
SmackAndroid.init(context);
Preferences.init();
// Set up our variables
contacts = GlazedLists.threadSafeList(new BasicEventList<Contact>());
// Start up the xmpp connection
Intent ixmpp = new Intent(context, XMPPService.class);
context.startService(ixmpp);
context.bindService(ixmpp, xmppConnection, Context.BIND_AUTO_CREATE);
initialized = true;
}
}
So as I said, i want to receive notifications from the service running in the background when the foreground activity is closed. Is there any way of doing this? Maybe have it in it's own process? Something else?
EDIT: the ongoing notification sticks around and works when clicked, in case you're wondering
use startforeground method
http://developer.android.com/reference/android/app/Service.html#startForeground%28int,%20android.app.Notification%29
in onCreate of service
public void onCreate() {
startForeground (int id, Notification notification)
}

Browser.BOOKMARKS_URI is no more?

I am trying to add content observer for AOSP Browser's history provider i.e., with Uri Browser.BOOKMARKS_URI. If I attach an observer, the onChange(boolean) gets called on my ICS running my Samsung GT-S7562, my JB running Samsung GT-I8262 or HTC Desire X but I don't get any notifications for AOSP Browser provider on my friend's Android 4.3 running Samsung SM-G7102. However for Google Chrome's provider, i.e., residing at content://com.android.chrome.browser/bookmarks, I get notified for changes on all android releases. Also if I query the AOSP Browser database & scan for entries, it only returns those opened in Chrome (I mean it returns Chrome's history in Browser.BOOKMARKS_URI provider).
Below is the sample service I tested for reference which is not actually required (don't mind but I think everything is quite clear above already):
package vpz.hp;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class Watcher extends Service {
private Provider Browser;
private Provider Chrome;
#Override public IBinder onBind(Intent intent) {
return null;
}
#Override public void onDestroy() {
if (this.Browser != null)
this.Browser.Stop();
if (this.Chrome != null)
this.Chrome.Stop();
super.onDestroy();
}
public final int onStartCommand(Intent Intention, int StartId, int Flags) {
this.Browser = new Provider(super.getApplicationContext(), android.provider.Browser.BOOKMARKS_URI);
this.Browser.Start();
if (Search(getApplicationContext(), "com.android.chrome")) {
this.Chrome = new Provider(super.getApplicationContext(), Uri.parse("content://com.android.chrome.browser/bookmarks"));
this.Chrome.Start();
}
return START_STICKY;
}
private static final boolean Search(Context Sender, String Package) {
try {
return Sender.getPackageManager().getPackageInfo(Package, PackageManager.GET_ACTIVITIES) != null;
} catch (NameNotFoundException Error) {
return false;
}
}
private static class Provider extends ContentObserver {
private Context Sender;
private Uri URI;
public Provider(Context Sender, Uri URI) {
super(new Handler());
this.Sender = Sender;
this.URI = URI;
}
#Override public void onChange(boolean Self) {
String Message = this.URI.toString() + " onChange(" + Self + ")";
Toast.makeText(this.Sender, Message, Toast.LENGTH_SHORT).show();
Log.e("VPZ", Message);
}
public final void Start() {
this.Sender.getContentResolver().registerContentObserver(this.URI, true, this);
}
public final void Stop() {
this.Sender.getContentResolver().unregisterContentObserver(this);
}
}
}
I have added below permission(s) already in the manifest:
<uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
Is content observer on AOSP Browser has been deprecated?

Categories

Resources