I am trying to develop a simple app which will record the user's activity (accelerometer values) on a txt or csv file.
My app consists of 2 java classes MainActivity and MyService. The MainActivity includes two buttons to start and stop the service and the required permissions. However, the onSensorChanged normally logs for the first 3 minutes after locking the phone (turning off the screen) and then stops logging. As soon as I open the screen the logd starts working again. Same behavior for the records in txt file. I found out that the app seems to be working excellent if I override the battery optimizations. However, I need the phone to also be working in doze mode to save some battery drain. Has anyone else had a similar issue?
Here is my Foreground Service:
public class MyService extends Service implements SensorEventListener {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
private static final String TAG = "MyService";
private Messenger messageHandler;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private Context mContext;
private PowerManager.WakeLock wakeLock = null;
//private HandlerThread mSensorThread;
//private Handler mHandler;
#SuppressLint("MissingPermission")
#Override
public void onCreate() {
super.onCreate();
Log.v("shake service startup", "registering for shake");
mContext = getApplicationContext();
//mHandler = new Handler(mSensorThread.getLooper());
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Wakelock :: TAG");
// Register our receiver for the ACTION_SCREEN_OFF action. This will make our receiver
// code be called whenever the phone enters standby mode.
//IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
//registerReceiver(mReceiver, filter);
}
/*
// BroadcastReceiver for handling ACTION_SCREEN_OFF.
public BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Check action just to be on the safe side.
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Log.v("shake mediator screen off","trying re-registration");
// Unregisters the listener and registers it again.
mSensorManager.unregisterListener(MyService.this);
mSensorManager.registerListener(MyService.this, mAccelerometer,
SensorManager.SENSOR_DELAY_NORMAL, mHandler);
}
}
};
*/
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
return START_STICKY;
//stopSelf();
//return START_NOT_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
#Override
public void onDestroy() {
super.onDestroy();
if(mSensorManager != null){
//noinspection MissingPermission
mSensorManager.unregisterListener(MyService.this);
}
//unregisterReceiver(mReceiver);
try{
wakeLock.release();//always release before acquiring for safety just in case
}
catch(Exception e){
//probably already released
Log.e(TAG, e.getMessage());
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onSensorChanged(SensorEvent event) {
Log.d(TAG, "onSensorChanged: " + event.timestamp + " " + event.values[0] + " " + event.values[1] + " " + event.values[2]);
recordAccelValues(String.valueOf(event.timestamp), event.values[0] + " " + event.values[1] + " " + event.values[2]);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
private void recordAccelValues(String time, String accel_values) {
String record = time + " " + accel_values + "\n";
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
if (Build.VERSION.SDK_INT >= 23) {
File sdcard = Environment.getExternalStorageDirectory();
File dir = new File(sdcard.getAbsolutePath() + "/text/");
if(!dir.exists()) {
dir.mkdir();
}
File file = new File(dir, "dailyRecordsAccel.dat");
FileOutputStream os = null;
try {
os = new FileOutputStream(file, true);
os.write(record.getBytes());
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
As you can see in the code I tried several recommendations from other questions I found, like wakelock and Intent.ACTION_SCREEN_OFF but they didn't seem to work.
Accelerometer stops delivering samples when the screen is off on Droid/Nexus One even with a WakeLock
The only one way to keep alive your service it's to avoid battery optimization for your application. Which is possible within two ways below. Please note! In both cases you will keep device alive, which means that device will never sleep (enter doze states obviously). It's whole point of device sleep, to avoid pending work of background services like yours.
Using Android WakeLocks, For ex. below.
val wakeLock: PowerManager.WakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager. FULL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire()
}
}
Changing setting to avoid battery optimization for specific app. As you mentioned in your question.
It is normal behavior. Android delete all proceses to save power. If you want do a job then ask user to keep screen on, else you can use AlarmManager only to call a Service (Intent, Reciver) do "small job" and go to sleep again.
Related
I want to send an sms to all saved contact only by shaking the phone. But i don't know how to link the database of saved contact and the code of sendind sms. Please help me with that.
This is the code for sending an sms by shaking the phone :
public class SensorService extends Service {
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private ShakeDetector mShakeDetector;
public SensorService() {
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
#Override
public void onCreate() {
super.onCreate();
// start the foreground service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startMyOwnForeground();
else
startForeground(1, new Notification());
// ShakeDetector initialization
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mShakeDetector = new ShakeDetector();
mShakeDetector.setOnShakeListener(new ShakeDetector.OnShakeListener() {
#SuppressLint("MissingPermission")
#Override
public void onShake(int count) {
// check if the user has shacked
// the phone for 3 time in a row
if (count == 3) {
// vibrate the phone
vibrate();
// create FusedLocationProviderClient to get the user location
FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(getApplicationContext());
// register the listener
mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
// method to vibrate the phone
public void vibrate() {
final Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
VibrationEffect vibEff;
// Android Q and above have some predefined vibrating patterns
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibEff = VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK);
vibrator.cancel();
vibrator.vibrate(vibEff);
} else {
vibrator.vibrate(500);
}
}
// For Build versions higher than Android Oreo, we launch
// a foreground service in a different way. This is due to the newly
// implemented strict notification rules, which require us to identify
// our own notification channel in order to view them correctly.
#RequiresApi(Build.VERSION_CODES.O)
private void startMyOwnForeground() {
String NOTIFICATION_CHANNEL_ID = "example.permanence";
String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_MIN);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle("You are protected.")
.setContentText("We are there for you")
// this is important, otherwise the notification will show the way
// you want i.e. it will show some default notification
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, notification);
}
#Override
public void onDestroy() {
// create an Intent to call the Broadcast receiver
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("restartservice");
broadcastIntent.setClass(this, ReactivateService.class);
this.sendBroadcast(broadcastIntent);
super.onDestroy();
}
}
And this is the code for saving contact number :
public void storeInDB(View v) {
Toast.makeText(getApplicationContext(), "Sauvegarde commencée",Toast.LENGTH_LONG).show();
name = (EditText) this.findViewById(R.id.textView1);
number = (EditText) this.findViewById(R.id.textView2);
String str_name=name.getText().toString();
String str_number=number.getText().toString();
SQLiteDatabase db;
db=openOrCreateDatabase("NumDB", Context.MODE_PRIVATE, null);
db.execSQL("CREATE TABLE IF NOT EXISTS details(name VARCHAR,number VARCHAR);");
Cursor c=db.rawQuery("SELECT * FROM details", null);
if(c.getCount()<3)
{
db.execSQL("INSERT INTO details VALUES('"+str_name+"','"+str_number+"');");
Toast.makeText(getApplicationContext(), "Enregistré avec succès",Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(getApplicationContext(),"Nombre maximum limité atteint",Toast.LENGTH_SHORT).show();
}
db.close();
}
Honestly i dont' know if you can send SMS to a list of contacts.
If you read this documentation:
https://www.tutorialspoint.com/android/android_sending_sms.htm
you'll read that the APIs receive MAX a telephone number.
I think this implementation is also due to a protection that android creates for avoiding to send a lot of sms togheter.
Assuming you have saved 1500 contacts on your device, your APP have to send 1500 sms???
It's strange....
But of couse you can create a loop and send a SMS a time...
I suggest you to:
detected shake gesture
retrieve all contacts saved (https://www.jackrutorial.com/2018/06/how-to-get-contacts-in-android-display-into-listview.html)
send a sms a time.
I am making a android app which will allow the user to press a button and show a notification with a timer counting down for a certain amount of time. Although I have made the notification persistent so it cannot be dismissed, when the app closes the notification gets destroyed.
Is there any way to allow a notification to continue running once the app is closed and not get destroyed.
Here is the code for starting my notification and timer:
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "notifyLemubit")
.setSmallIcon(holder.img_timer.getImageAlpha())
.setContentTitle("Timer Running")
.setContentText("Time Until Your " + timer.getTimer_name() + " Tree has Fully Grown: " + timer.getTimer_duration_s())
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
final NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
notificationManagerCompat.notify(timer.getTimer_id(), builder.build());
new CountDownTimer(10 * ONE_SECOND, ONE_SECOND) {
#Override
public void onTick(long ms_until_done) {
builder.setContentText("Time Until Your " + timer.getTimer_name() + " Tree has Fully Grown: " + ms_until_done / ONE_SECOND);
notificationManagerCompat.notify(timer.getTimer_id(), builder.build());
}
#Override
public void onFinish() {
notificationManagerCompat.cancel(timer.getTimer_id());
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "notifyLemubit")
.setSmallIcon(holder.img_timer.getImageAlpha())
.setContentTitle("Timer Finished")
.setContentText("Your " + timer.getTimer_name() + " is Fully Grown!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
final NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
notificationManagerCompat.notify(timer.getTimer_id(), builder.build());
}
}.start();
Any help is appreciated, Thanks
The only way by far i know is using a Foreground service by extending the Service or IntentService class
And inside your activity or adapter use this to start the service
context.startService(Intent(context,PersistentNotificationService.class))
For the service here use this one
public class PersistentNotificationService extends Service {
private final static int ONE_SECOND = 1000;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "notifyLemubit")
.setSmallIcon(R.drawable.img_timer)
.setContentTitle("Timer Running")
.setContentText("Your title goes here")
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
new CountDownTimer(10 * ONE_SECOND, ONE_SECOND) {
#Override
public void onTick(long ms_until_done) {
// Whatever code you want here
}
#Override
public void onFinish() {
// To cancel , just close the service
stopForeground(true);
stopSelf();
}
}.start();
startForeground(2342, builder.build());
return START_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
As my title says, I have a FOREGROUNDSERVICE which is stopped after around 3 minutes (I guess doze mode), and stops acquiring Accelerometry. I do use a PARTIAL_WAKE_LOCK and return START_STICKY... but I don't undestand the reason why this happens. I do really need Acc to not stop recording and log the values on a file... The way to stop the acquisition would be a battery check, a free space check or a button click on the app.
I tested the implementation below application on an Android 6.0.1 (API 23), and worked like charm. When testing the same application on a Xiaomi Pocophone Android 10 device (API 29), after 3 minutes the doze mode (I guess) kicks in and stops the acc acqquisition...
Any ideas of why? In theory a foreground service should keep the CPU running, and with the partial wake_lock I should ensure it keeps running and acquiring ACC...
Here there is my service:
public class AccelerometryService extends Service implements SensorEventListener {
// WakeLock variable to keep CPU running obtaining Acc
private PowerManager.WakeLock wakeLock;
//Notification Variable to show that Acc has started
private NotificationChannel notChannel = null;
private NotificationManager nMservice = null;
Intent notifIntent = null;
PendingIntent pendingIntent = null;
private int NOTIFICATION = 112020592;
private String channelID = "AccRecordService";
//Accelerometry variables
private SensorManager sensorManager = null;
private Sensor sensor = null;
//File Writting
private long SystemTime = 0;
private File rawDataFolder = null;
private String FileName = null;
private File rawDataTxt = null;
private FileOutputStream fileOutputStream = null;
//Acc data sharing
private Observable oString = null;
public static final String ACCNOTIFICATION = "com.example.android.sleeppos";
#SuppressLint("WakelockTimeout")
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Toast.makeText(this, "Logging service started new", Toast.LENGTH_SHORT).show();
//Acquire wake lock
PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WLTAG:MyWakelockTag");
wakeLock.acquire();
//Display notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(this.channelID, "AccBackgroundService");
}
this.notifIntent = new Intent(this, MainActivity.class);
this.pendingIntent = PendingIntent.getActivity(this, 0, this.notifIntent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder builder = new Notification.Builder(this, this.channelID)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("SleepPos app")
.setContentText("Acc has started being recorded")
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(this.pendingIntent)
.setOngoing(true);
startForeground(this.NOTIFICATION, builder.build());
} else {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, this.channelID)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("SleepPos app")
.setContentText("Acc has started being recorded")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(this.pendingIntent)
.setOngoing(true);
startForeground(this.NOTIFICATION, builder.build());
}
// register Acc listener
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
//Parse variables given from the intent
this.rawDataFolder = (File) intent.getExtras().get("DataFolder");
this.FileName = intent.getStringExtra("FileName");
this.SystemTime = intent.getLongExtra("SystemTime", 0);
this.rawDataTxt = new File(this.rawDataFolder, this.FileName);
try {
this.rawDataTxt.createNewFile();
this.fileOutputStream = new FileOutputStream(this.rawDataTxt);
this.fileOutputStream.write(("Time_elapsed_(nanoseconds);X-Axis;Y-Axis;Z-Axis" + System.lineSeparator()).getBytes());
} catch (IOException ioe) {
Log.v("ErrorFile", "Error while creating empty file:");
}
return START_STICKY;
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
//unregister sensor listener
sensorManager.unregisterListener(this);
//cancel notification
stopForeground(true);
//Close the file being used to register Acc
try {
this.fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
//release wakeLock
wakeLock.release();
Log.v("DEBUG_SLEEPPOS","onDestroyDone");
//Stop Service
stopSelf();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onSensorChanged(SensorEvent event) {
// grab the values and timestamp -- off the main thread
long dataTime = event.timestamp;
// Call the file handle to write
try {
String delimiter = ";";
fileOutputStream.write(((dataTime - AccelerometryService.this.SystemTime) + delimiter +
event.values[0] + delimiter +
event.values[1] + delimiter +
event.values[2] +
System.lineSeparator()).getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#RequiresApi(Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName) {
this.notChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
this.notChannel.enableLights(true);
this.notChannel.setLightColor(Color.RED);
this.notChannel.enableVibration(true);
long[] Vibrations = {100, 200, 300, 400, 500, 400, 300, 200, 400};
this.notChannel.setVibrationPattern(Vibrations);
this.notChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
this.notChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
this.nMservice = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
this.nMservice.createNotificationChannel(this.notChannel);
}
}
UPDATE:
I tested the command adb shell dumpsys deviceidle force-idle on the terminal to force idle state and Acc was still acquiring... so no idea why it stops after 3 minutes on my XIAOMI Pocophone F1 Android 10 (API 29). I don't know if the doze mode can be forced with any other command...
I create an application that implements simple geolocation problem: once in, say, 20 minutes, it takes a LatLng coordinate.
For this purpose, from MainActivity, I initiate BroadcastReceiver to work. It instantiates LocationManager to find coordinates, which needs application context.
The problem is: due to memory reasons, Android OS can kill my MainActivity, so, BroadcastReceiver, firing next time, catches null pointer exception, referring to application's context.
Ideas:
I. I could restart the activity inside BroadcastReceiver like this:
#Override
public void onReceive(Context context, Intent intent) {
//start activity
Intent i = new Intent();
i.setClassName("com.test", "com.test.MainActivity");
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
but context is null due to killed Activity.
II. Maybe, the paradigm, presented in my solution, too cumbersome?
Maybe here is graceful solution, I even didn't think of?
Well, my code snippet:
public class MainActivity extends AppCompatActivity {
// ...
public void onStartSessionButtonClicked (View view) {
Intent alarmRecIntent = new Intent(this, AlarmReceiver.class);
PendingIntent mAlarmIntent = PendingIntent.getBroadcast(this, 0, alarmRecIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager)this.getSystemService(ALARM_SERVICE);
alarmManager.cancel(mAlarmIntent);
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + 1000,
RConstants.locUpdateInterval,
mAlarmIntent
);
}
}
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive (Context context, Intent intent) {
Context mContext = context;
try {
LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
if (!locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
throw new Exception("network provider is not enabled");
}
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
MIN_TIME_BW_UPDTS,
MIN_DIST_CHANGE_FOR_UPDTS,
locationListener
);
}
catch (Exception e) { /* catch codeblock */ }
}
I really confused myself supposing that context comes NULL to BroadcastReceiver's onReceive() method, when MainActivity is killed by Android OS. The other one pointer came NULL in my code, so it caused exception.
If you interested, please, check out the code snippet below for details:
public class AlarmReceiver extends BroadcastReceiver {
LocationManager mlocationManager = null;
private CountDownTimer cdTimer = null;
private Context mContext;
final private static long MIN_TIME_BW_UPDTS = 1000 * 1; // ms
final private static float MIN_DIST_CHANGE_FOR_UPDTS = 5.0f; // meters
#Override
public void onReceive (Context context, Intent intent) {
mContext = context; // < -- 1 -- >
try {
mlocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
if (!mlocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
throw new Exception("network provider is not enabled");
}
// < -- 2 -- >
cdTimer = new CountDownTimer(RConstants.locUpdateTimeoutUsed, RConstants.locUpdateTimeoutUsed) {
/// other required methods overridden here ...
#Override
public void onFinish() {
mlocationManager.removeUpdates(locationListener);
mlocationManager = null; // < -- 4 -- >
Log.d("ALARM", getCurrentDateTime() + ", timeout");
}
}.start();
mlocationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
MIN_TIME_BW_UPDTS,
MIN_DIST_CHANGE_FOR_UPDTS,
locationListener
);
}
catch (Exception e) {
Log.d("ALARM", getCurrentDateTime() + ", exc: " + e.getMessage()); < -- 5 -- >
}
}
private LocationListener locationListener = new LocationListener() {
/// other required methods overridden here ...
#Override
public void onLocationChanged(Location location) {
cdTimer.cancel();
cdTimer = null;
mlocationManager.removeUpdates(locationListener); // < -- 6 -- >
mlocationManager = null;
String latLng = " {" + location.getLatitude() + ":" + location.getLongitude() + "}";
Log.d("ALARM", getCurrentDateTime() + latLng);
}
}
}
So, context (mark "< -- 1 -- >"), coming to onReceive is not null.
In note "< -- 2 -- >" I instantiate CountDownTimer with constant RConstants.locUpdateTimeoutUsed. The problem is, it defined and assigned outside of AlarmReceiver, so, when my Activity is killed, RConstants.locUpdateTimeoutUsed comes declared, but not defined. So, CountDownTimer constructs with (0, 0) values. That's why it's instance - cdTimer, fires onFinish() hardly being started. Here it cancels update for mLocationManager and sets it to NULL. But before mLocationManager completely stopped, method onLocationChanged manages to work, so, in line < -- 6 -- > caughts exception.
I need to keep broadcast receiver running all the time after app has been started.
Here is the code that registers this receiver in the application
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver mReceiver = new ScreenEventsReceiver();
registerReceiver(mReceiver, filter);
And code for receiver
public class ScreenEventsReceiver extends BroadcastReceiver {
public static boolean wasScreenOn = true;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
wasScreenOn = false;
Log.d("ScreenEventReceiver", "ON");
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
wasScreenOn = true;
Log.d("ScreenEventReceiver", "ON");
}
}
}
You can use a service
In main app start/stop the service
Intent service = new Intent(context, MyService.class);
context.startService(service);
...
Intent service = new Intent(context, MyService.class);
context.stopService(service);
service
public class MyService extends Service
{
private static BroadcastReceiver m_ScreenOffReceiver;
#Override
public IBinder onBind(Intent arg0)
{
return null;
}
#Override
public void onCreate()
{
registerScreenOffReceiver();
}
#Override
public void onDestroy()
{
unregisterReceiver(m_ScreenOffReceiver);
m_ScreenOffReceiver = null;
}
private void registerScreenOffReceiver()
{
m_ScreenOffReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.d(TAG, "ACTION_SCREEN_OFF");
// do something, e.g. send Intent to main app
}
};
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(m_ScreenOffReceiver, filter);
}
}
Accepted answer is not an actual answer i think. I will explain what the issue. I think you are testing your app in the Huawie, Oppo, Vivo, Xiomi,asus....... or some devices. With that devices if we close the application they will also close our broadcast receivers. So thats the problem.(To check that use a with pixel nexus emulator). I will explain how to resolve this.``
we would add our app to the protected app list. OS only allow to them to continue broadcast receiver activities.(Copy this array declaration to your code)
private static final Intent[] POWERMANAGER_INTENTS = {
new Intent().setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")),
new Intent().setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")),
new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")),
new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.appcontrol.activity.StartupAppControlActivity")),
new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")),
new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")),
new Intent().setComponent(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")),
new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")),
new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")),
new Intent().setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")),
new Intent().setComponent(new ComponentName("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity")),
new Intent().setComponent(new ComponentName("com.htc.pitroad", "com.htc.pitroad.landingpage.activity.LandingPageActivity")),
new Intent().setComponent(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.MainActivity"))};
Put these code to your onCreate Method. Here i used shared preference for check it only first time of the app open.
`
final SharedPreferences.Editor pref = getSharedPreferences("allow_notify", MODE_PRIVATE).edit(); pref.apply(); final SharedPreferences sp = getSharedPreferences("allow_notify", MODE_PRIVATE)`;
if(!sp.getBoolean("protected",false)) {
for (final Intent intent : POWERMANAGER_INTENTS)
if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Alert Title").setMessage("Alert Body")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
startActivity(intent);
sp.edit().putBoolean("protected",true).apply();
}
})
.setCancelable(false)
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
})
.create().show();
break;
Be careful if you are using Android 4.4.x as there is a bug which kills background services when closing the app. I was testing my app in Android 4.4.2 and I had the same problem. Here there is a detailed explanation:
http://www.androidpolice.com/2014/03/07/bug-watch-stopping-apps-on-android-4-4-2-can-silently-kill-related-background-services-a-fix-is-on-the-way/
You cannot receive some broadcast events through components declared in manifest.
These events are
ACTION_BATTERY_CHANGED
ACTION_CONFIGURATION_CHANGED
ACTION_SCREEN_OFF (You are playing with this event)
ACTION_SCREEN_ON (You are playing with this event)
ACTION_TIME_TICK
Reference https://developer.android.com/reference/android/content/Intent.html#ACTION_SCREEN_ON
So in your specific events,
you will have to create a Service & you will have to register your event explicitly in service onCreate() with with Context.registerReceiver().
For other events,
entry in manifest is sufficient.
If you declare BroadcastReceiver in the Manifest, it will always be active and be called even if the application is closed/stopped
You could start a service that is running in the foreground. That's the only way to ensure (mostly) that your app will get the events. There is still a chance that your foreground service could get killed in times of crazy memory pressure from the OS (so it's not foolproof). If you start a service in the foreground, the user will see a persistent notification to know that it is always running, though.
So the moral of the story is, do you really need to monitor the screen off/on events at all times? The reason that they force you to register a receiver not in the manifest is that they don't want people to always be monitoring these events and slowing down the device. What are you trying to accomplish?
The best way I found is the Foreground Services. I registered my BroadcastReceiver from my Service only under the onStartCommand() as I want my service needs to run always, I returned START_STICKY
This way, my broadcast receiver survives even after terminating the app from stack.
Used below code in my service
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("rht", "Received start id " + startId + ": " + intent);
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
}
This is how I started my service
Intent serviceIntent = new Intent(this, SpeechServiceForeground.class);
ContextCompat.startForegroundService(this, serviceIntent);