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)
}
Related
-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();
}
}
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);
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
I use a service that automatically sends me push notifications and I want to automatically press the confirm option of these notifications. I'm able to capture the notification using the code below:
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.support.v4.content.LocalBroadcastManager;
import java.io.ByteArrayOutputStream;
public class NotificationHandler extends NotificationListenerService {
Context context;
#Override
public void onCreate() {
Log.e("STATE","HERE AGAIN");
super.onCreate();
context = getApplicationContext();
}
#Override
public void onNotificationPosted(StatusBarNotification sbn) {
String pack = sbn.getPackageName();
String ticker = "";
if (sbn.getNotification().tickerText != null) {
ticker = sbn.getNotification().tickerText.toString();
}
Bundle extras = sbn.getNotification().extras;
// try {
// //sbn.getNotification().contentIntent.send();
// } catch (PendingIntent.CanceledException e) {
// e.printStackTrace();
// }
String title = extras.getString("android.title");
String text = extras.getCharSequence("android.text").toString();
int id1 = extras.getInt(Notification.EXTRA_SMALL_ICON);
Bitmap id = sbn.getNotification().largeIcon;
Log.i("NEW", "----------");
Log.i("Package", pack);
Log.i("Ticker", ticker);
Log.i("Title", title);
Log.i("Text", text);
sbn.getNotification().contentIntent.sen
for (String key : bundle.keySet()) {
Log.i("Intent", key);
}
Log.i("NEW", "----------");
Intent msgrcv = new Intent("Msg");
msgrcv.putExtra("package", pack);
msgrcv.putExtra("ticker", ticker);
msgrcv.putExtra("title", title);
msgrcv.putExtra("text", text);
if (id != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
id.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
msgrcv.putExtra("icon", byteArray);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(msgrcv);
}
#Override
public void onNotificationRemoved(StatusBarNotification sbn) {
Log.i("Msg", "Notification Removed");
}
}
I'm having trouble figuring out how to capture the custom option for confirming the notification and then clicking it. The confirm/deny buttons look similar to the buttons on this notification. Any ideas how I can capture this?
You can get the list of actions (if it exists) from the notification by using the following code
Notification n = sbn.getNotification();
for(Action action : n.actions){
String title = action.title.toString;
...
}
You can create a button with the action-title and then write the code onClick() to execute the action corresponding to the notifications.
To be more precise - you can call
action.actionIntent.send(); // or another variation of send()
from your onClick() on the button to execute the action corresponding to the pendingIntent supplied by the app that sent the notification.
I am basically working with Android Application , i have requirement to restrict to open others apps while my app is running, Means My app is running it will put some locks on other application while i am accessing the others app, Kind of Application Lock. I am trying but could not find good solution. If any buddy have some idea or link or sample code please share to me.
Basically you want Kiosk Mode .
Try this:
Home button press:
<category android:name="android.intent.category.HOME" /> //add in manifest
What id does: whenever you press the home button, all the applications installed in your phone which have category.HOME category in intent-filter in their AndroidManifest.xml will be listed .
then handle Long press Home Button:
#Override
protected void onPause() {
super.onPause();
ActivityManager activityManager = (ActivityManager) getApplicationContext()
.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.moveTaskToFront(getTaskId(), 0);
}
for this add this line in manifest :
<uses-permission android:name="android.permission.REORDER_TASKS" />
3.handle BackButton :
#Override
public void onBackPressed() {
// Stop user to exit
// super.onBackPressed();
}
You can also try this for Kiosk Mode:
Read this document
import android.app.ActivityManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.provider.SyncStateContract;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
public class HeartBeat extends Service {
private static final String TAG = HeartBeat.class.getSimpleName();
public Timer TIMER;
// private static Set<AccessGranted> mAccessGrantedList = new HashSet<AccessGranted>();
private Set<String> mLockedApps = new HashSet<String>();
private long lastModified = 0;
private BroadcastReceiver mScreenStateReceiver;
// private BroadcastReceiver mAccessGrantedReceiver;
private File mLockedAppsFile;
ArrayList<String> packagezList;
SharedPreferences sharedPrefs;
Map<String, ?> allEntries;
SharedPreferences sharedPrefsapp;
String prefix;
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
startService(new Intent(this, HeartBeat.class));
Log.i("LocalService", "Received start id " + startId + ": " + intent);
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
if (TIMER == null) {
TIMER = new Timer(true);
TIMER.scheduleAtFixedRate(new LockAppsTimerTask(), 1000, 250);
mScreenStateReceiver = new BroadcastReceiver() {
private boolean screenOff;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
screenOff = true;
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
screenOff = false;
}
if (screenOff) {
//Log.i(TAG, "Cancel Timer");
TIMER.cancel();
} else {
// Log.i(TAG, "Restart Timer");
TIMER = new Timer(true);
TIMER.scheduleAtFixedRate(new LockAppsTimerTask(), 1000, 250);
}
}
};
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mScreenStateReceiver, filter);
}
// this.stopSelf();
//startforeground goes here
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
startService(new Intent(this, HeartBeat.class));
}
private class LockAppsTimerTask extends TimerTask {
#Override
public void run() {
sharedPrefs = getApplicationContext().getSharedPreferences(getApplicationContext().getPackageName(), Context.MODE_PRIVATE);
sharedPrefsapp = getApplicationContext().getSharedPreferences("appdb", Context.MODE_PRIVATE);
allEntries= null;
allEntries = sharedPrefsapp.getAll();
Set keySet = allEntries.keySet();
Iterator<String> keySetIter = keySet .iterator();
while (keySetIter.hasNext()) {
String keyEntry= keySetIter.next();
Log.e("Shared Vaues",keyEntry);
}
//prefix = "m";
packagezList= null;
packagezList = new ArrayList<String>();
for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
packagezList.add(entry.getKey());
Log.e("right key: ", entry.getKey().toString() + "right value: " + entry.getValue().toString());
}
/* for (Map.Entry<String, ?> entry : allEntries.entrySet())
{
//Check if the package name starts with the prefix.
if (entry.getKey().startsWith(prefix)) {
//Add JUST the package name (trim off the prefix).
packagezList.add(entry.getKey().substring(prefix.length()));
packagezList.add(entry.getKey());
}
}*/
for(Object object: packagezList){
Log.e("YO!", (String) object);
}
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
try {
//List<RecentTaskInfo> recentTasks = activityManager.getRecentTasks(1, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> RunningTask = mActivityManager
.getRunningTasks(1);
ActivityManager.RunningTaskInfo ar = RunningTask.get(0);
String activityOnTop = ar.topActivity.getPackageName();
Log.e("activity on Top", "" + activityOnTop);
Log.e(" My package name", "" + getApplicationContext().getPackageName());
//for (Object data : newArrayList) {
for(Object object: packagezList){
// Provide the packagename(s) of apps here, you want to show password activity
if (!activityOnTop.contains(getApplicationContext().getPackageName()))
// if(!activityOnTop.contains(getApplicationContext().getPackageName()))
{ // you have to make this check even better
Intent i = new Intent(getApplicationContext(), LockScreenActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
i.putExtra( "", "");
startActivity(i);
}
}
} catch (Exception e) {
Log.e("Foreground App", e.getMessage(), e);
}
}
}