I've read that using System.exit(0) is frowned upon when it comes to Java and Android, but so far I can find no alternative for what I'm trying to accomplish. To be clear, this is for a Watchface, which is simply a service extending CanvasWatchFaceService. I cannot call finish() in this case. I've also tried stopService and startService with no success.
The issue I'm trying to solve: It's well known that changing timezones on your device will not be reflected on a watchface unless it is restarted. In my testing, I found that System.currentTimeMillis() quite literally does not respond to timezone changes in Android Wear. The watchface must be restarted in order for it to show the correct time after a timezone change.
So I built a system with the following code:
private final BroadcastReceiver timeChangeReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (!restarting) {
if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
if (upSeconds >= 15) {
System.exit(0);
} else {
restarting = true;
int delay = ((15 - upSeconds) * 1000);
new CountDownTimer(delay, delay) {
#Override
public void onTick(long millisUntilFinished) { }
#Override
public void onFinish() {
System.exit(0);
}
}.start();
}
}
}
}
};
The delay is in case a user triggers a time zone change more frequently than 15 seconds at a time. Android Wear seems to detect system exits that are too frequent and replace the watchface with the "Simple" watchface.
It seems to work great, Android Wear automatically boots the watchface back up on exit. But I would eventually like to put this app on the Google Play Store, so I thought I should make sure I'm not playing with fire here.
I can't believe I went through all that work when the proper solution was so simple. Thanks ianhanniballake for the link!
After looking at the Analog Watchface Sample, I found that all I needed to do was use mCalendar.setTimeZone(TimeZone.getDefault());. In many places I was directly comparing the time in milliseconds fetched with long now = System.currentTimeMillis();, so I simply did a now = mCalendar.getTimeInMillis() to take care of that.
Now the watchface changes time properly when the timezone is changed. I guess the other watchfaces I downloaded did not properly handle this!
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 am very new to the andriod app programming and would like to ask about beacons.
Currently, I am using AltBeacon android-beacon-library to write an app. There is one feature that I like to implement:
Ideally, I would like a notification to be displayed when a user is near a particular beacon. However, I would only like the notification to be displayed if the user is around that beacon for more than 30 seconds. If the user simply walk past the beacon, then I don't want the notification to be displayed.
May I ask if there is any existing methods for this? I understand there is a method called "startMonitoringBeaconsInRegion" but I dont really know if this is a suitable method. Any help would be much appreciated.
Use Background services to get beacon availability with timer. Start Timer when you get beacon presence and trigger notification after 30 second if beacon is available at location for 30 second else reset the timer.
Beacon background search : http://developer.estimote.com/android/tutorial/part-2-background-monitoring/
Above tutorial link looks promising
//start when found beacon in background
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
getActivity().runOnUiThread(new Runnable() {
public void run() {
try {
//send notification
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}, 30000);
//Reset if beacon status false or unavailable
if (timer != null) {
timer.cancel();
}
You can implement this easily with the Android Beacon Library using ranging. Follow the regular tutorials on setting up ranging. Then in your ranging callback add code like this:
HashMap<String,Long> beaconDetections = new HashMap<String,Long>();
public void didRangeBeaconsInRegion(Region region, Collection<Beacon> beacons) {
Long now = System.currentTimeMillis();
for (Beacon beacon : beacons) {
Long firstDetectionTime = beaconDetections.get(beacon.toString());
if (firstDetectionTime != null) {
if (now-firstDetectionTime > 30000l) {
// Put logic here for if beacon seen for 30 secs or more
}
}
else {
beaconDetections.put(beacon.toString(), now);
}
}
}
The code above uses a HashMap to track when each beacon was first seen, then executes special logic on each callback if it has been seen for 30 secs or more.
I am trying to detect screenshots on Android app using fileObserver, fileObserver does not work on Android 6 as expected.
Here is the snippet which detects the screenshot:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screenshot_detection);
observer = new FileObserver(Environment.getExternalStorageDirectory() + File.separator + Environment.DIRECTORY_PICTURES
+ File.separator + "Screenshots" + File.separator, FileObserver.ALL_EVENTS) {
#Override
public void onEvent(int event, String path) {
if (event == FileObserver.CREATE) {
Log.i(TAG, "Screenshot detected # " + path);
}
}
};
observer.startWatching();
}
I observe that the same code works on Nexus 5 running 4.4.4 where as does not work (the onEvent is never triggered) on Nexus 5 running 6.0.1 though I have taken care of run-time permissions for API 23+.
I see a known issue with fileObserver for Android M, is there a better alternative for detecting screenshots?
I tried contentObserver, faced issues with it as well.
You can check if, the
com.android.systemui:screenshot
process is running or not. If this is running then there is good chance that screen shot was taken while the user was on your app.
Try this code block,
private void screenShotTaken(Activity activity) {
final Handler handler = new Handler();
final int delay = 3000;
final ActivityManager am = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
handler.postDelayed(new Runnable() {
public void run() {
List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(200);
for (ActivityManager.RunningServiceInfo ar : services) {
if (ar.process.equals("com.android.systemui:screenshot")) {
Toast.makeText(activity, "Screenshot is taken!!", Toast.LENGTH_SHORT).show();
}
}
handler.postDelayed(this, delay);
}
}, delay);
}
This would be something which would keep on running with some delay and will give you enough chance to detect if the screenshot was taken or not. Also, you may like to check this git, where the whole code for the screenshot is there, I think it would take a just a little understanding how things are working.
Android 6.0 have extra security features, that is why it does not allow any application from unknown source to access any thing.
For the time being you can go the application manager then to your app and then go to permissions and then allow storage permission.
I hope this might help you now for the time being.
I am kinda confused at the moment. What is the "correct" / "optimal" way for a daily network operation in an android app?
Pseudocode:
If newDay
HTTP Request to server
If responseOfRequest equals something
Do something
If HTTP Request is unsuccessfull (no internet, server down, ...)
Try again in 1 hour
How can I achieve that? I thought about a JobService but my minSDK is below Android 5.
Cheers,
DDerTyp
What you need is a service to run the logic in the background and an alarm.
A little bit of theory first:
https://developer.android.com/training/scheduling/alarms.html#tradeoffs
A repeating alarm is a relatively simple mechanism with limited flexibility. It may not be the best choice for your app, particularly if you need to trigger network operations. A poorly designed alarm can cause battery drain and put a significant load on servers.
If you own the server that is hosting your app's data, using Google Cloud Messaging (GCM) in conjunction with sync adapter is a better solution than AlarmManager.
https://developer.android.com/training/sync-adapters/running-sync-adapter.html
By default, all alarms are canceled when a device shuts down.
You will need to set up the alarm somewhere in your app, at the beginning, but saving a flag because you don't want to set up this alarm every time the user opens the app
if (!appSettings.isAlarmSetUp()) {
final AlarmManager am = (AlarmManager) context.getSystemService(ALARM_SERVICE);
final Intent i = new Intent(context, CustomService.class);
final Intent intentNotRepeat = new Intent(context, CustomService.class);
final PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + AlarmManager.INTERVAL_HALF_HOUR, AlarmManager.INTERVAL_DAY, pi);
appSettings.setAlarmSetUp(true);
}
Here more info about alarms:
https://developer.android.com/training/scheduling/alarms.html#type
As you can see, this alarm is waking up a CustomService, where you will do all your logic
public class CustomService extends IntentService {
public CustomService(String name) {
super(name);
}
#Override
protected void onHandleIntent(Intent intent) {
// Request to server
client.requestToServer()
.subscribe(response -> {
// Successful response
doSomething(response);
}
},
error -> {
// Error
createAlarmInOneHour();
});
}
}
Good morning,
We have developed an android app, and I have been charged with finding out how to remove the undesired behavior of a notification sound every time that the screen orientation changes. Obviously this behavior only exists on devices running OS version 3.2.3 or later.
I have read several posts that indicate that this can be turned off by unchecking USB Debugging in the Settings --> Developer options, however this option is not checked and none of the other apps that are on any of our Android devices make this notification sound upon orientation change.
The application does require there to be a notification when a "message is received" (the app connects to a webservice and gets new messages from the service every so often). So this would rule out any solution that disabled notifications.
Thus far, I have tried several potential solutions:
1) When a message is received, instantiate a new NotificationManager, and after the notification is sounded, destroy the NotificationManager.
if(MessageReceived == true) {
String ns = Context.NOTIFICATION_SERVICE;
messageNotifyManager = (NotificationManager) getSystemService(ns);
}
showNotification();
messageNotifyManager = null;
2) I realize that an orientation change is essentially the view being destroyed and re-created. I put set a flag in the initial onCreate method and checked to see if that flag had value before recreating the Notification Manager.
public static int Flag = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(Flag == 0) {
String ns = Context.NOTIFICATION_SERVICE;
messageNotifyManager = (NotificationManager) getSystemService(ns);
Flag = 1;
}
}
3) In the application's main class, I created a public OrientationEventListener property and then set its value in the onCreate method, disabling it immediately. When that didn't disable the sound I tried disabling the property in every class that referenced the application's main class.
public OrientationEventListender listener;
#Override
public void onCreate() {
super.onCreate();
appContext = getApplicationContext();
GetPreferences();
//...
listener = new OrientationEventListener(appContext){
public void onOrientationChanged(int Orientation){
}
};
listener.disable();
}
Now, as you can probably tell, I am very new to Android development. I assume that this solution is something so simple that everyone knows, and that is why there are no answers anywhere handy on the web. But any help with this simple problem would be greatly appreciated. Thanks!
This issue was solved by modifying the AndroidManifest, adding the following tag to each activity: android:configChanges="orientation"