I have created a plugin that starts a service and this is working fine. However I wish to be able to send variables to the running service from the plugin, and to get variables out of the service. I have researched broadcast/receivers and binding but haven't be able to to get any examples working with the code structure I am using below. Does anyone have any tips? I'm new to android development and pretty new to Java (but not programming) so there is a conceptual leap that I haven't quite got yet.
Plugin
public class IOIOconnect extends CordovaPlugin {
private Context thiscontext;
private Intent ioioService;
// Handle calls from Javascript
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
// Call from javascript to startup the IOIO service
if (action.equals("ioioStartup")) {
this.ioioStartup(callbackContext);
return true;
}
}
// Initialise IOIO service (Called from Javascript)
private void ioioStartup(CallbackContext callbackContext) {
// Initialise the service variables and start it it up
thiscontext = this.cordova.getActivity().getApplicationContext();
ioioService = new Intent(thiscontext, HelloIOIOService.class);
ioioService.putExtra("loadinterval", 800); // Set LED flash interval
thiscontext.startService(ioioService);
}
}
Service
public class HelloIOIOService extends IOIOService {
private int interval = 100;
#Override
public IBinder onBind(Intent intent) {
return null;
}
// USUAL IOIO SERVICE STUFF
#Override
public void onStart(Intent intent, int startId) {
// Service has been started
super.onStart(intent, startId);
// IOIO When service is started load external vars (if set)
int loadinterval = intent.getIntExtra("loadinterval", -1);
if(loadinterval>=0){ interval = loadinterval; }
// Native IOIO stuff
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (intent != null && intent.getAction() != null && intent.getAction().equals("stop")) {
// User clicked the notification. Need to stop the service.
nm.cancel(0);
stopSelf();
} else {
// Service starting. Create a notification.
Notification notification = new Notification(
R.drawable.ic_launcher, "IOIO service running",
System.currentTimeMillis());
notification
.setLatestEventInfo(this, "IOIO Service", "Click to stop",
PendingIntent.getService(this, 0, new Intent(
"stop", null, this, this.getClass()), 0));
notification.flags |= Notification.FLAG_ONGOING_EVENT;
nm.notify(0, notification);
}
}
}
I had exact same problem: writing plugin for Salesforce Mobile SDK (based on Cordova 2.3.0).
My case: plugin and app is different android projects.
The solutions, is that you have to publish the Service in AndroidManifest.xml of the main (app) project. Remember to sign it with full qualified path and as exported, like this:
<service
android:name="full.qualified.path.to.Service"
android:exported="true">
</service>
I managed to create a working plugin which is used in this project:
https://github.com/opensystemsassociation/southendtransportresearch/tree/master/phonegap
The code's not tidy as its still in development, but the 'hacky' approach works fine so hopefully it will help someone along the line a bit
Related
I have code running a service behind the scenes. It is set to run when we copy the text to the phone.
This code works fine on Android 8 below
But the problem is when I run the app on Android 8 and above
In my searches, I realized that I had to use FOREGROUND_SERVICEs and give specific access to the project.
What solutions do you suggest now?
Service Class:
public class AutoDownloadService extends Service {
private ClipboardManager mClipboardManager;
public static final String CHANNEL_ID = "ForegroundServiceChannel";
#Override
public void onCreate() {
super.onCreate();
mClipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
mClipboardManager.addPrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
createNotificationChannel();
Intent notificationIntent = new Intent(this, SettingsActivity.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);
// stopSelf();
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
if (mClipboardManager != null) {
mClipboardManager.removePrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
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);
}
}
private ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener =
new ClipboardManager.OnPrimaryClipChangedListener() {
#Override
public void onPrimaryClipChanged() {
ClipData clip = mClipboardManager.getPrimaryClip();
String textClipBoard = clip.getItemAt(0).getText().toString();
Toast.makeText(AutoDownloadService.this, textClipBoard, Toast.LENGTH_SHORT).show();
}
};
}
Manifest
<service
android:name=".services.AutoDownloadService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
and add finally uses permission
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
I think you should use Intent Service instead of service.
what you can do is if system shutdown your service you can again trigger it after sometime using alarm manger.
As stated in documentation
While an app is in the foreground, it can create and run both
foreground and background services freely. When an app goes into the
background, it has a window of several minutes in which it is still
allowed to create and use services. At the end of that window, the app
is considered to be idle. At this time, the system stops the app's
background services, just as if the app had called the services'
Service.stopSelf() methods.
So, you solution is to run foreground service on Android >= 8.0 and do something like this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(Intent(this, AutoDownloadService.class))
} else {
startService(Intent(this, AutoDownloadService.class))
}
}
First of all, you should not do this.
Monitoring clipboard in background is not something right.
Android 8 added some protection on this, so you should run as foreground services, to let the end user aware your app is monitoring the clipboard.
Anyway clipboard access only available to default IME from Android 10. So, your apps will not work in Android 10.
This example of my code is currently working fine but I have problems with Chinese mobile
Tested on mobile: Xiaomi 7
public class AutoDownloadService extends IntentService {
private ClipboardManager mClipboardManager;
public AutoDownloadService() {
super("AutoDownloadService");
}
#Override
public void onCreate() {
super.onCreate();
mClipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
mClipboardManager.addPrimaryClipChangedListener(mOnPrimaryClipChangedListener);
}
#Override
protected void onHandleIntent(Intent intent) {
Toast.makeText(this, " service started.", Toast.LENGTH_SHORT).show();
}
private final ClipboardManager.OnPrimaryClipChangedListener mOnPrimaryClipChangedListener =
new ClipboardManager.OnPrimaryClipChangedListener() {
#Override
public void onPrimaryClipChanged() {
ClipData clip = mClipboardManager.getPrimaryClip();
String textClipBoard = clip.getItemAt(0).getText().toString();
if (textClipBoard.startsWith("https://www.instagram.com/")) {
Toast.makeText(AutoDownloadService.this, textClipBoard, Toast.LENGTH_SHORT).show();
}
}
};
}
And in the file Manifast.xml This is how it is defined
<service android:name="com.amirhf.inatasave.services.AutoDownloadService" />
When you swipe the app from recent apps, few Chinese OEMs force stop the apps.
Once the App is force stopped, you can't post notifications, start services, receive broadcasts etc.
The only work around is : Your app should be whitelisted, added in
auto start list. Apps like WhatsApp, Facebook etc are already added by
the OEMs in the list.
This blog talks about similar problem of not receiving notifications when App is force stopped. https://hackernoon.com/notifications-in-android-are-horribly-broken-b8dbec63f48a
You can take a similar approach where you can have a "Troubleshoot" section where you can educate the user to add your app in auto start list.
If by any chance your app is very popular you can get in touch with
Chinese Manufacturer and request them to get your app whitelisted but
they do it for very popular apps. For example in my experience
Microsoft and Hike Messenger got it done for their apps.
I didn't quite understand if you're messing with file download or anything else. But I guess you're not going the right way. So here's what I may share.
From https://developer.android.com/training/scheduling/wakelock
If your app is performing long-running HTTP downloads, consider using DownloadManager.
If your app is synchronizing data from an external server, consider creating a sync adapter.
If your app relies on background services, consider using JobScheduler or Firebase Cloud Messaging to trigger these services
at specific intervals.
Also note that if you just have a task that has to done often, use JobIntentService. Its compatible with Oreo and versions below it:
Helper for processing work that has been enqueued for a job/service.
When running on Android O or later, the work will be dispatched as a
job via JobScheduler.enqueue. When running on older versions of the
platform, it will use Context.startService.
On Oreo and above versions there are limitations helping the device save resources (battery, ram...) and even when using JobIntentService you must consider them; otherwise your app may be recognized as Battery Draining App.
If what you're about to do is heavy and is important enough to be shown in notification bar, do it using ForegroundService. So that it will be taken more seriously by android system and chances of it being killed gets fewer.
Try using WorkManager it's a JetPack Library.
Advantages:
Ensures task execution, even if the app or device restarts (Guaranteed Execution)
You don’t need to write device logic to figure out what capabilities the device has and choose an appropriate API; instead, you can just hand your task off to WorkManager and let it choose the best option. It is a wrapper on all the above concepts.
Uses JobScheduler on devices with API 23+
Uses a combination of BroadcastReceiver + AlarmManager on devices with API 14-22
Ref : WorkManager Docs
Ref : Medium Article
Ref : Medium Article(1)
[Update] - stable version is out WorkManager
I need write a service which will update the list in MainActivity every 30sec. I use MVVM with ViewModel and LiveData and so my Service class looks like this:
public class ArticleJobService extends JobService {
public static String TAG = "ArticleJobService";
private Context context = this;
#Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "onStartJob");
MainActivity.PAGE_NUMBER++;
LiveData<List<Article>> liveArticles = ArticleRepository.getInstance(getApplication()).getArticles(MainActivity.PAGE_NUMBER);
liveArticles.observeForever(new Observer<List<Article>>() {
#Override
public void onChanged(#Nullable List<Article> articles) {
Log.d(TAG, "onStartJob - onChanged!!!!!!");
liveArticles.removeObserver(this);
NotificationUtils.showNotification(context, articles.get(0).getSectionName(), articles.get(0).getWebTitle());
jobFinished(jobParameters, true);
}
});
return true;
}
}
Class for my notification:
public static void showNotification(Context context, String section, String title) {
PendingIntent contentPendingIntent = PendingIntent.getActivity
(context, REQUEST_CODE, new Intent(context, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager manager =
(NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel(createNotificationChannel(context));
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(section)
.setContentText(title)
.setContentIntent(contentPendingIntent)
.setSmallIcon(R.drawable.app_icon)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setAutoCancel(true);
manager.notify(0, builder.build());
}
When Onchanged in JobService works I get the list and show a notification. Notification opens MainActivity which makes new call to api as it always did. What changes do I have to make in order the MainActivity to show the list that I got from the service??? I really can't tie this up together.
I heard of IPC but wouldn't do that, I want some simpler practice which I sure exists which I just don't know about.
Also, there are two cases: Notification came and MainActivity is open, app is open but MainActivity is not in the foreground and app is on the background or closed. How should I handle each of these cases?
See also piece of code from MainActivity onCreate:
mArticleViewModel = ViewModelProviders.of(this).get(ArticleViewModel.class);
mArticleViewModel.getArticleList(PAGE_NUMBER).observe(this, articles -> {
Log.d(TAG, "List<Result> onChanged!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
mProgressBar.setVisibility(View.GONE);
mProgressBarMain.setVisibility(View.GONE);
mIsLoading = false;
mArticles = articles;
Please provide the best practices for this task, I know it's very common I just do it first time and using LiveData makes it way more complicated.
Here is Also Repository code:
public static ArticleRepository getInstance(Application application){
if(INSTANCE == null){
return new ArticleRepository(application);
}
return INSTANCE;
}
private ArticleRepository(Application application) {
Log.d(TAG, "ArticleRepository constructor");
mContext = application;
mArticles = new MutableLiveData<>();
ArticleRoomDatabase db = ArticleRoomDatabase.getInstance(application);
mArticleDao = db.articleDao();
}
public LiveData<List<Article>> getArticles(int page) {
Log.d(TAG, "getArticles");
if (NetworkUtils.isOnline(mContext)) {
Log.d(TAG, "isOnline");
mArticles = loadFromNetwork(page);
} else {
Log.d(TAG, "is NOT Online");
mArticles = loadFromDB(page);
}
}
You have this problem specifically because your Repository implementation is incorrect.
public LiveData<List<Article>> getArticles(int page) {
Log.d(TAG, "getArticles");
if (NetworkUtils.isOnline(mContext)) {
Log.d(TAG, "isOnline");
mArticles = loadFromNetwork(page);
} else {
Log.d(TAG, "is NOT Online");
mArticles = loadFromDB(page);
}
}
If you check the code for NetworkBoundResource, the trick is that you have a single LiveData that binds together the ability to both load from network, and to load from database.
In your case, you are replacing the database's auto-updating query results whenever you have network access - which is why you can't update the MainActivity.
The easiest way (without using a MediatorLiveData) is to have two separate functions on Repository: one for fetchFromNetwork, and one for fetchFromDatabase. The MainActivity should always fetch from database, while the Service always triggers load from network (and inserts it directly into database via a Dao).
This way, the observe function in MainActivity will receive the latest data when Service inserts the data into DB on background thread.
I have created an app that runs a service to read which app/activity user have opened and using it at the current time. The problem is that the service reads only the launcher application. It doesn't return me the cirrently open app/activity. Can you help? The code i write is below. Thanks in advance.
#Override
public void onStart(Intent intent, int startId) {
// For time consuming an long tasks you can launch a new thread here...
Toast.makeText(this, " Service Started", Toast.LENGTH_LONG).show();
Runnable runable = new Runnable() {
public void run() {
try{
ActivityManager am2 = (ActivityManager) getSystemService(Activity.ACTIVITY_SERVICE);
String packageName = am2.getRunningTasks(1).get(0).topActivity
.getPackageName();
Log.w("RunningTask", packageName);
handler.postDelayed(this, 8000);
}
catch (Exception e){
}
}
};
handler.postDelayed(runable, 8000);
}
If you use Android 5.0 or above, getRunningTasks() is deprecated and will only return a small subset, including the caller's own tasks, and possibly some other tasks such as home.
You may check out getRunningAppProcesses() which worked before Android 5.1.1. See Cannot get foreground activity name in Android Lollipop 5.0 only and Android 5.1.1 and above - getRunningAppProcesses() returns my application package only
Example using getRunningAppProcesses:
ActivityManager am2 = (ActivityManager) getSystemService(Activity.ACTIVITY_SERVICE);
String processName = am2.getRunningAppProcesses().get(0).processName;
Log.w("Running process", processName);
This question already has answers here:
How can I update information in an Android Activity from a background Service
(5 answers)
Closed 9 years ago.
I have an app that receives gcm push notifications. I also have a check in place that if the app is currently open, it does not create the notification. In Android, is it possible in my push service to tell the current activity (IF Available/connected), that a new notification has arrived, refresh your list with the new content? If this is possible, I believe I am on the right path with using IBinders on my service. The thing about that is I am confused on how the Service Calls the Activity (I understand vice verse). Thanks in advance if anyone could help!
Just to be clear. I am trying to tell the activity about a new push message.
Service
public class LocalBinder extends Binder {
GcmIntentService getService() {
return GcmIntentService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IBinder mBinder = new LocalBinder();
Client (Activity)
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((GcmIntentService.LocalBinder)service).getService();
// Tell the user about this for our demo.
Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Toast.makeText(MainActivity.this, "Disconnected",
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(MainActivity.this,
GcmIntentService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
#Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
Yes it's possible.
In your service class have a variable of type YourActivity
MyActivity myActivity = null;
In your onServiceConnected method set this myActivity variable on the service, obtained through the binder.
mBoundService.myActivity = MyActivity.this; // or something similar
Now your service has a pointer to your activity!!! YEah!!
Inside your activity, create a function, the body of this function should refresh the UI with data.
Finally, when the service detects new data, call:
if (myActivity)
myActivity.refreshMyData();
When unbinding, remember to set the myActivity variable to null, otherwise the previous code will fail.
I search a lot and I tried several ways but I couldn't find anything that avoid my error.
I'm working on app for my learning, where is an String in my MainActivity and then I call it in my Service. I tried something like this:
This next one goes in my myService.class
//In my myService.class that extends Service
public class myService extends Service{
AlarmManager manager;
PendingIntent pintent;
String te;
#Override
public void onCreate()
{
manager = (AlarmManager)(this.getSystemService(Context.ALARM_SERVICE));
pintent = PendingIntent.getBroadcast( this, 0, new Intent("blahblah"), 0 );}
#Override
public int onStartCommand(Intent intent, int flags, int startid)
{
super.onStartCommand(intent, flags, startid);
te = intent.getStringExtra("tst"); //if I change this line to te = "something", everything goes fine
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive( Context context, Intent intent )
{
Toast.makeText(getApplicationContext(),te, Toast.LENGTH_SHORT).show();
}
};
this.registerReceiver(receiver, new IntentFilter("blahblah") );
// set alarm to fire 30 min and waits 2 min
manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 1000*30*60, 1000*2*60, pintent);
return START_STICKY;}
public IBinder onBind(Intent p1)
{
// TODO: Implement this method
return null;
}
}
Here the code runs perfectly, but when I exit my App, it crashes. After 1 minute, my device shows again that my App crashes, confirming my app "successfully" ran into background. What is wrong with it?
I also learned I could use IntentService instead of Service, wich one is better for long tasks and what is the difference between them ?
EDIT***
I received the following error: java.lang.NullPointerExeption.
So I change this:
te = intent.getStringExtra("tst");
To this:
try
{
te = intent.getStringExtra("tst");
}
catch(NullPointerException e)
{}
When I changed it my app works with any error, but The problem is: I need to retrieve my String from my MainActivity, when I close my app my service runs without errors but my "te" String takes null valor, what can I do to "save" my String in my service, to be able to use it and keeping showing the "working" message after I close my App ? Should I use SharedPreferences ?
Hope I was clear.
IntentService is different from Service.
IntentService Self kills the service as it finishes the task. Whereas Service runs for ever unless you kill it.
For my coding experience, I would use IntentService only for small tasks that run for a couple of seconds and use Service for long running one and call StopSelf() as needed.
Kindly post the log to answer your problem