My app is a tracking app where I want to track user location in a service and then display every location in a fragment.
I have a problem with background service: my service is stopped after some minutes (sometimes < 10 minutes, sometimes > 30 minutes).
I start my location service in this fragment and everything goes fine when I use my app.
The problem is when I close (not kill) my app and start something else on my phone (or just block it), after a few minutes my service is stopped and I don't know why.
I show a foreground notification when my service is running but android system still stops it, I notice this because my notification disappear and my app restarts.
Here is my code for the service:
public class LocationService extends Service {
private static final String TAG = "LocationService";
public static final String NEW_VALUE_INTENT_ACTION = "service_new_value";
public static final String INTENT_LATITUDE = "latitude";
public static final String INTENT_LONGITUDE = "longitude";
public static final String INTENT_SPEED = "speed";
public static final String INTENT_ACCURACY = "accuracy";
private static final long TWO_MINUTES = 1000 * 60 * 2;
private static final long INTERVAL = 5000;
private static final long FASTEST_INTERVAL = 2000;
private int ONGOING_NOTIFICATION = 1111;
private Location currentLocation;
NotificationCompat.Builder builder = null;
private LocationRequest mLocationRequest;
/**
* Provides access to the Fused Location Provider API.
*/
private FusedLocationProviderClient mFusedLocationClient;
/**
* Callback for changes in location.
*/
private LocationCallback mLocationCallback;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
Log.d(LocationService.TAG, "LocationService ---> onCreate()");
super.onCreate();
}
#Override
public void onDestroy() {
Log.d(LocationService.TAG, "LocationService ---> onDestroy()");
super.onDestroy();
stopForeground(true);
mFusedLocationClient.removeLocationUpdates(mLocationCallback);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(LocationService.TAG, "LocationService ---> onStartCommand()");
startServiceTask();
return super.onStartCommand(intent, flags, startId);
}
#Override
public boolean onUnbind(Intent intent) {
Log.d(LocationService.TAG, "LocationService ---> onUnbind()");
return super.onUnbind(intent);
}
private void startServiceTask() {
Log.d(LocationService.TAG, "GpsService ---> Starting ...");
setServiceAsForeground();
initFusedLocationProvider();
Log.d(LocationService.TAG, "MyStartedService ---> Starting ...");
}
private void initFusedLocationProvider() {
mFusedLocationClient = new FusedLocationProviderClient(this);
mLocationRequest = new LocationRequest();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(INTERVAL);
mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
if (currentLocation == null)
currentLocation = locationResult.getLastLocation();
else if (isBetterLocation(locationResult.getLastLocation(), currentLocation)) {
Log.d(TAG, "onLocationChanged(): Updating Location ... " + currentLocation.getProvider());
currentLocation = locationResult.getLastLocation();
}
notifyValueUpdate();
} }, getMainLooper());
}
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use
// the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be
// worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and
// accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate
&& isFromSameProvider) {
return true;
}
return false;
}
/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
private void setServiceAsForeground() {
Log.d(LocationService.TAG, "GpsService ---> setServiceAsForeground()");
// Prepare the intent triggered if the notification is selected
Intent intent = new Intent(this, LocationService.class);
PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
builder = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel notificationChannel = new NotificationChannel("ID", "Name", importance);
notificationManager.createNotificationChannel(notificationChannel);
builder = new NotificationCompat.Builder(getApplicationContext(), notificationChannel.getId());
} else {
builder = new NotificationCompat.Builder(getApplicationContext());
}
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
//deprecated in API 26
v.vibrate(500);
}
// Build the notification
// Use NotificationCompat.Builder instead of just Notification.Builder to support older Android versions
Notification notification = builder.setContentTitle("MyMovements")
.setContentText("Running ...")
.setSmallIcon(getNotificationIcon())
.setBadgeIconType(getNotificationIcon())
.setContentIntent(pIntent)
.setAutoCancel(true).build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(ONGOING_NOTIFICATION, notification, FOREGROUND_SERVICE_TYPE_LOCATION);
}
else {
startForeground(ONGOING_NOTIFICATION,notification);
}
}
private int getNotificationIcon(){
SharedPreferences settings = this.getSharedPreferences(MainActivity.PREFERENCE,0);
String icon = settings.getString(MapFragment.NOTIFICATION_PREF, " ");
switch (icon) {
case "Walking":
return R.drawable.walk;
case "Public Transportation":
return R.drawable.bus;
case "Driving":
return R.drawable.car;
default:
return 0;
}
}
private void updateNotification(){
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
String conc = "Running...\n" + "Lat: " + currentLocation.getLatitude() + " " + "Lng: " + currentLocation.getLongitude();
Notification notification = builder.setStyle(new NotificationCompat.BigTextStyle()
.bigText(conc))
.setContentText(conc).build();
notificationManager.notify(ONGOING_NOTIFICATION, notification);
}
private void notifyValueUpdate(){
Log.d(TAG, "MyStartedService ---> notifyValueUpdate()");
if(currentLocation != null) {
updateNotification();
Log.d(LocationService.TAG, currentLocation.getProvider());
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(NEW_VALUE_INTENT_ACTION);
Bundle bundle = new Bundle();
bundle.putDouble(INTENT_LATITUDE, currentLocation.getLatitude());
bundle.putDouble(INTENT_LONGITUDE, currentLocation.getLongitude());
bundle.putDouble(INTENT_SPEED, currentLocation.getSpeed()*3.6);
bundle.putDouble(INTENT_ACCURACY, currentLocation.getAccuracy());
broadcastIntent.putExtras(bundle);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
}
}
}
I started my service in OnServiceCommand() -> startServiceTask() -> setServiceAsForeground()
I will really appreciate any tips for my code (this is a scholar project and this is my first app, so probably I am doing something wrong).
Thanks to everyone
Related
at the start of this year I had developed an app Wifi Direct is working fine with all other devices i.e Discovery staring and searching other devices but on Android 10 I am getting Discovery Failed error.
Here my class
public class NotificationsFragment extends Fragment implements View.OnClickListener{
private NotificationsViewModel notificationsViewModel;
private static final int MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
private static final int MY_PERMISSIONS_REQUEST_RECORD_AUDIO = 2;
private static final int MY_PERMISSIONS_REQUEST_REQUIRED_PERMISSION = 3;
private static final int SEPRATION_DIST_THRESHOLD = 50;
private static int device_count = 0;
public RippleBackground rippleBackground;
ImageView centerDeviceIcon;
ArrayList<Point> device_points = new ArrayList<>();
public TextView connectionStatus;
WifiManager wifiManager;
WifiP2pManager mManager;
WifiP2pManager.Channel mChannel;
public static final int PORT_USED = 9584;
BroadcastReceiver mReceiver;
IntentFilter mIntentFilter;
ArrayList<CustomDevice> custom_peers = new ArrayList<>();
ServerClass serverClass;
ClientClass clientClass;
private Menu menu;
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
notificationsViewModel =
ViewModelProviders.of(this).get(NotificationsViewModel.class);
View root = inflater.inflate(R.layout.fragment_notifications, container, false);
getPermissions();
initialSetup();
connectionStatus = root.findViewById(R.id.connectionStatus);
rippleBackground = root.findViewById(R.id.content);
centerDeviceIcon = root.findViewById(R.id.centerImage);
centerDeviceIcon.setOnClickListener(this);
return root;
}
private boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menu_inflater = getActivity().getMenuInflater();
menu_inflater.inflate(R.menu.main_menu3, menu);
this.menu = menu;
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.wifi_toggle) {
toggleWifiState();
}
return super.onOptionsItemSelected(item);
}
public class ServerClass extends Thread{
Socket socket;
ServerSocket serverSocket;
#Override
public void run() {
try {
serverSocket = new ServerSocket(PORT_USED);
socket = serverSocket.accept();
com.vikaskonaparthi.origin.SocketHandler.setSocket(socket);
startActivity(new Intent(getActivity().getApplicationContext(), com.vikaskonaparthi.origin.ChatWindow.class));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ClientClass extends Thread{
Socket socket;
String hostAddress;
ClientClass(InetAddress address){
this.socket = new Socket();
this.hostAddress = address.getHostAddress();
}
#Override
public void run() {
try {
socket.connect(new InetSocketAddress(hostAddress, PORT_USED), 500);
com.vikaskonaparthi.origin.SocketHandler.setSocket(socket);
startActivity(new Intent(getActivity().getApplicationContext(), com.vikaskonaparthi.origin.ChatWindow.class));
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onPause() {
super.onPause();
getActivity().unregisterReceiver(mReceiver);
}
#Override
public void onResume() {
super.onResume();
getActivity().registerReceiver(mReceiver, mIntentFilter);
}
private void initialSetup() {
// layout files
// add onClick Listeners
// center button position
Display display = getActivity().getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
device_points.add(new Point(size.x / 2, size.y / 2));
Log.d("Tab1", size.x + " " + size.y);
wifiManager = (WifiManager) getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
mManager = (WifiP2pManager) getActivity().getSystemService(WIFI_P2P_SERVICE);
mChannel = mManager.initialize(getActivity(), getMainLooper(), null);
mReceiver = new com.vikaskonaparthi.origin.WifiDirectBroadcastReceiver(mManager, mChannel, this);
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);
}
void checkLocationEnabled(){
LocationManager lm = (LocationManager)NotificationsFragment.this.getActivity().getSystemService(Context.LOCATION_SERVICE);
boolean gps_enabled = false;
boolean network_enabled = false;
try {
gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
} catch(Exception ex) {}
try {
network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
} catch(Exception ex) {}
if(!gps_enabled && !network_enabled) {
// notify user
new AlertDialog.Builder(getActivity())
.setTitle(R.string.gps_network_not_enabled_title)
.setMessage(R.string.gps_network_not_enabled)
.setPositiveButton(R.string.open_location_settings, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface paramDialogInterface, int paramInt) {
NotificationsFragment.this.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
})
.setNegativeButton(R.string.Cancel,null)
.show();
}
}
#Override
public void onClick(View v) {
int view_id = v.getId();
if(getIndexFromIdPeerList(view_id) != -1){
int idx = getIndexFromIdPeerList(view_id);
final WifiP2pDevice device = custom_peers.get(idx).device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
Toast.makeText(getActivity().getApplicationContext(), "Connected to "+device.deviceName, Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(int reason) {
Toast.makeText(getActivity().getApplicationContext(), "Error in connecting to "+device.deviceName, Toast.LENGTH_SHORT).show();
}
});
}else{
switch (v.getId()){
case R.id.centerImage:
rippleBackground.startRippleAnimation();
checkLocationEnabled();
discoverDevices();
break;
default:
break;
}
}
}
private int getIndexFromIdPeerList(int id){
for(CustomDevice d : custom_peers){
if(d.id == id){
return custom_peers.indexOf(d);
}
}
return -1;
}
private int checkPeersListByName(String deviceName){
for(CustomDevice d :custom_peers) {
if (d.deviceName.equals(deviceName)) {
return custom_peers.indexOf(d);
}
}
return -1;
}
private void discoverDevices() {
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
connectionStatus.setText("Discovery Started");
}
#Override
public void onFailure(int reason) {
connectionStatus.setText("Discovery start Failed");
}
});
}
public WifiP2pManager.PeerListListener peerListListener = new WifiP2pManager.PeerListListener() {
#Override
public void onPeersAvailable(WifiP2pDeviceList peersList) {
Log.d("DEVICE_NAME", "Listener called"+peersList.getDeviceList().size());
if(peersList.getDeviceList().size() != 0){
// first make a list of all devices already present
ArrayList<CustomDevice> device_already_present = new ArrayList<>();
for(WifiP2pDevice device : peersList.getDeviceList()){
int idx = checkPeersListByName(device.deviceName);
if(idx != -1){
// device already in list
device_already_present.add(custom_peers.get(idx));
}
}
if(device_already_present.size() == peersList.getDeviceList().size()){
// all discovered devices already present
return;
}
// clear previous views
clear_all_device_icons();
// this will remove all devices no longer in range
custom_peers.clear();
// add all devices in range
custom_peers.addAll(device_already_present);
// add all already present devices to the view
for(CustomDevice d : device_already_present){
rippleBackground.addView(d.icon_view);
}
for(WifiP2pDevice device : peersList.getDeviceList()) {
if (checkPeersListByName(device.deviceName) == -1) {
// device not already present
View tmp_device = createNewDevice(device.deviceName);
rippleBackground.addView(tmp_device);
foundDevice(tmp_device);
CustomDevice tmp_device_obj = new CustomDevice();
tmp_device_obj.deviceName = device.deviceName;
tmp_device_obj.id = tmp_device.getId();
tmp_device_obj.device = device;
tmp_device_obj.icon_view = tmp_device;
custom_peers.add(tmp_device_obj);
}
}
}
if(peersList.getDeviceList().size() == 0){
Toast.makeText(getActivity().getApplicationContext(), "No Peers Found", Toast.LENGTH_SHORT).show();
}
}
};
public void clear_all_device_icons(){
if(!custom_peers.isEmpty()){
for(CustomDevice d : custom_peers){
rippleBackground.removeView(getActivity().findViewById(d.id));
}
}
}
public WifiP2pManager.ConnectionInfoListener connectionInfoListener = new WifiP2pManager.ConnectionInfoListener() {
#Override
public void onConnectionInfoAvailable(WifiP2pInfo info) {
final InetAddress groupOwnerAddress = info.groupOwnerAddress;
if(info.groupFormed && info.isGroupOwner){
connectionStatus.setText("HOST");
serverClass = new ServerClass();
serverClass.start();
}else if(info.groupFormed){
connectionStatus.setText("CLIENT");
clientClass = new ClientClass(groupOwnerAddress);
clientClass.start();
}
}
};
Point generateRandomPosition(){
Display display = getActivity().getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int SCREEN_WIDTH = size.x;
int SCREEN_HEIGHT = size.y;
int height_start = SCREEN_HEIGHT / 2 - 300;
int x = 0;
int y = 0;
do{
x = (int)(Math.random() * SCREEN_WIDTH);
y = (int)(Math.random() * height_start);
}while(checkPositionOverlap(new Point(x, y)));
Point new_point = new Point(x, y);
device_points.add(new_point);
return new_point;
}
boolean checkPositionOverlap(Point new_p){
// if overlap, then return true, else return false
if(!device_points.isEmpty()){
for(Point p:device_points){
int distance = (int)Math.sqrt(Math.pow(new_p.x - p.x, 2) + Math.pow(new_p.y - p.y, 2));
Log.d(TAG, distance+"");
if(distance < SEPRATION_DIST_THRESHOLD){
return true;
}
}
}
return false;
}
public View createNewDevice(String device_name){
View device1 = LayoutInflater.from(getActivity()).inflate(R.layout.device_icon, null);
Point new_point = generateRandomPosition();
RippleBackground.LayoutParams params = new RippleBackground.LayoutParams(350,350);
params.setMargins(new_point.x, new_point.y, 0, 0);
device1.setLayoutParams(params);
TextView txt_device1 = device1.findViewById(R.id.myImageViewText);
int device_id = (int)System.currentTimeMillis() + device_count++;
txt_device1.setText(device_name);
device1.setId(device_id);
device1.setOnClickListener(this);
device1.setVisibility(View.INVISIBLE);
return device1;
}
private void foundDevice(View foundDevice){
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(400);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
ArrayList<Animator> animatorList=new ArrayList<Animator>();
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(foundDevice, "ScaleX", 0f, 1.2f, 1f);
animatorList.add(scaleXAnimator);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(foundDevice, "ScaleY", 0f, 1.2f, 1f);
animatorList.add(scaleYAnimator);
animatorSet.playTogether(animatorList);
foundDevice.setVisibility(View.VISIBLE);
animatorSet.start();
}
private void toggleWifiState() {
if(wifiManager.isWifiEnabled()){
wifiManager.setWifiEnabled(false);
menu.findItem(R.id.wifi_toggle).setTitle("Turn Wifi On");
}else{
wifiManager.setWifiEnabled(true);
menu.findItem(R.id.wifi_toggle).setTitle("Turn Wifi Off");
}
}
public void getPermissions() {
if ((ContextCompat.checkSelfPermission(getActivity(),
Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED)
|| (ContextCompat.checkSelfPermission(getActivity(),
Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED)) {
ActivityCompat.requestPermissions(getActivity(),
new String[]{Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_COARSE_LOCATION
},
MY_PERMISSIONS_REQUEST_REQUIRED_PERMISSION);
}
}
}
class CustomDevice{
int id;
String deviceName;
WifiP2pDevice device;
View icon_view;
CustomDevice(){
}
}
Discovery is getting failed only on Android 10 whereas everything and logic is fine from myside.
Here are the included permissions
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <!-- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
Thanks in advance
Nooooooo one in this world to help me
Wifi-Direct alliance in android is evolving, We need to understand How the discovery works.
Wifi-Direct discovery will never work if the location services are disabled.
How to check?
LocationManager manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
Toast.makeText(this, "please enable location services", Toast.LENGTH_LONG).show();
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
IF still, the discovery is not working then it comes from the OS, check from the Wifi-Preferences in Wifi-Direct discovery, is your device visible? If not then restart your phone.
About Android 10 and Wifi-Direct
Simply, If it was working on Android 9, it should work for Android 10 out of the box.
NOTE:
Keep this in mind that the Wifi-direct works best when the Location is High Accuracy.
Please uncomment the FINE_LOCATION permission in manifest and ask it on runtime from code.
I was facing the same problem when I upgraded to Android 10.
However, I solved the problem by activing the GPS location service.
Previously I tried to run background services using WorkManager, the requests I used were OneTimeWorkRequest and PeriodicWorkRequest.
When I tried PeriodicWorkRequest there was an oddity, the oddity was when I closed the application, the service stopped, but when I reopened the application, the service returned.
Constraints.Builder constraint = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED);
Data.Builder data = new Data.Builder();
data.putString("url", "https://html5demos.com/assets/dizzy.mp4");
WorkRequest request = new PeriodicWorkRequest.Builder(BackgroundDownloadService.class, 12,
TimeUnit.HOURS)
.setInputData(data.build())
.setConstraints(constraint.build())
.build();
WorkManager.getInstance().enqueue(request);
BackgroundDownloadService.java:
public class BackgroundDownloadService extends Worker {
private Context context;
private NotificationCompat.Builder notificationBuilder;
private NotificationManager notificationManager;
#Override
public WorkerResult doWork() {
context = getApplicationContext();
Data data = getInputData();
String url = "";
if(data != null) {
url = data.getString("url", "");
}
String mainName = "renaldi";
String mimeType = url.substring(url.length()-4);
String namePath = mainName + "Files-"+changeFormat(new Date())+mimeType;
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
createNotification();
AndroidNetworking.initialize(context);
AndroidNetworking.download(url, context.getExternalFilesDir(DIRECTORY_DOWNLOADS)+"/video/" , namePath)
.setTag("download")
.setPriority(Priority.HIGH)
.build()
.setDownloadProgressListener(new DownloadProgressListener() {
#Override
public void onProgress(long bytesDownloaded, long totalBytes) {
final int dl_progress = (int) ((bytesDownloaded * 100l) / totalBytes);
updateNotification(dl_progress);
}
})
.startDownload(new DownloadListener() {
#Override
public void onDownloadComplete() {
onDownloadCompleted();
}
#Override
public void onError(ANError error) {
}
});
return WorkerResult.SUCCESS;
}
public String changeFormat(Date date) {
SimpleDateFormat format = new SimpleDateFormat("HHmmssSSS");
String dateFormat = format.format(date);
return dateFormat;
}
private void updateNotification(int currentProgress) {
notificationBuilder.setProgress(100, currentProgress, false);
notificationBuilder.setContentText("Downloaded: " + currentProgress + "%");
notificationManager.notify(0, notificationBuilder.build());
}
private void onDownloadCompleted() {
notificationManager.cancel(0);
notificationBuilder.setProgress(0, 0, false);
notificationBuilder.setContentText("Video Download Complete");
notificationManager.notify(0, notificationBuilder.build());
}
private void createNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel("id", "an", NotificationManager.IMPORTANCE_LOW);
notificationChannel.setDescription("no sound");
notificationChannel.setSound(null, null);
notificationChannel.enableLights(false);
notificationChannel.setLightColor(Color.BLUE);
notificationChannel.enableVibration(false);
notificationManager.createNotificationChannel(notificationChannel);
}
notificationBuilder = new NotificationCompat.Builder(context, "id")
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle("Download")
.setContentText("Downloading Video")
.setDefaults(0)
.setAutoCancel(true);
notificationManager.notify(0, notificationBuilder.build());
}
}
Are you giving me a solution to run a background service when the application is closed using workmanager ??
According to the Worker API documentation:
This method (doWork() of Worker class) is for synchronous processing of your work, meaning that
once you return from that method, the Worker is considered to be
finished and will be destroyed. If you need to do your work
asynchronously or call asynchronous APIs, you should use
ListenableWorker.
So consider extending ListenableWorker instead of Worker and see how it works.
I want to create app of hockey results. On home page, user select his favorite team and than he can see in fragment Future Matches - matches of his favorite team.
After this, he can select his favorite match (by click on match) and than, If score is changed in this match, user get notification about changed score.
But I have problem, because when I see future matches, after click on match, application drop.
I donĀ“t have any idea about solve this problem.
I need help with this.
public class Future_matches extends Fragment {
private static Future_matches fragment;
private int favorite_team_id;
private SharedPreferences sp;
private RecyclerView rv_futureMatches;
// private FutureMatchesAdapter adapter;
private List<FutureMatchModel> teams;
private SharedPreferences.Editor ed;
List<Thread> listOfActiveThreads;
private RecyclerView.Adapter<FutureMatchesHolder> adapter;
private RecyclerView.LayoutManager layoutManager;
public Future_matches() {
}
public static Future_matches newInstance() {
if (fragment == null)
fragment = new Future_matches();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listOfActiveThreads = new ArrayList<>();
sp = getActivity().getSharedPreferences(Tools.PACKAGE_NAME, Context.MODE_PRIVATE);
favorite_team_id = sp.getInt(Tools.FAVORITE_TEAM_ID, -1);
ed = sp.edit();
}
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootview = inflater.inflate(R.layout.future_matches, container, false);
rv_futureMatches = rootview.findViewById(R.id.rv_future_matches);
RecyclerView.LayoutManager lm = new LinearLayoutManager(getActivity());
// rv_futureMatches.setLayoutManager(lm);
// rv_futureMatches.setHasFixedSize(true);
if (favorite_team_id == -1) {
Toast.makeText(getActivity(), R.string.warning_future_matches_choose_favorite_team, Toast.LENGTH_SHORT).show();
} else {
Tools.getApi().getFutureMatches(favorite_team_id, "2018-12-27", "2019-05-12").enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.code() == 200) {
teams = JsonTools.convertJsonToFutureMatches(response.body());
// JsonObject data = response.body();
layoutManager = new LinearLayoutManager(getActivity());
layoutManager = new LinearLayoutManager(getContext());
// adapter = new FutureMatchesAdapter(JsonTools.convertJsonToFutureMatches(data), new WeakReference<Context>(getActivity()));
adapter = new FutureMatchesAdapter(teams, new WeakReference<Context>(getActivity()), new FutureMatchesAdapter.TeamClickHandler() {
#Override
public void onClick(int id) {
takeCareOfChanges(id);
}
});
rv_futureMatches.setHasFixedSize(true);
rv_futureMatches.setLayoutManager(layoutManager);
rv_futureMatches.setAdapter(adapter);
}
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Toast.makeText(getActivity(), t.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
return rootview;
}
private boolean isItemInList(int id) {
if (sp.contains(Tools.PREFS_PICKED_GAMES)) {
Set<String> result = sp.getStringSet(Tools.PREFS_PICKED_GAMES, null);
if (result.contains(Integer.toString(id))) {
return true;
} else {
return false;
}
} else
return false;
}
private void takeCareOfChanges(int id) {
if (sp.contains(Tools.PREFS_PICKED_GAMES)) {
Set<String> result = sp.getStringSet(Tools.PREFS_PICKED_GAMES, null);
if (isItemInList(id)) {
result.remove(Integer.toString(id));
ed.putStringSet(Tools.PREFS_PICKED_GAMES, result);
quitService(id);
} else {
result.add(Integer.toString(id));
ed.putStringSet(Tools.PREFS_PICKED_GAMES, result);
launchService(id);
}
} else {
Set<String> result = new HashSet<>();
result.add(Integer.toString(id));
ed.putStringSet(Tools.PREFS_PICKED_GAMES, result);
launchService(id);
}
ed.apply();
}
private void launchService(final int id){
getActivity().startService(new Intent().putExtra(Tools.INTENT_EXTRA_ID,id));
}
private void quitService(int id){
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent().setAction(Tools.INTENT_ACTION_STOP_SERVICE).putExtra(Tools.INTENT_EXTRA_ID,id));
}
}
public class GameChangeService extends Service {
private final static String TAG = "GameChangeService";
private BroadcastReceiver broadcastReceiver;
private Handler h;
private SharedPreferences sp;
Notification notif;
NotificationManager notifManager;
private SharedPreferences.Editor ed;
private Runnable r;
private int id;
private String CHANNEL_ID = "ID";
private int notifId = 1000;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
sp = getSharedPreferences(Tools.PACKAGE_NAME, Context.MODE_PRIVATE);
ed = sp.edit();
notifManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
h = new Handler();
broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
stopSelf();
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(Tools.INTENT_ACTION_STOP_SERVICE));
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
id = intent.getIntExtra(Tools.INTENT_EXTRA_ID, -1);
if (id == -1) {
stopSelf();
} else {
r = new Runnable() {
#Override
public void run() {
doStuff(id);
h.postDelayed(r, 15000);
}
};
h.post(r);
return Service.START_STICKY;
}
//TOTO TU JE VELMI DISKUTABILNE
return Service.START_STICKY;
}
private void doStuff(final int id) {
//TODO: Checkni pls ci je boxscore updatovany live alebo nie. Ak je tak ho mozes pouzit v IApiDefinition namiesto live feed
// JA> V schedule je s gamepk aj online zapas s golmi - staci to pouzit
ApiTools.getApi().getGame(id).enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (sp.contains(Integer.toString(id))) {
int povodnyPocetGolovVZapase = sp.getInt(Integer.toString(id), 0);
//Z responsu zistit kolko eventov je teraz v zapase, t.j. ci uz zapas zacal.
//Dalej zistit ci su tam nejake eventy, ktore maju typ goal alebo ENDGAME (asi).
//AK sa zmenil pocet golov, tak posli notifikaciu ze padol gol aj s novym stavom
JsonObject data = response.body();
//pouzijem lastmatchmodel, aj ked to nie je pre toto robene, ale data mi stacia aj z neho
//List<LastMatchModel> livezapasy = new ArrayList<>();
//vytiahnem si zoznam
JsonArray zoznamZapasovZJsonu = data.get("dates").getAsJsonArray();
for (int i = 0; i < zoznamZapasovZJsonu.size(); i++) {
//pouzijem lastmatchmodel, aj ked to nie je pre toto robene, ale data mi stacia aj z neho
LastMatchModel novyZapas = new LastMatchModel();
JsonObject zapasDate = zoznamZapasovZJsonu.get(i).getAsJsonObject();
JsonArray games = zapasDate.get("games").getAsJsonArray();
if (games.size() >= 1) {
JsonObject teams = games.get(0).getAsJsonObject().get("teams").getAsJsonObject();
int golyHostia = teams.get("away").getAsJsonObject().get("score").getAsInt();
String timHostia = teams.get("away").getAsJsonObject().get("team").getAsJsonObject().get("name").getAsString();
int golyDomaci = teams.get("home").getAsJsonObject().get("score").getAsInt();
String timDomaci = teams.get("home").getAsJsonObject().get("team").getAsJsonObject().get("name").getAsString();
if (golyDomaci + golyHostia != povodnyPocetGolovVZapase) {
Intent intent = new Intent(GameChangeService.this, MatchNotification.class);
PendingIntent pendingIntent = PendingIntent.getActivity(GameChangeService.this, 0, intent, 0);
createNotificationChannel();
final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(GameChangeService.this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("GOAL")
.setContentText(timDomaci + golyDomaci + " vs " + golyHostia + timHostia)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
NotificationManagerCompat nm = NotificationManagerCompat.from(GameChangeService.this);
nm.notify(notifId, mBuilder.build());
}
}
}
} else {
int povodnyPocetEventovVZapase = 0;
//Z responsu zistit kolko eventov je teraz v zapase, t.j. ci uz zapas zacal.
//Dalej zistit ci su tam nejake eventy, ktore maju typ goal alebo ENDGAME (asi).
//AK sa zmenil pocet golov, tak posli notifikaciu ze padol gol aj s novym stavom
}
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Log.e(GameChangeService.TAG, "Nebavi to ");
}
});
}
#Override
public void onDestroy() {
super.onDestroy();
if (h != null)
h.removeCallbacks(r);
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Name of the channel";
String description = "Description of the channel";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
}
Run in Android Studio inform about errors:
https://ctrlv.cz/shots/2019/01/03/Y6RS.png
or
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.luky.nhlvysledky, PID: 23251
java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { (has extras) }
at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1519)
at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1560)
at android.app.ContextImpl.startService(ContextImpl.java:1532)
at android.content.ContextWrapper.startService(ContextWrapper.java:664)
at com.example.luky.nhlvysledky.Future_matches.launchService(Future_matches.java:148)
at com.example.luky.nhlvysledky.Future_matches.takeCareOfChanges(Future_matches.java:142)
at com.example.luky.nhlvysledky.Future_matches.access$300(Future_matches.java:33)
at com.example.luky.nhlvysledky.Future_matches$1$1.onClick(Future_matches.java:93)
at com.example.luky.nhlvysledky.RecycleView.FutureMatchesHolder$1.onClick(FutureMatchesHolder.java:46)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
You need to explicitly define which service you are starting in your intent.
getActivity().startService(new Intent(getActivity(), GameChangeService.class).putExtra(Tools.INTENT_EXTRA_ID,id));
You will also need to define your service in your AndroidManifest.xml.
<service android:enabled="true" android:name=".GameChangeService" />
I have an Intent Service called ParserService which performs some task and creates an object(ArtistInfo) which is parcelable. Then it checks if the app is in foreground. If it is then it sends a broadcast with the parcelable object in the intent extras. This broadcast is received by the activity MainActivity.java which then creates an intent with the same object and launches an activity called ListSongsActivity where the parcelable object is successfully received.
But if the app is not in foreground then the ParserService sends a notification which has the same intent as that of a broadcast. But when the ListSongsActivity is being launched through the notification the parcelable object(ArtistInfo) this time is null. And I am also passing a string in the intent. This string is being received correctly via the notification intent but the parcelable object is null.
Please find the relevant code snippets below.
Broadcast and notification code from the ParserService:
if (CommonUtils.appInForeground(getApplicationContext())) {
Log.d(TAG, "onHandleIntent(): Sending success broadcast.");
sendBroadcast(createSuccessIntent(artistInfo));
} else {
// TODO: 10-10-2018 tapping on notification does nothing!!
Log.d(TAG, "onHandleIntent(): Sending success notification.");
String body = "Parsing complete for the url: " + url;
Intent notifyIntent = new Intent(getApplicationContext(), ListSongsActivity.class);
notifyIntent.putExtra(Constants.MUSIC_SITE, siteName);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PARSED_ARTIST_INFO, artistInfo);
intent.putExtras(bundle);
CommonUtils.sendNotification(getApplicationContext(), Constants.LIST_SONGS_NOTIFICATION_TITLE
, body, Constants.LIST_SONGS_NOTIFICATION_CHANNEL_ID, notifyIntent,
Constants.LIST_SONGS_NOTIFICATION_ID, R.drawable.ic_launcher_background);
}
private Intent createSuccessIntent(ArtistInfo artistInfo) {
Intent intent = new Intent();
intent.setAction(Constants.PARSE_SUCCESS_ACTION_KEY);
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PARSE_SUCCESS_MESSAGE_KEY, artistInfo);
intent.putExtras(bundle);
return intent;
}
Broadcast Received in a fragment of the MainActivity:
private class ParserBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "ParserBroadcastReceiver, onReceive()");
String parseResult = intent.getAction();
if (parseResult == null || parseResult.equals(Constants.EMPTY_STRING)) {
return;
}
switch (parseResult) {
case Constants.PARSE_SUCCESS_ACTION_KEY:
ArtistInfo artistInfo = intent.getParcelableExtra(Constants.PARSE_SUCCESS_MESSAGE_KEY);
Log.d(TAG, "ParserBroadcastReceiver, onReceive() PARSE_SUCCESS_ACTION_KEY, artistInfo: "
+ artistInfo.toString());
Log.d(TAG, "site: " + musicSite);
createIntentAndDelegateActivity(artistInfo);
break;
default:
break;
}
}
}
private void createIntentAndDelegateActivity(ArtistInfo artistInfo) {
Log.d(TAG, "createIntentAndDelegateActivity()");
Intent intent = new Intent(getContext(), ListSongsActivity.class);
intent.putExtra(Constants.MUSIC_SITE, musicSite);
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PARSED_ARTIST_INFO, artistInfo);
intent.putExtras(bundle);
startActivity(intent);
}
sendNotification in CommonUtils:
public static void sendNotification(Context context, String title, String body,
String channelId, Intent intent, Integer id, Integer iconResourceId) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
Log.d(TAG, "sendNotification(): noti manager null!!");
return;
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId,
Constants.DEFAULT_NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(Constants.DEFAULT_NOTIFICATION_CHANNEL_DESCRIPTION);
notificationManager.createNotificationChannel(channel);
}
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntentWithParentStack(intent);
PendingIntent pendingIntent1 = stackBuilder.getPendingIntent(Constants.PENDING_INTENT_DEFAULT_REQ_CODE,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
builder.setContentTitle(title);
builder.setContentText(body);
builder.setSmallIcon(iconResourceId);
builder.setContentIntent(pendingIntent1);
Notification notification = builder.build();
// Play default notification sound
notification.defaults |= Notification.DEFAULT_SOUND;
// Vibrate if vibrate is enabled
notification.defaults |= Notification.DEFAULT_VIBRATE;
NotificationManagerCompat.from(context).notify(id, notification);
}
This is how I getIntentExtras in the ListSongsActivity:
private void getIntentExtras() {
Log.d(TAG, "getIntentExtras()");
Intent intent = getIntent();
parsedArtistInfo = intent.getParcelableExtra(Constants.PARSED_ARTIST_INFO);
String siteName = intent.getStringExtra(Constants.MUSIC_SITE);
Log.d(TAG, "getIntentExtras() sitename: " + siteName);
musicSite = Enum.valueOf(MusicSite.class, siteName);
Log.d(TAG, "getIntentExtras() artInfo: " + parsedArtistInfo.toString());
}
When the ListSongsAtivity is started by the broadcast receiver, the parsedArtistInfo object is the correct object passed by the ParserService, but when ListSongsActivity is opened by notification the parsedArtistInfo object is null.
ArtistInfo class:
public class ArtistInfo implements Parcelable {
private static final String TAG = ArtistInfo.class.getSimpleName();
private String url;
private String artist;
// album name to list of ids of songs
private HashMap<String, List<Integer>> albumInfo;
// song id to songInfo
private SparseArray<SongInfo> songsMap;
/**
* to be used only for ui display logic, don't use for downloading logic
*/
private HashMap<String, Boolean> albumCheckedStatus;
public ArtistInfo() {
}
private ArtistInfo(Parcel in) {
url = in.readString();
artist = in.readString();
// Read album info
getAlbumInfo();
int albumInfoSize = in.readInt();
for (int i = 0; i < albumInfoSize; i++) {
String key = in.readString();
List<Integer> value = new ArrayList<>();
in.readList(value, null);
albumInfo.put(key, value);
}
// Read songs map
getSongsMap();
int songsMapSize = in.readInt();
for (int i = 0; i < songsMapSize; i++) {
int key = in.readInt();
SongInfo value = in.readParcelable(SongInfo.class.getClassLoader());
songsMap.put(key, value);
}
getAlbumCheckedStatus();
int albumCheckStatusSize = in.readInt();
for (int i = 0; i < albumCheckStatusSize; i++) {
String key = in.readString();
Boolean value = in.readByte() != 0;
albumCheckedStatus.put(key, value);
}
}
public static final Creator<ArtistInfo> CREATOR = new Creator<ArtistInfo>() {
#Override
public ArtistInfo createFromParcel(Parcel in) {
return new ArtistInfo(in);
}
#Override
public ArtistInfo[] newArray(int size) {
return new ArtistInfo[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(url);
dest.writeString(artist);
// Write album info
getAlbumInfo();
dest.writeInt(albumInfo.size());
for (Map.Entry<String, List<Integer>> item : albumInfo.entrySet()) {
dest.writeString(item.getKey());
dest.writeList(item.getValue());
}
// Write song map
getSongsMap();
dest.writeInt(songsMap.size());
for (int i = 0; i < songsMap.size(); i++) {
int key = songsMap.keyAt(i);
dest.writeInt(key);
dest.writeParcelable(songsMap.get(key), flags);
}
getAlbumCheckedStatus();
dest.writeInt(albumCheckedStatus.size());
for (Map.Entry<String, Boolean> item : albumCheckedStatus.entrySet()) {
dest.writeString(item.getKey());
dest.writeByte((byte) (item.getValue() ? 1 : 0));
}
}
Can someone please point out the error I am making while sending the object in via notification. Thanks!
Before calling sendNotification() in your else condition, you are putting bundle to a different intent, not to notifyIntent. Change your code inside the else like below
else {
// TODO: 10-10-2018 tapping on notification does nothing!!
Log.d(TAG, "onHandleIntent(): Sending success notification.");
String body = "Parsing complete for the url: " + url;
Intent notifyIntent = new Intent(getApplicationContext(), ListSongsActivity.class);
notifyIntent.putExtra(Constants.MUSIC_SITE, siteName);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PARSED_ARTIST_INFO, artistInfo);
notifyIntent.putExtras(bundle);
CommonUtils.sendNotification(getApplicationContext(), Constants.LIST_SONGS_NOTIFICATION_TITLE
, body, Constants.LIST_SONGS_NOTIFICATION_CHANNEL_ID, notifyIntent,
Constants.LIST_SONGS_NOTIFICATION_ID, R.drawable.ic_launcher_background);
}
Maybe you are creating pending intents more than ones in your app. In this case, you should use,
PendingIntent.FLAG_CANCEL_CURRENT
to create the Intent from scratch and inflate your bundle
I'm Making an app which has 5 different notifications at different times every day. it works perfectly but after beta testing some users are complaining that some alarm fire again at wrong times I haven't faced this problem before and I don't know how to trace the problem so I can fix it. This is how I create the alarm:
Manager Class:(which has all the functions of the alarm)
public static Integer DEFAULT_SILENT_DURATION = 20 * 60 * 1000;
public static Integer DEFAULT_SILENT_START= 2 * 60 * 1000;
public Manager(Context applicationContext) {
this.context = applicationContext;
}
public static void acquireScreen(Context context) {
PowerManager pm = (PowerManager) context.getApplicationContext()
.getSystemService(Context.POWER_SERVICE);
WakeLock wakeLock = pm
.newWakeLock(
(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
| PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP),
"TAG");
wakeLock.acquire();
Log.v("Check Manager acquireScreen","YES");
}
public static void releaseScreen(Context context) {
KeyguardManager keyguardManager = (KeyguardManager) context
.getApplicationContext().getSystemService(
Context.KEYGUARD_SERVICE);
KeyguardLock keyguardLock = keyguardManager.newKeyguardLock("TAG");
keyguardLock.disableKeyguard();
Log.v("Check Manager releaseScreen","YES");
}
public static void initPrayerAlarm(Service service,
Class<PrayerReceiver> receiver) {
Manager.prayerService = service; // we may need it ?
Manager.prayerIntet = new Intent(service, receiver);
Manager.prayerPendingIntent = PendingIntent
.getBroadcast(service, 1234432, Manager.prayerIntet,
PendingIntent.FLAG_UPDATE_CURRENT);
Manager.prayerAlarmManager = (AlarmManager) service
.getSystemService(Context.ALARM_SERVICE);
Manager.prayerAlarmManager.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + 1000, Manager.prayerPendingIntent);
Log.v("Check Manager initPrayerAlarm",""+System.currentTimeMillis() + 1000);
}
public static void updatePrayerAlarm(long newTimeInterval) {
Manager.prayerAlarmManager.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + newTimeInterval,
Manager.prayerPendingIntent);
Log.v("Check Manager updatePrayerAlarm",""+System.currentTimeMillis() + newTimeInterval);
}
public void restartPrayerService(Activity activty) {
Intent intent = new Intent(activty, PrayerService.class);
context.startService(intent);
Log.v("Check Manager restartPrayerService","YES");
}
In my MainActivity I call Manager restartPrayerService function
In PrayerService I call as below:
Manager.initPrayerState(this);
Manager.initPrayerAlarm(this, PrayerReceiver.class);
and then register the receiver.
PrayerReceiver:
public class PrayerReceiver extends BroadcastReceiver {
static PrayerState prayerState;
private AudioManager am;
private Context context;
private SharedPreferences pref;
private Editor editor;
private int silentDuration;
private int silentStart ;
private int delayMilliSeconds = 1000 * 60; // one minute by default.
private Object obj;
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
pref = PreferenceManager.getDefaultSharedPreferences(this.context);
Manager m = new Manager(context);
Preference p = m.getPreference();
this.silentDuration = p.getSilentDuration();
this.silentStart = p.getSilentStart();
editor = pref.edit();
try {
prayerState = Manager.getPrayerState();
am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
switch (prayerState.getCurrentState()) {
case PrayerState.WAITING_AZAN:
{
Log.v("Check PrayerReceiver PrayerState","WAITING_AZAN");
onWaitingAzan();
}
break;
case PrayerState.DOING_AZAN:
{
Log.v("Check PrayerReceiver PrayerState","DOING_AZAN");
onDoingAzan();
}
break;
case PrayerState.WAITING_PRAYER:
{
Log.v("Check PrayerReceiver PrayerState","WAITING_PRAYER");
onWaitingPrayer();
}
break;
}
} catch (Exception e) {
// TODO Auto-generated catch block
Log.v("Check PrayerReceiver PrayerState","ERROR");
}
}
public int getDelayMilliSeconds() {
return delayMilliSeconds;
}
public void setDelayMilliSeconds(int delayMilliSeconds) {
this.delayMilliSeconds = delayMilliSeconds;
}
private void onWaitingAzan() {
try {
boolean isRingerModeChangedToSilent = pref.getBoolean(
"isRingerModeChangedToSilent", false);
if (isRingerModeChangedToSilent == true) {
am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
editor.putBoolean("isRingerModeChangedToSilent", false);
editor.commit();
}
// What is the remaining time until the next prayer ?
Date date = new Date();
int dd = date.getDate();
int mm = date.getMonth() + 1;
int yy = date.getYear() + 1900;
int h = date.getHours();
int m = date.getMinutes();
int s = date.getSeconds();
int nearestPrayerTime = Manager.computeNearestPrayerTime(context,
h, m, s, yy, mm, dd);
int deffTime = TimeHelper.different((h * 3600 + m * 60 + s),
nearestPrayerTime);
deffTime = deffTime * 1000; // to milliseconds
// ok , come back after X seconds to do the Azan
prayerState.setNextState(PrayerState.DOING_AZAN);
Manager.updatePrayerAlarm(deffTime);
} catch (Exception e) {
Log.v("Check PrayerReceiver onWaitingAzan","ERROR");
}
}
private void onDoingAzan() {
prayerState.setNextState(PrayerState.WAITING_PRAYER);
int delay = this.silentStart;
if(delay < 2000*60)
delay =2000*60; // two minutes - at lease
Log.v("Check PrayerReceiver onDoingAzan","delay "+delay);
Manager.playAzanNotification(context);
Manager.updatePrayerAlarm(delay);
}
private void onWaitingPrayer() {
Manager manager = new Manager(this.context);
Preference preference = manager.getPreference();
AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
if(am.getRingerMode() == AudioManager.RINGER_MODE_NORMAL && preference.isAutoSilentDisabled()==false ){
am.setRingerMode(AudioManager.RINGER_MODE_SILENT);
editor.putBoolean("isRingerModeChangedToSilent", true);
editor.commit();
}
this.delayMilliSeconds = silentDuration;
prayerState.setNextState(PrayerState.WAITING_AZAN);
Manager.updatePrayerAlarm(delayMilliSeconds);
}
Can some one please help me,exactly what am I doing wrong?
Manager.prayerAlarmManager.set is not set exact on the specified time on api 19 and above. That is probably why "some" users complain.
Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.
On api 19 and above you need setExact to schedule at a specific time
You will need something like this:
if(android.os.Build.VERSION.SDK_INT >= 19) {
// setExact
}
else {
//set
}